示例#1
0
 def test_init(self):
     c = RunnableController("mri", "/tmp")
     c.add_part(self.o)
     self.process.add_controller(c)
     b = c.block_view()
     assert list(b.configure.takes.elements) == [
         'generator', 'fileDir', 'axesToMove', 'formatName', 'fileTemplate'
     ]
示例#2
0
 def setUp(self):
     self.o = UnrollingPart(name="Unroll")
     self.process = Process("proc")
     self.process.start()
     self.addCleanup(self.process.stop, 2)
     c = RunnableController("mri", "/tmp")
     c.add_part(self.o)
     self.process.add_controller(c)
     self.b = c.block_view()
示例#3
0
 def setUp(self):
     self.o = SimultaneousAxesPart(value=["x", "y"])
     self.process = Process("proc")
     self.process.start()
     self.addCleanup(self.process.stop, 2)
     c = RunnableController("mri", "/tmp")
     c.add_part(self.o)
     self.process.add_controller(c)
     self.b = c.block_view()
示例#4
0
 def setUp(self):
     self.o = UnrollingPart(name="Unroll",
                            mri="mri",
                            initial_visibility=True)
     self.process = Process("proc")
     self.process.start()
     self.addCleanup(self.process.stop, 2)
     self.config_dir = tmp_dir("config_dir")
     c = RunnableController("mri", self.config_dir.value)
     c.add_part(self.o)
     self.process.add_controller(c)
     self.b = c.block_view()
    def setUp(self):
        self.process = Process("test_process")
        self.context = Context(self.process)

        # Create a raw motor mock to handle axis request
        self.child = self.create_child_block(
            raw_motor_block, self.process, mri="BS", pv_prefix="PV:PRE"
        )
        # Add Beam Selector object
        self.o = MotorPreMovePart(name="MotorPreMovePart", mri="BS", demand=50)

        controller = RunnableController("SCAN", "/tmp", use_git=False)
        controller.add_part(self.o)

        self.process.add_controller(controller)
        self.process.start()
 def test_init(self):
     self.o = HDFWriterPart(name="m", mri="BLOCK:HDF5")
     self.context.set_notify_dispatch_request(
         self.o.notify_dispatch_request)
     c = RunnableController("mri", self.config_dir.value)
     c.add_part(self.o)
     self.process.add_controller(c)
     b = c.block_view()
     assert list(b.configure.meta.takes.elements) == [
         "generator",
         "fileDir",
         "axesToMove",
         "breakpoints",
         "formatName",
         "fileTemplate",
     ]
示例#7
0
    def test_real(self):
        c = RunnableController(mri="top", config_dir="/tmp")
        c.add_part(self.o)

        class ValidatePart(Part):
            data = []

            def setup(self, registrar):
                super(ValidatePart, self).setup(registrar)
                self.register_hooked(ValidateHook, self.validate)

            @add_call_types
            def validate(self, part_info):
                # type: (APartInfo) -> None
                self.data.append(part_info)

        c.add_part(ValidatePart("validate"))
        self.process.add_controller(c)
        c.block_view().validate(CompoundGenerator([], [], []))
        assert len(ValidatePart.data) == 1
        assert list(ValidatePart.data[0]) == ["scan"]
        assert len(ValidatePart.data[0]["scan"]) == 1
    def setUp(self):
        self.p = Process('process1')
        self.context = Context(self.p)

        # Make a fast child, this will load the wait of 0.01 from saved file
        c1 = RunnableController(mri="fast",
                                config_dir=DESIGN_PATH,
                                use_git=False,
                                initial_design="fast")
        c1.add_part(WaitingPart("wait"))
        self.p.add_controller(c1)

        # And a slow one, this has the same saved files as fast, but doesn't
        # load at startup
        c2 = RunnableController(mri="slow",
                                config_dir=DESIGN_PATH,
                                use_git=False)
        c2.add_part(WaitingPart("wait", 0.123))
        self.p.add_controller(c2)

        # And a top level one, this loads slow and fast designs for the
        # children on every configure (or load), but not at init
        c3 = RunnableController(mri="top",
                                config_dir=DESIGN_PATH,
                                use_git=False,
                                initial_design="default")
        c3.add_part(
            RunnableChildPart(name="FAST", mri="fast",
                              initial_visibility=True))
        c3.add_part(
            RunnableChildPart(name="SLOW", mri="slow",
                              initial_visibility=True))
        self.p.add_controller(c3)

        # Some blocks to interface to them
        self.b = self.context.block_view("top")
        self.bf = self.context.block_view("fast")
        self.bs = self.context.block_view("slow")

        # start the process off
        self.p.start()
示例#9
0
    def setUp(self):
        self.p = Process("process1")
        self.context = Context(self.p)

        # Make a fast child, this will load the wait of 0.01 from saved file
        c1 = RunnableController(
            mri="fast", config_dir=DESIGN_PATH, use_git=False, initial_design="fast"
        )
        c1.add_part(WaitingPart("wait"))
        c1.add_part(ExposureDeadtimePart("dt", 0.001))
        c1.add_part(DatasetTablePart("dset"))
        self.p.add_controller(c1)

        # And a slow one, this has the same saved files as fast, but doesn't
        # load at startup
        c2 = RunnableController(mri="slow", config_dir=DESIGN_PATH, use_git=False)
        c2.add_part(WaitingPart("wait", 0.123))
        c2.add_part(DatasetTablePart("dset"))
        self.p.add_controller(c2)

        # And a faulty one, this is hidden at startup by default
        c3 = RunnableController(mri="faulty", config_dir=DESIGN_PATH, use_git=False)
        c3.add_part(FaultyPart("bad"))
        c3.add_part(DatasetTablePart("dset"))
        self.p.add_controller(c3)

        # And a top level one, this loads slow and fast designs for the
        # children on every configure (or load), but not at init
        self.ct = RunnableController(
            mri="top", config_dir=DESIGN_PATH, use_git=False, initial_design="default"
        )
        self.ct.add_part(
            DetectorChildPart(name="FAST", mri="fast", initial_visibility=True)
        )
        self.ct.add_part(
            DetectorChildPart(name="SLOW", mri="slow", initial_visibility=True)
        )
        self.ct.add_part(
            DetectorChildPart(name="BAD", mri="faulty", initial_visibility=False)
        )
        self.ct.add_part(
            DetectorChildPart(
                name="BAD2",
                mri="faulty",
                initial_visibility=False,
                initial_frames_per_step=0,
            )
        )
        self.fast_multi = MaybeMultiPart("fast")
        self.slow_multi = MaybeMultiPart("slow")
        self.ct.add_part(self.fast_multi)
        self.ct.add_part(self.slow_multi)
        self.p.add_controller(self.ct)

        # Some blocks to interface to them
        self.b = self.context.block_view("top")
        self.bf = self.context.block_view("fast")
        self.bs = self.context.block_view("slow")

        # start the process off
        self.p.start()
        self.tmpdir = tempfile.mkdtemp()
