Exemplo n.º 1
0
    def __init__(
        self,
        serial: str,
        device_type: str,
        session: "Session",
    ):
        self._serial = serial
        self._device_type = device_type
        self._session = session
        try:
            self._options = session.daq_server.getString(f"/{serial}/features/options")
        except RuntimeError:
            self._options = ""

        # HF2 does not support listNodesJSON so we have the information hardcoded
        # (the node of HF2 will not change any more so this is safe)
        preloaded_json = None
        if "HF2" in self._device_type:
            preloaded_json = self._load_preloaded_json(
                Path(__file__).parent / "../../resources/nodedoc_hf2.json"
            )

        nodetree = NodeTree(
            self._session.daq_server,
            prefix_hide=self._serial,
            list_nodes=[f"/{self._serial}/*"],
            preloaded_json=preloaded_json,
        )
        # Add predefined parseres (in node_parser) to nodetree nodes
        nodetree.update_nodes(
            node_parser.get(self.__class__.__name__, {}), raise_for_invalid_node=False
        )

        super().__init__(nodetree, tuple())
Exemplo n.º 2
0
def test_init(connection, data_dir):
    tree = NodeTree(connection)
    connection.listNodesJSON.assert_called_once_with("*")
    assert "zi" in tree
    assert "ZI" in tree
    assert "dev1234" in tree
    assert "DEV1234" in tree
    assert "zi" in dir(tree)
    assert "dev1234" in dir(tree)

    tree = NodeTree(connection, "DEV1234")
    connection.listNodesJSON.assert_called_with("*")
    assert "zi" in tree
    assert "ZI" in tree
    assert "dev1234" not in tree
    assert "DEV1234" not in tree
    assert "zi" in dir(tree)
    assert "dev1234" not in dir(tree)

    json_path = data_dir / "nodedoc_daq_test.json"
    with json_path.open("r", encoding="UTF-8") as file:
        nodes_json = file.read()
    connection_module = MagicMock()
    connection_module.listNodesJSON.return_value = nodes_json
    tree = NodeTree(connection_module)
    connection_module.listNodesJSON.assert_called_with("*")
    assert "awgcontrol" in tree

    connection_broken = MagicMock()
    connection_broken.listNodesJSON.return_value = '{\n"zi/open": {\n"Node": "/ZI/OPEN"\n},\n"zi/close": {\n"Node": "/ZI/CLOSE"\n}\n}\n'
    with pytest.raises(Exception) as e_info:
        tree = NodeTree(connection_broken)
    assert "Leading slash not found" in e_info.value.args[0]
Exemplo n.º 3
0
def test_get_wildcard(connection):
    tree = NodeTree(connection, "DEV1234")

    connection.get.return_value = OrderedDict([(
        "/dev1234/demods/0/impedance",
        {
            "timestamp": array("q", [3880906635]),
            "value": array("l", [123]),
        },
    )])
    result = tree.demods()
    assert result[tree.demods[0].impedance] == 123
    connection.get.assert_called_with("/dev1234/demods",
                                      settingsonly=False,
                                      flat=True)

    # deep includes the timestamp
    result = tree.demods(deep=True)
    assert result[tree.demods[0].impedance] == (3880906635, 123)

    # nested dict
    result = tree.demods(flat=False)
    assert isinstance(result, OrderedDict)

    # nested dict
    result = tree.demods["*"].impedance(flat=False)
    assert isinstance(result, OrderedDict)

    # invalid node
    with pytest.raises(KeyError) as e_info:
        tree.invalid()

    # HF2 support
    connection.get.return_value = OrderedDict([("/dev1234/demods/0/impedance",
                                                nparray([125]))])
    result = tree.demods()
    assert result[tree.demods[0].impedance] == 125
    connection.get.assert_called_with("/dev1234/demods",
                                      settingsonly=False,
                                      flat=True)

    result = tree.demods(deep=True)
    assert result[tree.demods[0].impedance] == (None, 125)

    connection.get.return_value = OrderedDict([(
        "/dev1234/demods/0/impedance",
        {
            "timestamp": array("q", [3880906635]),
            "x": array("l", [123]),
            "y": array("l", [123]),
        },
    )])
    result = tree.demods()
    assert (result[tree.demods[0].impedance] ==
            connection.get.return_value["/dev1234/demods/0/impedance"])
    connection.get.assert_called_with("/dev1234/demods",
                                      settingsonly=False,
                                      flat=True)
