class TestManagerController(unittest.TestCase):
    maxDiff = None

    def setUp(self):
        self.p = Process("process1")

        # create a child to client
        self.c_child = StatefulController("childBlock")
        self.c_part = MyPart("cp1")
        self.c_child.add_part(self.c_part)
        self.p.add_controller(self.c_child)

        # Create temporary config directory for ProcessController
        self.config_dir = tmp_dir("config_dir")
        self.main_block_name = "mainBlock"
        self.c = ManagerController("mainBlock",
                                   config_dir=self.config_dir.value)
        self.c.add_part(MyPart("part1"))
        self.c.add_part(
            ChildPart("part2", mri="childBlock", initial_visibility=True))
        self.p.add_controller(self.c)
        self.b = self.p.block_view("mainBlock")

        # check that do_initial_reset works asynchronously
        assert self.c.state.value == "Disabled"
        self.p.start()
        assert self.c.state.value == "Ready"

    def tearDown(self):
        self.p.stop(timeout=1)
        shutil.rmtree(self.config_dir.value)

    def test_init(self):
        assert self.c.layout.value.name == ["part2"]
        assert self.c.layout.value.mri == ["childBlock"]
        assert self.c.layout.value.x == [0.0]
        assert self.c.layout.value.y == [0.0]
        assert self.c.layout.value.visible == [True]
        assert self.c.layout.meta.elements["name"].writeable is False
        assert self.c.layout.meta.elements["mri"].writeable is False
        assert self.c.layout.meta.elements["x"].writeable is True
        assert self.c.layout.meta.elements["y"].writeable is True
        assert self.c.layout.meta.elements["visible"].writeable is True
        assert self.c.design.value == ""
        assert self.c.exports.value.source == []
        assert self.c.exports.meta.elements["source"].choices == [
            "part2.health",
            "part2.state",
            "part2.disable",
            "part2.reset",
            "part2.attr",
        ]
        assert self.c.exports.value.export == []
        assert self.c.modified.value is False
        assert self.c.modified.alarm.message == ""
        assert self.b.mri.value == "mainBlock"
        assert self.b.mri.meta.tags == ["sourcePort:block:mainBlock"]

    def _get_design_filename(self, block_name, design_name):
        return f"{self.config_dir.value}/{block_name}/{design_name}.json"

    def check_expected_save(self,
                            design_name,
                            x=0.0,
                            y=0.0,
                            visible="true",
                            attr="defaultv"):
        expected = [
            x.strip() for x in ("""{
          "attributes": {
             "layout": {
               "part2": {
                 "x": %s,
                 "y": %s,
                 "visible": %s
               }
             },
             "exports": {},
             "attr": "defaultv"
          },
          "children": {
             "part2": {
               "attr": "%s"
             }
          }
        }""" % (x, y, visible, attr)).splitlines()
        ]
        with open(self._get_design_filename(self.main_block_name,
                                            design_name)) as f:
            actual = [x.strip() for x in f.readlines()]
        assert actual == expected

    def test_save(self):
        self.c._run_git_cmd = MagicMock()
        assert self.c.design.value == ""
        assert self.c.design.meta.choices == [""]
        c = Context(self.p)
        li = []
        c.subscribe(["mainBlock", "design", "meta"], li.append)
        # Wait for long enough for the other process to get a look in
        c.sleep(0.1)
        assert len(li) == 1
        assert li.pop()["choices"] == [""]
        b = c.block_view("mainBlock")
        design_name = "testSaveLayout"
        b.save(designName=design_name)
        assert len(li) == 3
        assert li[0]["writeable"] is False
        assert li[1]["choices"] == ["", design_name]
        assert li[2]["writeable"] is True
        assert self.c.design.meta.choices == ["", design_name]
        self.check_expected_save(design_name)
        assert self.c.state.value == "Ready"
        assert self.c.design.value == design_name
        assert self.c.modified.value is False
        os.remove(self._get_design_filename(self.main_block_name, design_name))
        self.c_part.attr.set_value("newv")
        assert self.c.modified.value is True
        assert (self.c.modified.alarm.message ==
                "part2.attr.value = 'newv' not 'defaultv'")
        self.c.save(designName="")
        self.check_expected_save(design_name, attr="newv")
        design_filename = self._get_design_filename(self.main_block_name,
                                                    design_name)
        assert self.c.design.value == "testSaveLayout"
        assert self.c._run_git_cmd.call_args_list == [
            call("add", design_filename),
            call(
                "commit",
                "--allow-empty",
                "-m",
                "Saved mainBlock testSaveLayout",
                design_filename,
            ),
            call("add", design_filename),
            call(
                "commit",
                "--allow-empty",
                "-m",
                "Saved mainBlock testSaveLayout",
                design_filename,
            ),
        ]

    def move_child_block(self):
        new_layout = dict(name=["part2"],
                          mri=["anything"],
                          x=[10],
                          y=[20],
                          visible=[True])
        self.b.layout.put_value(new_layout)

    def test_move_child_block_dict(self):
        assert self.b.layout.value.x == [0]
        self.move_child_block()
        assert self.b.layout.value.x == [10]

    def test_set_and_load_layout(self):
        new_layout = LayoutTable(name=["part2"],
                                 mri=["anything"],
                                 x=[10],
                                 y=[20],
                                 visible=[False])
        self.c.set_layout(new_layout)
        assert self.c.parts["part2"].x == 10
        assert self.c.parts["part2"].y == 20
        assert self.c.parts["part2"].visible is False
        assert self.c.modified.value is True
        assert self.c.modified.alarm.message == "layout changed"

        # save the layout, modify and restore it
        design_name = "testSaveLayout"
        self.b.save(designName=design_name)
        assert self.c.modified.value is False
        self.check_expected_save(design_name, 10.0, 20.0, "false")
        self.c.parts["part2"].x = 30
        self.c.set_design(design_name)
        assert self.c.parts["part2"].x == 10

    def test_set_export_parts(self):
        context = Context(self.p)
        b = context.block_view("mainBlock")
        assert list(b) == [
            "meta",
            "health",
            "state",
            "disable",
            "reset",
            "mri",
            "layout",
            "design",
            "exports",
            "modified",
            "save",
            "attr",
        ]
        assert b.attr.meta.tags == ["widget:textinput"]
        new_exports = ExportTable.from_rows([("part2.attr", "childAttr"),
                                             ("part2.reset", "childReset")])
        self.c.set_exports(new_exports)
        assert self.c.modified.value is True
        assert self.c.modified.alarm.message == "exports changed"
        self.c.save(designName="testSaveLayout")
        assert self.c.modified.value is False
        # block has changed, get a new view
        b = context.block_view("mainBlock")
        assert list(b) == [
            "meta",
            "health",
            "state",
            "disable",
            "reset",
            "mri",
            "layout",
            "design",
            "exports",
            "modified",
            "save",
            "attr",
            "childAttr",
            "childReset",
        ]
        assert self.c.state.value == "Ready"
        assert b.childAttr.value == "defaultv"
        assert self.c.modified.value is False
        m = MagicMock()
        b.childAttr.subscribe_value(m)
        # allow a subscription to come through
        context.sleep(0.1)
        m.assert_called_once_with("defaultv")
        m.reset_mock()
        self.c_part.attr.set_value("newv")
        assert b.childAttr.value == "newv"
        assert self.c_part.attr.value == "newv"
        assert self.c.modified.value is True
        assert (self.c.modified.alarm.message ==
                "part2.attr.value = 'newv' not 'defaultv'")
        # allow a subscription to come through
        context.sleep(0.1)
        m.assert_called_once_with("newv")
        b.childAttr.put_value("again")
        assert b.childAttr.value == "again"
        assert self.c_part.attr.value == "again"
        assert self.c.modified.value is True
        assert (self.c.modified.alarm.message ==
                "part2.attr.value = 'again' not 'defaultv'")
        # remove the field
        new_exports = ExportTable([], [])
        self.c.set_exports(new_exports)
        assert self.c.modified.value is True
        self.c.save()
        assert self.c.modified.value is False
        # block has changed, get a new view
        b = context.block_view("mainBlock")
        assert "childAttr" not in b