示例#10
0
class TestDetectorChildPart(unittest.TestCase):
    def setUp(self):
        self.p = Process("process1")
        self.context = Context(self.p)

        # Make a fast child, this will load the wait of 0.01 from saved file
        c1 = RunnableController(
            mri="fast", config_dir=DESIGN_PATH, use_git=False, initial_design="fast"
        )
        c1.add_part(WaitingPart("wait"))
        c1.add_part(ExposureDeadtimePart("dt", 0.001))
        c1.add_part(DatasetTablePart("dset"))
        self.p.add_controller(c1)

        # And a slow one, this has the same saved files as fast, but doesn't
        # load at startup
        c2 = RunnableController(mri="slow", config_dir=DESIGN_PATH, use_git=False)
        c2.add_part(WaitingPart("wait", 0.123))
        c2.add_part(DatasetTablePart("dset"))
        self.p.add_controller(c2)

        # And a faulty one, this is hidden at startup by default
        c3 = RunnableController(mri="faulty", config_dir=DESIGN_PATH, use_git=False)
        c3.add_part(FaultyPart("bad"))
        c3.add_part(DatasetTablePart("dset"))
        self.p.add_controller(c3)

        # And a top level one, this loads slow and fast designs for the
        # children on every configure (or load), but not at init
        self.ct = RunnableController(
            mri="top", config_dir=DESIGN_PATH, use_git=False, initial_design="default"
        )
        self.ct.add_part(
            DetectorChildPart(name="FAST", mri="fast", initial_visibility=True)
        )
        self.ct.add_part(
            DetectorChildPart(name="SLOW", mri="slow", initial_visibility=True)
        )
        self.ct.add_part(
            DetectorChildPart(name="BAD", mri="faulty", initial_visibility=False)
        )
        self.ct.add_part(
            DetectorChildPart(
                name="BAD2",
                mri="faulty",
                initial_visibility=False,
                initial_frames_per_step=0,
            )
        )
        self.fast_multi = MaybeMultiPart("fast")
        self.slow_multi = MaybeMultiPart("slow")
        self.ct.add_part(self.fast_multi)
        self.ct.add_part(self.slow_multi)
        self.p.add_controller(self.ct)

        # Some blocks to interface to them
        self.b = self.context.block_view("top")
        self.bf = self.context.block_view("fast")
        self.bs = self.context.block_view("slow")

        # start the process off
        self.p.start()
        self.tmpdir = tempfile.mkdtemp()

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

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

    def test_init(self):
        assert list(self.b.configure.meta.defaults["detectors"].rows()) == [
            [True, "FAST", "fast", 0.0, 1],
            [True, "SLOW", "slow", 0.0, 1],
        ]

    def test_validate_returns_exposures(self):
        ret = self.b.validate(
            self.make_generator(),
            self.tmpdir,
            detectors=DetectorTable.from_rows(
                [(True, "SLOW", "slow", 0.0, 1), (True, "FAST", "fast", 0.0, 1)]
            ),
        )
        assert list(ret.detectors.rows()) == [
            [True, "SLOW", "slow", 0.0, 1],
            [True, "FAST", "fast", 0.99895, 1],
        ]

    def test_guessing_frames_1(self):
        ret = self.b.validate(
            self.make_generator(),
            self.tmpdir,
            detectors=DetectorTable.from_rows(
                [(True, "FAST", "fast", 0.5, 0), (True, "SLOW", "slow", 0.0, 1)]
            ),
        )
        assert list(ret.detectors.rows()) == [
            [True, "FAST", "fast", 0.5, 1],
            [True, "SLOW", "slow", 0.0, 1],
        ]

    def test_setting_exposure_on_no_exposure_det_fails(self):
        with self.assertRaises(BadValueError) as cm:
            self.b.validate(
                self.make_generator(),
                self.tmpdir,
                detectors=DetectorTable.from_rows(
                    [(True, "FAST", "fast", 0.0, 1), (True, "SLOW", "slow", 0.5, 1)]
                ),
            )
        assert str(cm.exception) == "Detector SLOW doesn't take exposure"

    def test_guessing_frames_and_exposure(self):
        self.slow_multi.active = True
        ret = self.b.validate(
            self.make_generator(),
            self.tmpdir,
            detectors=DetectorTable.from_rows([(True, "FAST", "fast", 0.0, 0)]),
        )
        assert list(ret.detectors.rows()) == [
            [True, "FAST", "fast", 0.99895, 1],
            [False, "SLOW", "slow", 0, 0],
        ]

    def test_guessing_frames_5(self):
        self.fast_multi.active = True
        ret = self.b.validate(
            self.make_generator(),
            self.tmpdir,
            detectors=DetectorTable.from_rows(
                [(True, "FAST", "fast", 0.198, 0), (True, "SLOW", "slow", 0.0, 1)]
            ),
        )
        assert list(ret.detectors.rows()) == [
            [True, "FAST", "fast", 0.198, 5],
            [True, "SLOW", "slow", 0.0, 1],
        ]

    def test_adding_faulty_fails(self):
        t = LayoutTable.from_rows([["BAD", "faulty", 0, 0, True]])
        self.b.layout.put_value(t)
        assert list(self.b.configure.meta.defaults["detectors"].rows()) == [
            [True, "FAST", "fast", 0.0, 1],
            [True, "SLOW", "slow", 0.0, 1],
            [True, "BAD", "faulty", 0.0, 1],
        ]
        with self.assertRaises(BadValueError) as cm:
            self.b.configure(self.make_generator(), self.tmpdir)
        assert str(cm.exception) == (
            "Detector BAD was faulty at init and is unusable. "
            "If the detector is now working please restart Malcolm"
        )
        self.b.configure(
            self.make_generator(),
            self.tmpdir,
            detectors=DetectorTable.from_rows([(False, "BAD", "faulty", 0.0, 1)]),
        )
        self.b.reset()
        t = LayoutTable.from_rows([["BAD", "faulty", 0, 0, False]])
        self.b.layout.put_value(t)
        self.test_init()
        self.b.configure(self.make_generator(), self.tmpdir)

    def test_adding_faulty_non_default_works(self):
        t = LayoutTable.from_rows([["BAD2", "faulty", 0, 0, True]])
        self.b.layout.put_value(t)
        assert list(self.b.configure.meta.defaults["detectors"].rows()) == [
            [True, "FAST", "fast", 0.0, 1],
            [True, "SLOW", "slow", 0.0, 1],
            [False, "BAD2", "faulty", 0.0, 1],
        ]
        self.b.configure(self.make_generator(), self.tmpdir)

    def test_only_one_det(self):
        # Disable one detector
        self.b.configure(
            self.make_generator(),
            self.tmpdir,
            detectors=DetectorTable.from_rows(
                [(False, "SLOW", "slow", 0.0, 0), [True, "FAST", "fast", 0.0, 1]]
            ),
        )
        assert self.b.state.value == "Armed"
        assert self.bs.state.value == "Ready"
        assert self.bf.state.value == "Armed"
        self.b.completedSteps.put_value(2)
        assert self.b.state.value == "Armed"
        assert self.bs.state.value == "Ready"
        assert self.bf.state.value == "Armed"
        self.b.run()
        assert self.b.state.value == "Finished"
        assert self.bs.state.value == "Ready"
        assert self.bf.state.value == "Finished"
        self.b.reset()
        assert self.b.state.value == "Ready"
        assert self.bs.state.value == "Ready"
        assert self.bf.state.value == "Ready"
        self.b.abort()
        assert self.b.state.value == "Aborted"
        assert self.bs.state.value == "Aborted"
        assert self.bf.state.value == "Aborted"

    def test_multi_frame_no_infos_fails(self):
        with self.assertRaises(BadValueError) as cm:
            self.b.configure(
                self.make_generator(),
                self.tmpdir,
                detectors=DetectorTable.from_rows(
                    [(True, "SLOW", "slow", 0.0, 1), (True, "FAST", "fast", 0.0, 5)]
                ),
            )
        assert str(cm.exception) == (
            "There are no trigger multipliers setup for Detector 'FAST' "
            "so framesPerStep can only be 0 or 1 for this row in the detectors "
            "table"
        )

    def test_multi_frame_fast_det(self):
        self.fast_multi.active = True
        self.b.configure(
            self.make_generator(),
            self.tmpdir,
            detectors=DetectorTable.from_rows(
                [(True, "SLOW", "slow", 0.0, 1), (True, "FAST", "fast", 0.0, 5)]
            ),
        )
        assert self.b.completedSteps.value == 0
        assert self.b.totalSteps.value == 6
        assert self.b.configuredSteps.value == 6
        assert self.bs.completedSteps.value == 0
        assert self.bs.totalSteps.value == 6
        assert self.bs.configuredSteps.value == 6
        assert self.bf.completedSteps.value == 0
        assert self.bf.totalSteps.value == 30
        assert self.bf.configuredSteps.value == 30

    def test_bad_det_mri(self):
        # Send mismatching rows
        with self.assertRaises(AssertionError) as cm:
            self.b.configure(
                self.make_generator(),
                self.tmpdir,
                axesToMove=(),
                detectors=DetectorTable.from_rows([(True, "SLOW", "fast", 0.0, 0)]),
            )
        assert str(cm.exception) == "SLOW has mri slow, passed fast"

    def test_not_paused_when_resume(self):
        # Set it up to do 6 steps
        self.b.configure(
            self.make_generator(),
            self.tmpdir,
            axesToMove=(),
            detectors=DetectorTable.from_rows(
                [(True, "FAST", "fast", 0, 1), (True, "SLOW", "slow", 0, 1)]
            ),
        )
        assert self.b.completedSteps.value == 0
        assert self.b.totalSteps.value == 6
        assert self.b.configuredSteps.value == 1
        # Do one step
        self.b.run()
        assert self.b.completedSteps.value == 1
        assert self.b.totalSteps.value == 6
        assert self.b.configuredSteps.value == 2
        assert self.b.state.value == "Armed"
        assert self.bs.state.value == "Armed"
        assert self.bf.state.value == "Armed"
        # Now do a second step but pause before the second one is done
        f = self.b.run_async()
        self.context.sleep(0.2)
        assert self.b.state.value == "Running"
        assert self.bf.state.value == "Armed"
        assert self.bs.state.value == "Running"
        self.b.pause()
        assert self.b.state.value == "Paused"
        assert self.bf.state.value == "Armed"
        assert self.bs.state.value == "Paused"
        assert self.b.completedSteps.value == 1
        assert self.b.totalSteps.value == 6
        assert self.b.configuredSteps.value == 2
        self.b.resume()
        self.context.wait_all_futures(f)
        assert self.b.completedSteps.value == 2
        assert self.b.totalSteps.value == 6
        assert self.b.configuredSteps.value == 3

    def test_parent_with_initial_config_does_not_set_child(self):
        assert self.bs.wait.value == 0.123
        assert self.bs.design.value == ""
        assert self.bf.wait.value == 0.01
        assert self.bf.design.value == "fast"
        assert self.b.design.value == "default"
        assert self.b.modified.value is True
        assert self.b.modified.alarm.message == "SLOW.design.value = '' not 'slow'"
        self.b.configure(self.make_generator(), self.tmpdir, axesToMove=())
        assert self.bs.wait.value == 1.0
        assert self.bs.design.value == "slow"
        assert self.bf.wait.value == 0.01
        assert self.bf.design.value == "fast"
        assert self.b.design.value == "default"
        assert self.b.modified.value is False

    def make_generator_breakpoints(self):
        line1 = LineGenerator("x", "mm", -10, -10, 5)
        line2 = LineGenerator("x", "mm", 0, 180, 10)
        line3 = LineGenerator("x", "mm", 190, 190, 2)
        duration = 0.01
        concat = ConcatGenerator([line1, line2, line3])

        return CompoundGenerator([concat], [], [], duration)

    def checkSteps(self, block, configured, completed, total):
        assert block.configuredSteps.value == configured
        assert block.completedSteps.value == completed
        assert block.totalSteps.value == total

    def checkState(self, block, state):
        assert block.state.value == state

    def test_breakpoints_tomo(self):
        breakpoints = [2, 3, 10, 2]
        # Configure RunnableController(mri='top')
        self.b.configure(
            generator=self.make_generator_breakpoints(),
            fileDir=self.tmpdir,
            detectors=DetectorTable.from_rows(
                [[False, "SLOW", "slow", 0.0, 1], [True, "FAST", "fast", 0.0, 1]]
            ),
            axesToMove=["x"],
            breakpoints=breakpoints,
        )

        assert self.ct.configure_params.generator.size == 17
        self.checkSteps(self.b, 2, 0, 17)
        self.checkSteps(self.bf, 2, 0, 17)
        assert self.b.state.value == "Armed"
        assert self.bs.state.value == "Ready"
        assert self.bf.state.value == "Armed"

        self.b.run()
        self.checkSteps(self.b, 5, 2, 17)
        self.checkSteps(self.bf, 5, 2, 17)
        assert self.b.state.value == "Armed"
        assert self.bs.state.value == "Ready"
        assert self.bf.state.value == "Armed"

        self.b.run()
        self.checkSteps(self.b, 15, 5, 17)
        assert self.b.state.value == "Armed"
        assert self.bs.state.value == "Ready"
        assert self.bf.state.value == "Armed"

        self.b.run()
        self.checkSteps(self.b, 17, 15, 17)
        assert self.b.state.value == "Armed"
        assert self.bs.state.value == "Ready"
        assert self.bf.state.value == "Armed"

        self.b.run()
        self.checkSteps(self.b, 17, 17, 17)
        self.checkSteps(self.bf, 17, 17, 17)
        assert self.b.state.value == "Finished"
        assert self.bs.state.value == "Ready"
        assert self.bf.state.value == "Finished"

    def test_breakpoints_with_pause(self):
        breakpoints = [2, 3, 10, 2]
        self.b.configure(
            generator=self.make_generator_breakpoints(),
            fileDir=self.tmpdir,
            detectors=DetectorTable.from_rows(
                [[False, "SLOW", "slow", 0.0, 1], [True, "FAST", "fast", 0.0, 1]]
            ),
            axesToMove=["x"],
            breakpoints=breakpoints,
        )

        assert self.ct.configure_params.generator.size == 17

        self.checkSteps(self.b, 2, 0, 17)
        self.checkSteps(self.bf, 2, 0, 17)
        self.checkState(self.b, RunnableStates.ARMED)

        self.b.run()
        self.checkSteps(self.b, 5, 2, 17)
        self.checkSteps(self.bf, 5, 2, 17)
        self.checkState(self.b, RunnableStates.ARMED)

        # rewind
        self.b.pause(lastGoodStep=1)
        self.checkSteps(self.b, 2, 1, 17)
        self.checkSteps(self.bf, 2, 1, 17)
        self.checkState(self.b, RunnableStates.ARMED)
        self.b.run()
        self.checkSteps(self.b, 5, 2, 17)
        self.checkSteps(self.bf, 5, 2, 17)
        self.checkState(self.b, RunnableStates.ARMED)

        self.b.run()
        self.checkSteps(self.b, 15, 5, 17)
        self.checkSteps(self.bf, 15, 5, 17)
        self.checkState(self.b, RunnableStates.ARMED)

        self.b.run()
        self.checkSteps(self.b, 17, 15, 17)
        self.checkSteps(self.bf, 17, 15, 17)
        self.checkState(self.b, RunnableStates.ARMED)

        # rewind
        self.b.pause(lastGoodStep=11)
        self.checkSteps(self.b, 15, 11, 17)
        self.checkSteps(self.bf, 15, 11, 17)
        self.checkState(self.b, RunnableStates.ARMED)
        self.b.run()
        self.checkSteps(self.b, 17, 15, 17)
        self.checkSteps(self.bf, 17, 15, 17)
        self.checkState(self.b, RunnableStates.ARMED)

        self.b.run()
        self.checkSteps(self.b, 17, 17, 17)
        self.checkSteps(self.bf, 17, 17, 17)
        self.checkState(self.b, RunnableStates.FINISHED)