Exemplo n.º 4
0
def test_update_nodes(connection):
    tree = NodeTree(connection, "DEV1234")

    tree.update_nodes({
        "demods/0/rate": {
            "Unit": "test"
        },
        "demods/0/order": {
            "Unit": "test2"
        }
    })
    assert tree.demods[0].rate.node_info.unit == "test"
    assert tree.demods[0].order.node_info.unit == "test2"

    with pytest.raises(KeyError) as e_info:
        tree.update_nodes({
            "demods/0/rate": {
                "Unit": "test3"
            },
            "test": {
                "Node": "test"
            }
        })
    assert tree.demods[0].rate.node_info.unit == "test3"
    tree.update_nodes(
        {
            "demods/0/rate": {
                "Unit": "test3"
            },
            "test": {
                "Node": "test4"
            }
        },
        add=True)
    assert tree.test.node_info.path == "test4"
    assert tree.test.is_valid() is True

    # Test for not raising any errors
    tree.update_nodes(
        {
            "312/123": {
                "Unit": "test5"
            },
            "testNOtexists": {
                "Node": "test5"
            }
        },
        add=False,
        raise_for_invalid_node=False,
    )
    assert tree.testNOtexists.is_valid() is False
Exemplo n.º 5
0
def test_transactional_set(connection):
    tree = NodeTree(connection, "DEV1234")
    with tree.set_transaction():
        tree.demods[0].rate(22.0)
    connection.set.assert_called_once_with([("/dev1234/demods/0/rate", 22.0)])
    with tree.set_transaction():
        tree.demods[0].rate(22.0)
        tree.demods[0].phaseshift(1)
    connection.set.assert_called_with([("/dev1234/demods/0/rate", 22.0),
                                       ("/dev1234/demods/0/phaseshift", 1)])
    with tree.set_transaction():
        tree.demods[0].rate(22.0)
        tree.transaction.add("demods/0/phaseshift", 2)
    connection.set.assert_called_with([("/dev1234/demods/0/rate", 22.0),
                                       ("/dev1234/demods/0/phaseshift", 2)])

    with tree.set_transaction():
        tree.system.impedance.calib.user.data(b"Test Binary Data")
    connection.set.assert_called_with([
        ("/dev1234/system/impedance/calib/user/data", b"Test Binary Data")
    ])

    with pytest.raises(AttributeError) as e_info:
        tree.transaction.add("demods/0/phaseshift", 2)

    with pytest.raises(RuntimeError) as e_info:
        with tree.set_transaction():
            with tree.set_transaction():
                pass
Exemplo n.º 6
0
def test_parser(connection):
    tree = NodeTree(connection, "DEV1234")

    tree.update_node(
        "demods/0/rate",
        {
            "GetParser": lambda value: value + 10,
            "SetParser": lambda value: value + 10,
        },
    )
    connection.getDouble.return_value = 0
    assert tree.demods[0].rate() == 10
    tree.demods[0].rate(0)
    connection.set.assert_called_with(tree.demods[0].rate.node_info.path, 10)
Exemplo n.º 7
0
def test_hash_node(connection):
    tree = NodeTree(connection, "DEV1234")

    test_dict = {tree.demods[0].rate: 0, tree.demods[0].trigger: 1}

    assert test_dict[tree.demods[0].rate] == 0
    assert test_dict[tree.demods[0].trigger] == 1
Exemplo n.º 8
0
def test_set(connection):
    tree = NodeTree(connection, "DEV1234")
    tree.demods[0].rate(22.0)
    connection.set.assert_called_with(tree.demods[0].rate.node_info.path, 22.0)
    tree.demods[0].rate(22.0, test=True)
    connection.set.assert_called_with(tree.demods[0].rate.node_info.path,
                                      22.0,
                                      test=True)

    # sync set of nodes is only dependend on the input parameter not the
    # type of the node iself
    tree.demods[0].rate(22.0, deep=True)
    connection.syncSetDouble.assert_called_with(
        tree.demods[0].rate.node_info.path, 22.0)
    tree.demods[0].rate(0, deep=True)
    connection.syncSetInt.assert_called_once_with(
        tree.demods[0].rate.node_info.path, 0)
    tree.demods[0].rate("test", deep=True)
    connection.syncSetString.assert_called_once_with(
        tree.demods[0].rate.node_info.path, "test")
    with pytest.raises(Exception) as e_info:
        tree.demods[0].rate(complex(2, -3), deep=True)

    connection.syncSetString = None
    with pytest.raises(TypeError):
        tree.demods[0].rate("test", deep=True)

    with pytest.raises(KeyError):
        tree.demods.test("test")
