Coverage for tests/unit_tests/managers/test_project.py: 100%
71 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 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
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_all_options_env(apio_runner: ApioRunner, capsys: LogCaptureFixture):
50 """Tests an apio.ini with all options"""
52 apio_ini = {
53 "[env:default]": {
54 "board": "alhambra-ii", # required.
55 "default-testbench": "main_tb.v",
56 "defines": "\n aaa=111\n bbb=222",
57 "format-verible-options": "\n --aaa bbb\n --ccc ddd",
58 "programmer-cmd": "iceprog ${VID}:${PID}",
59 "top-module": "my_module",
60 "yosys-synth-extra-options": "-dsp -xyz",
61 "nextpnr-extra-options": "--freq 13",
62 "constraint-file": "pinout.lpf",
63 }
64 }
66 # -- Make sure we covered all the options.
67 assert len(apio_ini["[env:default]"]) == len(ENV_OPTIONS)
69 project, _ = load_apio_ini(
70 apio_ini=apio_ini,
71 env_arg=None,
72 apio_runner=apio_runner,
73 capsys=capsys,
74 )
76 assert project.env_name == "default"
77 assert project.env_options == {
78 "board": "alhambra-ii",
79 "default-testbench": "main_tb.v",
80 "defines": ["aaa=111", "bbb=222"],
81 "format-verible-options": ["--aaa bbb", "--ccc ddd"],
82 "programmer-cmd": "iceprog ${VID}:${PID}",
83 "top-module": "my_module",
84 "yosys-synth-extra-options": ["-dsp -xyz"],
85 "nextpnr-extra-options": ["--freq 13"],
86 "constraint-file": "pinout.lpf",
87 }
89 # -- Try a few as dict lookup on the project object.
90 assert project.get_str_option("board") == "alhambra-ii"
91 assert project.get_str_option("top-module") == "my_module"
92 assert project.get_str_option("constraint-file") == "pinout.lpf"
95def test_required_options_only_env(
96 apio_runner: ApioRunner, capsys: LogCaptureFixture
97):
98 """Tests a minimal apio.ini with required only options."""
100 project, stdout = load_apio_ini(
101 apio_ini={
102 "[env:default]": {
103 "board": "alhambra-ii",
104 }
105 },
106 env_arg=None,
107 apio_runner=apio_runner,
108 capsys=capsys,
109 )
111 assert project.env_name == "default"
112 assert project.env_options == {
113 "board": "alhambra-ii",
114 "top-module": "main",
115 }
116 assert (
117 "Option 'top-module' is missing for env default, assuming 'main'"
118 in stdout
119 )
122def test_list_options(apio_runner: ApioRunner, capsys: LogCaptureFixture):
123 """Tests list optionss."""
125 project, _ = load_apio_ini(
126 apio_ini={
127 "[env:default]": {
128 "board": "alhambra-ii",
129 "yosys-synth-extra-options": " k1=v1 k2=v2 \n\n k3=v3 \n\n",
130 "nextpnr-extra-options": " k5=v5 k6=v6 \n\n k7=v7 \n\n",
131 }
132 },
133 env_arg=None,
134 apio_runner=apio_runner,
135 capsys=capsys,
136 )
138 assert project.get_list_option("yosys-synth-extra-options") == [
139 "k1=v1 k2=v2",
140 "k3=v3",
141 ]
143 assert project.get_list_option("nextpnr-extra-options") == [
144 "k5=v5 k6=v6",
145 "k7=v7",
146 ]
149def test_legacy_board_id(apio_runner: ApioRunner, capsys: LogCaptureFixture):
150 """Tests with 'board' option having a legacy board id. It should
151 be converted to the canonical board id"""
153 project, stdout = load_apio_ini(
154 apio_ini={
155 "[env:default]": {
156 "board": "iCE40-HX8K",
157 "top-module": "my_top_module",
158 }
159 },
160 env_arg=None,
161 apio_runner=apio_runner,
162 capsys=capsys,
163 )
165 assert project.env_name == "default"
166 assert project.env_options == {
167 "board": "ice40-hx8k",
168 "top-module": "my_top_module",
169 }
170 assert (
171 "Warning: 'Board iCE40-HX8K' was renamed to 'ice40-hx8k'. "
172 "Please update apio.ini" in stdout
173 )
176def test_legacy_apio_ini(apio_runner: ApioRunner, capsys: LogCaptureFixture):
177 """Tests with an old style apio.ini that has a single [env] section."""
179 project, stdout = load_apio_ini(
180 apio_ini={
181 "[env]": {
182 "board": "alhambra-ii",
183 }
184 },
185 env_arg=None,
186 apio_runner=apio_runner,
187 capsys=capsys,
188 )
190 assert project.env_name == "default"
191 assert project.env_options == {
192 "board": "alhambra-ii",
193 "top-module": "main",
194 }
195 assert (
196 "Warning: Apio.ini has a legacy [env] section. "
197 "Please rename it to [env:default]" in stdout
198 )
201def test_first_env_is_default(
202 apio_runner: ApioRunner, capsys: LogCaptureFixture
203):
204 """Tests that with no --env and no default-env, the first env in
205 apio.ini is selected"""
207 project, _ = load_apio_ini(
208 apio_ini={
209 "[common]": {
210 "default-testbench": "main_tb.v",
211 },
212 "[env:env1]": {
213 "board": "alhambra-ii",
214 "top-module": "module1",
215 },
216 "[env:env2]": {
217 "board": "ice40-hx8k",
218 "top-module": "module2",
219 },
220 },
221 env_arg=None,
222 apio_runner=apio_runner,
223 capsys=capsys,
224 )
226 assert project.env_name == "env1"
227 assert project.env_options == {
228 "default-testbench": "main_tb.v",
229 "board": "alhambra-ii",
230 "top-module": "module1",
231 }
234def test_env_selection_from_apio_ini(
235 apio_runner: ApioRunner, capsys: LogCaptureFixture
236):
237 """Tests that with no --env, and with default env defined in apio.ini
238 using default-env option."""
240 project, _ = load_apio_ini(
241 apio_ini={
242 "[apio]": {
243 "default-env": "env2",
244 },
245 "[common]": {
246 "default-testbench": "main_tb.v",
247 },
248 "[env:env1]": {
249 "board": "alhambra-ii",
250 "top-module": "module1",
251 },
252 "[env:env2]": {
253 "board": "ice40-hx8k",
254 "top-module": "module2",
255 },
256 },
257 env_arg=None,
258 apio_runner=apio_runner,
259 capsys=capsys,
260 )
262 assert project.env_name == "env2"
263 assert project.env_options == {
264 "default-testbench": "main_tb.v",
265 "board": "ice40-hx8k",
266 "top-module": "module2",
267 }
270def test_env_selection_from_env_arg(
271 apio_runner: ApioRunner, capsys: LogCaptureFixture
272):
273 """Tests that with --env overriding default-env in apio.ini."""
275 project, _ = load_apio_ini(
276 apio_ini={
277 "[apio]": {
278 "default-env": "env1",
279 },
280 "[common]": {
281 "default-testbench": "main_tb.v",
282 },
283 "[env:env1]": {
284 "board": "alhambra-ii",
285 "top-module": "module1",
286 },
287 "[env:env2]": {
288 "board": "ice40-hx8k",
289 "top-module": "module2",
290 },
291 },
292 env_arg="env2", # --env env2
293 apio_runner=apio_runner,
294 capsys=capsys,
295 )
297 assert project.env_name == "env2"
298 assert project.env_options == {
299 "default-testbench": "main_tb.v",
300 "board": "ice40-hx8k",
301 "top-module": "module2",
302 }
305def error_tester(
306 env_arg: Optional[str],
307 apio_ini: Dict[str, Dict[str, str]],
308 expected_error: str,
309 apio_runner: ApioRunner,
310 capsys: LogCaptureFixture,
311):
312 """A helper function to tests apio.ini content that is expected to
313 exit with an error."""
315 with apio_runner.in_sandbox() as sb:
316 # -- Create the apio.ini file
317 sb.write_apio_ini(apio_ini)
319 # -- Try to create the context with the project info.
320 capsys.readouterr() # Reset capture
321 with pytest.raises(SystemExit) as e:
322 ApioContext(
323 project_policy=ProjectPolicy.PROJECT_REQUIRED,
324 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
325 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
326 env_arg=env_arg,
327 )
329 # -- Check the errors.
330 capture = cunstyle(capsys.readouterr().out)
331 assert e.value.code == 1
332 assert expected_error in capture
335def test_validation_errors(apio_runner: ApioRunner, capsys: LogCaptureFixture):
336 """Tests the validation of apio.ini errors."""
338 # -- No [env:name] section.
339 error_tester(
340 env_arg=None,
341 apio_ini={
342 "[common]": {
343 "board": "alhambra-ii",
344 "top-module": "main",
345 }
346 },
347 expected_error=(
348 "Error: Project file 'apio.ini' should have at "
349 "least one [env:name] section."
350 ),
351 apio_runner=apio_runner,
352 capsys=capsys,
353 )
355 # -- Unknown board id.
356 error_tester(
357 env_arg=None,
358 apio_ini={
359 "[env:default]": {
360 "board": "no-such-board",
361 }
362 },
363 expected_error="Error: Unknown board id 'no-such-board' in apio.ini",
364 apio_runner=apio_runner,
365 capsys=capsys,
366 )
368 # -- Env name has an invalid char (Uppercase).
369 error_tester(
370 env_arg=None,
371 apio_ini={
372 "[env:Default]": {
373 "board": "alhambra-ii",
374 }
375 },
376 expected_error="Error: Invalid env name 'Default'",
377 apio_runner=apio_runner,
378 capsys=capsys,
379 )
381 # -- Env name has an extra space.
382 error_tester(
383 env_arg=None,
384 apio_ini={
385 "[env: default]": {
386 "board": "alhambra-ii",
387 }
388 },
389 expected_error="Error: Invalid env name ' default'",
390 apio_runner=apio_runner,
391 capsys=capsys,
392 )
394 # -- default-env points to a non existing env.
395 error_tester(
396 env_arg=None,
397 apio_ini={
398 "[apio]": {
399 "default-env": "no-such-env",
400 },
401 "[env:default]": {
402 "board": "alhambra-ii",
403 },
404 },
405 expected_error="Error: Env 'no-such-env' not found in apio.ini",
406 apio_runner=apio_runner,
407 capsys=capsys,
408 )
410 # -- Missing required option.
411 error_tester(
412 env_arg=None,
413 apio_ini={
414 "[env:default]": {"top-module": "main"},
415 },
416 expected_error="Error: Missing required option 'board' for "
417 "env 'default'.",
418 apio_runner=apio_runner,
419 capsys=capsys,
420 )
422 # -- env_arg has a non existing env name.
423 error_tester(
424 env_arg="no-such-env",
425 apio_ini={
426 "[env:default]": {"board": "alhambra-ii"},
427 },
428 expected_error="Error: Env 'no-such-env' not found in apio.ini",
429 apio_runner=apio_runner,
430 capsys=capsys,
431 )
433 # -- env_arg has an env name with an invalid char (upper case).
434 error_tester(
435 env_arg="Default",
436 apio_ini={
437 "[env:default]": {"board": "alhambra-ii"},
438 },
439 expected_error="Error: Invalid --env value 'Default'",
440 apio_runner=apio_runner,
441 capsys=capsys,
442 )