class TestRunnableController(unittest.TestCase): def checkState(self, state, child=True, parent=True): if child: self.assertEqual(self.b_child.state, state) if parent: self.assertEqual(self.b.state, state) def checkSteps(self, configured, completed, total): self.assertEqual(self.b.configuredSteps, configured) self.assertEqual(self.b.completedSteps, completed) self.assertEqual(self.b.totalSteps, total) self.assertEqual(self.b_child.configuredSteps, configured) self.assertEqual(self.b_child.completedSteps, completed) self.assertEqual(self.b_child.totalSteps, total) def setUp(self): self.maxDiff = 5000 self.p = Process('process1', SyncFactory('threading')) # Make a ticker block to act as our child params = Ticker.MethodMeta.prepare_input_map(mri="childBlock", configDir="/tmp") self.b_child = Ticker(self.p, params)[-1] # Make an empty part for our parent params = Part.MethodMeta.prepare_input_map(name='part1') part1 = Part(self.p, params) # Make a RunnableChildPart to control the ticker params = RunnableChildPart.MethodMeta.prepare_input_map( mri='childBlock', name='part2') part2 = RunnableChildPart(self.p, params) # create a root block for the RunnableController block to reside in params = RunnableController.MethodMeta.prepare_input_map( mri='mainBlock', configDir="/tmp") self.c = RunnableController(self.p, [part1, part2], params) self.b = self.c.block self.sm = self.c.stateMachine # start the process off self.p.start() # wait until block is Ready task = Task("block_ready_task", self.p) task.when_matches(self.b["state"], self.sm.IDLE, timeout=1) self.checkState(self.sm.IDLE) def tearDown(self): self.p.stop() def test_init(self): # the following block attributes should be created by a call to # set_attributes via _set_block_children in __init__ self.assertEqual(self.b['totalSteps'].meta.typeid, 'malcolm:core/NumberMeta:1.0') self.assertEqual(self.b['completedSteps'].meta.typeid, 'malcolm:core/NumberMeta:1.0') self.assertEqual(self.b['configuredSteps'].meta.typeid, 'malcolm:core/NumberMeta:1.0') self.assertEqual(self.b['axesToMove'].meta.typeid, 'malcolm:core/StringArrayMeta:1.0') # the following hooks should be created via _find_hooks in __init__ self.assertEqual( self.c.hook_names, { self.c.Reset: "Reset", self.c.Disable: "Disable", self.c.ReportOutports: "ReportOutports", self.c.Layout: "Layout", self.c.Load: "Load", self.c.Save: "Save", self.c.Validate: "Validate", self.c.ReportStatus: "ReportStatus", self.c.Configure: "Configure", self.c.PostConfigure: "PostConfigure", self.c.Run: "Run", self.c.PostRunReady: "PostRunReady", self.c.PostRunIdle: "PostRunIdle", self.c.Seek: "Seek", self.c.Pause: "Pause", self.c.Resume: "Resume", self.c.Abort: "Abort", }) # check instantiation of object tree via logger names self.assertEqual(self.c._logger.name, 'RunnableController(mainBlock)') self.assertEqual(self.c.parts['part1']._logger.name, 'RunnableController(mainBlock).part1') self.assertEqual(self.c.parts['part2']._logger.name, 'RunnableController(mainBlock).part2') def test_edit(self): self.b.edit() self.checkState(self.sm.EDITABLE, child=False) def test_reset(self): self.b.disable() self.checkState(self.sm.DISABLED) self.b.reset() self.checkState(self.sm.IDLE) def test_set_axes_to_move(self): self.c.set_axes_to_move(['y']) self.assertEqual(self.c.axes_to_move.value, ('y', )) 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']) self.assertEqual(actual["generator"], compound) self.assertEqual(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): self.prepare_half_run() self.checkSteps(2, 0, 6) self.checkState(self.sm.READY) self.b.run() self.checkState(self.sm.READY) self.checkSteps(4, 2, 6) self.b.run() self.checkState(self.sm.READY) self.checkSteps(6, 4, 6) self.b.run() self.checkState(self.sm.IDLE) def test_abort(self): self.prepare_half_run() self.b.run() self.b.abort() self.checkState(self.sm.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.sm.READY) self.checkSteps(4, 2, 6) self.b.pause(completedSteps=1) self.checkState(self.sm.READY) self.checkSteps(2, 1, 6) self.b.run() self.checkSteps(4, 2, 6) self.b.completedSteps = 5 self.checkSteps(6, 5, 6) self.b.run() self.checkState(self.sm.IDLE) def test_resume_in_run(self): self.prepare_half_run(duration=0.5) w = self.p.spawn(self.b.run) time.sleep(0.85) self.b.pause() self.checkState(self.sm.PAUSED) self.checkSteps(2, 1, 6) self.b.resume() # return to PRERUN should continue original run to completion and # READY state then = time.time() w.wait() self.assertAlmostEqual(time.time() - then, 0.5, delta=0.4) self.checkState(self.sm.READY) def test_run_exception(self): self.prepare_half_run(exception=1) with self.assertRaises(ResponseError): self.b.run() self.checkState(self.sm.FAULT) def test_run_stop(self): self.prepare_half_run(duration=0.5) w = self.p.spawn(self.b.run) time.sleep(0.5) self.b.abort() with self.assertRaises(AbortedError): w.get() self.checkState(self.sm.ABORTED)
class TestHDFWriterPart(ChildTestCase): maxDiff = None def setUp(self): self.process = Process("Process") self.context = Context(self.process) self.child = self.create_child_block(hdf_writer_block, self.process, mri="BLOCK:HDF5", prefix="prefix") self.config_dir = tmp_dir("config_dir") self.process.start() def tearDown(self): self.process.stop(2) shutil.rmtree(self.config_dir.value) 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", ] @patch("malcolm.modules.ADCore.parts.hdfwriterpart.check_driver_version") def test_validate(self, check_mock): self.o = HDFWriterPart(name="m", mri="BLOCK:HDF5") # Version check should not be called with require_version None self.o.require_version = None self.set_attributes(self.child, driverVersion="1.1") self.o.on_validate(self.context) check_mock.assert_not_called() # Test version check called if required_version not None self.o.required_version = "1.0" self.o.on_validate(self.context) check_mock.assert_called_once_with("1.1", "1.0") def configure_and_check_output(self, on_windows=False): energy = LineGenerator("energy", "kEv", 13.0, 15.2, 2) spiral = SpiralGenerator(["x", "y"], ["mm", "mm"], [0.0, 0.0], 5.0, scale=2.0) generator = CompoundGenerator([energy, spiral], [], [], 0.1) generator.prepare() fileDir = "/tmp" formatName = "xspress3" fileTemplate = "thing-%s.h5" completed_steps = 0 steps_to_do = 38 part_info = { "DET": [NDArrayDatasetInfo(2)], "PANDA": [ NDAttributeDatasetInfo.from_attribute_type( "I0", AttributeDatasetType.DETECTOR, "COUNTER1.COUNTER"), NDAttributeDatasetInfo.from_attribute_type( "It", AttributeDatasetType.MONITOR, "COUNTER2.COUNTER"), NDAttributeDatasetInfo.from_attribute_type( "t1x", AttributeDatasetType.POSITION, "INENC1.VAL"), ], "STAT": [CalculatedNDAttributeDatasetInfo("sum", "StatsTotal")], } if on_windows: part_info["WINPATH"] = [FilePathTranslatorInfo("Y", "/tmp", "")] infos = self.o.on_configure( self.context, completed_steps, steps_to_do, part_info, generator, fileDir, formatName, fileTemplate, ) assert len(infos) == 8 assert infos[0].name == "xspress3.data" assert infos[0].filename == "thing-xspress3.h5" assert infos[0].type == DatasetType.PRIMARY assert infos[0].rank == 4 assert infos[0].path == "/entry/detector/detector" assert infos[0].uniqueid == "/entry/NDAttributes/NDArrayUniqueId" assert infos[1].name == "xspress3.sum" assert infos[1].filename == "thing-xspress3.h5" assert infos[1].type == DatasetType.SECONDARY assert infos[1].rank == 2 assert infos[1].path == "/entry/sum/sum" assert infos[1].uniqueid == "/entry/NDAttributes/NDArrayUniqueId" assert infos[2].name == "I0.data" assert infos[2].filename == "thing-xspress3.h5" assert infos[2].type == DatasetType.PRIMARY assert infos[2].rank == 2 assert infos[2].path == "/entry/I0.data/I0.data" assert infos[2].uniqueid == "/entry/NDAttributes/NDArrayUniqueId" assert infos[3].name == "It.data" assert infos[3].filename == "thing-xspress3.h5" assert infos[3].type == DatasetType.MONITOR assert infos[3].rank == 2 assert infos[3].path == "/entry/It.data/It.data" assert infos[3].uniqueid == "/entry/NDAttributes/NDArrayUniqueId" assert infos[4].name == "t1x.value" assert infos[4].filename == "thing-xspress3.h5" assert infos[4].type == DatasetType.POSITION_VALUE assert infos[4].rank == 2 assert infos[4].path == "/entry/t1x.value/t1x.value" assert infos[4].uniqueid == "/entry/NDAttributes/NDArrayUniqueId" assert infos[5].name == "energy.value_set" assert infos[5].filename == "thing-xspress3.h5" assert infos[5].type == DatasetType.POSITION_SET assert infos[5].rank == 1 assert infos[5].path == "/entry/detector/energy_set" assert infos[5].uniqueid == "" assert infos[6].name == "x.value_set" assert infos[6].filename == "thing-xspress3.h5" assert infos[6].type == DatasetType.POSITION_SET assert infos[6].rank == 1 assert infos[6].path == "/entry/detector/x_set" assert infos[6].uniqueid == "" assert infos[7].name == "y.value_set" assert infos[7].filename == "thing-xspress3.h5" assert infos[7].type == DatasetType.POSITION_SET assert infos[7].rank == 1 assert infos[7].path == "/entry/detector/y_set" assert infos[7].uniqueid == "" expected_xml_filename_local = "/tmp/BLOCK_HDF5-layout.xml" if on_windows: expected_xml_filename_remote = "Y:\\BLOCK_HDF5-layout.xml" expected_filepath = "Y:" + os.sep else: expected_xml_filename_remote = expected_xml_filename_local expected_filepath = "/tmp" + os.sep # Wait for the start_future so the post gets through to our child # even on non-cothread systems self.o.start_future.result(timeout=1) assert self.child.handled_requests.mock_calls == [ call.put("positionMode", True), call.put("arrayCounter", 0), call.put("dimAttDatasets", True), call.put("enableCallbacks", True), call.put("fileName", "xspress3"), call.put("filePath", expected_filepath), call.put("fileTemplate", "%sthing-%s.h5"), call.put("fileWriteMode", "Stream"), call.put("lazyOpen", True), call.put("storeAttr", True), call.put("swmrMode", True), call.put("extraDimSize3", 1), call.put("extraDimSize4", 1), call.put("extraDimSize5", 1), call.put("extraDimSize6", 1), call.put("extraDimSize7", 1), call.put("extraDimSize8", 1), call.put("extraDimSize9", 1), call.put("extraDimSizeN", 20), call.put("extraDimSizeX", 2), call.put("extraDimSizeY", 1), call.put("numExtraDims", 1), call.put("posNameDim3", ""), call.put("posNameDim4", ""), call.put("posNameDim5", ""), call.put("posNameDim6", ""), call.put("posNameDim7", ""), call.put("posNameDim8", ""), call.put("posNameDim9", ""), call.put("posNameDimN", "d1"), call.put("posNameDimX", "d0"), call.put("posNameDimY", ""), call.put("flushAttrPerNFrames", 0), call.put("flushDataPerNFrames", 38), call.put("xmlLayout", expected_xml_filename_remote), call.put("numCapture", 0), call.post("start"), call.when_value_matches("arrayCounterReadback", greater_than_zero, None), ] with open(expected_xml_filename_local) as f: actual_xml = f.read().replace(">", ">\n") # Check the layout filename Malcolm uses for file creation assert self.o.layout_filename == expected_xml_filename_local return actual_xml @staticmethod def mock_xml_is_valid_check(part): mock_xml_layout_value = MagicMock(name="mock_xml_layout_value") mock_xml_layout_value.return_value = True part._check_xml_is_valid = mock_xml_layout_value def test_configure(self): self.mock_when_value_matches(self.child) self.o = HDFWriterPart(name="m", mri="BLOCK:HDF5") self.mock_xml_is_valid_check(self.o) self.context.set_notify_dispatch_request( self.o.notify_dispatch_request) actual_xml = self.configure_and_check_output() assert actual_xml == expected_xml actual_tree = ElementTree.XML(actual_xml) expected_tree = ElementTree.XML(expected_xml) assert ElementTree.dump(actual_tree) == ElementTree.dump(expected_tree) def test_honours_write_all_attributes_flag(self): self.mock_when_value_matches(self.child) self.o = HDFWriterPart(name="m", mri="BLOCK:HDF5", write_all_nd_attributes=False) self.mock_xml_is_valid_check(self.o) self.context.set_notify_dispatch_request( self.o.notify_dispatch_request) actual_xml = self.configure_and_check_output() actual_tree = ElementTree.XML(actual_xml) expected_tree = ElementTree.XML(expected_xml) assert ElementTree.dump(actual_tree) == ElementTree.dump(expected_tree) def test_configure_windows(self): self.mock_when_value_matches(self.child) self.o = HDFWriterPart(name="m", mri="BLOCK:HDF5", runs_on_windows=True) self.mock_xml_is_valid_check(self.o) self.context.set_notify_dispatch_request( self.o.notify_dispatch_request) actual_xml = self.configure_and_check_output(on_windows=True) assert actual_xml == expected_xml actual_tree = ElementTree.XML(actual_xml) expected_tree = ElementTree.XML(expected_xml) assert ElementTree.dump(actual_tree) == ElementTree.dump(expected_tree) def test_run(self): self.o = HDFWriterPart(name="m", mri="BLOCK:HDF5") self.context.set_notify_dispatch_request( self.o.notify_dispatch_request) self.o.done_when_captured = 38 # Need a registrar object or we get AssertionError self.o.registrar = MagicMock() # Run waits for this value, so say we have finished immediately self.set_attributes(self.child, numCapturedReadback=self.o.done_when_captured) self.mock_when_value_matches(self.child) # Run self.o.on_run(self.context) # Check calls assert self.child.handled_requests.mock_calls == [ call.when_value_matches("numCapturedReadback", 38, None) ] assert self.o.registrar.report.called_once assert self.o.registrar.report.call_args_list[0][0][0].steps == 38 def test_run_and_flush(self): self.o = HDFWriterPart(name="m", mri="BLOCK:HDF5") def set_num_captured(): # Sleep for 2.5 seconds to ensure 2 flushes, and then set value to finish cothread.Sleep(2.5) self.set_attributes(self.child, numCapturedReadback=self.o.done_when_captured) self.o.done_when_captured = 5 # Say that we're getting the first frame self.o.first_array_future = Future(None) self.o.first_array_future.set_result(None) self.o.start_future = Future(None) # Need a registrar object or we get AssertionError self.o.registrar = MagicMock() # Reduce frame timeout so we don't hang on this test for too long self.o.frame_timeout = 5 # Spawn process to finish it after a few seconds self.process.spawn(set_num_captured) # Run self.o.on_run(self.context) # Check calls assert self.child.handled_requests.mock_calls == [ call.post("flushNow"), call.post("flushNow"), ] assert self.o.registrar.report.called_once assert self.o.registrar.report.call_args_list[0][0][0].steps == 0 assert self.o.registrar.report.call_args_list[1][0][0].steps == 5 def test_run_raises_TimeoutError_for_stalled_writer(self): self.o = HDFWriterPart(name="m", mri="BLOCK:HDF5") self.context.set_notify_dispatch_request( self.o.notify_dispatch_request) self.o.done_when_captured = 10 # Need a registrar object or we get AssertionError self.o.registrar = MagicMock() self.o.start_future = MagicMock() # Mock the last update self.o.last_capture_update = MagicMock() self.o.last_capture_update.return_value = 0.0 # Set a short timeout for testing self.o.frame_timeout = 0.1 # Now check the error is raised self.assertRaises(TimeoutError, self.o.on_run, self.context) def test_seek(self): self.o = HDFWriterPart(name="m", mri="BLOCK:HDF5") self.context.set_notify_dispatch_request( self.o.notify_dispatch_request) # Num captured readback usually reads a bit higher than the completed steps # after a pause is requested completed_steps = 4 self.set_attributes(self.child, numCapturedReadback=6) # Call the seek steps_to_do = 3 self.o.on_seek(self.context, completed_steps, steps_to_do) # We expect done when captured to be the current captured readback + steps to do assert self.o.done_when_captured == 9 def test_post_run_ready(self): self.o = HDFWriterPart(name="m", mri="BLOCK:HDF5") self.context.set_notify_dispatch_request( self.o.notify_dispatch_request) # Say that we've returned from start self.o.start_future = Future(None) self.o.start_future.set_result(None) fname = "/tmp/test_filename" with open(fname, "w") as f: f.write("thing") assert os.path.isfile(fname) self.o.layout_filename = fname self.o.on_post_run_ready(self.context) assert self.child.handled_requests.mock_calls == [] assert os.path.isfile(fname) self.o.on_reset(self.context) assert not os.path.isfile(fname) def test_post_run_ready_not_done_flush(self): # Say that we've returned from start self.o = HDFWriterPart(name="m", mri="BLOCK:HDF5") self.o.start_future = Future(None) fname = "/tmp/test_filename" with open(fname, "w") as f: f.write("thing") assert os.path.isfile(fname) self.o.layout_filename = fname self.o.on_post_run_ready(self.context) assert self.child.handled_requests.mock_calls == [ call.post("flushNow") ] assert os.path.isfile(fname) self.o.on_reset(self.context) assert not os.path.isfile(fname) def test_check_xml_is_valid_method_succeeds_for_valid_value(self): self.o = HDFWriterPart(name="m", mri="BLOCK:HDF5") child = MagicMock(name="child_mock") child.xmlLayoutValid.value = True try: self.o._check_xml_is_valid(child) except AssertionError: self.fail("_check_xml_is_valid() threw unexpected AssertionError") def test_check_xml_is_valid_method_throws_AssertionError_for_bad_value( self): self.o = HDFWriterPart(name="m", mri="BLOCK:HDF5") child = MagicMock(name="child_mock") child.xmlLayoutValid.value = False child.xmlErrorMsg.value = "XML description file cannot be opened" self.assertRaises(AssertionError, self.o._check_xml_is_valid, child) @patch("malcolm.modules.ADCore.parts.hdfwriterpart.time.time") def test_has_file_writing_stalled(self, time_mock): self.o = HDFWriterPart(name="m", mri="BLOCK:HDF5") # First case - no last capture update so return False assert self.o._has_file_writing_stalled() is False # Set up the attributes and mock for the last two cases self.o.last_capture_update = 10.0 self.o.frame_timeout = 60.0 time_mock.side_effect = [30.0, 71.0] # Second case - last capture update is within frame timeout assert self.o._has_file_writing_stalled() is False # Final case - last capture update is outside frame timeout assert self.o._has_file_writing_stalled() is True
class TestRunnableController(unittest.TestCase): def checkState(self, state, child=True, parent=True): if child: self.assertEqual(self.b_child.state, state) if parent: self.assertEqual(self.b.state, state) def checkSteps(self, configured, completed, total): self.assertEqual(self.b.configuredSteps, configured) self.assertEqual(self.b.completedSteps, completed) self.assertEqual(self.b.totalSteps, total) self.assertEqual(self.b_child.configuredSteps, configured) self.assertEqual(self.b_child.completedSteps, completed) self.assertEqual(self.b_child.totalSteps, total) def setUp(self): self.maxDiff = 5000 self.p = Process('process1', SyncFactory('threading')) # Make a ticker block to act as our child params = Ticker.MethodMeta.prepare_input_map(mri="childBlock") self.b_child = Ticker(self.p, params)[-1] # Make an empty part for our parent params = Part.MethodMeta.prepare_input_map(name='part1') part1 = Part(self.p, params) # Make a RunnableChildPart to control the ticker params = RunnableChildPart.MethodMeta.prepare_input_map( mri='childBlock', name='part2') part2 = RunnableChildPart(self.p, params) # create a root block for the RunnableController block to reside in params = RunnableController.MethodMeta.prepare_input_map( mri='mainBlock') self.c = RunnableController(self.p, [part1, part2], params) self.b = self.c.block self.sm = self.c.stateMachine # start the process off self.p.start() # wait until block is Ready task = Task("block_ready_task", self.p) task.when_matches(self.b["state"], self.sm.IDLE, timeout=1) self.checkState(self.sm.IDLE) def tearDown(self): self.p.stop() def test_init(self): # the following block attributes should be created by a call to # set_attributes via _set_block_children in __init__ self.assertEqual(self.b['totalSteps'].meta.typeid, 'malcolm:core/NumberMeta:1.0') self.assertEqual(self.b['layout'].meta.typeid, 'malcolm:core/TableMeta:1.0') self.assertEqual(self.b['completedSteps'].meta.typeid, 'malcolm:core/NumberMeta:1.0') self.assertEqual(self.b['configuredSteps'].meta.typeid, 'malcolm:core/NumberMeta:1.0') self.assertEqual(self.b['axesToMove'].meta.typeid, 'malcolm:core/StringArrayMeta:1.0') self.assertEqual(self.b['layoutName'].meta.typeid, 'malcolm:core/StringMeta:1.0') # the following hooks should be created via _find_hooks in __init__ self.assertEqual(self.c.hook_names, { self.c.Reset: "Reset", self.c.Disable: "Disable", self.c.ReportOutports: "ReportOutports", self.c.Layout: "Layout", self.c.Load: "Load", self.c.Save: "Save", self.c.Validate: "Validate", self.c.ReportStatus: "ReportStatus", self.c.Configure: "Configure", self.c.PostConfigure: "PostConfigure", self.c.Run: "Run", self.c.PostRunReady: "PostRunReady", self.c.PostRunIdle: "PostRunIdle", self.c.Seek: "Seek", self.c.Pause: "Pause", self.c.Resume: "Resume", self.c.Abort: "Abort", }) # check instantiation of object tree via logger names self.assertEqual(self.c._logger.name, 'RunnableController(mainBlock)') self.assertEqual(self.c.parts['part1']._logger.name, 'RunnableController(mainBlock).part1') self.assertEqual(self.c.parts['part2']._logger.name, 'RunnableController(mainBlock).part2') def test_edit(self): self.b.edit() self.checkState(self.sm.EDITABLE, child=False) def test_reset(self): self.b.disable() self.checkState(self.sm.DISABLED) self.b.reset() self.checkState(self.sm.IDLE) def test_set_axes_to_move(self): self.c.set_axes_to_move(['y']) self.assertEqual(self.c.axes_to_move.value, ['y']) 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']) self.assertEqual(actual["generator"], compound) self.assertEqual(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) duration = FixedDurationMutator(duration) compound = CompoundGenerator([line1, line2], [], [duration]) self.b.configure( generator=compound, axesToMove=['x'], exceptionStep=exception) def test_configure_run(self): self.prepare_half_run() self.checkSteps(2, 0, 6) self.checkState(self.sm.READY) self.b.run() self.checkState(self.sm.READY) self.checkSteps(4, 2, 6) self.b.run() self.checkState(self.sm.READY) self.checkSteps(6, 4, 6) self.b.run() self.checkState(self.sm.IDLE) def test_abort(self): self.prepare_half_run() self.b.run() self.b.abort() self.checkState(self.sm.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.sm.READY) self.checkSteps(4, 2, 6) self.b.pause(completedSteps=1) self.checkState(self.sm.READY) self.checkSteps(2, 1, 6) self.b.run() self.checkSteps(4, 2, 6) self.b.completedSteps = 5 self.checkSteps(6, 5, 6) self.b.run() self.checkState(self.sm.IDLE) def test_resume_in_run(self): self.prepare_half_run(duration=0.5) w = self.p.spawn(self.b.run) time.sleep(0.85) self.b.pause() self.checkState(self.sm.PAUSED) self.checkSteps(2, 1, 6) self.b.resume() # return to PRERUN should continue original run to completion and # READY state then = time.time() w.wait() self.assertAlmostEqual(time.time() - then, 0.5, delta=0.4) self.checkState(self.sm.READY) def test_run_exception(self): self.prepare_half_run(exception=1) with self.assertRaises(ResponseError): self.b.run() self.checkState(self.sm.FAULT) def test_run_stop(self): self.prepare_half_run(duration=0.5) w = self.p.spawn(self.b.run) time.sleep(0.5) self.b.abort() with self.assertRaises(AbortedError): w.get() self.checkState(self.sm.ABORTED)