统一多相机连接与 UART 数据协议
启动时完整连接所有配置相机,移除旧命令与单路数据格式。通过 22 项回归测试、真实 SDK 的 1–4 路录像验证、Ruff 和文档检查。
This commit is contained in:
+173
-227
@@ -103,176 +103,126 @@ class HardwareFreeTest(unittest.TestCase):
|
||||
self.addCleanup(modules.stop)
|
||||
|
||||
|
||||
class ExistingRegressionTests(HardwareFreeTest):
|
||||
def test_partial_command_waits_for_terminator(self):
|
||||
ser = ByteSerial(b"st")
|
||||
controller = uart.UartFingerController(ser)
|
||||
self.assertIsNone(controller._poll_command_line(blocking=False))
|
||||
ser.input.extend(b"op\n")
|
||||
self.assertEqual(controller._poll_command_line(blocking=False), "stop")
|
||||
|
||||
def test_cr_only_terminal_commands(self):
|
||||
controller = uart.UartFingerController(ByteSerial(b"stop\rrun\r"))
|
||||
self.assertEqual(controller._poll_command_line(blocking=False), "stop")
|
||||
self.assertEqual(controller._poll_command_line(blocking=False), "run")
|
||||
|
||||
def test_failed_warmup_releases_runtime(self):
|
||||
runtime = FakeRuntime(warmup_error=True)
|
||||
ser = ByteSerial()
|
||||
controller = uart.UartFingerController(ser)
|
||||
with patch("orisys.finger.FingerRuntime.open", return_value=runtime):
|
||||
controller.cmd_connect([])
|
||||
self.assertTrue(runtime.closed)
|
||||
self.assertTrue(ser.output[-1].startswith("ERR connect"))
|
||||
|
||||
def test_unsuccessful_warmup_is_not_connected(self):
|
||||
runtime = FakeRuntime(warmup_ok=False)
|
||||
ser = ByteSerial()
|
||||
controller = uart.UartFingerController(ser)
|
||||
with patch("orisys.finger.FingerRuntime.open", return_value=runtime):
|
||||
controller.cmd_connect([])
|
||||
self.assertTrue(ser.output[-1].startswith("ERR connect"))
|
||||
self.assertTrue(runtime.closed)
|
||||
|
||||
|
||||
class MultiSensorTests(HardwareFreeTest):
|
||||
def test_autostart_service_streams_one_through_four_sensors_then_stops(self):
|
||||
for count in range(1, 5):
|
||||
with self.subTest(count=count):
|
||||
ser = ByteSerial()
|
||||
controller = uart.UartFingerController(ser, sources=list(range(count)), autostart=True)
|
||||
original_write = ser.write
|
||||
def write(data, original_write=original_write, ser=ser, count=count):
|
||||
size = original_write(data)
|
||||
if data.startswith(b"force,") and sum(line.startswith("force,") for line in ser.output) == count:
|
||||
ser.input.extend(b"stop all\nquit\n")
|
||||
return size
|
||||
ser.write = write
|
||||
controller.serve()
|
||||
self.assertEqual(sum(line.startswith("force,") for line in ser.output), count)
|
||||
self.assertEqual(ser.output[-1], "OK quit")
|
||||
self.assertFalse(controller._streaming)
|
||||
controller.close_runtime()
|
||||
|
||||
def test_one_through_four_sensors_have_identifiable_readings(self):
|
||||
class ConnectionFirstTests(HardwareFreeTest):
|
||||
def test_connects_every_source_before_ready_or_force(self):
|
||||
for count in range(1, 5):
|
||||
with self.subTest(count=count):
|
||||
ser = ByteSerial()
|
||||
controller = uart.UartFingerController(ser, sources=list(range(count)))
|
||||
controller.cmd_connect(["all"])
|
||||
controller._dispatch("read all")
|
||||
forces = [line for line in ser.output if line.startswith("force,")]
|
||||
expected = ["force,1.250000,-2.500000,3.750000"] if count == 1 else [
|
||||
events = []
|
||||
def open_runtime(events=events, **kwargs):
|
||||
events.append(kwargs["video"])
|
||||
return FakeRuntime()
|
||||
original_write = ser.write
|
||||
def write(data, events=events, count=count, ser=ser, original_write=original_write):
|
||||
self.assertEqual(events, list(range(count)))
|
||||
size = original_write(data)
|
||||
if data.startswith(b"force,") and sum(x.startswith("force,") for x in ser.output) == count:
|
||||
ser.input.extend(b"quit\n")
|
||||
return size
|
||||
ser.write = write
|
||||
with patch("orisys.finger.FingerRuntime.open", side_effect=open_runtime):
|
||||
controller.bank.connect_all()
|
||||
controller.serve()
|
||||
self.assertEqual(ser.output[0], f"OK ready {count}")
|
||||
self.assertEqual([x for x in ser.output if x.startswith("force,")], [
|
||||
f"force,{i},1.250000,-2.500000,3.750000" for i in range(1, count + 1)
|
||||
]
|
||||
self.assertEqual(forces, expected)
|
||||
self.assertFalse(controller._streaming)
|
||||
])
|
||||
controller.close_runtime()
|
||||
|
||||
def test_selective_stop_and_reconnect_leave_other_sensor_running(self):
|
||||
controller = uart.UartFingerController(ByteSerial(), sources=[0, 2, 4, 6])
|
||||
controller.cmd_connect(["all"])
|
||||
controller._dispatch("run all")
|
||||
controller._dispatch("stop 2")
|
||||
self.assertEqual(controller._streaming, {1, 3, 4})
|
||||
old = controller.bank.runtimes[3]
|
||||
controller._dispatch("connect 3 8")
|
||||
self.assertTrue(old.closed)
|
||||
self.assertEqual(controller._streaming, {1, 4})
|
||||
self.assertEqual(controller.bank.sources[3], 8)
|
||||
def test_one_failed_connection_rolls_back_every_opened_camera(self):
|
||||
bank = FingerSensorBank([0, 2, 4])
|
||||
opened = []
|
||||
def open_runtime(**kwargs):
|
||||
runtime = FakeRuntime(warmup_error=kwargs["video"] == 2)
|
||||
opened.append(runtime)
|
||||
return runtime
|
||||
with (patch("orisys.finger.FingerRuntime.open", side_effect=open_runtime),
|
||||
self.assertRaisesRegex(RuntimeError, "sensor 2")):
|
||||
bank.connect_all()
|
||||
self.assertEqual(bank.runtimes, {})
|
||||
self.assertTrue(all(r.closed for r in opened))
|
||||
|
||||
def test_failed_sensor_does_not_stop_healthy_sensor(self):
|
||||
def test_no_uart_is_opened_if_camera_preparation_fails(self):
|
||||
with (patch("orisys.finger.FingerRuntime.open", return_value=FakeRuntime(warmup_ok=False)),
|
||||
patch.object(uart, "open_serial") as serial):
|
||||
self.assertEqual(uart.main(["--sources", "0", "2"]), 1)
|
||||
serial.assert_not_called()
|
||||
|
||||
def test_legacy_commands_are_rejected(self):
|
||||
ser = ByteSerial()
|
||||
controller = uart.UartFingerController(ser, sources=[0, 2])
|
||||
controller.cmd_connect(["all"])
|
||||
controller.bank.runtimes[1].grab_error = True
|
||||
controller.cmd_run([])
|
||||
for sensor_id in sorted(controller._streaming):
|
||||
controller._read_one(sensor_id, context="run")
|
||||
self.assertIn("ERR run 1 processing failed", ser.output)
|
||||
self.assertIn("force,2,1.250000,-2.500000,3.750000", ser.output)
|
||||
self.assertEqual(controller._streaming, {2})
|
||||
|
||||
def test_connect_all_retains_successes_when_one_source_fails(self):
|
||||
ser = ByteSerial()
|
||||
controller = uart.UartFingerController(ser, sources=[0, 2, 4])
|
||||
def open_runtime(**kwargs):
|
||||
if kwargs["video"] == 2:
|
||||
raise RuntimeError("camera unavailable")
|
||||
return FakeRuntime()
|
||||
with patch("orisys.finger.FingerRuntime.open", side_effect=open_runtime):
|
||||
controller.cmd_connect(["all"])
|
||||
self.assertEqual(set(controller.bank.runtimes), {1, 3})
|
||||
self.assertIn("ERR connect 2 camera unavailable", ser.output)
|
||||
|
||||
def test_rejects_duplicate_live_source_without_closing_other_camera(self):
|
||||
controller = uart.UartFingerController(ByteSerial(), sources=[0, 2])
|
||||
controller.cmd_connect(["all"])
|
||||
original = controller.bank.runtimes[2]
|
||||
controller._dispatch("connect 2 0")
|
||||
self.assertIs(controller.bank.runtimes[2], original)
|
||||
self.assertFalse(original.closed)
|
||||
|
||||
def test_config_is_selected_per_sensor(self):
|
||||
bank = FingerSensorBank([0, 2], configs=["left.json", "right.json"])
|
||||
seen = []
|
||||
def open_runtime(**kwargs):
|
||||
seen.append(kwargs["config"])
|
||||
return FakeRuntime()
|
||||
with patch("orisys.finger.FingerRuntime.open", side_effect=open_runtime):
|
||||
bank.connect(1)
|
||||
bank.connect(2)
|
||||
self.assertEqual(seen, ["left.json", "right.json"])
|
||||
self.assertEqual(bank.read(1).units, ("raw", "raw", "raw"))
|
||||
|
||||
def test_invalid_sensor_count_and_config_count(self):
|
||||
for sources, configs in [([], ["finger"]), (range(5), ["finger"]), ([0, 2], ["a", "b", "c"])]:
|
||||
with self.assertRaises(ValueError):
|
||||
FingerSensorBank(sources, configs=configs)
|
||||
|
||||
def test_invalid_command_id_and_unconnected_run_are_errors(self):
|
||||
ser = ByteSerial()
|
||||
controller = uart.UartFingerController(ser, sources=[0, 2])
|
||||
for command in ["run 0", "read 3", "stop -1", "connect 3 4", "run all", "run 2 extra"]:
|
||||
for command in ["connect", "connect 2", "connect all", "run", "run all", "search", "exit"]:
|
||||
self.assertTrue(controller._dispatch(command))
|
||||
self.assertTrue(ser.output[-1].startswith("ERR "))
|
||||
self.assertTrue(ser.output[-1].startswith("ERR unknown"))
|
||||
|
||||
|
||||
class ProtocolTests(HardwareFreeTest):
|
||||
def test_repeated_run_is_rejected_without_recursion(self):
|
||||
def test_no_per_sensor_or_all_command_arguments(self):
|
||||
ser = ByteSerial()
|
||||
controller = uart.UartFingerController(ser)
|
||||
controller.cmd_connect([])
|
||||
controller._dispatch("run")
|
||||
controller._dispatch("run")
|
||||
self.assertEqual(ser.output[-1], "ERR run already running")
|
||||
self.assertEqual(controller._streaming, {1})
|
||||
controller = uart.UartFingerController(ser, sources=[0, 2])
|
||||
for command in ["start 1", "stop all", "read 2", "quit extra"]:
|
||||
self.assertTrue(controller._dispatch(command))
|
||||
self.assertTrue(ser.output[-1].startswith("ERR command"))
|
||||
|
||||
def test_camera_search_failure_is_reported_without_exiting(self):
|
||||
def test_start_stop_and_read_control_entire_connected_bank(self):
|
||||
ser = ByteSerial()
|
||||
controller = uart.UartFingerController(ser)
|
||||
with patch("orisys.capture.list_cameras", side_effect=OSError("camera scan failed")):
|
||||
self.assertTrue(controller._dispatch("search"))
|
||||
self.assertEqual(ser.output[-1], "ERR search camera scan failed")
|
||||
|
||||
def test_legacy_connect_and_batched_stop_quit(self):
|
||||
ser = ByteSerial(b"connect 2\r\nrun\nstop\nquit\n")
|
||||
controller = uart.UartFingerController(ser)
|
||||
controller.serve()
|
||||
self.assertEqual(ser.output, ["OK ready", "OK connect 2 (camera)", "OK run", "OK stop", "OK quit"])
|
||||
controller = uart.UartFingerController(ser, sources=[0, 2])
|
||||
controller.bank.connect_all()
|
||||
controller._dispatch("stop")
|
||||
controller._dispatch("read")
|
||||
self.assertEqual(ser.output[-2:], [
|
||||
"force,1,1.250000,-2.500000,3.750000",
|
||||
"force,2,1.250000,-2.500000,3.750000",
|
||||
])
|
||||
self.assertFalse(controller._streaming)
|
||||
controller._dispatch("start")
|
||||
self.assertTrue(controller._streaming)
|
||||
self.assertEqual(ser.output[-1], "OK start")
|
||||
|
||||
def test_stop_is_processed_before_next_frame(self):
|
||||
ser = ByteSerial(b"stop\nquit\n")
|
||||
controller = uart.UartFingerController(ser)
|
||||
controller.cmd_connect([])
|
||||
controller.cmd_run([])
|
||||
controller.serve()
|
||||
self.assertFalse(any(line.startswith("force,") for line in ser.output))
|
||||
def test_read_all_never_publishes_partial_round_on_processing_failure(self):
|
||||
ser = ByteSerial()
|
||||
controller = uart.UartFingerController(ser, sources=[0, 2])
|
||||
controller.bank.connect_all()
|
||||
controller.bank.runtimes[2].grab_error = True
|
||||
controller._streaming = True
|
||||
controller._dispatch("read")
|
||||
self.assertFalse(controller._streaming)
|
||||
self.assertFalse(any(x.startswith("force,") for x in ser.output))
|
||||
self.assertIn("sensor 2", ser.output[-1])
|
||||
|
||||
def test_read_only_autostarts_and_ignores_host_commands(self):
|
||||
ser = ByteSerial(b"quit\nstop\nconnect 9\n" + b"a" * 1100 + b"\n")
|
||||
controller = uart.UartFingerController(ser, read_only=True)
|
||||
def test_serve_rejects_incomplete_bank_before_ready(self):
|
||||
ser = ByteSerial()
|
||||
controller = uart.UartFingerController(ser, sources=[0, 2])
|
||||
with self.assertRaises(RuntimeError):
|
||||
controller.serve()
|
||||
self.assertEqual(ser.output, [])
|
||||
|
||||
|
||||
class FramingTests(HardwareFreeTest):
|
||||
def test_partial_line_waits_for_terminator(self):
|
||||
ser = ByteSerial(b"st")
|
||||
controller = uart.UartFingerController(ser, sources=[0])
|
||||
self.assertIsNone(controller._poll_command_line(blocking=False))
|
||||
ser.input.extend(b"op\n")
|
||||
self.assertEqual(controller._poll_command_line(blocking=False), "stop")
|
||||
|
||||
def test_cr_lf_crlf_commands_are_separate(self):
|
||||
controller = uart.UartFingerController(ByteSerial(b"stop\rstart\nread\r\n"), sources=[0])
|
||||
self.assertEqual(controller._poll_command_line(blocking=False), "stop")
|
||||
self.assertEqual(controller._poll_command_line(blocking=False), "start")
|
||||
self.assertEqual(controller._poll_command_line(blocking=False), "read")
|
||||
|
||||
def test_overlong_command_is_discarded_until_terminator(self):
|
||||
ser = ByteSerial(b"a" * 1025)
|
||||
controller = uart.UartFingerController(ser, sources=[0])
|
||||
self.assertIsNone(controller._poll_command_line(blocking=False))
|
||||
ser.input.extend(b"quit\nstop\r")
|
||||
self.assertEqual(controller._poll_command_line(blocking=False), "stop")
|
||||
self.assertTrue(ser.output[-1].startswith("ERR command"))
|
||||
|
||||
def test_read_only_ignores_every_command_and_still_streams(self):
|
||||
ser = ByteSerial(b"stop\nquit\n" + b"a" * 1100 + b"\n")
|
||||
controller = uart.UartFingerController(ser, sources=[0], read_only=True)
|
||||
controller.bank.connect_all()
|
||||
original_write = ser.write
|
||||
def write(data):
|
||||
size = original_write(data)
|
||||
@@ -282,62 +232,69 @@ class ProtocolTests(HardwareFreeTest):
|
||||
ser.write = write
|
||||
with self.assertRaises(KeyboardInterrupt):
|
||||
controller.serve()
|
||||
self.assertIn("OK run", ser.output)
|
||||
self.assertIn("force,1.250000,-2.500000,3.750000", ser.output)
|
||||
self.assertNotIn("OK quit", ser.output)
|
||||
self.assertFalse(any(line.startswith("ERR ") for line in ser.output))
|
||||
self.assertEqual(ser.output, ["OK ready 1", "force,1,1.250000,-2.500000,3.750000"])
|
||||
|
||||
def test_long_line_is_discarded_until_delimiter(self):
|
||||
ser = ByteSerial(b"a" * 1025)
|
||||
controller = uart.UartFingerController(ser)
|
||||
self.assertIsNone(controller._poll_command_line(blocking=False))
|
||||
ser.input.extend(b"quit\nstop\r")
|
||||
self.assertEqual(controller._poll_command_line(blocking=False), "stop")
|
||||
self.assertEqual(ser.output, ["ERR command line too long (max 1024 bytes)"])
|
||||
self.assertFalse(controller._should_quit)
|
||||
|
||||
def test_quoted_paths_and_malformed_quotes(self):
|
||||
controller = uart.UartFingerController(ByteSerial(), sources=[0, 2])
|
||||
with patch.object(controller.bank, "connect", return_value="/tmp/a b.mp4") as connect:
|
||||
controller._dispatch('connect 2 "/tmp/a b.mp4"')
|
||||
connect.assert_called_once_with(2, "/tmp/a b.mp4")
|
||||
controller._dispatch('connect "broken')
|
||||
self.assertTrue(controller.ser.output[-1].startswith("ERR command"))
|
||||
|
||||
def test_non_finite_measurements_are_not_published(self):
|
||||
ser = ByteSerial()
|
||||
controller = uart.UartFingerController(ser)
|
||||
controller.cmd_connect([])
|
||||
controller.bank.runtimes[1].tactile.read_info = lambda *args: (float("nan"), 0, 0)
|
||||
controller._dispatch("read")
|
||||
self.assertEqual(ser.output[-1], "ERR read non-finite force measurement")
|
||||
self.assertFalse(any(line.startswith("force,") for line in ser.output))
|
||||
|
||||
def test_serial_short_write_is_a_transport_failure(self):
|
||||
def test_short_write_is_transport_failure(self):
|
||||
ser = ByteSerial()
|
||||
ser.write = lambda data: len(data) - 1
|
||||
with self.assertRaises(OSError):
|
||||
uart.UartFingerController(ser).reply("OK ready")
|
||||
uart.UartFingerController(ser, sources=[0]).reply("OK ready 1")
|
||||
|
||||
def test_error_newlines_cannot_inject_protocol_records(self):
|
||||
def test_error_newlines_do_not_inject_records(self):
|
||||
ser = ByteSerial()
|
||||
uart.UartFingerController(ser).reply("ERR read bad\nOK quit\r\n")
|
||||
uart.UartFingerController(ser, sources=[0]).reply("ERR read bad\nOK quit\r\n")
|
||||
self.assertEqual(ser.output, ["ERR read bad OK quit"])
|
||||
|
||||
def test_empty_looping_video_retries_once_then_reports_eof(self):
|
||||
ser = ByteSerial()
|
||||
controller = uart.UartFingerController(ser)
|
||||
runtime = FakeRuntime()
|
||||
runtime.frames = 0
|
||||
controller.bank.sources[1] = "empty.mp4"
|
||||
controller.bank.runtimes[1] = runtime
|
||||
controller._dispatch("read")
|
||||
self.assertEqual(ser.output, ["OK eof"])
|
||||
|
||||
def test_close_attempts_every_sensor_even_when_one_close_fails(self):
|
||||
class BankTests(HardwareFreeTest):
|
||||
def test_invalid_count_config_count_and_duplicate_sources(self):
|
||||
for sources, configs in [([], ["finger"]), (range(5), ["finger"]),
|
||||
([0, 2], ["a", "b", "c"]), ([0, "0"], ["finger"]),
|
||||
([0, "/dev/video0"], ["finger"]), ([-1], ["finger"])]:
|
||||
with self.subTest(sources=sources), self.assertRaises(ValueError):
|
||||
FingerSensorBank(sources, configs=configs)
|
||||
|
||||
def test_configs_and_runtime_states_are_independent(self):
|
||||
bank = FingerSensorBank([0, 2], configs=["left.json", "right.json"])
|
||||
configs = []
|
||||
def open_runtime(**kwargs):
|
||||
configs.append(kwargs["config"])
|
||||
return FakeRuntime()
|
||||
with patch("orisys.finger.FingerRuntime.open", side_effect=open_runtime):
|
||||
bank.connect_all()
|
||||
self.assertEqual(configs, ["left.json", "right.json"])
|
||||
self.assertIsNot(bank.runtimes[1], bank.runtimes[2])
|
||||
readings = bank.read_all()
|
||||
self.assertEqual([r.sensor_id for r in readings], [1, 2])
|
||||
self.assertEqual(readings[0].units, ("raw", "raw", "raw"))
|
||||
|
||||
def test_no_frame_during_warmup_closes_runtime(self):
|
||||
runtime = FakeRuntime(warmup_ok=False)
|
||||
bank = FingerSensorBank([0])
|
||||
with (patch("orisys.finger.FingerRuntime.open", return_value=runtime),
|
||||
self.assertRaisesRegex(RuntimeError, "warmup")):
|
||||
bank.connect_all()
|
||||
self.assertTrue(runtime.closed)
|
||||
self.assertEqual(bank.runtimes, {})
|
||||
|
||||
def test_nonfinite_force_is_not_a_valid_round(self):
|
||||
bank = FingerSensorBank([0])
|
||||
bank.connect_all()
|
||||
bank.runtimes[1].tactile.read_info = lambda *args: (float("nan"), 0, 0)
|
||||
with self.assertRaisesRegex(RuntimeError, "non-finite"):
|
||||
bank.read_all()
|
||||
|
||||
def test_eof_returns_no_partial_round(self):
|
||||
bank = FingerSensorBank([0, 2])
|
||||
bank.connect(1)
|
||||
bank.connect(2)
|
||||
bank.connect_all()
|
||||
bank.runtimes[2].frames = 0
|
||||
bank.sources[2] = "ended.mp4"
|
||||
with self.assertRaisesRegex(EOFError, "sensor 2"):
|
||||
bank.read_all()
|
||||
|
||||
def test_all_resources_are_attempted_even_when_close_fails(self):
|
||||
bank = FingerSensorBank([0, 2])
|
||||
bank.connect_all()
|
||||
second = bank.runtimes[2]
|
||||
bank.runtimes[1].close = lambda: (_ for _ in ()).throw(RuntimeError("close failed"))
|
||||
with self.assertRaisesRegex(RuntimeError, "close failed"):
|
||||
@@ -347,38 +304,27 @@ class ProtocolTests(HardwareFreeTest):
|
||||
|
||||
|
||||
class CliTests(HardwareFreeTest):
|
||||
def test_open_serial_has_finite_write_timeout_and_no_flow_control(self):
|
||||
serial = types.ModuleType("serial")
|
||||
serial.EIGHTBITS, serial.PARITY_NONE, serial.STOPBITS_ONE = 8, "N", 1
|
||||
serial.Serial = types.SimpleNamespace()
|
||||
with patch.dict(sys.modules, {"serial": serial}), patch.object(serial, "Serial") as factory:
|
||||
uart.open_serial("/dev/ttyHS1", 115200, 0.1, 0.5)
|
||||
factory.assert_called_once_with(
|
||||
port="/dev/ttyHS1", baudrate=115200, bytesize=8, parity="N",
|
||||
stopbits=1, timeout=0.1, write_timeout=0.5, exclusive=True,
|
||||
)
|
||||
|
||||
def test_invalid_options_do_not_open_uart(self):
|
||||
for args in [["--timeout", "0"], ["--write-timeout", "nan"], ["--sources", "0", "1", "2", "3", "4"],
|
||||
["--sources", "0", "2", "--config", "a", "--config", "b", "--config", "c"]]:
|
||||
with self.subTest(args=args), patch.object(uart, "open_serial") as open_serial:
|
||||
with self.assertRaises(SystemExit) as exit_status:
|
||||
def test_sources_are_required_and_legacy_options_are_removed(self):
|
||||
for args in [[], ["--camera-id", "0"], ["--video", "a.mp4"],
|
||||
["--sources", "0", "--autostart"], ["--sources", "0", "--max-camera-index", "10"],
|
||||
["--sources", "0", "1", "2", "3", "4"], ["--sources", "0", "--timeout", "nan"]]:
|
||||
with self.subTest(args=args), patch.object(uart, "open_serial") as serial:
|
||||
with self.assertRaises(SystemExit) as status:
|
||||
uart.main(args)
|
||||
self.assertEqual(exit_status.exception.code, 2)
|
||||
open_serial.assert_not_called()
|
||||
self.assertEqual(status.exception.code, 2)
|
||||
serial.assert_not_called()
|
||||
|
||||
def test_transport_failure_returns_nonzero_and_closes_all_resources(self):
|
||||
ser = ByteSerial()
|
||||
ser.closed = False
|
||||
ser.close = lambda: setattr(ser, "closed", True)
|
||||
def serve(controller):
|
||||
controller.cmd_connect(["all"])
|
||||
self.runtimes = list(controller.bank.runtimes.values())
|
||||
raise OSError("UART disconnected")
|
||||
with patch.object(uart, "open_serial", return_value=ser), patch.object(uart.UartFingerController, "serve", serve):
|
||||
def test_serial_failure_closes_every_connected_runtime(self):
|
||||
opened = []
|
||||
def open_runtime(**kwargs):
|
||||
runtime = FakeRuntime()
|
||||
opened.append(runtime)
|
||||
return runtime
|
||||
with (patch("orisys.finger.FingerRuntime.open", side_effect=open_runtime),
|
||||
patch.object(uart, "open_serial", side_effect=OSError("UART unavailable"))):
|
||||
self.assertEqual(uart.main(["--sources", "0", "2"]), 1)
|
||||
self.assertTrue(ser.closed)
|
||||
self.assertTrue(all(runtime.closed for runtime in self.runtimes))
|
||||
self.assertEqual(len(opened), 2)
|
||||
self.assertTrue(all(r.closed for r in opened))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user