Esempio n. 2
0
class TestTitlePart(unittest.TestCase):

    def setUp(self):
        self.o = TitlePart(value="My label")
        self.p = Process("proc")
        self.c = BasicController("mri")
        self.c.add_part(self.o)
        self.p.add_controller(self.c)
        self.p.start()
        self.b = self.p.block_view(self.c.mri)

    def tearDown(self):
        self.p.stop(1)

    def test_init(self):
        assert self.o.name == "label"
        assert self.o.attr.value == "My label"
        assert self.o.attr.meta.tags == [
            "widget:textinput", "config:1"]
        assert self.b.meta.label == "My label"

    def test_setter(self):
        self.b.label.put_value("My label2")
        assert self.b.label.value == "My label2"
        assert self.b.meta.label == "My label2"

    def test_concurrency(self):
        q = Queue()
        # Subscribe to the whole block
        sub = Subscribe(id=0, path=["mri"], delta=True)
        sub.set_callback(q.put)
        self.c.handle_request(sub)
        # We should get first Delta through with initial value
        r = q.get().to_dict()
        assert r["id"] == 0
        assert len(r["changes"]) == 1
        assert len(r["changes"][0]) == 2
        assert r["changes"][0][0] == []
        assert r["changes"][0][1]["meta"]["label"] == "My label"
        assert r["changes"][0][1]["label"]["value"] == "My label"
        # Do a Put on the label
        put = Put(id=2, path=["mri", "label", "value"], value="New", get=True)
        put.set_callback(q.put)
        self.c.handle_request(put)
        # Check we got two updates before the return
        r = q.get().to_dict()
        assert r["id"] == 0
        assert len(r["changes"]) == 2
        assert len(r["changes"][0]) == 2
        assert r["changes"][0][0] == ["label", "value"]
        assert r["changes"][0][1] == "New"
        assert len(r["changes"][0]) == 2
        assert r["changes"][1][0] == ["label", "timeStamp"]
        r = q.get().to_dict()
        assert r["id"] == 0
        assert len(r["changes"]) == 1
        assert len(r["changes"][0]) == 2
        assert r["changes"][0][0] == ["meta", "label"]
        assert r["changes"][0][1] == "New"
        # Then the return
        r3 = q.get().to_dict()
        assert r3["id"] == 2
        assert r3["value"] is "New"
class TestPandaPulseTriggerPart(ChildTestCase):
    def setUp(self):
        self.process = Process("Process")
        self.context = Context(self.process)

        # Create a fake PandA with a pulse block
        self.panda = ManagerController("PANDA", "/tmp", use_git=False)
        controller = BasicController("PANDA:PULSE3")
        self.pulse_part = PulsePart("part")
        controller.add_part(self.pulse_part)
        self.process.add_controller(controller)
        self.panda.add_part(
            ChildPart("PULSE3",
                      "PANDA:PULSE3",
                      initial_visibility=True,
                      stateful=False))
        self.process.add_controller(self.panda)

        # And the detector
        self.config_dir = tmp_dir("config_dir")
        for c in detector_block("DET", config_dir=self.config_dir.value):
            self.process.add_controller(c)

        # Make the child block holding panda and pmac mri
        self.child = self.create_child_block(
            panda_pulse_trigger_block,
            self.process,
            mri="SCAN:PULSE",
            panda="PANDA",
            detector="DET",
        )

        # And our part under test
        self.o = PandAPulseTriggerPart("detTrigger", "SCAN:PULSE")

        # Add in a scan block
        self.scan = RunnableController("SCAN", "/tmp", use_git=False)
        self.scan.add_part(DetectorChildPart("det", "DET", True))
        self.scan.add_part(self.o)
        self.process.add_controller(self.scan)

        # Now start the process off and tell the panda which sequencer tables
        # to use
        self.process.start()
        exports = ExportTable.from_rows([
            ("PULSE3.width", "detTriggerWidth"),
            ("PULSE3.step", "detTriggerStep"),
            ("PULSE3.delay", "detTriggerDelay"),
            ("PULSE3.pulses", "detTriggerPulses"),
        ])
        self.panda.set_exports(exports)
        self.tmpdir = tempfile.mkdtemp()

    def tearDown(self):
        self.process.stop(timeout=2)
        shutil.rmtree(self.tmpdir)
        shutil.rmtree(self.config_dir.value)

    def check_pulse_mocks(self, width, step, delay, pulses):
        self.pulse_part.mocks["width"].assert_called_once_with(
            pytest.approx(width))
        self.pulse_part.mocks["step"].assert_called_once_with(
            pytest.approx(step))
        self.pulse_part.mocks["delay"].assert_called_once_with(
            pytest.approx(delay))
        self.pulse_part.mocks["pulses"].assert_called_once_with(pulses)

    def test_configure_multiple_no_exposure(self):
        xs = LineGenerator("x", "mm", 0.0, 0.3, 4)
        ys = LineGenerator("y", "mm", 0.0, 0.1, 2)
        generator = CompoundGenerator([ys, xs], [], [], 1.0)
        generator.prepare()
        detectors = DetectorTable.from_rows([[True, "det", "DET", 0.0, 5]])
        self.o.on_configure(self.context, generator, detectors)
        assert self.o.generator_duration == 1.0
        assert self.o.frames_per_step == 5
        # Detector would normally be configured by DetectorChildPart
        detector = self.process.block_view("DET")
        spg = StaticPointGenerator(5, axes=["det_frames_per_step"])
        ex = SquashingExcluder(axes=["det_frames_per_step", "x"])
        generatormultiplied = CompoundGenerator([ys, xs, spg], [ex], [], 0.2)
        detector.configure(generatormultiplied, self.tmpdir)

        self.o.on_post_configure()

        self.check_pulse_mocks(0.19899, 0.2, 0.000505, 5)

    def test_configure_multiple_no_exposure_with_zero_delay(self):
        xs = LineGenerator("x", "mm", 0.0, 0.3, 4)
        ys = LineGenerator("y", "mm", 0.0, 0.1, 2)
        generator = CompoundGenerator([ys, xs], [], [], 1.0)
        generator.prepare()
        detectors = DetectorTable.from_rows([[True, "det", "DET", 0.0, 5]])
        # Set delay to zero (normally done in constructor)
        self.o.zero_delay = True
        self.o.on_configure(self.context, generator, detectors)
        assert self.o.generator_duration == 1.0
        assert self.o.frames_per_step == 5
        # Detector would normally be configured by DetectorChildPart
        detector = self.process.block_view("DET")
        spg = StaticPointGenerator(5, axes=["det_frames_per_step"])
        ex = SquashingExcluder(axes=["det_frames_per_step", "x"])
        generatormultiplied = CompoundGenerator([ys, xs, spg], [ex], [], 0.2)
        detector.configure(generatormultiplied, self.tmpdir)

        self.o.on_post_configure()

        self.check_pulse_mocks(0.19899, 0.2, 0.0, 5)

    def test_system(self):
        xs = LineGenerator("x", "mm", 0.0, 0.3, 4)
        ys = LineGenerator("y", "mm", 0.0, 0.1, 2)
        generator = CompoundGenerator([ys, xs], [], [], 1.0)
        generator.prepare()
        detectors = DetectorTable.from_rows([[True, "det", "DET", 0.0, 5]])

        b = self.scan.block_view()
        b.configure(generator, self.tmpdir, detectors=detectors)

        self.check_pulse_mocks(0.19899, 0.2, 0.000505, 5)

    def test_system_defined_exposure(self):
        xs = LineGenerator("x", "mm", 0.0, 0.3, 4)
        ys = LineGenerator("y", "mm", 0.0, 0.1, 2)
        generator = CompoundGenerator([ys, xs], [], [], 1.0)
        generator.prepare()
        detectors = DetectorTable.from_rows([[True, "det", "DET", 0.1, 5]])

        b = self.scan.block_view()
        b.configure(generator, self.tmpdir, detectors=detectors)

        self.check_pulse_mocks(0.1, 0.2, 0.05, 5)

    def test_on_validate_tweaks_zero_duration(self):
        points = StaticPointGenerator(10)
        generator = CompoundGenerator([points], [], [], 0.0)
        generator.prepare()
        # Disable the detector
        detectors = DetectorTable.from_rows([[False, "det", "DET", 0.0, 5]])
        # Expected duration is 2 clock cycles
        expected_duration = 2 * 8.0e-9

        b = self.scan.block_view()
        params = b.validate(generator, self.tmpdir, detectors=detectors)

        self.assertEqual(expected_duration, params["generator"]["duration"])

    def test_on_validate_raises_AssertionError_for_negative_duration(self):
        xs = LineGenerator("x", "mm", 0.0, 0.3, 4)
        ys = LineGenerator("y", "mm", 0.0, 0.1, 2)
        generator = CompoundGenerator([ys, xs], [], [], -1.0)
        generator.prepare()
        # Disable the detector
        detectors = DetectorTable.from_rows([[False, "det", "DET", 0.0, 5]])

        b = self.scan.block_view()
        self.assertRaises(AssertionError,
                          b.validate,
                          generator,
                          self.tmpdir,
                          detectors=detectors)