示例#11
0
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")
        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")
        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)
class TestRunnableController(unittest.TestCase):
    def setUp(self):
        self.p = Process('process1')
        self.context = Context(self.p)

        # Make a ticker_block block to act as our child
        for c in ticker_block(mri="childBlock", config_dir="/tmp"):
            self.p.add_controller(c)
        self.b_child = self.context.block_view("childBlock")

        # Make an empty part for our parent
        part1 = Part("part1")

        # Make a RunnableChildPart to control the ticker_block
        part2 = RunnableChildPart(mri='childBlock',
                                  name='part2',
                                  initial_visibility=True)

        # create a root block for the RunnableController block to reside in
        self.c = RunnableController(mri='mainBlock', config_dir="/tmp")
        self.c.add_part(part1)
        self.c.add_part(part2)
        self.p.add_controller(self.c)
        self.b = self.context.block_view("mainBlock")
        self.ss = self.c.state_set

        # start the process off
        self.checkState(self.ss.DISABLED)
        self.p.start()
        self.checkState(self.ss.READY)

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

    def checkState(self, state, child=True, parent=True):
        if child:
            assert self.b_child.state.value == state
        if parent:
            assert self.c.state.value == state

    def checkSteps(self, configured, completed, total):
        assert self.b.configuredSteps.value == configured
        assert self.b.completedSteps.value == completed
        assert self.b.totalSteps.value == total
        assert self.b_child.configuredSteps.value == configured
        assert self.b_child.completedSteps.value == completed
        assert self.b_child.totalSteps.value == total

    def test_init(self):
        assert self.c.completed_steps.value == 0
        assert self.c.configured_steps.value == 0
        assert self.c.total_steps.value == 0
        assert list(self.b.configure.takes.elements) == \
               ["generator", "axesToMove", "exceptionStep"]

    def test_reset(self):
        self.c.disable()
        self.checkState(self.ss.DISABLED)
        self.c.reset()
        self.checkState(self.ss.READY)

    def test_modify_child(self):
        # Save an initial setting for the child
        self.b_child.save("init_child")
        assert self.b_child.modified.value is False
        x = self.context.block_view("COUNTERX")
        x.counter.put_value(31)
        # x counter now at 31, child should be modified
        assert x.counter.value == 31
        assert self.b_child.modified.value is True
        assert self.b_child.modified.alarm.severity == AlarmSeverity.MINOR_ALARM
        assert self.b_child.modified.alarm.status == AlarmStatus.CONF_STATUS
        assert self.b_child.modified.alarm.message == \
            "x.counter.value = 31.0 not 0.0"
        self.prepare_half_run()
        self.b.run()
        # x counter now at 2, child should be modified by us
        assert self.b_child.modified.value is True
        assert self.b_child.modified.alarm.severity == AlarmSeverity.NO_ALARM
        assert self.b_child.modified.alarm.status == AlarmStatus.CONF_STATUS
        assert self.b_child.modified.alarm.message == \
            "(We modified) x.counter.value = 2.0 not 0.0"
        assert x.counter.value == 2.0
        x.counter.put_value(0.0)
        # x counter now at 0, child should be unmodified
        assert x.counter.value == 0
        assert self.b_child.modified.alarm.message == ""
        assert self.b_child.modified.value is False

    def test_modify_parent(self):
        # Save an initial setting for child and parent
        self.b_child.save("init_child")
        self.b.save("init_parent")
        # Change a value and save as a new child setting
        x = self.context.block_view("COUNTERX")
        x.counter.put_value(31)
        self.b_child.save("new_child")
        assert self.b_child.modified.value is False
        assert self.b.modified.value is True
        assert self.b.modified.alarm.severity == AlarmSeverity.MINOR_ALARM
        assert self.b.modified.alarm.status == AlarmStatus.CONF_STATUS
        assert self.b.modified.alarm.message == \
            "part2.design.value = 'new_child' not 'init_child'"
        # Load the child again
        self.b_child.design.put_value("new_child")
        assert self.b.modified.value is True
        # And check that loading parent resets it
        self.b.design.put_value("init_parent")
        assert self.b.modified.value is False
        assert self.b_child.design.value == "init_child"
        # Put back
        self.b_child.design.put_value("new_child")
        assert self.b.modified.value is True
        # Do a configure, and check we get set back
        self.prepare_half_run()
        assert self.b_child.design.value == "init_child"
        assert self.b_child.modified.value is False
        assert self.b.modified.value is False

    def test_abort(self):
        self.b.abort()
        self.checkState(self.ss.ABORTED)

    def test_validate(self):
        line1 = LineGenerator('y', 'mm', 0, 2, 3)
        line2 = LineGenerator('x', 'mm', 0, 2, 2)
        compound = CompoundGenerator([line1, line2], [], [])
        actual = self.b.validate(generator=compound, axesToMove=['x'])
        assert actual["generator"].to_dict() == compound.to_dict()
        assert actual["axesToMove"] == ['x']

    def prepare_half_run(self, duration=0.01, exception=0):
        line1 = LineGenerator('y', 'mm', 0, 2, 3)
        line2 = LineGenerator('x', 'mm', 0, 2, 2)
        compound = CompoundGenerator([line1, line2], [], [], duration)
        self.b.configure(generator=compound,
                         axesToMove=['x'],
                         exceptionStep=exception)

    def test_configure_run(self):
        assert self.b.configure.writeable is True
        assert self.b.configure.takes.elements["generator"].writeable is True
        assert self.b.validate.takes.elements["generator"].writeable is True
        assert self.b.validate.returns.elements["generator"].writeable is False
        self.prepare_half_run()
        self.checkSteps(2, 0, 6)
        self.checkState(self.ss.ARMED)
        assert self.b.configure.writeable is False
        assert self.b.configure.takes.elements["generator"].writeable is True
        assert self.b.validate.takes.elements["generator"].writeable is True
        assert self.b.validate.returns.elements["generator"].writeable is False

        self.b.run()
        self.checkState(self.ss.ARMED)
        self.checkSteps(4, 2, 6)

        self.b.run()
        self.checkState(self.ss.ARMED)
        self.checkSteps(6, 4, 6)

        self.b.run()
        self.checkState(self.ss.READY)

    def test_abort(self):
        self.prepare_half_run()
        self.b.run()
        self.b.abort()
        self.checkState(self.ss.ABORTED)

    def test_pause_seek_resume(self):
        self.prepare_half_run()
        self.checkSteps(configured=2, completed=0, total=6)
        self.b.run()
        self.checkState(self.ss.ARMED)
        self.checkSteps(4, 2, 6)
        self.b.pause(completedSteps=1)
        self.checkState(self.ss.ARMED)
        self.checkSteps(2, 1, 6)
        self.b.run()
        self.checkSteps(4, 2, 6)
        self.b.completedSteps.put_value(5)
        self.checkSteps(6, 5, 6)
        self.b.run()
        self.checkState(self.ss.READY)

    def test_resume_in_run(self):
        self.prepare_half_run(duration=0.5)
        f = self.b.run_async()
        self.context.sleep(0.95)
        self.b.pause()
        self.checkState(self.ss.PAUSED)
        self.checkSteps(2, 1, 6)
        self.b.resume()
        # Parent should be running, child won't have got request yet
        then = time.time()
        self.checkState(self.ss.RUNNING, child=False)
        self.context.wait_all_futures(f, timeout=2)
        now = time.time()
        self.checkState(self.ss.ARMED)
        self.checkSteps(4, 2, 6)
        # This test fails on Travis sometimes, looks like the docker container
        # just gets starved
        # self.assertAlmostEqual(now - then, 0.5, delta=0.1)

    def test_run_exception(self):
        self.prepare_half_run(exception=1)
        with self.assertRaises(AssertionError):
            self.b.run()
        self.checkState(self.ss.FAULT)

    def test_run_stop(self):
        self.prepare_half_run(duration=0.1)
        f = self.b.run_async()
        self.context.sleep(0.1)
        self.b.abort()
        with self.assertRaises(AbortedError):
            f.result()
        self.checkState(self.ss.ABORTED)