Exemplo n.º 9
0
def test_wait_for_state_change(connection):
    tree = NodeTree(connection, "DEV1234")

    connection.getInt.side_effect = [1] * 3 + [2] * 2
    tree.demods[0].trigger.wait_for_state_change(2)
    connection.getInt.side_effect = None

    connection.getInt.return_value = 2
    tree.demods[0].trigger.wait_for_state_change(2)

    connection.getInt.return_value = 1
    with pytest.raises(TimeoutError) as e:
        tree.demods[0].trigger.wait_for_state_change(2, timeout=0.5)
    assert (str(e.value) == "/dev1234/demods/0/trigger did not change to the "
            "expected value within 0.5s. 2 != 1")

    with pytest.raises(TimeoutError) as e:
        tree.demods[0].trigger.wait_for_state_change(1,
                                                     invert=True,
                                                     timeout=0.5)
    assert (str(
        e.value) == "/dev1234/demods/0/trigger did not change from the "
            "expected value 1 within 0.5s.")

    connection.getInt.side_effect = [1] * 3 + [2] * 8
    tree.demods["*"].trigger.wait_for_state_change(2)

    with pytest.raises(KeyError) as e_info:
        tree.hello.wait_for_state_change(1)

    with pytest.raises(KeyError) as e_info:
        tree.hello["*"].test.wait_for_state_change(1)
Exemplo n.º 10
0
def test_node_access(connection):
    tree = NodeTree(connection, "DEV1234")

    assert tree.demods.root == tree

    # test local variable
    assert not tree._test
    assert not tree.demods._test
    # test attribute vs item
    assert tree.demods == tree["demods"]
    assert tree.demods == tree["/demods"]
    assert tree.demods.test == tree["demods/test"]
    assert tree.demods.test == tree.demods["/test"]
    assert tree.demods.test.world == tree.demods["test/world"]

    # buildin keywords are escaped with tailing underscore
    assert tree.in_.test == tree["in/test"]
    assert tree.test.and_ == tree["test/and"]

    # test invalid nodes
    assert tree.no.real.element.raw_tree == ("no", "real", "element")
    assert str(tree.no.real.element) == "/dev1234/no/real/element"
    with pytest.raises(KeyError) as e_info:
        tree.no.real.element()
    assert e_info.value.args[0] == str(tree.no.real.element)
Exemplo n.º 11
0
def test_init_preloaded_json(nodedoc_dev1234_json):
    nodes_json = json.loads(nodedoc_dev1234_json)
    connection = MagicMock()  # plain mock without json info
    tree = NodeTree(connection,
                    prefix_hide="DEV1234",
                    preloaded_json=nodes_json)
    assert tree.prefix_hide == "dev1234"
    connection.listNodesJSON.mock_object.assert_not_called()
Exemplo n.º 12
0
def test_nodetree_iterator(connection):
    tree = NodeTree(connection, "DEV1234")

    counter = 0
    for node, info in tree:
        assert node.node_info.path.lower() == info["Node"].lower()
        counter = counter + 1
    assert counter == len(tree._flat_dict)
Exemplo n.º 13
0
def test_to_raw_path(connection):
    tree = NodeTree(connection)

    with pytest.raises(ValueError):
        tree.to_raw_path("hello/world")

    assert tree.to_raw_path(Node(tree, tuple())) == "/"

    tree = NodeTree(connection, "DEV1234")
    assert tree.to_raw_path(Node(tree, tuple())) == "/dev1234"
Exemplo n.º 14
0
def test_module_get_wildcard(connection):
    tree = NodeTree(connection, "DEV1234")

    return_value = OrderedDict([(
        "/dev1234/demods/0/impedance",
        {
            "timestamp": array("q", [3880906635]),
            "value": array("l", [123]),
        },
    )])
    connection.get.side_effect = [TypeError(), return_value]
    assert tree.demods()[tree.demods[0].impedance] == 123

    connection.get.side_effect = [TypeError(), RuntimeError(), return_value]
    assert tree.demods()[tree.demods[0].impedance] == 123

    connection.get.side_effect = [RuntimeError(), return_value]
    assert tree.demods()[tree.demods[0].impedance] == 123