Esempio n. 4
0
class TestCAParts(unittest.TestCase):
    def setUp(self):
        self.process = Process("proc")
        self.process.start()

    def create_block(self, p, mri="mri"):
        c = StatefulController(mri)
        c.add_part(p)
        self.process.add_controller(c)
        b = self.process.block_view(mri)
        return b

    def tearDown(self):
        self.process.stop(timeout=2)

    def test_caboolean(self, catools):
        from malcolm.modules.ca.parts import CABooleanPart

        class Initial(int):
            ok = True
            severity = 0

        catools.caget.side_effect = [[Initial(0), Initial(0)]]
        b = self.create_block(
            CABooleanPart(name="attrname", description="desc", pv="pv", rbv_suffix="2")
        )
        assert b.attrname.value is False
        assert b.attrname.meta.description == "desc"
        assert b.attrname.meta.writeable
        catools.caget.assert_called_once_with(
            ["pv2", "pv"],
            datatype=catools.DBR_LONG,
            format=catools.FORMAT_CTRL,
            throw=True,
        )
        catools.caget.reset_mock()

        class Update(int):
            ok = True
            severity = 0
            raw_stamp = (34, 4355)

        catools.caget.side_effect = [Update(1)]
        b.attrname.put_value(True)
        catools.caput.assert_called_once_with(
            "pv", 1, datatype=catools.DBR_LONG, timeout=10.0, wait=True
        )
        catools.caget.assert_called_once_with(
            "pv2", datatype=catools.DBR_LONG, format=catools.FORMAT_TIME, throw=True
        )
        assert b.attrname.value is True
        assert b.attrname.alarm.is_ok()
        assert b.attrname.timeStamp.to_time() == 34.000004355

    def test_cachararray(self, catools):
        from malcolm.modules.ca.parts import CACharArrayPart

        class Initial(str):
            ok = True
            severity = 1

        catools.caget.side_effect = [[Initial("long_and_bad_string")]]
        b = self.create_block(
            CACharArrayPart(name="cattr", description="desc", rbv="pvr")
        )
        assert b.cattr.value == "long_and_bad_string"
        assert b.cattr.alarm.severity == AlarmSeverity.MINOR_ALARM
        catools.caget.assert_called_once_with(
            ["pvr"],
            datatype=catools.DBR_CHAR_STR,
            format=catools.FORMAT_CTRL,
            throw=True,
        )

    def test_cachoice(self, catools):
        from malcolm.modules.ca.parts import CAChoicePart

        class Initial(int):
            ok = True
            severity = 0
            enums = ["a", "b", "c"]

        catools.caget.side_effect = [[Initial(1), Initial(2)]]
        b = self.create_block(
            CAChoicePart(name="attrname", description="desc", pv="pv", rbv="rbv")
        )
        assert b.attrname.value == "b"
        assert b.attrname.meta.description == "desc"
        assert b.attrname.meta.writeable
        catools.caget.assert_called_once_with(
            ["rbv", "pv"],
            datatype=catools.DBR_ENUM,
            format=catools.FORMAT_CTRL,
            throw=True,
        )
        catools.caget.reset_mock()

        class Update(int):
            ok = True
            severity = 2
            raw_stamp = (34, 4355)

        catools.caget.side_effect = [Update(0)]
        b.attrname.put_value("c")
        catools.caput.assert_called_once_with(
            "pv", 2, datatype=catools.DBR_ENUM, timeout=10.0, wait=True
        )
        catools.caget.assert_called_once_with(
            "rbv", datatype=catools.DBR_ENUM, format=catools.FORMAT_TIME, throw=True
        )
        assert b.attrname.value == "a"
        assert b.attrname.alarm.severity == AlarmSeverity.MAJOR_ALARM
        assert b.attrname.timeStamp.to_time() == 34.000004355
        catools.caget.reset_mock()
        catools.caput.reset_mock()

        catools.caget.side_effect = [Update(1)]
        b.attrname.put_value(1)
        catools.caput.assert_called_once_with(
            "pv", 1, datatype=catools.DBR_ENUM, timeout=10.0, wait=True
        )
        assert b.attrname.value == "b"

    def test_cadoublearray(self, catools):
        from malcolm.modules.ca.parts import CADoubleArrayPart

        class Initial(np.ndarray):
            ok = True
            severity = 0
            precision = 5
            units = ""
            lower_disp_limit = -1.0
            upper_disp_limit = 10.0

        initial = Initial(dtype=np.float64, shape=(3,))
        initial[:] = np.arange(3) + 1.2

        catools.caget.side_effect = [[initial]]
        b = self.create_block(
            CADoubleArrayPart(name="attrname", description="desc", pv="pv", timeout=-1)
        )

        assert list(b.attrname.value) == [1.2, 2.2, 3.2]
        assert b.attrname.meta.description == "desc"
        assert b.attrname.meta.writeable
        assert b.attrname.meta.display.limitLow == -1.0
        assert b.attrname.meta.display.limitHigh == 10.0
        assert b.attrname.meta.display.precision == 5
        catools.caget.assert_called_once_with(
            ["pv"], datatype=catools.DBR_DOUBLE, format=catools.FORMAT_CTRL, throw=True
        )
        catools.caget.reset_mock()

        class Update(np.ndarray):
            ok = False

        catools.caget.side_effect = [Update(shape=(6,))]
        b.attrname.put_value([])
        catools.caput.assert_called_once_with(
            "pv", ANY, datatype=catools.DBR_DOUBLE, timeout=None, wait=True
        )
        assert list(catools.caput.call_args[0][1]) == []
        catools.caget.assert_called_once_with(
            "pv", datatype=catools.DBR_DOUBLE, format=catools.FORMAT_TIME, throw=True
        )
        assert list(b.attrname.value) == [1.2, 2.2, 3.2]
        assert b.attrname.alarm.severity == AlarmSeverity.UNDEFINED_ALARM

    def test_cawaveformtable(self, catools):
        from malcolm.modules.ca.parts import CAWaveformTablePart

        class Initial(np.ndarray):
            ok = True
            severity = 0
            precision = 7
            units = ""
            lower_disp_limit = 0.0
            upper_disp_limit = 0.0

        initialY = Initial(dtype=np.float64, shape=(3,))
        initialY.lower_disp_limit = np.e
        initialY.upper_disp_limit = 10.0
        initialY.name = "yPv"
        initialY[:] = np.arange(3) + 1.2
        initialX = Initial(dtype=np.float64, shape=(3,))
        initialX.upper_disp_limit = np.pi
        initialX.name = "xPv"
        initialX.units = "s"
        initialX[:] = (np.arange(3) + 1) ** 2
        initial = {"yPv": initialY, "xPv": initialX}

        def mock_get(pvs, **kwargs):
            return_vals = []
            for pv in pvs:
                return_vals.append(initial[pv])
            return return_vals

        catools.caget.side_effect = mock_get
        c = self.create_block(
            CAWaveformTablePart(
                name="attrname",
                description="desc",
                pv_list=(
                    "yPv",
                    "xPv",
                ),
                name_list=(
                    "yData",
                    "xData",
                ),
                timeout=-1,
            ),
            "withDisplayFromPv",
        )

        assert isinstance(c.attrname.value, Table)

        assert c.attrname.value["yData"] == [1.2, 2.2, 3.2]
        assert c.attrname.meta.description == "desc"
        assert not c.attrname.meta.writeable
        assert c.attrname.meta.elements["yData"].display.limitLow == np.e
        assert c.attrname.meta.elements["yData"].display.limitHigh == 10.0
        assert c.attrname.meta.elements["yData"].display.precision == 7
        assert c.attrname.meta.elements["xData"].display.limitLow == 0.0
        assert c.attrname.meta.elements["xData"].display.limitHigh == np.pi
        assert c.attrname.meta.elements["xData"].display.units == "s"

        catools.caget.assert_called_with(
            ("yPv", "xPv"),
            datatype=catools.DBR_DOUBLE,
            format=catools.FORMAT_CTRL,
            throw=True,
        )

        catools.caget.reset_mock()

    def test_cadouble(self, catools):
        from malcolm.modules.ca.parts import CADoublePart

        class Initial(float):
            ok = True
            severity = 0
            precision = 99
            lower_disp_limit = 189
            upper_disp_limit = 1527
            units = "tests"

        catools.caget.side_effect = [[Initial(5.2)], [Initial(5.2)]]
        b = self.create_block(
            CADoublePart(
                name="attrname", description="desc", rbv="pv", display_from_pv=False
            ),
            "noDisplayFromPv",
        )
        assert b.attrname.value == 5.2
        assert b.attrname.meta.description == "desc"
        assert not b.attrname.meta.writeable

        assert b.attrname.meta.display.limitLow == 0.0
        assert b.attrname.meta.display.limitHigh == 0.0
        assert b.attrname.meta.display.precision == 8
        assert b.attrname.meta.display.units == ""

        catools.caget.assert_called_once_with(
            ["pv"], datatype=catools.DBR_DOUBLE, format=catools.FORMAT_CTRL, throw=True
        )

        li = []
        b.attrname.subscribe_value(li.append)
        b._context.sleep(0.1)
        assert li == [5.2]

        catools.camonitor.assert_called_once()
        callback = catools.camonitor.call_args[0][1]
        callback(Initial(8.7))
        callback(Initial(8.8))
        assert b.attrname.value == 8.8

        # TODO: why does this seg fault on travis VMs when cothread is
        # stack sharing?
        b._context.sleep(0.1)
        assert li == [5.2, 8.7, 8.8]

        c = self.create_block(
            CADoublePart(name="attrname", description="desc", rbv="pv"),
            "withDisplayFromPv",
        )

        assert c.attrname.meta.display.limitLow == 189
        assert c.attrname.meta.display.limitHigh == 1527
        assert c.attrname.meta.display.precision == 99
        assert c.attrname.meta.display.units == "tests"

    def test_calongarray(self, catools):
        from malcolm.modules.ca.parts import CALongArrayPart

        class Initial(np.ndarray):
            ok = True
            severity = 0

        initial = Initial(dtype=np.int32, shape=(4,))
        initial[:] = [5, 6, 7, 8]

        catools.caget.side_effect = [[initial]]
        b = self.create_block(
            CALongArrayPart(
                name="attrname", description="desc", pv="pv", widget=Widget.TEXTINPUT
            )
        )
        assert list(b.attrname.value) == [5, 6, 7, 8]
        assert b.attrname.meta.tags == ["widget:textinput", "config:1"]
        assert b.attrname.meta.writeable
        catools.caget.assert_called_once_with(
            ["pv"], datatype=catools.DBR_LONG, format=catools.FORMAT_CTRL, throw=True
        )
        catools.caget.reset_mock()

        class Update(np.ndarray):
            ok = True
            severity = 0

        update = Update(shape=(2,), dtype=np.int32)
        update[:] = [4, 5]

        catools.caget.side_effect = [update]
        b.attrname.put_value([4, 4.2])
        catools.caput.assert_called_once_with(
            "pv", ANY, datatype=catools.DBR_LONG, timeout=10.0, wait=True
        )
        assert list(catools.caput.call_args[0][1]) == [4, 4]
        catools.caget.assert_called_once_with(
            "pv", datatype=catools.DBR_LONG, format=catools.FORMAT_TIME, throw=True
        )
        assert list(b.attrname.value) == [4, 5]
        assert b.attrname.alarm.is_ok()

    def test_calong(self, catools):
        from malcolm.modules.ca.parts import CALongPart

        class Initial(int):
            ok = True
            severity = 0

        catools.caget.side_effect = [[Initial(3)]]
        b = self.create_block(CALongPart(name="attrname", description="desc", pv="pv"))
        assert b.attrname.value == 3
        assert b.attrname.meta.description == "desc"
        assert b.attrname.meta.writeable
        catools.caget.assert_called_once_with(
            ["pv"], datatype=catools.DBR_LONG, format=catools.FORMAT_CTRL, throw=True
        )

    def test_castring(self, catools):
        from malcolm.modules.ca.parts import CAStringPart

        class Initial(str):
            ok = True
            severity = 0

        catools.caget.side_effect = [[Initial("thing")]]
        b = self.create_block(
            CAStringPart(name="attrname", description="desc", rbv="pv")
        )
        assert b.attrname.value == "thing"
        assert b.attrname.meta.description == "desc"
        assert not b.attrname.meta.writeable
        catools.caget.assert_called_once_with(
            ["pv"], datatype=catools.DBR_STRING, format=catools.FORMAT_CTRL, throw=True
        )

    def test_init_no_pv_no_rbv(self, catools):
        from malcolm.modules.ca.parts import CABooleanPart

        # create test for no pv or rbv
        with self.assertRaises(ValueError):
            CABooleanPart(name="attrname", description="desc")
