Coverage for tests/unit_tests/managers/test_apio_definitions.py: 100%
132 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-23 03:53 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-23 03:53 +0000
1"""
2Tests of apio_definitions.py
3"""
5from pytest import raises
6from google.protobuf.json_format import MessageToDict
7from tests.conftest import ApioRunner
8from apio.common import proto_util
9from apio.common.proto.apio_definitions_pb2 import FpgaDefinition
10from apio.apio_context import (
11 ApioContext,
12 PackagesPolicy,
13 ProjectPolicy,
14 RemoteConfigPolicy,
15)
18def test_default_loading_no_project(apio_runner: ApioRunner):
19 """Tests loading of apio standard definitions with no project."""
21 with apio_runner.in_sandbox():
23 apio_ctx = ApioContext(
24 project_policy=ProjectPolicy.NO_PROJECT,
25 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
26 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
27 )
29 assert not apio_ctx.has_project
30 assert apio_ctx.definitions is not None
31 assert "alhambra-ii" in apio_ctx.definitions.boards
32 assert "ice40hx4k-tq144-8k" in apio_ctx.definitions.fpgas
33 assert "openfpgaloader" in apio_ctx.definitions.programmers
36def test_default_loading_with_project(apio_runner: ApioRunner):
37 """Tests loading of apio standard definitions with a project and
38 without project's custom definition."""
40 with apio_runner.in_sandbox() as sb:
42 # -- Fake apio project file.
43 sb.write_default_apio_ini()
45 apio_ctx = ApioContext(
46 project_policy=ProjectPolicy.PROJECT_REQUIRED,
47 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
48 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
49 )
51 assert apio_ctx.has_project
52 assert apio_ctx.definitions is not None
53 assert "alhambra-ii" in apio_ctx.definitions.boards
54 assert "ice40hx4k-tq144-8k" in apio_ctx.definitions.fpgas
55 assert "openfpgaloader" in apio_ctx.definitions.programmers
58def test_loading_with_custom_boards(apio_runner: ApioRunner):
59 """Tests loading of apio standard definitions with a with project's
60 custom boards definitions.
61 """
63 with apio_runner.in_sandbox() as sb:
65 # -- Fake apio project file.
66 sb.write_default_apio_ini()
68 # -- Write fake custom boards
69 board_definition1 = {
70 "description": "My Custom Alhambra II",
71 "fpga-id": "ice40hx4k-tq144-8k",
72 "programmer": {
73 "id": "openfpgaloader",
74 },
75 "usb": {
76 "vid": "0403",
77 "pid": "6010",
78 },
79 }
81 board_definition2 = {
82 "description": "My new board",
83 "fpga-id": "lfe5u-45f-6bg381c",
84 "programmer": {
85 "id": "openfpgaloader",
86 },
87 }
89 sb.write_json_file(
90 "boards.jsonc",
91 {
92 # -- Overrides a standard definition
93 "alhambra-ii": board_definition1,
94 # -- Adds a new definition
95 "my-custom-board": board_definition2,
96 },
97 )
99 # -- Load
100 apio_ctx = ApioContext(
101 project_policy=ProjectPolicy.PROJECT_REQUIRED,
102 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
103 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
104 )
106 # -- Verify
107 definitions = apio_ctx.definitions
108 assert definitions is not None
110 assert apio_ctx.has_project
111 assert "ice40hx4k-tq144-8k" in definitions.fpgas
112 assert "openfpgaloader" in definitions.programmers
114 assert (
115 MessageToDict(definitions.boards["alhambra-ii"])
116 == board_definition1
117 )
118 assert (
119 MessageToDict(definitions.boards["my-custom-board"])
120 == board_definition2
121 )
123 assert definitions.is_custom_board("alhambra-ii")
124 assert definitions.is_custom_board("my-custom-board")
125 assert not definitions.is_custom_board("sipeed-tang-nano-9k")
128def test_loading_with_custom_fpgas(apio_runner: ApioRunner):
129 """Tests loading of apio standard definitions with a with project's
130 custom fpgas definitions.
131 """
133 with apio_runner.in_sandbox() as sb:
135 # -- Fake apio project file.
136 sb.write_default_apio_ini()
138 # -- Write fake custom boards
139 fpga_json1 = {
140 "part-num": "ICE40HX4K-TQ144",
141 "arch": "ice40",
142 "size": "99k", # Overriding 8k
143 "ice40-params": {"type": "hx8k", "package": "tq144:4k"},
144 }
146 fpga_json2 = {
147 "part-num": "MY-CUSTOM-FPGA",
148 "arch": "ice40",
149 "size": "4k",
150 "ice40-params": {"type": "hx4k", "package": "bg121"},
151 }
153 sb.write_json_file(
154 "fpgas.jsonc",
155 {
156 # -- Overrides a standard definition
157 "ice40hx4k-tq144-8k": fpga_json1,
158 # -- Adds a new definition
159 "my-custom-fpga": fpga_json2,
160 },
161 )
163 # -- Load
164 apio_ctx = ApioContext(
165 project_policy=ProjectPolicy.PROJECT_REQUIRED,
166 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
167 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
168 )
170 # -- Verify
171 definitions = apio_ctx.definitions
172 assert definitions is not None
174 assert apio_ctx.has_project
175 assert "alhambra-ii" in definitions.boards
176 assert "openfpgaloader" in definitions.programmers
178 # TODO: Use proto ascii literals instead of converting from json
179 fpga_proto1 = proto_util.proto_from_json_dict(
180 fpga_json1, FpgaDefinition, "Failed to parse fpga_json1"
181 )
182 fpga_proto2 = proto_util.proto_from_json_dict(
183 fpga_json2, FpgaDefinition, "Failed to parse fpga_json2"
184 )
186 assert definitions.fpgas["ice40hx4k-tq144-8k"] == fpga_proto1
187 assert definitions.fpgas["my-custom-fpga"] == fpga_proto2
189 assert definitions.is_custom_fpga("ice40hx4k-tq144-8k")
190 assert definitions.is_custom_fpga("my-custom-fpga")
191 assert not definitions.is_custom_fpga("ice40lp1k-cm36")
194def test_loading_with_custom_programmer(apio_runner: ApioRunner):
195 """Tests loading of apio standard definitions with a with project's
196 custom programmer definitions.
197 """
199 with apio_runner.in_sandbox() as sb:
201 # -- Fake apio project file.
202 sb.write_default_apio_ini()
204 # -- Write fake custom programmers
205 programmer_info1 = {
206 "command": "custom-iceprog",
207 "args": "-d d:${BUS}/${DEV}",
208 }
209 programmer_info2 = {"command": "iceprog", "args": "my custom args"}
211 sb.write_json_file(
212 "programmers.jsonc",
213 {
214 # -- Overrides a standard definition
215 "iceprog": programmer_info1,
216 # -- Adds a new definition
217 "my-custom-programmer": programmer_info2,
218 },
219 )
221 # -- Load
222 apio_ctx = ApioContext(
223 project_policy=ProjectPolicy.PROJECT_REQUIRED,
224 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
225 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
226 )
228 # -- Verify
229 definitions = apio_ctx.definitions
230 assert definitions is not None
232 assert apio_ctx.has_project
233 assert "alhambra-ii" in definitions.boards
234 assert "ice40hx4k-tq144-8k" in definitions.fpgas
236 programmer_definition1 = definitions.programmers["iceprog"]
237 assert (
238 proto_util.proto_to_json_dict(programmer_definition1)
239 == programmer_info1
240 )
242 programmer_definition2 = definitions.programmers[
243 "my-custom-programmer"
244 ]
245 assert (
246 proto_util.proto_to_json_dict(programmer_definition2)
247 == programmer_info2
248 )
250 assert definitions.is_custom_programmer("iceprog")
251 assert definitions.is_custom_programmer("my-custom-programmer")
252 assert not definitions.is_custom_programmer("openfpgaloader")
255def test_loading_invalid_custom_board(apio_runner: ApioRunner):
256 """Tests loading attempt of an invalid board custom definition."""
258 with apio_runner.in_sandbox() as sb:
260 # -- Fake apio project file.
261 sb.write_default_apio_ini()
263 # -- Write a custom definition file with an invalid definition
264 # -- Missing description field.
265 sb.write_json_file(
266 "boards.jsonc",
267 {
268 "alhambra-ii": {
269 "fpga-id": "ice40hx4k-tq144-8k",
270 "programmer": {
271 "id": "openfpgaloader",
272 },
273 "usb": {
274 "vid": "0403",
275 "pid": "6010",
276 },
277 }
278 },
279 )
281 # -- Test
282 with apio_runner.with_logger() as log:
283 with raises(SystemExit) as e:
284 _ = ApioContext(
285 project_policy=ProjectPolicy.PROJECT_REQUIRED,
286 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
287 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
288 )
290 # -- Verify
291 assert e.value.code == 1
292 assert "Missing required field 'description'" in log.out
295def test_loading_invalid_custom_fpga(apio_runner: ApioRunner):
296 """Tests loading attempt of an invalid board custom definition."""
298 with apio_runner.in_sandbox() as sb:
300 # -- Fake apio project file.
301 sb.write_default_apio_ini()
303 # -- Write a custom definition file with an invalid definition
304 # -- Missing part-num field.
305 sb.write_json_file(
306 "fpgas.jsonc",
307 {
308 "ice40lp384-cm36": {
309 "arch": "ice40",
310 "size": "384",
311 "ice40-params": {"type": "lp384", "package": "cm36"},
312 }
313 },
314 )
316 # -- Test
317 with apio_runner.with_logger() as log:
318 with raises(SystemExit) as e:
319 _ = ApioContext(
320 project_policy=ProjectPolicy.PROJECT_REQUIRED,
321 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
322 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
323 )
325 # -- Verify
326 assert e.value.code == 1
327 assert "Missing required field 'part-num'" in log.out
330def test_loading_invalid_custom_programmer(apio_runner: ApioRunner):
331 """Tests loading attempt of an invalid programmer custom definition."""
333 with apio_runner.in_sandbox() as sb:
335 # -- Fake apio project file.
336 sb.write_default_apio_ini()
338 # -- Write a custom definition file with an invalid definition
339 # -- Missing command field.
340 sb.write_json_file(
341 "programmers.jsonc",
342 {
343 "openfpgaloader": {
344 "args": "--force-terminal-mode --verify",
345 }
346 },
347 )
349 # -- Test
350 with apio_runner.with_logger() as log:
351 with raises(SystemExit) as e:
352 _ = ApioContext(
353 project_policy=ProjectPolicy.PROJECT_REQUIRED,
354 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
355 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
356 )
358 # -- Verify
359 assert e.value.code == 1
360 assert "Missing required field 'command'" in log.out
363def test_loading_invalid_custom_board_id(apio_runner: ApioRunner):
364 """Tests loading attempt of an invalid board id."""
366 with apio_runner.in_sandbox() as sb:
368 # -- Fake apio project file.
369 sb.write_default_apio_ini()
371 # -- Write a custom definition file with an invalid definition
372 # -- Missing description field.
373 sb.write_json_file(
374 "boards.jsonc",
375 {
376 "invalid-ID": {
377 "description": "BlackIce MX",
378 "fpga-id": "ice40hx4k-tq144-8k",
379 "programmer": {"id": "blackiceprog"},
380 "usb": {"vid": "0483", "pid": "5740"},
381 }
382 },
383 )
385 # -- Test
386 with apio_runner.with_logger() as log:
387 with raises(SystemExit) as e:
388 _ = ApioContext(
389 project_policy=ProjectPolicy.PROJECT_REQUIRED,
390 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
391 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
392 )
394 # -- Verify
395 assert e.value.code == 1
396 assert "Board id `invalid-ID` has an invalid format" in log.out
399def test_loading_invalid_custom_fpga_id(apio_runner: ApioRunner):
400 """Tests loading attempt of an invalid fpga id."""
402 with apio_runner.in_sandbox() as sb:
404 # -- Fake apio project file.
405 sb.write_default_apio_ini()
407 # -- Write a custom definition file with an invalid definition
408 # -- Missing description field.
409 sb.write_json_file(
410 "fpgas.jsonc",
411 {
412 "invalid-ID": {
413 "part-num": "ICE40LP1K-SWG16TR",
414 "arch": "ice40",
415 "size": "1k",
416 "ice40-params": {"type": "lp1k", "package": "swg16tr"},
417 }
418 },
419 )
421 # -- Test
422 with apio_runner.with_logger() as log:
424 with raises(SystemExit) as e:
425 _ = ApioContext(
426 project_policy=ProjectPolicy.PROJECT_REQUIRED,
427 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
428 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
429 )
431 # -- Verify
432 assert e.value.code == 1
433 assert "FPGA id has an invalid format: invalid-ID" in log.out
436def test_loading_invalid_custom_programmer_id(apio_runner: ApioRunner):
437 """Tests loading attempt of an invalid programmer id."""
439 with apio_runner.in_sandbox() as sb:
441 # -- Fake apio project file.
442 sb.write_default_apio_ini()
444 # -- Write a custom definition file with an invalid definition
445 # -- Missing description field.
446 sb.write_json_file(
447 "programmers.jsonc",
448 {
449 "invalid-ID": {
450 "command": "openFPGALoader",
451 "args": "--force-terminal-mode --verify",
452 }
453 },
454 )
456 # -- Test
457 with apio_runner.with_logger() as log:
458 with raises(SystemExit) as e:
459 _ = ApioContext(
460 project_policy=ProjectPolicy.PROJECT_REQUIRED,
461 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
462 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
463 )
465 # -- Verify
466 assert e.value.code == 1
467 assert "Programmer id has an invalid format: invalid-ID" in log.out