示例#13
0
class TestRunnableControllerBreakpoints(unittest.TestCase):
    def setUp(self):
        self.p = Process("process1")
        self.context = Context(self.p)

        self.p2 = Process("process2")
        self.context2 = Context(self.p2)

        # Make a motion block to act as our child
        for c in motion_block(mri="childBlock", config_dir="/tmp"):
            self.p.add_controller(c)
        self.b_child = self.context.block_view("childBlock")

        # create a root block for the RunnableController block to reside in
        self.c = RunnableController(mri="mainBlock", config_dir="/tmp")
        self.p.add_controller(self.c)
        self.b = self.context.block_view("mainBlock")
        self.ss = self.c.state_set

        # start the process off
        self.checkState(self.ss.DISABLED)
        self.p.start()
        self.checkState(self.ss.READY)

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

    def checkState(self, state):
        assert self.c.state.value == state

    def checkSteps(self, configured, completed, total):
        assert self.b.configuredSteps.value == configured
        assert self.b.completedSteps.value == completed
        assert self.b.totalSteps.value == total

    def test_steps_per_run_one_axis(self):
        line = LineGenerator("x", "mm", 0, 180, 10)
        duration = 0.01
        compound = CompoundGenerator([line], [], [], duration)
        compound.prepare()

        steps_per_run = self.c.get_steps_per_run(generator=compound,
                                                 axes_to_move=["x"],
                                                 breakpoints=[])
        assert steps_per_run == [10]

    def test_steps_per_run_concat(self):
        line1 = LineGenerator("x", "mm", -10, -10, 5)
        line2 = LineGenerator("x", "mm", 0, 180, 10)
        line3 = LineGenerator("x", "mm", 190, 190, 2)
        duration = 0.01
        concat = ConcatGenerator([line1, line2, line3])
        compound = CompoundGenerator([concat], [], [], duration)
        compound.prepare()
        breakpoints = [2, 3, 10, 2]

        steps_per_run = self.c.get_steps_per_run(generator=compound,
                                                 axes_to_move=["x"],
                                                 breakpoints=breakpoints)
        assert steps_per_run == breakpoints

    def test_breakpoints_tomo(self):
        line1 = LineGenerator("x", "mm", -10, -10, 5)
        line2 = LineGenerator("x", "mm", 0, 180, 10)
        line3 = LineGenerator("x", "mm", 190, 190, 2)
        duration = 0.01
        concat = ConcatGenerator([line1, line2, line3])
        breakpoints = [2, 3, 10, 2]
        self.b.configure(
            generator=CompoundGenerator([concat], [], [], duration),
            axesToMove=["x"],
            breakpoints=breakpoints,
        )

        assert self.c.configure_params.generator.size == 17
        self.checkSteps(2, 0, 17)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(5, 2, 17)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(15, 5, 17)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(17, 15, 17)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(17, 17, 17)
        self.checkState(self.ss.FINISHED)

    def test_breakpoints_sum_larger_than_total_steps_raises_AssertionError(
            self):
        line1 = LineGenerator("x", "mm", -10, -10, 5)
        line2 = LineGenerator("x", "mm", 0, 180, 10)
        line3 = LineGenerator("x", "mm", 190, 190, 2)
        duration = 0.01
        concat = ConcatGenerator([line1, line2, line3])

        breakpoints = [2, 3, 100, 2]

        self.assertRaises(
            AssertionError,
            self.b.configure,
            generator=CompoundGenerator([concat], [], [], duration),
            axesToMove=["x"],
            breakpoints=breakpoints,
        )

    def test_breakpoints_without_last(self):
        line1 = LineGenerator("x", "mm", -10, -10, 5)
        line2 = LineGenerator("x", "mm", 0, 180, 10)
        line3 = LineGenerator("x", "mm", 190, 190, 2)
        duration = 0.01
        concat = ConcatGenerator([line1, line2, line3])
        breakpoints = [2, 3, 10]
        self.b.configure(
            generator=CompoundGenerator([concat], [], [], duration),
            axesToMove=["x"],
            breakpoints=breakpoints,
        )

        assert self.c.configure_params.generator.size == 17
        self.checkSteps(2, 0, 17)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(5, 2, 17)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(15, 5, 17)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(17, 15, 17)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(17, 17, 17)
        self.checkState(self.ss.FINISHED)

    def test_breakpoints_rocking_tomo(self):
        line1 = LineGenerator("x", "mm", -10, -10, 5)
        line2 = LineGenerator("x", "mm", 0, 180, 10)
        line3 = LineGenerator("x", "mm", 190, 190, 2)
        line4 = LineGenerator("x", "mm", 180, 0, 10)
        duration = 0.01
        concat = ConcatGenerator([line1, line2, line3, line4])
        breakpoints = [2, 3, 10, 2]
        self.b.configure(
            generator=CompoundGenerator([concat], [], [], duration),
            axesToMove=["x"],
            breakpoints=breakpoints,
        )

        assert self.c.configure_params.generator.size == 27
        self.checkSteps(2, 0, 27)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(5, 2, 27)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(15, 5, 27)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(17, 15, 27)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(27, 17, 27)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(27, 27, 27)
        self.checkState(self.ss.FINISHED)

    def test_breakpoints_repeat_with_static(self):
        line1 = LineGenerator("x", "mm", -10, -10, 5)
        line2 = LineGenerator("x", "mm", 0, 180, 10)
        line3 = LineGenerator("x", "mm", 190, 190, 2)
        duration = 0.01
        concat = ConcatGenerator([line1, line2, line3])

        staticGen = StaticPointGenerator(2)
        breakpoints = [2, 3, 10, 2, 2, 3, 10, 2]

        self.b.configure(
            generator=CompoundGenerator([staticGen, concat], [], [], duration),
            axesToMove=["x"],
            breakpoints=breakpoints,
        )

        assert self.c.configure_params.generator.size == 34

        self.checkState(self.ss.ARMED)
        self.checkSteps(2, 0, 34)

        self.b.run()
        self.checkSteps(5, 2, 34)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(15, 5, 34)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(17, 15, 34)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(19, 17, 34)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(22, 19, 34)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(32, 22, 34)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(34, 32, 34)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkState(self.ss.FINISHED)

    def test_breakpoints_repeat_rocking_tomo(self):
        line1 = LineGenerator("x", "mm", -10, -10, 5)
        line2 = LineGenerator("x", "mm", 0, 180, 10)
        line3 = LineGenerator("x", "mm", 190, 190, 2)
        line4 = LineGenerator("x", "mm", 180, 0, 10)
        concat = ConcatGenerator([line1, line2, line3, line4])

        staticGen = StaticPointGenerator(2)

        duration = 0.01
        breakpoints = [2, 3, 10, 2, 10, 2, 3, 10, 2, 10]
        self.b.configure(
            generator=CompoundGenerator([staticGen, concat], [], [], duration),
            axesToMove=["x"],
            breakpoints=breakpoints,
        )

        assert self.c.configure_params.generator.size == 54

        self.checkState(self.ss.ARMED)
        self.checkSteps(2, 0, 54)

        self.b.run()
        self.checkSteps(5, 2, 54)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(15, 5, 54)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(17, 15, 54)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(27, 17, 54)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(29, 27, 54)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(32, 29, 54)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(42, 32, 54)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(44, 42, 54)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(54, 44, 54)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkState(self.ss.FINISHED)

    def test_breakpoints_helical_scan(self):
        line1 = LineGenerator(["y", "x"], ["mm", "mm"], [-0.555556, -10],
                              [-0.555556, -10], 5)
        line2 = LineGenerator(["y", "x"], ["mm", "mm"], [0, 0], [10, 180], 10)
        line3 = LineGenerator(["y", "x"], ["mm", "mm"], [10.555556, 190],
                              [10.555556, 190], 2)
        duration = 0.01
        concat = ConcatGenerator([line1, line2, line3])

        breakpoints = [2, 3, 10, 2]
        self.b.configure(
            generator=CompoundGenerator([concat], [], [], duration),
            axesToMove=["y", "x"],
            breakpoints=breakpoints,
        )

        assert self.c.configure_params.generator.size == 17

        self.checkState(self.ss.ARMED)
        self.checkSteps(2, 0, 17)

        self.b.run()
        self.checkSteps(5, 2, 17)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(15, 5, 17)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(17, 15, 17)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkState(self.ss.FINISHED)

    def test_breakpoints_with_pause(self):
        line1 = LineGenerator("x", "mm", -10, -10, 5)
        line2 = LineGenerator("x", "mm", 0, 180, 10)
        line3 = LineGenerator("x", "mm", 190, 190, 2)
        duration = 0.01
        concat = ConcatGenerator([line1, line2, line3])
        breakpoints = [2, 3, 10, 2]
        self.b.configure(
            generator=CompoundGenerator([concat], [], [], duration),
            axesToMove=["x"],
            breakpoints=breakpoints,
        )

        assert self.c.configure_params.generator.size == 17

        self.checkSteps(2, 0, 17)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(5, 2, 17)
        self.checkState(self.ss.ARMED)

        # rewind
        self.b.pause(lastGoodStep=1)
        self.checkSteps(2, 1, 17)
        self.checkState(self.ss.ARMED)
        self.b.run()
        self.checkSteps(5, 2, 17)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(15, 5, 17)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(17, 15, 17)
        self.checkState(self.ss.ARMED)

        # rewind
        self.b.pause(lastGoodStep=11)
        self.checkSteps(15, 11, 17)
        self.checkState(self.ss.ARMED)
        self.b.run()
        self.checkSteps(17, 15, 17)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkSteps(17, 17, 17)
        self.checkState(self.ss.FINISHED)

    def abort_after_1s(self):
        # Need a new context as in a different cothread
        c = Context(self.p)
        b = c.block_view("mainBlock")
        c.sleep(1.0)
        self.checkState(self.ss.RUNNING)
        b.abort()
        self.checkState(self.ss.ABORTED)

    def test_run_returns_in_ABORTED_state_when_aborted(self):
        # Add our forever running part
        forever_part = RunForeverPart(mri="childBlock",
                                      name="forever_part",
                                      initial_visibility=True)
        self.c.add_part(forever_part)

        # Configure our block
        duration = 0.1
        line1 = LineGenerator("y", "mm", 0, 2, 3)
        line2 = LineGenerator("x", "mm", 0, 2, 2, alternate=True)
        compound = CompoundGenerator([line1, line2], [], [], duration)
        self.b.configure(generator=compound, axesToMove=["x"])

        # Spawn the abort thread
        abort_thread = cothread.Spawn(self.abort_after_1s, raise_on_wait=True)

        # Do the run, which will be aborted
        with self.assertRaises(AbortedError):
            self.b.run()

        self.checkState(self.ss.ABORTED)

        # Check the abort thread didn't raise
        abort_thread.Wait(1.0)

    def test_breakpoints_tomo_with_outer_axis(self):
        # Outer axis we don't move
        outer_steps = 2
        line_outer = LineGenerator("y", "mm", 0, 1, outer_steps)

        # ConcatGenerator we do move
        line1 = LineGenerator("x", "mm", -10, -10, 5)
        line2 = LineGenerator("x", "mm", 0, 180, 10)
        line3 = LineGenerator("x", "mm", 190, 190, 2)
        concat = ConcatGenerator([line1, line2, line3])

        compound = CompoundGenerator([line_outer, concat], [], [],
                                     duration=0.01)
        breakpoints = [2, 3, 10, 2]
        inner_steps = sum(breakpoints)
        total_steps = inner_steps * outer_steps

        self.b.configure(generator=compound,
                         axesToMove=["x"],
                         breakpoints=breakpoints)
        # Configured, completed, total
        self.checkSteps(2, 0, total_steps)
        self.checkState(self.ss.ARMED)

        # Check we have the full configured steps
        assert self.c.configure_params.generator.size == total_steps

        # Check our breakpoints steps
        expected_breakpoint_steps = [2, 5, 15, 17, 19, 22, 32, 34]
        self.assertEqual(expected_breakpoint_steps, self.c.breakpoint_steps)

        # Run our controller through all but last breakpoint
        breakpoints = len(expected_breakpoint_steps)
        for index in range(breakpoints - 1):
            self.b.run()
            self.checkSteps(
                expected_breakpoint_steps[index + 1],
                expected_breakpoint_steps[index],
                total_steps,
            )
            self.checkState(self.ss.ARMED)

        # Final breakpoint
        self.b.run()
        self.checkSteps(total_steps, total_steps, total_steps)
        self.checkState(self.ss.FINISHED)

    def test_breakpoints_tomo_with_two_outer_axes(self):
        # Outer axes we don't move
        outer_steps = 2
        line_outer = LineGenerator("y", "mm", 0, 1, outer_steps)
        outer_outer_steps = 3
        line_outer_outer = LineGenerator("z", "mm", 0, 1, outer_outer_steps)

        # ConcatGenerator we do move
        line1 = LineGenerator("x", "mm", -10, -10, 5)
        line2 = LineGenerator("x", "mm", 0, 180, 10)
        concat = ConcatGenerator([line1, line2])

        compound = CompoundGenerator([line_outer_outer, line_outer, concat],
                                     [], [],
                                     duration=0.01)
        breakpoints = [2, 3, 10]
        inner_steps = sum(breakpoints)
        total_steps = inner_steps * outer_steps * outer_outer_steps

        self.b.configure(generator=compound,
                         axesToMove=["x"],
                         breakpoints=breakpoints)
        # Configured, completed, total
        self.checkSteps(2, 0, total_steps)
        self.checkState(self.ss.ARMED)

        # Check we have the full configured steps
        assert self.c.configure_params.generator.size == total_steps

        # Check our breakpoints steps
        expected_breakpoint_steps = [
            2,
            5,
            15,
            17,
            20,
            30,
            32,
            35,
            45,
            47,
            50,
            60,
            62,
            65,
            75,
            77,
            80,
            90,
        ]
        self.assertEqual(expected_breakpoint_steps, self.c.breakpoint_steps)

        # Run our controller through all but last breakpoint
        breakpoints = len(expected_breakpoint_steps)
        for index in range(breakpoints - 1):
            self.b.run()
            self.checkSteps(
                expected_breakpoint_steps[index + 1],
                expected_breakpoint_steps[index],
                total_steps,
            )
            self.checkState(self.ss.ARMED)

        # Final breakpoint
        self.b.run()
        self.checkSteps(total_steps, total_steps, total_steps)
        self.checkState(self.ss.FINISHED)

    def test_breakpoints_2d_inner_scan(self):
        # Y-axis
        outer_steps = 2
        line_y = LineGenerator("y", "mm", 0, 1, outer_steps)

        # X-axis
        line_x_1 = LineGenerator("x", "mm", -10, -10, 5)
        line_x_2 = LineGenerator("x", "mm", 0, 180, 10)
        line_x_3 = LineGenerator("x", "mm", 190, 190, 2)
        line_x = ConcatGenerator([line_x_1, line_x_2, line_x_3])

        compound = CompoundGenerator([line_y, line_x], [], [], duration=0.01)
        breakpoints = [2, 3, 10, 2, 17]
        total_steps = sum(breakpoints)

        # Configure the scan
        self.b.configure(generator=compound,
                         axesToMove=["x", "y"],
                         breakpoints=breakpoints)
        self.checkSteps(2, 0, total_steps)
        self.checkState(self.ss.ARMED)

        # Check we have the full amount of configured steps
        assert self.c.configure_params.generator.size == total_steps

        # Check our breakpoints steps
        expected_breakpoint_steps = [2, 5, 15, 17, 34]
        self.assertEqual(expected_breakpoint_steps, self.c.breakpoint_steps)

        # Run our controller through all but last breakpoint
        breakpoints = len(expected_breakpoint_steps)
        for index in range(breakpoints - 1):
            self.b.run()
            self.checkSteps(
                expected_breakpoint_steps[index + 1],
                expected_breakpoint_steps[index],
                total_steps,
            )
            self.checkState(self.ss.ARMED)

        # Final breakpoint
        self.b.run()
        self.checkSteps(total_steps, total_steps, total_steps)
        self.checkState(self.ss.FINISHED)

    def test_breakpoints_2d_inner_scan_with_outer_axis(self):
        # Outer axes we don't move
        outer_steps = 2
        line_outer = LineGenerator("z", "mm", 0, 1, outer_steps)

        # Y-axis
        line_y = LineGenerator("y", "mm", 0, 1, 2)

        # X-axis
        line_x_1 = LineGenerator("x", "mm", -10, -10, 5)
        line_x_2 = LineGenerator("x", "mm", 0, 180, 10)
        line_x_3 = LineGenerator("x", "mm", 190, 190, 2)
        line_x = ConcatGenerator([line_x_1, line_x_2, line_x_3])

        compound = CompoundGenerator([line_outer, line_y, line_x], [], [],
                                     duration=0.01)
        breakpoints = [2, 3, 10, 2, 17]
        total_steps = sum(breakpoints) * outer_steps

        # Configure the scan
        self.b.configure(generator=compound,
                         axesToMove=["x", "y"],
                         breakpoints=breakpoints)
        self.checkSteps(2, 0, total_steps)
        self.checkState(self.ss.ARMED)

        # Check we have the full amount of configured steps
        assert self.c.configure_params.generator.size == total_steps

        # Check our breakpoints steps
        expected_breakpoint_steps = [2, 5, 15, 17, 34, 36, 39, 49, 51, 68]
        self.assertEqual(expected_breakpoint_steps, self.c.breakpoint_steps)

        # Run our controller through all but last breakpoint
        breakpoints = len(expected_breakpoint_steps)
        for index in range(breakpoints - 1):
            self.b.run()
            self.checkSteps(
                expected_breakpoint_steps[index + 1],
                expected_breakpoint_steps[index],
                total_steps,
            )
            self.checkState(self.ss.ARMED)

        # Final breakpoint
        self.b.run()
        self.checkSteps(total_steps, total_steps, total_steps)
        self.checkState(self.ss.FINISHED)