Esempio n. 5
0
class TestSystemPVA(unittest.TestCase):
    def setUp(self):
        self.process = Process("proc")
        for controller in \
                ticker_block(mri="TICKER", config_dir="/tmp") + \
                pva_server_block(mri="PVA-SERVER"):
            self.process.add_controller(controller)
        self.process.start()
        self.process2 = Process("proc2")
        for controller in \
                pva_client_block(mri="PVA-CLIENT") + \
                proxy_block(mri="TICKER", comms="PVA-CLIENT"):
            self.process2.add_controller(controller)
        self.process2.start()

    def tearDown(self):
        self.process.stop(timeout=2)
        self.process2.stop(timeout=2)

    def make_generator(self):
        line1 = LineGenerator('y', 'mm', 0, 3, 3)
        line2 = LineGenerator('x', 'mm', 1, 2, 2)
        compound = CompoundGenerator([line1, line2], [], [], duration=0.05)
        return compound

    def check_blocks_equal(self):
        src_block = self.process.block_view("TICKER")
        block = self.process2.block_view("TICKER")
        for k in src_block:
            assert block[k].to_dict() == src_block[k].to_dict()

    def test_init(self):
        self.check_blocks_equal()

    def test_validate(self):
        block = self.process2.block_view("TICKER")
        generator = self.make_generator()
        params = block.validate(generator, axesToMove=["x", "y"])
        assert params == dict(
            axesToMove=["x", "y"],
            exceptionStep=0,
            generator=generator.to_dict(),
        )

    def test_configure(self):
        block = self.process2.block_view("TICKER")
        generator = self.make_generator()
        block.configure(generator, axesToMove=["x", "y"])
        # TODO: ordering is not maintained in PVA, so need to wait before get
        # block._context.sleep(0.1)
        assert "Armed" == block.state.value
        self.check_blocks_equal()

    def test_exports(self):
        block = self.process2.block_view("TICKER")
        fields = [
            'meta', 'health', 'state', 'disable', 'reset', 'mri', 'layout',
            'design', 'exports', 'modified', 'save', 'completedSteps',
            'configuredSteps', 'totalSteps', 'validate', 'configure', 'run',
            'abort', 'pause', 'resume'
        ]
        assert list(block) == fields
        generator = self.make_generator()
        block.configure(generator, axesToMove=["x", "y"])
        block.run()
        # Export X
        t = ExportTable(source=["x.counter"], export=["xValue"])
        block.exports.put_value(t)
        assert list(block) == fields + ["xValue"]
        assert block.xValue.value == 2.0
        # Export Y
        t = ExportTable(source=["y.counter"], export=["yValue"])
        block.exports.put_value(t)
        assert list(block) == fields + ["yValue"]
        assert block.yValue.value == 3.0
        # Export Nothing
        t = ExportTable(source=[], export=[])
        block.exports.put_value(t)
        assert list(block) == fields