Exemplo n.º 15
0
def test_wildcard_node(connection):
    tree = NodeTree(connection, "DEV1234")
    wildcard_node = tree.demods["*"].rate
    assert wildcard_node.node_info.path == "/dev1234/demods/*/rate"
    with pytest.raises(KeyError) as e_info:
        wildcard_node.node_info.description

    # TODO add dummy Data to mock
    wildcard_node()
    wildcard_node(1)
    with tree.set_transaction():
        wildcard_node = tree.demods["*"].rate(1)

    connection.get.return_value = None
    with pytest.raises(KeyError) as e_info:
        tree.hello["*"].rate()
    with pytest.raises(KeyError) as e_info:
        tree.hello["*"].rate(1)
Exemplo n.º 16
0
def test_runtimerror_set_set_vector(connection):
    # tree.system.impedance.calib.user.data does work with set, but for
    # testing purposes call it
    tree = NodeTree(connection, "DEV1234")
    connection.set = Mock(side_effect=RuntimeError)
    tree.system.impedance.calib.user.data(b"Test Binary Data")
    connection.setVector.assert_called_with(
        tree.system.impedance.calib.user.data.node_info.path,
        b"Test Binary Data")
Exemplo n.º 17
0
    def _create_nodetree(self) -> NodeTree:
        """Create node tree for the SHFQA sweeper.

        Uses the hardcoded "resources/shfqa_sweeper_nodes.json" information
        and the SHFSweeper data classes to automatically create a valid node
        tree. (Automatically adds new parameters with dummy information)

        Returns:
            node tree for the shfqa sweeper
        """
        json_path = Path(
            __file__).parent / "../../resources/shfqa_sweeper_nodes.json"
        with json_path.open("r") as file:
            raw_info = json.loads(file.read())
        values = {}
        info = {}
        for config_class, parent_name in self._config_classes.items():
            for parameter, default_value in asdict(config_class()).items():
                node = (
                    f"/{parent_name[0]}/{self._renamed_nodes.get(parameter,parameter)}"
                )
                try:
                    info[node] = raw_info[node]
                except KeyError:
                    # node not in json
                    logger.warning(f"{node} is missing in {json_path}.")
                    type_mapping = {
                        int: "Integer (64 bit)",
                        float: "Double",
                        str: "String",
                        bool: "Integer (64 bit)",
                        np.ndarray: "ZIVectorData",
                    }
                    info[node] = {
                        "Node": node,
                        "Description": node,
                        "Properties": "Read, Write",
                        "Unit": "None",
                        "Type": type_mapping[type(default_value)],
                    }

                if "Options" in info[node]:
                    option_map = {}
                    for key, value in info[node]["Options"].items():
                        options = re.findall(r'"(.+?)"[,:]+', value)
                        option_map.update({x: int(key) for x in options})
                    values[node] = option_map.get(default_value, default_value)
                else:
                    values[node] = default_value
        info["/device"] = raw_info["/device"]
        values["/device"] = ""
        info["/envelope/enable"] = raw_info["/envelope/enable"]
        values["/envelope/enable"] = 0

        return NodeTree(ConnectionDict(values, info))
