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' ]
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()
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()
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", ]
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()
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()
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)
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)
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)
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()