Esempio n. 6
0
class TestPMACTrajectoryPart(ChildTestCase):
    def setUp(self):
        self.process = Process("Process")
        self.context = Context(self.process)
        self.cs = self.create_child_block(cs_block,
                                          self.process,
                                          mri="PMAC:CS1",
                                          prefix="PV:CSPRE")
        self.child = self.create_child_block(pmac_trajectory_block,
                                             self.process,
                                             mri="PMAC:TRAJ",
                                             prefix="PV:PRE")
        self.o = PmacTrajectoryPart(name="pmac", mri="PMAC:TRAJ")
        self.process.start()

    def tearDown(self):
        del self.context
        self.process.stop(timeout=1)

    def test_init(self):
        registrar = Mock()
        self.o.setup(registrar)
        registrar.add_attribute_model.assert_called_once_with(
            "minTurnaround", self.o.min_turnaround,
            self.o.min_turnaround.set_value)

    def test_bad_units(self):
        with self.assertRaises(AssertionError) as cm:
            self.do_configure(["x", "y"], units="m")
        assert str(cm.exception) == "x: Expected scan units of 'm', got 'mm'"

    def resolutions_and_use_call(self, useB=True):
        return [
            call.put('useA', True),
            call.put('useB', useB),
            call.put('useC', False),
            call.put('useU', False),
            call.put('useV', False),
            call.put('useW', False),
            call.put('useX', False),
            call.put('useY', False),
            call.put('useZ', False)
        ]

    def make_part_info(self, x_pos=0.5, y_pos=0.0, units="mm"):
        part_info = dict(xpart=[
            MotorInfo(cs_axis="A",
                      cs_port="CS1",
                      acceleration=2.5,
                      resolution=0.001,
                      offset=0.0,
                      max_velocity=1.0,
                      current_position=x_pos,
                      scannable="x",
                      velocity_settle=0.0,
                      units=units)
        ],
                         ypart=[
                             MotorInfo(cs_axis="B",
                                       cs_port="CS1",
                                       acceleration=2.5,
                                       resolution=0.001,
                                       offset=0.0,
                                       max_velocity=1.0,
                                       current_position=y_pos,
                                       scannable="y",
                                       velocity_settle=0.0,
                                       units=units)
                         ],
                         brick=[ControllerInfo(i10=1705244)],
                         cs1=[CSInfo(mri="PMAC:CS1", port="CS1")])
        return part_info

    def do_configure(self,
                     axes_to_scan,
                     completed_steps=0,
                     x_pos=0.5,
                     y_pos=0.0,
                     duration=1.0,
                     units="mm"):
        part_info = self.make_part_info(x_pos, y_pos, units)
        steps_to_do = 3 * len(axes_to_scan)
        xs = LineGenerator("x", "mm", 0.0, 0.5, 3, alternate=True)
        ys = LineGenerator("y", "mm", 0.0, 0.1, 2)
        generator = CompoundGenerator([ys, xs], [], [], duration)
        generator.prepare()
        self.o.configure(self.context, completed_steps, steps_to_do, part_info,
                         generator, axes_to_scan)

    def test_validate(self):
        generator = CompoundGenerator([], [], [], 0.0102)
        axesToMove = ["x"]
        part_info = self.make_part_info()
        ret = self.o.validate(part_info, generator, axesToMove)
        expected = 0.010166
        assert ret.value.duration == expected

    @patch(
        "malcolm.modules.pmac.parts.pmactrajectorypart.INTERPOLATE_INTERVAL",
        0.2)
    def test_configure(self):
        # Pretend to respond on demand values before they are actually set
        self.set_attributes(self.cs, demandA=-0.1375, demandB=0.0)
        self.do_configure(axes_to_scan=["x", "y"])
        assert self.cs.handled_requests.mock_calls == [
            call.put('deferMoves', True),
            call.put('csMoveTime', 0),
            call.put('demandA', -0.1375),
            call.put('demandB', 0.0),
            call.put('deferMoves', False)
        ]
        assert self.child.handled_requests.mock_calls == [
            call.put('numPoints', 4000000),
            call.put('cs', 'CS1'),
            call.put('useA', False),
            call.put('useB', False),
            call.put('useC', False),
            call.put('useU', False),
            call.put('useV', False),
            call.put('useW', False),
            call.put('useX', False),
            call.put('useY', False),
            call.put('useZ', False),
            call.put('pointsToBuild', 1),
            call.put('timeArray', pytest.approx([2000])),
            call.put('userPrograms', pytest.approx([8])),
            call.put('velocityMode', pytest.approx([3])),
            call.post('buildProfile'),
            call.post('executeProfile'),
        ] + self.resolutions_and_use_call() + [
            call.put('pointsToBuild', 16),
            # pytest.approx to allow sensible compare with numpy arrays
            call.put(
                'positionsA',
                pytest.approx([
                    -0.125, 0.0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.6375, 0.625,
                    0.5, 0.375, 0.25, 0.125, 0.0, -0.125, -0.1375
                ])),
            call.put(
                'positionsB',
                pytest.approx([
                    0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.05, 0.1, 0.1, 0.1,
                    0.1, 0.1, 0.1, 0.1, 0.1
                ])),
            call.put(
                'timeArray',
                pytest.approx([
                    100000, 500000, 500000, 500000, 500000, 500000, 500000,
                    200000, 200000, 500000, 500000, 500000, 500000, 500000,
                    500000, 100000
                ])),
            call.put(
                'userPrograms',
                pytest.approx([1, 4, 1, 4, 1, 4, 2, 8, 1, 4, 1, 4, 1, 4, 2, 8
                               ])),
            call.put(
                'velocityMode',
                pytest.approx([2, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 0, 0, 1, 3
                               ])),
            call.post('buildProfile')
        ]
        assert self.o.completed_steps_lookup == [
            0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 5, 5, 6, 6
        ]

    @patch("malcolm.modules.pmac.parts.pmactrajectorypart.PROFILE_POINTS", 4)
    @patch(
        "malcolm.modules.pmac.parts.pmactrajectorypart.INTERPOLATE_INTERVAL",
        0.2)
    def test_update_step(self):
        # Pretend to respond on demand values before they are actually set
        self.set_attributes(self.cs, demandA=-0.1375, demandB=0.0)
        self.do_configure(axes_to_scan=["x", "y"], x_pos=0.0, y_pos=0.2)
        positionsA = self.child.handled_requests.put.call_args_list[-5][0][1]
        assert len(positionsA) == 4
        assert positionsA[-1] == 0.25
        assert self.o.end_index == 2
        assert len(self.o.completed_steps_lookup) == 5
        assert len(self.o.profile["time_array"]) == 1
        self.o.registrar = Mock()
        self.child.handled_requests.reset_mock()
        self.o.update_step(3, self.context.block_view("PMAC:TRAJ"))
        self.o.registrar.report.assert_called_once()
        assert self.o.registrar.report.call_args[0][0].steps == 1
        assert not self.o.loading
        assert self.child.handled_requests.mock_calls == [
            call.put('pointsToBuild', 4),
            call.put('positionsA', pytest.approx([0.375, 0.5, 0.625, 0.6375])),
            call.put('positionsB', pytest.approx([0.0, 0.0, 0.0, 0.05])),
            call.put('timeArray',
                     pytest.approx([500000, 500000, 500000, 200000])),
            call.put('userPrograms', pytest.approx([1, 4, 2, 8])),
            call.put('velocityMode', pytest.approx([0, 0, 1, 0])),
            call.post('appendProfile')
        ]
        assert self.o.end_index == 3
        assert len(self.o.completed_steps_lookup) == 9
        assert len(self.o.profile["time_array"]) == 1

    def test_run(self):
        self.o.run(self.context)
        assert self.child.handled_requests.mock_calls == [
            call.post('executeProfile')
        ]

    def test_reset(self):
        self.o.reset(self.context)
        assert self.child.handled_requests.mock_calls == [
            call.post('abortProfile')
        ]

    def test_multi_run(self):
        # Pretend to respond on demand values before they are actually set
        self.set_attributes(self.cs, demandA=-0.1375)
        self.do_configure(axes_to_scan=["x"])
        assert self.o.completed_steps_lookup == ([0, 0, 1, 1, 2, 2, 3, 3])
        self.child.handled_requests.reset_mock()
        # Pretend to respond on demand values before they are actually set
        self.set_attributes(self.cs, demandA=0.6375)
        self.do_configure(axes_to_scan=["x"], completed_steps=3, x_pos=0.6375)
        assert self.child.handled_requests.mock_calls == [
            call.put('numPoints', 4000000),
            call.put('cs', 'CS1'),
            call.put('useA', False),
            call.put('useB', False),
            call.put('useC', False),
            call.put('useU', False),
            call.put('useV', False),
            call.put('useW', False),
            call.put('useX', False),
            call.put('useY', False),
            call.put('useZ', False),
            call.put('pointsToBuild', 1),
            call.put('timeArray', pytest.approx([2000])),
            call.put('userPrograms', pytest.approx([8])),
            call.put('velocityMode', pytest.approx([3])),
            call.post('buildProfile'),
            call.post('executeProfile'),
        ] + self.resolutions_and_use_call(useB=False) + [
            call.put('pointsToBuild', 8),
            call.put(
                'positionsA',
                pytest.approx(
                    [0.625, 0.5, 0.375, 0.25, 0.125, 0.0, -0.125, -0.1375])),
            call.put(
                'timeArray',
                pytest.approx([
                    100000, 500000, 500000, 500000, 500000, 500000, 500000,
                    100000
                ])),
            call.put('userPrograms', pytest.approx([1, 4, 1, 4, 1, 4, 2, 8])),
            call.put('velocityMode', pytest.approx([2, 0, 0, 0, 0, 0, 1, 3])),
            call.post('buildProfile')
        ]

    @patch(
        "malcolm.modules.pmac.parts.pmactrajectorypart.INTERPOLATE_INTERVAL",
        0.2)
    def test_long_steps_lookup(self):
        # Pretend to respond on demand values before they are actually set
        self.set_attributes(self.cs, demandA=0.6250637755102041)
        self.do_configure(axes_to_scan=["x"],
                          completed_steps=3,
                          x_pos=0.62506,
                          duration=14.0)
        assert self.child.handled_requests.mock_calls[-6:] == [
            call.put('pointsToBuild', 14),
            call.put(
                'positionsA',
                pytest.approx([
                    0.625, 0.5625, 0.5, 0.4375, 0.375, 0.3125, 0.25, 0.1875,
                    0.125, 0.0625, 0.0, -0.0625, -0.125, -0.12506377551020409
                ])),
            call.put(
                'timeArray',
                pytest.approx([
                    7143, 3500000, 3500000, 3500000, 3500000, 3500000, 3500000,
                    3500000, 3500000, 3500000, 3500000, 3500000, 3500000, 7143
                ])),
            call.put('userPrograms',
                     pytest.approx([1, 0, 4, 0, 1, 0, 4, 0, 1, 0, 4, 0, 2,
                                    8])),
            call.put('velocityMode',
                     pytest.approx([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                                    3])),
            call.post('buildProfile')
        ]
        assert self.o.completed_steps_lookup == ([
            3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6
        ])

    @patch("malcolm.modules.pmac.parts.pmactrajectorypart.PROFILE_POINTS", 9)
    def test_split_in_a_long_step_lookup(self):
        # Pretend to respond on demand values before they are actually set
        self.set_attributes(self.cs, demandA=0.6250637755102041)
        self.do_configure(axes_to_scan=["x"],
                          completed_steps=3,
                          x_pos=0.62506,
                          duration=14.0)
        # The last 6 calls show what trajectory we are building, ignore the
        # first 11 which are just the useX calls and cs selection
        assert self.child.handled_requests.mock_calls[-6:] == [
            call.put('pointsToBuild', 9),
            call.put(
                'positionsA',
                pytest.approx([
                    0.625, 0.5625, 0.5, 0.4375, 0.375, 0.3125, 0.25, 0.1875,
                    0.125
                ])),
            call.put(
                'timeArray',
                pytest.approx([
                    7143, 3500000, 3500000, 3500000, 3500000, 3500000, 3500000,
                    3500000, 3500000
                ])),
            call.put('userPrograms', pytest.approx([1, 0, 4, 0, 1, 0, 4, 0,
                                                    1])),
            call.put('velocityMode', pytest.approx([2, 0, 0, 0, 0, 0, 0, 0,
                                                    0])),
            call.post('buildProfile')
        ]
        # The completed steps works on complete (not split) steps, so we expect
        # the last value to be the end of step 6, even though it doesn't
        # actually appear in the velocity arrays
        assert self.o.completed_steps_lookup == ([
            3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6
        ])
        # Mock out the registrar that would have been registered when we
        # attached to a controller
        self.o.registrar = Mock()
        # Now call update step and get it to generate the next lot of points
        # scanned can be any index into completed_steps_lookup so that there
        # are less than PROFILE_POINTS left to go in it
        self.o.update_step(scanned=2,
                           child=self.process.block_view("PMAC:TRAJ"))
        # Expect the rest of the points
        assert self.child.handled_requests.mock_calls[-6:] == [
            call.put('pointsToBuild', 5),
            call.put(
                'positionsA',
                pytest.approx(
                    [0.0625, 0.0, -0.0625, -0.125, -0.12506377551020409])),
            call.put('timeArray',
                     pytest.approx([3500000, 3500000, 3500000, 3500000,
                                    7143])),
            call.put('userPrograms', pytest.approx([0, 4, 0, 2, 8])),
            call.put('velocityMode', pytest.approx([0, 0, 0, 1, 3])),
            call.post('appendProfile')
        ]
        assert self.o.registrar.report.call_count == 1
        assert self.o.registrar.report.call_args[0][0].steps == 3
        # And for the rest of the lookup table to be added
        assert self.o.completed_steps_lookup == ([
            3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6
        ])