Exemplo n.º 18
0
def test_get_cached(connection):
    tree = NodeTree(connection, "DEV1234")

    tree.demods[0].adcselect()
    connection.getInt.assert_called_with(
        tree.demods[0].adcselect.node_info.path)
    tree.demods[0].rate()
    connection.getDouble.assert_called_with(tree.demods[0].rate.node_info.path)
    tree.features.serial()
    connection.getString.assert_called_with(
        tree.features.serial.node_info.path)

    # Complex Double
    tree.update_node("demods/0/rate", {"Type": "Complex Double"})
    tree.demods[0].rate()
    connection.getComplex.assert_called_with(
        tree.demods[0].rate.node_info.path)
    connection.getComplex = None
    with pytest.raises(TypeError) as e_info:
        tree.demods[0].rate()

    # ZIVectorData
    with pytest.raises(TypeError) as e_info:
        tree.system.impedance.calib.user.data()

    # ZIDemodSample
    tree.demods[0].sample()
    connection.getSample.assert_called_with(
        tree.demods[0].sample.node_info.path)
    connection.getSample = None
    with pytest.raises(TypeError) as e_info:
        tree.demods[0].sample()

    # ZIDIOSample
    tree.dios[0].input()
    connection.getDIO.assert_called_with(tree.dios[0].input.node_info.path)

    # ZIAdvisorWave
    tree.update_node(tree.dios[0].input, {"Type": "ZIAdvisorWave"})
    with pytest.raises(StopIteration):
        tree.dios[0].input()
    connection.get.assert_called_with(tree.dios[0].input.node_info.path,
                                      flat=True)

    # invalid type
    tree.update_node(tree.dios[0].input, {"Type": "InvalidType"})
    with pytest.raises(RuntimeError):
        tree.dios[0].input()
Exemplo n.º 19
0
 def __init__(self, raw_module: ZIModule, session: "Session"):
     self._raw_module = raw_module
     self._session = session
     super().__init__(NodeTree(raw_module), tuple())
     if "device" in self.root:
         self.root.update_nodes(
             {
                 "/device": {
                     "GetParser": self._get_device,
                     "SetParser": self._set_device,
                 }
             },
             raise_for_invalid_node=False,
         )
Exemplo n.º 20
0
def test_connection_dict(data_dir):
    data = {"/car/seat": 4, "/car/color": "blue", "/street/length": 110.4}
    json_path = data_dir / "nodedoc_fake.json"
    with json_path.open("r", encoding="UTF-8") as file:
        nodes_json = json.loads(file.read())
    connection = ConnectionDict(data, nodes_json)

    tree = NodeTree(connection)
    assert tree.car.seat() == 4
    assert tree.car.color() == "blue"
    assert tree.street.length() == 110.4

    tree.car.seat(5)
    assert tree.car.seat() == 5

    tree.street.length(1)
    assert tree.street.length() == 1.0
    assert tree.street.length(deep=True) == (None, 1.0)

    tree.car["*"](1)
    assert tree.car.seat() == 1
    assert tree.car.color() == "1"

    # subscribe and unsubscribe is not supported
    with pytest.raises(RuntimeError) as e_info:
        tree.car.seat.subscribe()
    with pytest.raises(RuntimeError) as e_info:
        tree.car.seat.unsubscribe()

    tree = NodeTree(connection, list_nodes=["/car/*"])
    assert tree.car.seat() == 1
    assert tree.car.color() == "1"
    with pytest.raises(KeyError):
        tree.street.length()

    connection.setVector("/car/seat", 5)
    assert tree.car.seat() == 5
Exemplo n.º 21
0
def test_get_deep(connection):
    tree = NodeTree(connection, "DEV1234")

    with pytest.raises(TypeError) as e_info:
        tree.demods[0].rate(deep=True)
        connection.get.assert_called_with(tree.demods[0].rate.node_info.path,
                                          settingsonly=False,
                                          flat=True)
    with pytest.raises(TypeError) as e_info:
        tree.demods[0].rate(deep=True, test=True)

    # overwrite internally used kwarg
    with pytest.raises(TypeError) as e_info:
        tree.demods[0].rate(deep=True, settingsonly=True)

    connection.get.return_value = OrderedDict()
    with pytest.raises(TypeError) as e_info:
        tree.demods[0].rate(deep=True)

    # Real data
    # double node
    connection.get.return_value = OrderedDict([(
        "/dev1234/demods/0/rate",
        {
            "timestamp": array("q", [1236389131550]),
            "value": array("d", [1674.10717773]),
        },
    )])
    timestamp, data = tree.demods[0].rate(deep=True)
    assert timestamp == 1236389131550
    assert data == 1674.10717773
    # vector node
    connection.get.return_value = OrderedDict([(
        "/dev1234/system/impedance/calib/user/data",
        [{
            "timestamp": 3880906635,
            "flags": 0,
            "vector": array("l", [123, 10, 32, 10, 125, 10]),
        }],
    )])
    timestamp, data = tree.demods[0].sample(deep=True)
    assert timestamp == 3880906635
    assert data == array("l", [123, 10, 32, 10, 125, 10])
    # HF2 node
    connection.get.return_value = OrderedDict([("/dev1234/demods/0/rate",
                                                nparray([1674.10717773]))])
    timestamp, data = tree.demods[0].sample(deep=True)
    assert data == 1674.10717773
    assert timestamp == None