示例#14
0
class TestRunnableController(unittest.TestCase):
    def setUp(self):
        self.p = Process("process")
        self.context = Context(self.p)

        # Make a motion block to act as our child
        for c in motion_block(mri="childBlock", config_dir="/tmp"):
            self.p.add_controller(c)
        self.b_child = self.context.block_view("childBlock")

        part = MisbehavingPart(mri="childBlock",
                               name="part",
                               initial_visibility=True)

        # create a root block for the RunnableController block to reside in
        self.c = RunnableController(mri="mainBlock", config_dir="/tmp")
        self.c.add_part(part)
        self.p.add_controller(self.c)
        self.b = self.context.block_view("mainBlock")
        self.ss = self.c.state_set

        # start the process off
        self.checkState(self.ss.DISABLED)
        self.p.start()
        self.checkState(self.ss.READY)

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

    def checkState(self, state):
        assert self.c.state.value == state

    def checkSteps(self, configured, completed, total):
        assert self.b.configuredSteps.value == configured
        assert self.b.completedSteps.value == completed
        assert self.b.totalSteps.value == total

    def test_init(self):
        assert self.c.completed_steps.value == 0
        assert self.c.configured_steps.value == 0
        assert self.c.total_steps.value == 0
        assert list(self.b.configure.meta.takes.elements) == [
            "generator",
            "axesToMove",
            "breakpoints",
            "exceptionStep",
        ]

    def test_reset(self):
        self.c.disable()
        self.checkState(self.ss.DISABLED)
        self.c.reset()
        self.checkState(self.ss.READY)

    def test_modify_child(self):
        # Save an initial setting for the child
        self.b_child.save("init_child")
        assert self.b_child.modified.value is False
        x = self.context.block_view("childBlock:COUNTERX")
        x.delta.put_value(31)
        # x delta now at 31, child should be modified
        assert x.delta.value == 31
        assert self.b_child.modified.value is True
        assert self.b_child.modified.alarm.severity == AlarmSeverity.MINOR_ALARM
        assert self.b_child.modified.alarm.status == AlarmStatus.CONF_STATUS
        assert self.b_child.modified.alarm.message == "x.delta.value = 31.0 not 1.0"
        self.prepare_half_run()
        self.b.run()
        # x counter now at 3 (lower bound of first run of x in reverse),
        # child should still be modified
        assert self.b_child.modified.value is True
        assert self.b_child.modified.alarm.severity == AlarmSeverity.MINOR_ALARM
        assert self.b_child.modified.alarm.status == AlarmStatus.CONF_STATUS
        assert self.b_child.modified.alarm.message == "x.delta.value = 31.0 not 1.0"
        assert x.counter.value == 3.0
        assert x.delta.value == 31
        x.delta.put_value(1.0)
        # x counter now at 0, child should be unmodified
        assert x.delta.value == 1.0
        assert self.b_child.modified.alarm.message == ""
        assert self.b_child.modified.value is False

    def test_modify_parent(self):
        # Save an initial setting for child and parent
        self.b_child.save("init_child")
        self.b.save("init_parent")
        # Change a value and save as a new child setting
        x = self.context.block_view("childBlock:COUNTERX")
        x.counter.put_value(31)
        self.b_child.save("new_child")
        assert self.b_child.modified.value is False
        assert self.b.modified.value is True
        assert self.b.modified.alarm.severity == AlarmSeverity.MINOR_ALARM
        assert self.b.modified.alarm.status == AlarmStatus.CONF_STATUS
        assert (self.b.modified.alarm.message ==
                "part.design.value = 'new_child' not 'init_child'")
        # Load the child again
        self.b_child.design.put_value("new_child")
        assert self.b.modified.value is True
        # And check that loading parent resets it
        self.b.design.put_value("init_parent")
        assert self.b.modified.value is False
        assert self.b_child.design.value == "init_child"
        # Put back
        self.b_child.design.put_value("new_child")
        assert self.b.modified.value is True
        # Do a configure, and check we get set back
        self.prepare_half_run()
        assert self.b_child.design.value == "init_child"
        assert self.b_child.modified.value is False
        assert self.b.modified.value is False

    def test_abort(self):
        self.b.abort()
        self.checkState(self.ss.ABORTED)

    def test_validate(self):
        line1 = LineGenerator("y", "mm", 0, 2, 3)
        line2 = LineGenerator("x", "mm", 0, 2, 2)
        compound = CompoundGenerator([line1, line2], [], [], duration=0.001)
        actual = self.b.validate(generator=compound, axesToMove=["x"])
        assert actual["generator"].duration == 0.1
        actual["generator"].duration = 0.001
        assert actual["generator"].to_dict() == compound.to_dict()
        assert actual["axesToMove"] == ["x"]

    def prepare_half_run(self, duration=0.01, exception=0):
        line1 = LineGenerator("y", "mm", 0, 2, 3)
        line2 = LineGenerator("x", "mm", 0, 2, 2, alternate=True)
        compound = CompoundGenerator([line1, line2], [], [], duration)
        self.b.configure(generator=compound,
                         axesToMove=["x"],
                         exceptionStep=exception)

    def test_configure_run(self):
        assert self.b.configure.meta.writeable is True
        assert self.b.configure.meta.takes.elements[
            "generator"].writeable is True
        assert self.b.validate.meta.takes.elements[
            "generator"].writeable is True
        assert self.b.validate.meta.returns.elements[
            "generator"].writeable is False
        self.prepare_half_run()
        self.checkSteps(2, 0, 6)
        self.checkState(self.ss.ARMED)
        assert self.b.configure.meta.writeable is False
        assert self.b.configure.meta.takes.elements[
            "generator"].writeable is True
        assert self.b.validate.meta.takes.elements[
            "generator"].writeable is True
        assert self.b.validate.meta.returns.elements[
            "generator"].writeable is False

        self.b.run()
        self.checkState(self.ss.ARMED)
        self.checkSteps(4, 2, 6)

        self.b.run()
        self.checkState(self.ss.ARMED)
        self.checkSteps(6, 4, 6)

        self.b.run()
        self.checkState(self.ss.FINISHED)

    def test_abort_during_run(self):
        self.prepare_half_run()
        self.b.run()
        self.b.abort()
        self.checkState(self.ss.ABORTED)

    def test_pause_seek_resume(self):
        self.prepare_half_run()
        self.checkSteps(configured=2, completed=0, total=6)
        self.b.run()
        self.checkState(self.ss.ARMED)
        self.checkSteps(4, 2, 6)
        self.b.pause(lastGoodStep=1)
        self.checkState(self.ss.ARMED)
        self.checkSteps(2, 1, 6)
        self.b.run()
        self.checkSteps(4, 2, 6)
        self.b.completedSteps.put_value(5)
        self.checkSteps(6, 5, 6)
        self.b.run()
        self.checkState(self.ss.FINISHED)

    def test_pause_seek_resume_outside_limits(self):
        self.prepare_half_run()
        self.checkSteps(configured=2, completed=0, total=6)
        self.b.run()
        self.checkState(self.ss.ARMED)
        self.checkSteps(4, 2, 6)
        self.b.pause(lastGoodStep=7)
        self.checkState(self.ss.ARMED)
        self.checkSteps(6, 5, 6)
        self.b.run()
        self.checkState(self.ss.FINISHED)

    def test_resume_in_run(self):
        self.prepare_half_run(duration=0.5)
        f = self.b.run_async()
        self.context.sleep(0.95)
        self.b.pause()
        self.checkState(self.ss.PAUSED)
        self.checkSteps(2, 1, 6)
        self.b.resume()
        # Parent should be running, child won't have got request yet
        # then = time.time()
        self.checkState(self.ss.RUNNING)
        self.context.wait_all_futures(f, timeout=2)
        # now = time.time()
        self.checkState(self.ss.ARMED)
        self.checkSteps(4, 2, 6)
        # This test fails on Travis sometimes, looks like the docker container
        # just gets starved
        # self.assertAlmostEqual(now - then, 0.5, delta=0.1)

    def test_pause_seek_resume_from_finished(self):
        self.prepare_half_run()
        self.checkSteps(configured=2, completed=0, total=6)

        self.b.run()
        self.checkState(self.ss.ARMED)
        self.checkSteps(4, 2, 6)

        self.b.run()
        self.checkState(self.ss.ARMED)
        self.checkSteps(6, 4, 6)

        self.b.run()
        self.checkState(self.ss.FINISHED)

        self.b.pause(lastGoodStep=1)
        self.checkState(self.ss.ARMED)
        self.checkSteps(2, 1, 6)

        self.b.run()
        self.checkState(self.ss.ARMED)
        self.checkSteps(4, 2, 6)

        self.b.run()
        self.checkState(self.ss.ARMED)
        self.checkSteps(6, 4, 6)

        self.b.run()
        self.checkState(self.ss.FINISHED)

    def test_pause_seek_resume_from_postrun(self):
        self.prepare_half_run()
        self.checkSteps(configured=2, completed=0, total=6)

        self.b.run()
        self.checkState(self.ss.ARMED)
        self.checkSteps(4, 2, 6)

        self.b.run()
        self.checkState(self.ss.ARMED)
        self.checkSteps(6, 4, 6)

        self.b.run()
        self.checkState(self.ss.FINISHED)

        self.b.pause(lastGoodStep=1)
        self.checkState(self.ss.ARMED)
        self.checkSteps(2, 1, 6)

        self.b.run()
        self.checkState(self.ss.ARMED)
        self.checkSteps(4, 2, 6)

        self.b.run()
        self.checkState(self.ss.ARMED)
        self.checkSteps(6, 4, 6)

        self.b.run()
        self.checkState(self.ss.FINISHED)

    def test_reset_from_finished(self):
        self.prepare_half_run()
        self.checkSteps(2, 0, 6)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkState(self.ss.ARMED)
        self.checkSteps(4, 2, 6)

        self.b.run()
        self.checkState(self.ss.ARMED)
        self.checkSteps(6, 4, 6)

        self.b.run()
        self.checkState(self.ss.FINISHED)

        self.c.reset()
        self.checkState(self.ss.READY)

    def test_configure_from_finished(self):
        self.prepare_half_run()
        self.checkSteps(2, 0, 6)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkState(self.ss.ARMED)
        self.checkSteps(4, 2, 6)

        self.b.run()
        self.checkState(self.ss.ARMED)
        self.checkSteps(6, 4, 6)

        self.b.run()
        self.checkState(self.ss.FINISHED)

        self.prepare_half_run()
        self.checkSteps(2, 0, 6)
        self.checkState(self.ss.ARMED)

        self.b.run()
        self.checkState(self.ss.ARMED)
        self.checkSteps(4, 2, 6)

        self.b.run()
        self.checkState(self.ss.ARMED)
        self.checkSteps(6, 4, 6)

        self.b.run()
        self.checkState(self.ss.FINISHED)

    def test_run_exception(self):
        self.prepare_half_run(exception=1)
        with self.assertRaises(AssertionError):
            self.b.run()
        self.checkState(self.ss.FAULT)

    def test_run_stop(self):
        self.prepare_half_run(duration=0.1)
        f = self.b.run_async()
        self.context.sleep(0.1)
        self.b.abort()
        with self.assertRaises(AbortedError):
            f.result()
        self.checkState(self.ss.ABORTED)

    def test_error_in_pause_returns_run(self):
        self.prepare_half_run(duration=0.5)
        f = self.b.run_async()
        self.context.sleep(0.95)
        with self.assertRaises(MisbehavingPauseException):
            self.b.pause(lastGoodStep=3)
        self.checkState(self.ss.FAULT)
        with self.assertRaises(AbortedError):
            f.result()