Coverage for tests/unit_tests/managers/test_programmers.py: 100%
134 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-06 10:20 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-06 10:20 +0000
1"""
2Tests of the apio.managers.programmers.py module.
3"""
5from typing import List
6from pytest import LogCaptureFixture, raises
7from tests.conftest import ApioRunner
8from apio.apio_context import (
9 ApioContext,
10 PackagesPolicy,
11 ProjectPolicy,
12 RemoteConfigPolicy,
13)
14from apio.utils.usb_util import UsbDevice
15from apio.utils.serial_util import SerialDevice
17from apio.managers.programmers import (
18 _construct_cmd_template,
19 _construct_programmer_cmd,
20 _DeviceScanner,
21)
24class FakeDeviceScanner(_DeviceScanner):
25 """A fake device scanner for testing."""
27 def __init__(
28 self,
29 usb_devices: List[UsbDevice] = None,
30 serial_devices: List[SerialDevice] = None,
31 ):
32 super().__init__(apio_ctx=None)
33 self._usb_devices = usb_devices
34 self._serial_devices = serial_devices
36 # @override
37 def get_usb_devices(self) -> List[UsbDevice]:
38 """Returns the fake usb devices."""
39 assert self._usb_devices
40 return self._usb_devices
42 # @override
43 def get_serial_devices(self) -> List[UsbDevice]:
44 """Returns the fake serial devices."""
45 assert self._serial_devices
46 return self._serial_devices
49def fake_usb_device(
50 *,
51 vid="0403",
52 pid="6010",
53 bus=0,
54 dev=0,
55 manuf="AlhambraBits",
56 prod="Alhambra II v1.0A",
57 sn="SNXXXX",
58 device_type="FT2232H",
59) -> UsbDevice:
60 """Create a fake usb device for resting."""
61 # pylint: disable=too-many-arguments
62 return UsbDevice(
63 vendor_id=vid,
64 product_id=pid,
65 bus=bus,
66 device=dev,
67 manufacturer=manuf,
68 product=prod,
69 serial_number=sn,
70 device_type=device_type,
71 )
74def fake_serial_device(
75 *,
76 port_name="port0",
77 vid="04D8",
78 pid="FFEE",
79 manuf="IceFUN",
80 prod="Ice Fun",
81 sn="SNXXXX",
82 device_type="FT2232H",
83 location="0.1",
84) -> UsbDevice:
85 """Create a fake serial device for resting."""
86 # pylint: disable=too-many-arguments
87 return SerialDevice(
88 port="/dev/" + port_name,
89 port_name=port_name,
90 vendor_id=vid,
91 product_id=pid,
92 manufacturer=manuf,
93 product=prod,
94 serial_number=sn,
95 device_type=device_type,
96 location=location,
97 )
100def test_default_cmd_template(
101 apio_runner: ApioRunner, capsys: LogCaptureFixture
102):
103 """Tests _construct_cmd_template() with the default board template."""
105 with apio_runner.in_sandbox() as sb:
107 # -- Construct an apio context.
108 sb.write_apio_ini(
109 {
110 "[env:default]": {
111 "board": "alhambra-ii",
112 }
113 }
114 )
116 apio_ctx = ApioContext(
117 project_policy=ProjectPolicy.PROJECT_REQUIRED,
118 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
119 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
120 )
121 programmer_cmd = _construct_cmd_template(apio_ctx)
123 # -- Check result.
124 assert (
125 programmer_cmd == "openFPGALoader --verify -b ice40_generic "
126 "--vid ${VID} --pid ${PID} "
127 "--busdev-num ${BUS}:${DEV} "
128 "${BIN_FILE}"
129 )
131 # -- Check no 'custom' warning.
132 assert "Using custom programmer cmd" not in capsys.readouterr().out
135def test_custom_cmd_template(
136 apio_runner: ApioRunner, capsys: LogCaptureFixture
137):
138 """Tests _construct_cmd_template() with custom command template."""
140 with apio_runner.in_sandbox() as sb:
142 # -- Construct an apio context.
143 sb.write_apio_ini(
144 {
145 "[env:default]": {
146 "board": "alhambra-ii",
147 "programmer-cmd": "my template ${VID} ${PID}",
148 }
149 }
150 )
152 apio_ctx = ApioContext(
153 project_policy=ProjectPolicy.PROJECT_REQUIRED,
154 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
155 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
156 )
157 programmer_cmd = _construct_cmd_template(apio_ctx)
159 # -- Check the result.
160 assert programmer_cmd == "my template ${VID} ${PID}"
162 # -- Check the 'custom' warning.
163 assert "Using custom programmer cmd" in capsys.readouterr().out
166def test_get_cmd_usb(apio_runner: ApioRunner, capsys: LogCaptureFixture):
167 """Test generation of a programmer command for a usb device."""
168 with apio_runner.in_sandbox() as sb:
170 # -- Create a fake apio.ini file.
171 sb.write_apio_ini(
172 {
173 "[env:default]": {
174 "board": "alhambra-ii",
175 "programmer-cmd": (
176 "my-programmer --bus ${BUS} --dev ${DEV} "
177 "--vid ${VID} --pid ${PID} "
178 "--serial-num ${SERIAL_NUM} --bin-file ${BIN_FILE}"
179 ),
180 }
181 }
182 )
184 # -- Construct the apio context.
185 apio_ctx = ApioContext(
186 project_policy=ProjectPolicy.PROJECT_REQUIRED,
187 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
188 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
189 )
191 # -- Create fake devices
192 scanner = FakeDeviceScanner(
193 usb_devices=[
194 fake_usb_device(dev=0, prod="non alhambra"),
195 fake_usb_device(dev=1),
196 fake_usb_device(dev=2, prod="non alhambra"),
197 ],
198 )
200 # -- Call the tested function
201 cmd = _construct_programmer_cmd(
202 apio_ctx, scanner, serial_port_flag=None, serial_num_flag=None
203 )
205 # -- Test the result programmer command.
206 assert cmd == (
207 "my-programmer --bus 0 --dev 1 --vid 0403 --pid 6010 "
208 "--serial-num SNXXXX --bin-file $SOURCE"
209 )
211 # -- Check the log.
212 log = capsys.readouterr().out
213 assert "Scanning for a USB device:" in log
214 assert 'FILTER [VID=0403, PID=6010, REGEX="^Alhambra II.*"]' in log
215 assert (
216 "DEVICE [0403:6010] [0:1] [AlhambraBits] "
217 "[Alhambra II v1.0A] [SNXXXX]"
218 ) in log
221def test_get_cmd_usb_no_match(
222 apio_runner: ApioRunner, capsys: LogCaptureFixture
223):
224 """Test command generation error when the usb device is not found."""
225 with apio_runner.in_sandbox() as sb:
227 # -- Create a fake apio.ini file.
228 sb.write_apio_ini(
229 {
230 "[env:default]": {
231 "board": "alhambra-ii",
232 "programmer-cmd": "my-programmer ${VID} ${PID}",
233 }
234 }
235 )
237 # -- Construct the apio context.
238 apio_ctx = ApioContext(
239 project_policy=ProjectPolicy.PROJECT_REQUIRED,
240 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
241 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
242 )
244 # -- Create fake devices
245 scanner = FakeDeviceScanner(
246 usb_devices=[
247 fake_usb_device(dev=0, prod="non alhambra"),
248 fake_usb_device(dev=2, prod="non alhambra"),
249 ],
250 )
252 # -- Call the tested function
254 with raises(SystemExit) as e:
255 _construct_programmer_cmd(
256 apio_ctx, scanner, serial_port_flag=None, serial_num_flag=None
257 )
259 assert e.value.code == 1
261 log = capsys.readouterr().out
262 assert "Scanning for a USB device:" in log
263 assert 'FILTER [VID=0403, PID=6010, REGEX="^Alhambra II.*"]' in log
264 assert "No matching USB device" in log
267def test_get_cmd_usb_multiple_matches(
268 apio_runner: ApioRunner, capsys: LogCaptureFixture
269):
270 """Test command generation error when multiple usb devices match the
271 filter."""
272 with apio_runner.in_sandbox() as sb:
274 # -- Create a fake apio.ini file.
275 sb.write_apio_ini(
276 {
277 "[env:default]": {
278 "board": "alhambra-ii",
279 "programmer-cmd": "my-programmer ${VID} ${PID}",
280 }
281 }
282 )
284 # -- Construct the apio context.
285 apio_ctx = ApioContext(
286 project_policy=ProjectPolicy.PROJECT_REQUIRED,
287 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
288 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
289 )
291 # -- Create fake devices
292 scanner = FakeDeviceScanner(
293 usb_devices=[
294 fake_usb_device(dev=0, sn="SN001"),
295 fake_usb_device(dev=1, prod="non alhambra"),
296 fake_usb_device(dev=2, sn="SN002"),
297 ],
298 )
300 # -- Call the tested function
301 with raises(SystemExit) as e:
302 _construct_programmer_cmd(
303 apio_ctx, scanner, serial_port_flag=None, serial_num_flag=None
304 )
306 assert e.value.code == 1
308 log = capsys.readouterr().out
309 assert "Scanning for a USB device:" in log
310 assert 'FILTER [VID=0403, PID=6010, REGEX="^Alhambra II.*"]' in log
311 assert (
312 "DEVICE [0403:6010] [0:0] [AlhambraBits] "
313 "[Alhambra II v1.0A] [SN001]"
314 ) in log
315 assert (
316 "DEVICE [0403:6010] [0:2] [AlhambraBits] "
317 "[Alhambra II v1.0A] [SN002]"
318 ) in log
319 assert "Error: Found multiple matching usb devices" in log
322def test_get_cmd_serial(apio_runner: ApioRunner, capsys: LogCaptureFixture):
323 """Test generation of a programmer command for a serial device."""
324 with apio_runner.in_sandbox() as sb:
326 # -- Create a fake apio.ini file.
327 sb.write_apio_ini(
328 {
329 "[env:default]": {
330 "board": "icefun",
331 "programmer-cmd": "my-programmer --port ${SERIAL_PORT}",
332 }
333 }
334 )
336 # -- Construct the apio context.
337 apio_ctx = ApioContext(
338 project_policy=ProjectPolicy.PROJECT_REQUIRED,
339 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
340 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
341 )
343 # -- Create fake devices
344 scanner = FakeDeviceScanner(
345 serial_devices=[
346 fake_serial_device(port_name="port1", pid="1234"),
347 fake_serial_device(port_name="port2"),
348 fake_serial_device(port_name="port3", pid="1234"),
349 ],
350 )
352 # -- Call the tested function
353 cmd = _construct_programmer_cmd(
354 apio_ctx, scanner, serial_port_flag=None, serial_num_flag=None
355 )
357 # -- Test the result programmer command.
358 assert cmd == "my-programmer --port /dev/port2"
360 # -- Check the log.
361 log = capsys.readouterr().out
362 assert "Scanning for a serial device:" in log
363 assert "FILTER [VID=04D8, PID=FFEE]" in log
364 assert (
365 "DEVICE [/dev/port2] [04D8:FFEE] [IceFUN] [Ice Fun] [SNXXXX]"
366 in log
367 )
370def test_get_cmd_serial_no_match(
371 apio_runner: ApioRunner, capsys: LogCaptureFixture
372):
373 """Test command generation error when the serial device is not found."""
374 with apio_runner.in_sandbox() as sb:
376 # -- Create a fake apio.ini file.
377 sb.write_apio_ini(
378 {
379 "[env:default]": {
380 "board": "icefun",
381 "programmer-cmd": "my-programmer --port ${SERIAL_PORT}",
382 }
383 }
384 )
386 # -- Construct the apio context.
387 apio_ctx = ApioContext(
388 project_policy=ProjectPolicy.PROJECT_REQUIRED,
389 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
390 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
391 )
393 # -- Create fake devices
394 scanner = FakeDeviceScanner(
395 serial_devices=[
396 fake_serial_device(port_name="port1", pid="1234"),
397 fake_serial_device(port_name="port3", pid="1234"),
398 ],
399 )
401 # -- Call the tested function
402 with raises(SystemExit) as e:
403 _construct_programmer_cmd(
404 apio_ctx, scanner, serial_port_flag=None, serial_num_flag=None
405 )
407 assert e.value.code == 1
409 log = capsys.readouterr().out
410 assert "Scanning for a serial device:" in log
411 assert "FILTER [VID=04D8, PID=FFEE]" in log
412 assert "No matching serial device" in log
415def test_get_cmd_serial_multiple_matches(
416 apio_runner: ApioRunner, capsys: LogCaptureFixture
417):
418 """Test command generation error when multiple serial devices match the
419 filter."""
420 with apio_runner.in_sandbox() as sb:
422 # -- Create a fake apio.ini file.
423 sb.write_apio_ini(
424 {
425 "[env:default]": {
426 "board": "icefun",
427 "programmer-cmd": "my-programmer --port ${SERIAL_PORT}",
428 }
429 }
430 )
432 # -- Construct the apio context.
433 apio_ctx = ApioContext(
434 project_policy=ProjectPolicy.PROJECT_REQUIRED,
435 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
436 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
437 )
439 # -- Create fake devices
440 scanner = FakeDeviceScanner(
441 serial_devices=[
442 fake_serial_device(port_name="port1"),
443 fake_serial_device(port_name="port2", pid="1234"),
444 fake_serial_device(port_name="port3"),
445 ],
446 )
448 # -- Call the tested function
449 with raises(SystemExit) as e:
450 _construct_programmer_cmd(
451 apio_ctx, scanner, serial_port_flag=None, serial_num_flag=None
452 )
454 assert e.value.code == 1
456 log = capsys.readouterr().out
457 assert "Scanning for a serial device:" in log
458 assert "FILTER [VID=04D8, PID=FFEE]" in log
459 assert (
460 "DEVICE [/dev/port1] [04D8:FFEE] [IceFUN] [Ice Fun] [SNXXXX]"
461 ) in log
462 assert (
463 "DEVICE [/dev/port3] [04D8:FFEE] [IceFUN] [Ice Fun] [SNXXXX]"
464 ) in log
465 assert "Error: Found multiple matching serial devices" in log
468def test_device_presence_ok(
469 apio_runner: ApioRunner, capsys: LogCaptureFixture
470):
471 """Test generation of a presence check only device."""
472 with apio_runner.in_sandbox() as sb:
474 # -- Create a fake apio.ini file.
475 sb.write_apio_ini(
476 {
477 "[env:default]": {
478 "board": "alhambra-ii",
479 # -- The command has no serial or usb vars.
480 "programmer-cmd": "my programmer command ${BIN_FILE}",
481 }
482 }
483 )
485 # -- Construct the apio context.
486 apio_ctx = ApioContext(
487 project_policy=ProjectPolicy.PROJECT_REQUIRED,
488 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
489 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
490 )
492 # -- Create fake devices, with two matching devices.
493 scanner = FakeDeviceScanner(
494 usb_devices=[
495 fake_usb_device(dev=0),
496 fake_usb_device(dev=1, prod="non alhambra"),
497 fake_usb_device(dev=2),
498 ],
499 )
501 # -- Call the tested function
502 cmd = _construct_programmer_cmd(
503 apio_ctx, scanner, serial_port_flag=None, serial_num_flag=None
504 )
506 # -- Test the result programmer command.
507 assert cmd == "my programmer command $SOURCE"
509 # -- Check the log.
510 log = capsys.readouterr().out
511 assert "Checking device presence" in log
512 assert 'FILTER [VID=0403, PID=6010, REGEX="^Alhambra II.*"]' in log
513 assert (
514 "DEVICE [0403:6010] [0:0] [AlhambraBits] "
515 "[Alhambra II v1.0A] [SNXXXX]"
516 ) in log
518 assert (
519 "DEVICE [0403:6010] [0:2] [AlhambraBits] "
520 "[Alhambra II v1.0A] [SNXXXX]"
521 ) in log
524def test_device_presence_not_found(
525 apio_runner: ApioRunner, capsys: LogCaptureFixture
526):
527 """Test generation of a presence only device, with no device."""
528 with apio_runner.in_sandbox() as sb:
530 # -- Create a fake apio.ini file.
531 sb.write_apio_ini(
532 {
533 "[env:default]": {
534 "board": "alhambra-ii",
535 # -- The command has no serial or usb vars.
536 "programmer-cmd": "my programmer command ${BIN_FILE}",
537 }
538 }
539 )
541 # -- Construct the apio context.
542 apio_ctx = ApioContext(
543 project_policy=ProjectPolicy.PROJECT_REQUIRED,
544 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
545 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
546 )
548 # -- Create fake devices, with two matching devices.
549 scanner = FakeDeviceScanner(
550 usb_devices=[
551 fake_usb_device(dev=0, prod="non alhambra"),
552 fake_usb_device(dev=1, prod="non alhambra"),
553 ],
554 )
556 # -- Call the tested function
557 with raises(SystemExit) as e:
558 _construct_programmer_cmd(
559 apio_ctx, scanner, serial_port_flag=None, serial_num_flag=None
560 )
562 assert e.value.code == 1
564 # -- Check the log.
565 log = capsys.readouterr().out
566 assert "Checking device presence" in log
567 assert 'FILTER [VID=0403, PID=6010, REGEX="^Alhambra II.*"]' in log
568 assert "Error: No matching device." in log