Exemplo n.º 22
0
def test_node_iter(connection):
    tree = NodeTree(connection, "DEV1234")

    childs = []
    for child, info in tree.demods[0]:
        childs.append(child)
        assert child.node_info.path.lower() == info["Node"].lower()

    assert all([child.raw_tree[-1] in tree.demods[0] for child in childs])

    childs = []
    for child, info in tree.demods["*"]:
        childs.append(child)
        assert child.node_info.path.lower() == info["Node"].lower()
    assert all([child.raw_tree[-1] in tree.demods["*"] for child in childs])
Exemplo n.º 23
0
def test_get_as_event(connection):
    tree = NodeTree(connection, "DEV1234")

    tree.demods[0].sample.get_as_event()
    connection.getAsEvent.assert_called_once_with(
        tree.demods[0].sample.node_info.path)

    connection.getAsEvent.side_effect = [RuntimeError(), None, None]
    tree.demods["[1,2]"].sample.get_as_event()
    connection.getAsEvent.assert_any_call(tree.demods[1].sample.node_info.path)
    connection.getAsEvent.assert_any_call(tree.demods[2].sample.node_info.path)

    connection.getAsEvent.side_effect = RuntimeError()
    with pytest.raises(KeyError):
        tree.demods["*"].test.get_as_event()
Exemplo n.º 24
0
def test_subscribe(connection):
    tree = NodeTree(connection, "DEV1234")

    tree.demods[0].sample.subscribe()
    connection.subscribe.assert_called_once_with(
        tree.demods[0].sample.node_info.path)

    tree.demods[0].sample.unsubscribe()
    connection.unsubscribe.assert_called_once_with(
        tree.demods[0].sample.node_info.path)

    connection.subscribe.side_effect = RuntimeError()
    connection.unsubscribe.side_effect = RuntimeError()
    with pytest.raises(KeyError):
        tree.demods["*"].test.subscribe()
    with pytest.raises(KeyError):
        tree.demods["*"].test.unsubscribe()
Exemplo n.º 25
0
def test_options(connection):
    tree = NodeTree(connection, "DEV1234")

    tree.demods[0].trigger("continuous")
    connection.set.assert_called_with(tree.demods[0].trigger.node_info.path,
                                      "continuous")

    tree.demods[0].trigger("continuou")
    connection.set.assert_called_with(tree.demods[0].trigger.node_info.path,
                                      "continuou")

    connection.getInt.return_value = 0
    assert tree.demods[0].trigger(
    ) == tree.demods[0].trigger.node_info.enum.continuous

    connection.getInt.return_value = 12345678
    assert tree.demods[0].trigger() == 12345678
Exemplo n.º 26
0
 def node_tree(self, connection):
     tree = NodeTree(connection, "DEV1234")
     connection.get.return_value = OrderedDict([
         (
             "/dev1234/demods/0/rate",
             {
                 "timestamp": array("q", [123]),
                 "value": array("d", [2]),
             },
         ),
         (
             "/dev1234/demods/1/rate",
             {
                 "timestamp": array("q", [1234]),
                 "value": array("d", [3]),
             },
         ),
     ])
     return tree
Exemplo n.º 27
0
def test_node_contains(connection):
    tree = NodeTree(connection, "DEV1234")

    assert "rate" in tree.demods[0]  # existing child node
    assert "invalid" not in tree.demods[0]  # non existing child node
    assert "node" not in tree.demods[0]  # property
Exemplo n.º 28
0
def test_nodelist_is_node(connection, hdawg):
    nt = NodeTree(connection, "DEV1234")
    bar = NodeList([hdawg], nt, ("foobar", ))
    assert bar[0] == hdawg
    assert bar == Node(nt, ("foobar", ))
Exemplo n.º 29
0
def test_root_node(connection):
    tree = NodeTree(connection, "DEV1234")

    root_node = Node(tree, tuple())
    assert "demods" in root_node
Exemplo n.º 30
0
def test_len(connection):
    tree = NodeTree(connection, "DEV1234")
    assert len(tree.demods) == 4
    with pytest.raises(TypeError):
        len(tree.demods[0])