Esempio n. 7
0
class TestRawMotorCSPart(unittest.TestCase):
    def setUp(self):
        self.process = Process("proc")
        self.o = RawMotorCSPart("cs", "PV:PRE")
        c = StatefulController("mri")
        c.add_part(self.o)
        self.process.add_controller(c)
        self.b = self.process.block_view("mri")
        self.addCleanup(self.process.stop)
        
    def do_init(self, catools):
        catools.caget.side_effect = [[
            caenum(2), castr("I"),
            caenum(1), castr("A")
        ]]
        self.process.start()

    def test_init(self, catools):
        self.do_init(catools)
        catools.caget.assert_called_once_with(
            ["PV:PRE:CsPort", "PV:PRE:CsAxis", "PV:PRE:CsPort_RBV",
             "PV:PRE:CsAxis_RBV"], format=catools.FORMAT_CTRL)
        assert list(self.b) == [
            'meta', 'health', 'state', 'disable', 'reset', 'cs']
        assert self.b.cs.value == "BRICK1CS1,A"

    def test_update_axis(self, catools):
        self.do_init(catools)
        update = castr("I")
        self.o._update_value(update, 1)
        assert self.b.cs.value == "BRICK1CS1,I"

    def test_update_port(self, catools):
        self.do_init(catools)
        update = caenum(2)
        self.o._update_value(update, 0)
        assert self.b.cs.value == "BRICK1CS2,A"

    def test_update_disconnect(self, catools):
        self.do_init(catools)
        update = caenum(0)
        self.o._update_value(update, 0)
        assert self.b.cs.value == ""

    def test_update_bad(self, catools):
        self.do_init(catools)
        update = castr("")
        update.ok = False
        self.o._update_value(update, 1)
        assert self.b.cs.value == ""
        assert self.b.cs.alarm.severity == AlarmSeverity.INVALID_ALARM

    def test_caput(self, catools):
        self.do_init(catools)
        catools.caget.side_effect = [[caenum(2), castr("Y")]]
        self.o.caput("BRICK1CS2,X")
        catools.caput.assert_called_once_with(
            ['PV:PRE:CsPort', 'PV:PRE:CsAxis'], (2, 'X'), wait=True
        )
        assert self.b.cs.value == "BRICK1CS2,Y"

    def test_caput_none(self, catools):
        self.do_init(catools)
        catools.caget.side_effect = [[caenum(0), castr("")]]
        self.o.caput("")
        catools.caput.assert_called_once_with(
            ['PV:PRE:CsPort', 'PV:PRE:CsAxis'], (0, ''), wait=True
        )
        assert self.b.cs.value == ""
