Coverage for tests / unit_tests / managers / test_project.py: 100%
73 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 02:47 +0000
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 02:47 +0000
1"""
2Tests of project.py
3"""
5from typing import Dict, Optional, Tuple
6from pytest import LogCaptureFixture
7import pytest
8from tests.conftest import ApioRunner
9from apio.managers.project import Project, ENV_OPTIONS_SPEC
10from apio.common.apio_console import cunstyle
11from apio.apio_context import (
12 ApioContext,
13 PackagesPolicy,
14 ProjectPolicy,
15 RemoteConfigPolicy,
16)
18# TODO: Add more tests.
21def load_apio_ini(
22 apio_ini: Dict[str, Dict[str, str]],
23 env_arg: Optional[str],
24 apio_runner: ApioRunner,
25 capsys: LogCaptureFixture,
26) -> Tuple[Project, str]:
27 """A helper function load apio.ini. Returns (project, stdout)"""
29 with apio_runner.in_sandbox() as sb:
30 # -- Create the apio.ini file
31 sb.write_apio_ini(apio_ini)
33 # -- Try to create the context with the project info.
34 capsys.readouterr() # Reset capture
35 apio_ctx = ApioContext(
36 project_policy=ProjectPolicy.PROJECT_REQUIRED,
37 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
38 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
39 env_arg=env_arg,
40 )
42 # -- Return the values.
43 return (
44 apio_ctx.project,
45 cunstyle(capsys.readouterr().out),
46 )
49def test_env_options_specs():
50 """Tests the option spec keys match options specs names"""
52 for name, env_options_spec in ENV_OPTIONS_SPEC.items():
53 assert name == env_options_spec.name
56def test_all_options_env(apio_runner: ApioRunner, capsys: LogCaptureFixture):
57 """Tests an apio.ini with all options"""
59 apio_ini = {
60 "[env:default]": {
61 "board": "alhambra-ii", # required.
62 "default-testbench": "main_tb.v",
63 "defines": "\n aaa=111\n bbb=222",
64 "format-verible-options": "\n --aaa bbb\n --ccc ddd",
65 "programmer-cmd": "iceprog ${VID}:${PID}",
66 "top-module": "my_module",
67 "yosys-synth-extra-options": "-dsp -xyz",
68 "nextpnr-extra-options": "--freq 13",
69 "gtkwave-extra-options": "--rcvar=do_initial_zoom_fit 1",
70 "constraint-file": "pinout.lpf",
71 }
72 }
74 # -- Make sure we covered all the options.
75 assert len(apio_ini["[env:default]"]) == len(ENV_OPTIONS_SPEC)
77 project, _ = load_apio_ini(
78 apio_ini=apio_ini,
79 env_arg=None,
80 apio_runner=apio_runner,
81 capsys=capsys,
82 )
84 assert project.env_name == "default"
85 assert project.env_options == {
86 "board": "alhambra-ii",
87 "default-testbench": "main_tb.v",
88 "defines": ["aaa=111", "bbb=222"],
89 "format-verible-options": ["--aaa bbb", "--ccc ddd"],
90 "programmer-cmd": "iceprog ${VID}:${PID}",
91 "top-module": "my_module",
92 "yosys-synth-extra-options": ["-dsp -xyz"],
93 "nextpnr-extra-options": ["--freq 13"],
94 "gtkwave-extra-options": ["--rcvar=do_initial_zoom_fit 1"],
95 "constraint-file": "pinout.lpf",
96 }
98 # -- Try a few as dict lookup on the project object.
99 assert project.get_str_option("board") == "alhambra-ii"
100 assert project.get_str_option("top-module") == "my_module"
101 assert project.get_str_option("constraint-file") == "pinout.lpf"
104def test_required_options_only_env(
105 apio_runner: ApioRunner, capsys: LogCaptureFixture
106):
107 """Tests a minimal apio.ini with required only options."""
109 project, _ = load_apio_ini(
110 apio_ini={
111 "[env:default]": {
112 "board": "alhambra-ii",
113 "top-module": "main",
114 }
115 },
116 env_arg=None,
117 apio_runner=apio_runner,
118 capsys=capsys,
119 )
121 assert project.env_name == "default"
122 assert project.env_options == {
123 "board": "alhambra-ii",
124 "top-module": "main",
125 }
128def test_list_options(apio_runner: ApioRunner, capsys: LogCaptureFixture):
129 """Tests list optionss."""
131 project, _ = load_apio_ini(
132 apio_ini={
133 "[env:default]": {
134 "board": "alhambra-ii",
135 "top-module": "my_top_module",
136 "yosys-synth-extra-options": " k1=v1 k2=v2 \n\n k3=v3 \n\n",
137 "nextpnr-extra-options": " k5=v5 k6=v6 \n\n k7=v7 \n\n",
138 }
139 },
140 env_arg=None,
141 apio_runner=apio_runner,
142 capsys=capsys,
143 )
145 assert project.get_list_option("yosys-synth-extra-options") == [
146 "k1=v1 k2=v2",
147 "k3=v3",
148 ]
150 assert project.get_list_option("nextpnr-extra-options") == [
151 "k5=v5 k6=v6",
152 "k7=v7",
153 ]
156def test_legacy_board_id(apio_runner: ApioRunner, capsys: LogCaptureFixture):
157 """Tests with 'board' option having a legacy board id. It should
158 be converted to the canonical board id"""
160 project, stdout = load_apio_ini(
161 apio_ini={
162 "[env:default]": {
163 "board": "iCE40-HX8K",
164 "top-module": "my_top_module",
165 }
166 },
167 env_arg=None,
168 apio_runner=apio_runner,
169 capsys=capsys,
170 )
172 assert project.env_name == "default"
173 assert project.env_options == {
174 "board": "ice40-hx8k",
175 "top-module": "my_top_module",
176 }
177 assert (
178 "Warning: 'Board iCE40-HX8K' was renamed to 'ice40-hx8k'. "
179 "Please update apio.ini" in stdout
180 )
183def test_legacy_apio_ini(apio_runner: ApioRunner, capsys: LogCaptureFixture):
184 """Tests with an old style apio.ini that has a single [env] section."""
186 project, stdout = load_apio_ini(
187 apio_ini={
188 "[env]": {
189 "board": "alhambra-ii",
190 "top-module": "main",
191 }
192 },
193 env_arg=None,
194 apio_runner=apio_runner,
195 capsys=capsys,
196 )
198 assert project.env_name == "default"
199 assert project.env_options == {
200 "board": "alhambra-ii",
201 "top-module": "main",
202 }
203 assert (
204 "Warning: Apio.ini has a legacy [env] section. "
205 "Please rename it to [env:default]" in stdout
206 )
209def test_first_env_is_default(
210 apio_runner: ApioRunner, capsys: LogCaptureFixture
211):
212 """Tests that with no --env and no default-env, the first env in
213 apio.ini is selected"""
215 project, _ = load_apio_ini(
216 apio_ini={
217 "[common]": {
218 "default-testbench": "main_tb.v",
219 },
220 "[env:env1]": {
221 "board": "alhambra-ii",
222 "top-module": "module1",
223 },
224 "[env:env2]": {
225 "board": "ice40-hx8k",
226 "top-module": "module2",
227 },
228 },
229 env_arg=None,
230 apio_runner=apio_runner,
231 capsys=capsys,
232 )
234 assert project.env_name == "env1"
235 assert project.env_options == {
236 "default-testbench": "main_tb.v",
237 "board": "alhambra-ii",
238 "top-module": "module1",
239 }
242def test_env_selection_from_apio_ini(
243 apio_runner: ApioRunner, capsys: LogCaptureFixture
244):
245 """Tests that with no --env, and with default env defined in apio.ini
246 using default-env option."""
248 project, _ = load_apio_ini(
249 apio_ini={
250 "[apio]": {
251 "default-env": "env2",
252 },
253 "[common]": {
254 "default-testbench": "main_tb.v",
255 },
256 "[env:env1]": {
257 "board": "alhambra-ii",
258 "top-module": "module1",
259 },
260 "[env:env2]": {
261 "board": "ice40-hx8k",
262 "top-module": "module2",
263 },
264 },
265 env_arg=None,
266 apio_runner=apio_runner,
267 capsys=capsys,
268 )
270 assert project.env_name == "env2"
271 assert project.env_options == {
272 "default-testbench": "main_tb.v",
273 "board": "ice40-hx8k",
274 "top-module": "module2",
275 }
278def test_env_selection_from_env_arg(
279 apio_runner: ApioRunner, capsys: LogCaptureFixture
280):
281 """Tests that with --env overriding default-env in apio.ini."""
283 project, _ = load_apio_ini(
284 apio_ini={
285 "[apio]": {
286 "default-env": "env1",
287 },
288 "[common]": {
289 "default-testbench": "main_tb.v",
290 },
291 "[env:env1]": {
292 "board": "alhambra-ii",
293 "top-module": "module1",
294 },
295 "[env:env2]": {
296 "board": "ice40-hx8k",
297 "top-module": "module2",
298 },
299 },
300 env_arg="env2", # --env env2
301 apio_runner=apio_runner,
302 capsys=capsys,
303 )
305 assert project.env_name == "env2"
306 assert project.env_options == {
307 "default-testbench": "main_tb.v",
308 "board": "ice40-hx8k",
309 "top-module": "module2",
310 }
313def error_tester(
314 env_arg: Optional[str],
315 apio_ini: Dict[str, Dict[str, str]],
316 expected_error: str,
317 apio_runner: ApioRunner,
318 capsys: LogCaptureFixture,
319):
320 """A helper function to tests apio.ini content that is expected to
321 exit with an error."""
323 with apio_runner.in_sandbox() as sb:
324 # -- Create the apio.ini file
325 sb.write_apio_ini(apio_ini)
327 # -- Try to create the context with the project info.
328 capsys.readouterr() # Reset capture
329 with pytest.raises(SystemExit) as e:
330 ApioContext(
331 project_policy=ProjectPolicy.PROJECT_REQUIRED,
332 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
333 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
334 env_arg=env_arg,
335 )
337 # -- Check the errors.
338 capture = cunstyle(capsys.readouterr().out)
339 assert e.value.code == 1
340 assert expected_error in capture
343def test_validation_errors(apio_runner: ApioRunner, capsys: LogCaptureFixture):
344 """Tests the validation of apio.ini errors."""
346 # -- No [env:name] section.
347 error_tester(
348 env_arg=None,
349 apio_ini={
350 "[common]": {
351 "board": "alhambra-ii",
352 "top-module": "main",
353 }
354 },
355 expected_error=(
356 "Error: Project file 'apio.ini' should have at "
357 "least one [env:name] section."
358 ),
359 apio_runner=apio_runner,
360 capsys=capsys,
361 )
363 # -- Unknown board id.
364 error_tester(
365 env_arg=None,
366 apio_ini={
367 "[env:default]": {
368 "board": "no-such-board",
369 }
370 },
371 expected_error="Error: Unknown board id 'no-such-board' in apio.ini",
372 apio_runner=apio_runner,
373 capsys=capsys,
374 )
376 # -- Env name has an invalid char (Uppercase).
377 error_tester(
378 env_arg=None,
379 apio_ini={
380 "[env:Default]": {
381 "board": "alhambra-ii",
382 "top-module": "main",
383 }
384 },
385 expected_error="Error: Invalid env name 'Default'",
386 apio_runner=apio_runner,
387 capsys=capsys,
388 )
390 # -- Env name has an extra space.
391 error_tester(
392 env_arg=None,
393 apio_ini={
394 "[env: default]": {
395 "board": "alhambra-ii",
396 "top-module": "main",
397 }
398 },
399 expected_error="Error: Invalid env name ' default'",
400 apio_runner=apio_runner,
401 capsys=capsys,
402 )
404 # -- default-env points to a non existing env.
405 error_tester(
406 env_arg=None,
407 apio_ini={
408 "[apio]": {
409 "default-env": "no-such-env",
410 },
411 "[env:default]": {
412 "board": "alhambra-ii",
413 "top-module": "main",
414 },
415 },
416 expected_error="Error: Env 'no-such-env' not found in apio.ini",
417 apio_runner=apio_runner,
418 capsys=capsys,
419 )
421 # -- Missing required option.
422 error_tester(
423 env_arg=None,
424 apio_ini={
425 "[env:default]": {"top-module": "main"},
426 },
427 expected_error="Error: Missing required option 'board' for "
428 "env 'default'.",
429 apio_runner=apio_runner,
430 capsys=capsys,
431 )
433 # -- env_arg has a non existing env name.
434 error_tester(
435 env_arg="no-such-env",
436 apio_ini={
437 "[env:default]": {
438 "board": "alhambra-ii",
439 "top-module": "main",
440 },
441 },
442 expected_error="Error: Env 'no-such-env' not found in apio.ini",
443 apio_runner=apio_runner,
444 capsys=capsys,
445 )
447 # -- env_arg has an env name with an invalid char (upper case).
448 error_tester(
449 env_arg="Default",
450 apio_ini={
451 "[env:default]": {
452 "board": "alhambra-ii",
453 "top-module": "main",
454 },
455 },
456 expected_error="Error: Invalid --env value 'Default'",
457 apio_runner=apio_runner,
458 capsys=capsys,
459 )