Esempio n. 8
0
class TestDetectorBlock(unittest.TestCase):
    def setUp(self):
        self.p = Process("proc")
        for c in detector_block("mri", config_dir="/tmp"):
            self.p.add_controller(c)
        self.p.start()
        self.b = self.p.block_view("mri")
        self.tmpdir = tempfile.mkdtemp()

    def tearDown(self):
        self.p.stop(timeout=2)
        shutil.rmtree(self.tmpdir)

    def test_init(self):
        assert list(self.b) == [
            "meta",
            "health",
            "state",
            "disable",
            "reset",
            "mri",
            "layout",
            "design",
            "exports",
            "modified",
            "save",
            "completedSteps",
            "configuredSteps",
            "totalSteps",
            "validate",
            "configure",
            "run",
            "abort",
            "pause",
            "resume",
            "label",
            "datasets",
            "readoutTime",
            "frequencyAccuracy",
            "exposure",
        ]
        assert list(self.b.configure.meta.takes.elements) == [
            "generator",
            "fileDir",
            "axesToMove",
            "breakpoints",
            "exposure",
            "formatName",
            "fileTemplate",
        ]
        assert self.b.label.value == "DemoDetector"

    def make_generator(self):
        linex = LineGenerator("stage_x", "mm", 0, 2, 3, alternate=True)
        liney = LineGenerator("stage_y", "mm", 0, 2, 2)
        compound = CompoundGenerator([liney, linex], [], [], 0.5)
        return compound

    def test_scan(self):
        self.b.configure(self.make_generator(), self.tmpdir)
        assert list(self.b.datasets.value.rows()) == [
            [
                "det.data", "det.h5", DatasetType.PRIMARY, 4, "/entry/data",
                "/entry/uid"
            ],
            [
                "det.sum", "det.h5", DatasetType.SECONDARY, 4, "/entry/sum",
                "/entry/uid"
            ],
            [
                "stage_y.value_set",
                "det.h5",
                DatasetType.POSITION_SET,
                1,
                "/entry/stage_y_set",
                "",
            ],
            [
                "stage_x.value_set",
                "det.h5",
                DatasetType.POSITION_SET,
                1,
                "/entry/stage_x_set",
                "",
            ],
        ]
        filepath = os.path.join(self.tmpdir, "det.h5")
        with h5py.File(filepath, "r") as hdf:
            assert hdf["/entry/data"].shape == (1, 1, 120, 160)
            assert hdf["/entry/sum"].shape == (1, 1, 1, 1)
            assert hdf["/entry/uid"].shape == (1, 1, 1, 1)
            assert hdf["/entry/uid"][0][0][0][0] == 0
            assert tuple(hdf["/entry/stage_x_set"]) == (0, 1, 2)
            assert tuple(hdf["/entry/stage_y_set"]) == (0, 2)
            fs = self.b.run_async()
            # Wait for 2 frames to be written and reported
            cothread.Sleep(1.3)
            assert self.b.completedSteps.value == 2
            assert hdf["/entry/data"].shape == (1, 2, 120, 160)
            assert hdf["/entry/sum"].shape == (1, 2, 1, 1)
            assert hdf["/entry/uid"].shape == (1, 2, 1, 1)
            assert hdf["/entry/sum"][0][0][0][0] == 208036.0
            assert hdf["/entry/uid"][0][0][0][0] == 1
            assert hdf["/entry/sum"][0][1][0][0] == 970444.0
            assert hdf["/entry/uid"][0][1][0][0] == 2
            # pause
            self.b.pause(lastGoodStep=3)
            assert self.b.completedSteps.value == 3
            # resume
            self.b.resume()
            # Wait for the rest
            before_end = time.time()
            fs.result(timeout=10)
            self.assertAlmostEqual(time.time() - before_end, 1.5, delta=0.25)
            # Check the rest of the data, including the blank
            assert hdf["/entry/data"].shape == (2, 3, 120, 160)
            assert hdf["/entry/sum"].shape == (2, 3, 1, 1)
            assert hdf["/entry/uid"].shape == (2, 3, 1, 1)
            assert hdf["/entry/sum"][0][2][0][0] == 0
            assert hdf["/entry/uid"][0][2][0][0] == 0
            assert hdf["/entry/sum"][1][2][0][0] == 1788628.0
            assert hdf["/entry/uid"][1][2][0][0] == 7
            assert hdf["/entry/sum"][1][0][0][0] == 208036.0
            assert hdf["/entry/uid"][1][0][0][0] == 9
            # Reset to close the file
            self.b.reset()
Esempio n. 9
0
class PandABoxBlockMakerTest(unittest.TestCase):
    def setUp(self):
        self.client = Mock()
        self.process = Process()
        self.process.start()

    def tearDown(self):
        self.process.stop()

    def test_block_fields_adder(self):
        fields = OrderedDict()
        block_data = BlockData(2, "Adder description", fields)
        fields["INPA"] = FieldData("pos_mux", "", "Input A", ["A.OUT", "B.OUT"])
        fields["INPB"] = FieldData("pos_mux", "", "Input B", ["A.OUT", "B.OUT"])
        fields["DIVIDE"] = FieldData(
            "param", "enum", "Divide output", ["/1", "/2", "/4"]
        )
        fields["OUT"] = FieldData("pos_out", "", "Output", ["No", "Capture"])
        fields["HEALTH"] = FieldData("read", "enum", "What's wrong", ["OK", "Very Bad"])

        o = PandABlockController(self.client, "MRI", "ADDER1", block_data, "/docs")
        self.process.add_controller(o)
        b = self.process.block_view("MRI:ADDER1")

        assert list(b) == [
            "meta",
            "health",
            "icon",
            "label",
            "help",
            "inputs",
            "inpa",
            "inpb",
            "parameters",
            "divide",
            "outputs",
            "out",
        ]

        group = b.inputs
        assert group.meta.tags == ["widget:group", "config:1"]

        inpa = b.inpa
        assert inpa.meta.writeable is True
        assert inpa.meta.typeid == ChoiceMeta.typeid
        assert inpa.meta.tags == [
            "group:inputs",
            "sinkPort:int32:ZERO",
            "widget:combo",
            "config:1",
        ]
        assert inpa.meta.choices == ["A.OUT", "B.OUT"]
        inpa.put_value("A.OUT")
        self.client.set_field.assert_called_once_with("ADDER1", "INPA", "A.OUT")
        self.client.reset_mock()

        divide = b.divide
        assert divide.meta.writeable is True
        assert divide.meta.typeid == ChoiceMeta.typeid
        assert divide.meta.tags == ["group:parameters", "widget:combo", "config:1"]
        assert divide.meta.choices == ["/1", "/2", "/4"]

        out = b.out
        assert out.meta.writeable is False
        assert out.meta.typeid == NumberMeta.typeid
        assert out.meta.dtype == "int32"
        assert out.meta.tags == [
            "group:outputs",
            "sourcePort:int32:ADDER1.OUT",
            "widget:textupdate",
        ]

        queue = Queue()
        subscribe = Subscribe(path=["MRI:ADDER1", "out"], delta=True)
        subscribe.set_callback(queue.put)
        o.handle_request(subscribe)
        delta = queue.get(timeout=1)
        assert delta.changes[0][1]["value"] == 0

        ts = TimeStamp()
        o.handle_changes({"OUT": "145"}, ts)
        delta = queue.get(timeout=1)
        assert delta.changes == [
            [["value"], 145],
            [["timeStamp"], ts],
        ]

        subscribe = Subscribe(path=["MRI:ADDER1", "health"], delta=True)
        subscribe.set_callback(queue.put)
        o.handle_request(subscribe)
        delta = queue.get(timeout=1)
        assert delta.changes[0][1]["value"] == "OK"

        ts = TimeStamp()
        o.handle_changes({"HEALTH": "Very Bad"}, ts)
        delta = queue.get(timeout=1)
        assert delta.changes == [
            [["value"], "Very Bad"],
            [["alarm"], Alarm.major("Very Bad")],
            [["timeStamp"], ts],
        ]
        o.handle_changes({"HEALTH": "OK"}, ts)
        delta = queue.get(timeout=1)
        assert delta.changes == [
            [["value"], "OK"],
            [["alarm"], Alarm.ok],
            [["timeStamp"], ts],
        ]

    def test_block_fields_pulse(self):
        fields = OrderedDict()
        block_data = BlockData(4, "Pulse description", fields)
        fields["DELAY"] = FieldData("time", "", "Time", [])
        fields["INP"] = FieldData("bit_mux", "", "Input", ["ZERO", "X.OUT", "Y.OUT"])
        fields["OUT"] = FieldData("bit_out", "", "Output", [])
        fields["ERR_PERIOD"] = FieldData("read", "bit", "Error", [])

        o = PandABlockController(self.client, "MRI", "PULSE2", block_data, "/docs")
        self.process.add_controller(o)
        b = self.process.block_view("MRI:PULSE2")

        assert list(b) == [
            "meta",
            "health",
            "icon",
            "label",
            "help",
            "parameters",
            "delay",
            "delayUnits",
            "inputs",
            "inp",
            "inpDelay",
            "outputs",
            "out",
            "readbacks",
            "errPeriod",
        ]

        assert b.meta.label == "Pulse description 2"
        assert b.label.value == "Pulse description 2"

        # check setting label
        b.label.put_value("A new label")
        assert b.meta.label == "A new label"
        assert b.label.value == "A new label"
        self.client.set_field.assert_called_once_with(
            "*METADATA", "LABEL_PULSE2", "A new label"
        )
        self.client.set_field.reset_mock()

        # check updated with nothing
        o.handle_changes(dict(LABEL=""), ts=TimeStamp())
        assert b.meta.label == "Pulse description 2"
        assert b.label.value == "Pulse description 2"
        self.client.set_field.assert_not_called()

        # check updated with something from the server
        o.handle_changes(dict(LABEL="A server label"), ts=TimeStamp())
        assert b.meta.label == "A server label"
        assert b.label.value == "A server label"
        self.client.set_field.assert_not_called()

        help = b.help
        assert help.value == "/docs/build/pulse_doc.html"

        delay = b.delay
        assert delay.meta.writeable is True
        assert delay.meta.typeid == NumberMeta.typeid
        assert delay.meta.dtype == "float64"
        assert delay.meta.tags == ["group:parameters", "widget:textinput", "config:2"]

        units = b.delayUnits
        assert units.meta.writeable is True
        assert units.meta.typeid == ChoiceMeta.typeid
        assert units.meta.tags == ["group:parameters", "widget:combo", "config:1"]
        assert units.meta.choices == ["s", "ms", "us"]

        inp = b.inp
        assert inp.meta.writeable is True
        assert inp.meta.typeid == ChoiceMeta.typeid
        assert inp.meta.tags == [
            "group:inputs",
            "sinkPort:bool:ZERO",
            "widget:combo",
            "badgevalue:plus:inpDelay:MRI:PULSE2",
            "config:1",
        ]
        assert inp.meta.choices == ["ZERO", "X.OUT", "Y.OUT"]

        delay = b.inpDelay
        assert delay.meta.writeable is True
        assert delay.meta.typeid == NumberMeta.typeid
        assert delay.meta.dtype == "uint8"
        assert delay.meta.tags == ["group:inputs", "widget:textinput", "config:1"]

        out = b.out
        assert out.meta.writeable is False
        assert out.meta.typeid == BooleanMeta.typeid
        assert out.meta.tags == [
            "group:outputs",
            "sourcePort:bool:PULSE2.OUT",
            "widget:led",
        ]

        err = b.errPeriod
        assert err.meta.writeable is False
        assert err.meta.typeid == BooleanMeta.typeid
        assert err.meta.tags == ["group:readbacks", "widget:led"]

        queue = Queue()
        subscribe = Subscribe(path=["MRI:PULSE2", "inp"], delta=True)
        subscribe.set_callback(queue.put)
        o.handle_request(subscribe)
        delta = queue.get()
        assert delta.changes[0][1]["value"] == "ZERO"

        ts = TimeStamp()
        o.handle_changes({"INP": "X.OUT"}, ts)
        delta = queue.get()
        assert delta.changes == [
            [["value"], "X.OUT"],
            [["timeStamp"], ts],
            [
                ["meta", "tags"],
                [
                    "group:inputs",
                    "sinkPort:bool:ZERO",
                    "widget:combo",
                    "badgevalue:plus:inpDelay:MRI:PULSE2",
                    "config:1",
                    "linkedvalue:out:MRI:X",
                ],
            ],
        ]

    def test_block_fields_lut(self):
        fields = OrderedDict()
        block_data = BlockData(8, "Lut description", fields)
        fields["FUNC"] = FieldData("param", "lut", "Function", [])

        o = PandABlockController(self.client, "MRI", "LUT3", block_data, "/docs")
        self.process.add_controller(o)
        b = self.process.block_view("MRI:LUT3")

        func = b.func
        assert func.meta.writeable is True
        assert func.meta.typeid == StringMeta.typeid
        assert func.meta.tags == ["group:parameters", "widget:textinput", "config:1"]

        queue = Queue()
        subscribe = Subscribe(path=["MRI:LUT3"], delta=True)
        subscribe.set_callback(queue.put)
        o.handle_request(subscribe)
        delta = queue.get()
        assert delta.changes[0][1]["func"]["value"] == ""
        assert '<path id="OR"' in delta.changes[0][1]["icon"]["value"]

        # This is the correct FUNC.RAW value for !A&!B&!C&!D&!E
        self.client.get_field.return_value = "1"
        ts = TimeStamp()
        o.handle_changes({"FUNC": "!A&!B&!C&!D&!E"}, ts)
        self.client.get_field.assert_called_once_with("LUT3", "FUNC.RAW")
        delta = queue.get()
        assert delta.changes == [
            [["func", "value"], "!A&!B&!C&!D&!E"],
            [["func", "timeStamp"], ts],
            [["icon", "value"], ANY],
            [["icon", "timeStamp"], ts],
        ]
        assert '<path id="OR"' not in delta.changes[2][1]
Esempio n. 10
0
class TestScanBlock(unittest.TestCase):
    def setUp(self):
        self.p = Process("proc")
        for c in (detector_block("DETECTOR", config_dir="/tmp") +
                  motion_block("MOTION", config_dir="/tmp") +
                  scan_1det_block("SCANMRI", config_dir="/tmp")):
            self.p.add_controller(c)
        self.pub = PublishController("PUB")
        self.p.add_controller(self.pub)
        self.p.start()
        self.b = self.p.block_view("SCANMRI")
        self.bd = self.p.block_view("DETECTOR")
        self.tmpdir = tempfile.mkdtemp()

    def tearDown(self):
        self.p.stop(timeout=2)
        shutil.rmtree(self.tmpdir)

    def test_init(self):
        assert self.b.label.value == "Mapping x, y with demo detector"
        assert list(self.b.configure.meta.defaults["detectors"].rows()) == [[
            True, "DET", "DETECTOR", 0.0, 1
        ]]
        assert self.pub.published == [
            "SCANMRI",
            "PUB",
            "DETECTOR",
            "MOTION",
            "MOTION:COUNTERX",
            "MOTION:COUNTERY",
        ]

    def make_generator(self):
        linex = LineGenerator("x", "mm", 0, 2, 3, alternate=True)
        liney = LineGenerator("y", "mm", 0, 2, 2)
        compound = CompoundGenerator([liney, linex], [], [], 0.1)
        return compound

    def test_validate(self):
        compound = self.make_generator()
        detectors = DetectorTable.from_rows([[True, "DET", "DETECTOR", 0.0,
                                              1]])
        ret = self.b.validate(compound, self.tmpdir, detectors=detectors)
        assert list(ret["detectors"].rows()) == [[
            True, "DET", "DETECTOR", 0.098995, 1
        ]]

    def prepare_half_run(self):
        compound = self.make_generator()
        self.b.configure(compound,
                         self.tmpdir,
                         axesToMove=["x"],
                         fileTemplate="my-%s.h5")

    def test_configure(self):
        self.prepare_half_run()
        assert list(self.b.datasets.value.rows()) == [
            [
                "DET.data",
                "my-DET.h5",
                DatasetType.PRIMARY,
                4,
                "/entry/data",
                "/entry/uid",
            ],
            [
                "DET.sum",
                "my-DET.h5",
                DatasetType.SECONDARY,
                4,
                "/entry/sum",
                "/entry/uid",
            ],
            [
                "y.value_set",
                "my-DET.h5",
                DatasetType.POSITION_SET,
                1,
                "/entry/y_set",
                "",
            ],
            [
                "x.value_set",
                "my-DET.h5",
                DatasetType.POSITION_SET,
                1,
                "/entry/x_set",
                "",
            ],
        ]
        for b in (self.b, self.bd):
            assert b.completedSteps.value == 0
            assert b.configuredSteps.value == 3
            assert b.totalSteps.value == 6

    def test_run(self):
        self.prepare_half_run()
        assert self.b.state.value == "Armed"
        self.b.run()
        for b in (self.b, self.bd):
            assert b.completedSteps.value == 3
            assert b.configuredSteps.value == 6
            assert b.totalSteps.value == 6
            assert b.state.value == "Armed"
        self.b.run()
        for b in (self.b, self.bd):
            assert b.completedSteps.value == 6
            assert b.configuredSteps.value == 6
            assert b.totalSteps.value == 6
            assert b.state.value == "Finished"
        self.b.reset()
        for b in (self.b, self.bd):
            assert b.state.value == "Ready"