Coverage for tests/integration_tests/test_projects.py: 100%
197 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-09 01:55 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-09 01:55 +0000
1"""
2Test various "apio" commands.
3"""
5import os
6from os.path import getsize
7from pathlib import Path
8from typing import cast
9from tests.conftest import ApioRunner
10from apio.commands.apio import apio_top_cli as apio
13def test_project_with_legacy_board_id(apio_runner: ApioRunner):
14 """Test a project that uses a legacy board id."""
16 # -- We shared the apio home with the other tests in this file to speed
17 # -- up apio package installation. Tests should not mutate the shared home
18 # -- to avoid cross-interference between tests in this file.
19 with apio_runner.in_sandbox() as sb:
21 # -- Fetch an example of a board that has a legacy name.
22 result = sb.invoke_apio_cmd(
23 apio, ["examples", "fetch", "ice40-hx8k/leds"]
24 )
25 sb.assert_result_ok(result)
27 # -- Run 'apio build'
28 result = sb.invoke_apio_cmd(apio, ["build"])
29 sb.assert_result_ok(result)
31 # -- Modify the apio.ini to have the legacy board id
32 sb.write_apio_ini(
33 {
34 "[env:default]": {
35 "board": "iCE40-HX8K",
36 "top-module": "leds",
37 }
38 }
39 )
41 # -- Run 'apio clean'
42 result = sb.invoke_apio_cmd(apio, ["clean"])
43 sb.assert_result_ok(result)
45 # -- Run 'apio build' again. It should also succeed.
46 result = sb.invoke_apio_cmd(apio, ["build"])
47 sb.assert_result_ok(result)
50def _test_project(
51 apio_runner: ApioRunner,
52 *,
53 remote_proj_dir: bool,
54 example: str,
55 testbench_file: str,
56 bitstream: str,
57 report_item: str,
58):
59 """A common project integration test. Invoked per each tested
60 architecture.
61 """
63 # pylint: disable=too-many-arguments
64 # pylint: disable=too-many-statements
66 # -- Extract the base name of the testbench file
67 testbench, _ = os.path.splitext(testbench_file)
69 # -- We shared the apio home with the other tests in this file to speed
70 # -- up apio package installation. Tests should not mutate the shared home
71 # -- to avoid cross-interference between tests in this file.
72 with apio_runner.in_sandbox() as sb:
74 # -- If testing from a remote dir, step out of the proj dir, and
75 # -- use -p proj_dir arg.
76 if remote_proj_dir:
77 os.chdir(sb.sandbox_dir)
78 sb.proj_dir.rmdir()
79 proj_arg = ["-p", str(sb.proj_dir)]
80 dst_arg = ["-d", str(sb.proj_dir)]
81 else:
82 proj_arg = []
83 dst_arg = []
85 # -- If testing from a remote dir, the proj dir should not exist yet,
86 # -- else, the project dir should be empty.
87 if remote_proj_dir:
88 assert not sb.proj_dir.exists()
89 else:
90 assert not os.listdir(sb.proj_dir)
92 # -- 'apio examples fetch <example> -d <proj_dir>'
93 args = ["examples", "fetch", example] + dst_arg
94 result = sb.invoke_apio_cmd(apio, args)
95 sb.assert_result_ok(result)
96 assert f"Copying {example} example files" in result.output
97 assert "fetched successfully" in result.output
98 assert getsize(sb.proj_dir / "apio.ini")
100 # -- Remember the original list of project files.
101 project_files = os.listdir(sb.proj_dir)
103 # -- 'apio build'
104 args = ["build"] + proj_arg
105 result = sb.invoke_apio_cmd(apio, args)
106 sb.assert_result_ok(result)
107 assert "SUCCESS" in result.output
108 assert "yosys -p" in result.output
109 assert "-DSYNTHESIZE" in result.output
110 assert "-DAPIO_SIM" not in result.output
112 assert getsize(sb.proj_dir / "_build/default" / bitstream)
114 # -- 'apio build' (no change, yosys is not invoked)
115 args = ["build"] + proj_arg
116 result = sb.invoke_apio_cmd(apio, args)
117 sb.assert_result_ok(result)
118 assert "SUCCESS" in result.output
119 assert "yosys -p" not in result.output
121 # -- Modify apio.ini
122 apio_ini_lines = cast(
123 list[str], sb.read_file(sb.proj_dir / "apio.ini", lines_mode=True)
124 )
125 apio_ini_lines.append(" ")
126 sb.write_file(sb.proj_dir / "apio.ini", apio_ini_lines, exists_ok=True)
128 # -- 'apio build'
129 # -- Apio.ini modification should triggers a new build.
130 args = ["build"] + proj_arg
131 result = sb.invoke_apio_cmd(apio, args)
132 sb.assert_result_ok(result)
133 assert "SUCCESS" in result.output
134 assert "yosys -p" in result.output
135 assert "-DSYNTHESIZE" in result.output
136 assert "-DAPIO_SIM" not in result.output
138 # -- 'apio lint'
139 args = ["lint"] + proj_arg
140 result = sb.invoke_apio_cmd(apio, args)
141 sb.assert_result_ok(result)
142 assert "SUCCESS" in result.output
143 assert "-DSYNTHESIZE" in result.output
144 assert "-DAPIO_SIM=0" in result.output
145 assert getsize(sb.proj_dir / "_build/default/hardware.vlt")
147 # -- 'apio lint <testbench.v>'
148 args = ["lint", testbench_file] + proj_arg
149 result = sb.invoke_apio_cmd(apio, args)
150 sb.assert_result_ok(result)
151 assert "SUCCESS" in result.output
152 assert "TOP_MODULE" not in result.output
153 assert "-DAPIO_SIM=0" in result.output
155 # -- 'apio lint --nosynth'
156 args = ["lint", "--nosynth"] + proj_arg
157 result = sb.invoke_apio_cmd(apio, args)
158 sb.assert_result_ok(result)
159 assert "SUCCESS" in result.output
160 assert "-DSYNTHESIZE" not in result.output
161 assert "-DAPIO_SIM=0" in result.output
162 assert getsize(sb.proj_dir / "_build/default/hardware.vlt")
164 # -- 'apio format'
165 args = ["format"] + proj_arg
166 result = sb.invoke_apio_cmd(apio, args)
167 sb.assert_result_ok(result)
168 assert "-DSYNTHESIZE" not in result.output
169 assert "-DAPIO_SIM" not in result.output
171 # -- 'apio format <testbench-file>'
172 # -- This tests the project relative specification even when
173 # -- the option --project-dir is used.
174 args = ["format", testbench_file] + proj_arg
175 result = sb.invoke_apio_cmd(apio, args)
176 sb.assert_result_ok(result)
178 # -- 'apio test'
179 args = ["test"] + proj_arg
180 result = sb.invoke_apio_cmd(apio, args)
181 sb.assert_result_ok(result)
182 assert "SUCCESS" in result.output
183 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
184 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
185 # -- For issue https://github.com/FPGAwars/apio/issues/557
186 assert "warning: Timing checks are not supported" not in result.output
187 assert "-DSYNTHESIZE" not in result.output
188 assert "-DAPIO_SIM=0" in result.output
190 # -- 'apio clean'
191 args = ["clean"] + proj_arg
192 result = sb.invoke_apio_cmd(apio, args)
193 sb.assert_result_ok(result)
194 assert "Cleanup completed" in result.output
195 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
196 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
198 # -- 'apio test --default'
199 args = ["test", "--default"] + proj_arg
200 result = sb.invoke_apio_cmd(apio, args)
201 sb.assert_result_ok(result)
202 assert "SUCCESS" in result.output
203 assert "-DAPIO_SIM=0" in result.output
204 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
205 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
207 # -- 'apio clean'
208 args = ["clean"] + proj_arg
209 result = sb.invoke_apio_cmd(apio, args)
210 sb.assert_result_ok(result)
211 assert "Cleanup completed" in result.output
212 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
213 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
215 # -- 'apio sim --no-gtkwave'
216 args = ["sim", "--no-gtkwave"] + proj_arg
217 result = sb.invoke_apio_cmd(apio, args)
218 sb.assert_result_ok(result)
219 assert "SUCCESS" in result.output
220 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
221 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
222 # -- For issue https://github.com/FPGAwars/apio/issues/557
223 assert "warning: Timing checks are not supported" not in result.output
224 assert "-DSYNTHESIZE" not in result.output
225 assert "-DAPIO_SIM=1" in result.output
227 # -- 'apio clean'
228 args = ["clean"] + proj_arg
229 result = sb.invoke_apio_cmd(apio, args)
230 sb.assert_result_ok(result)
231 assert "Cleanup completed" in result.output
232 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
233 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
235 # -- 'apio test <testbench-file>'
236 args = ["test", testbench_file] + proj_arg
237 result = sb.invoke_apio_cmd(apio, args)
238 sb.assert_result_ok(result)
239 assert "SUCCESS" in result.output
240 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
241 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
242 # -- For issue https://github.com/FPGAwars/apio/issues/557
243 assert "warning: Timing checks are not supported" not in result.output
245 # -- 'apio sim --no-gtkw <testbench-file>'
246 args = ["sim", "--no-gtkwave", testbench_file] + proj_arg
247 result = sb.invoke_apio_cmd(apio, args)
248 sb.assert_result_ok(result)
249 assert "SUCCESS" in result.output
250 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
251 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
252 # -- For issue https://github.com/FPGAwars/apio/issues/557
253 assert "warning: Timing checks are not supported" not in result.output
255 # -- 'apio clean'
256 args = ["clean"] + proj_arg
257 result = sb.invoke_apio_cmd(apio, args)
258 sb.assert_result_ok(result)
259 assert "Cleanup completed" in result.output
260 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
261 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
263 # -- 'apio report'
264 args = ["report"] + proj_arg
265 result = sb.invoke_apio_cmd(apio, args)
266 sb.assert_result_ok(result)
267 assert "SUCCESS" in result.output
268 assert "-DSYNTHESIZE" in result.output
269 assert "-DAPIO_SIM" not in result.output
270 assert report_item in result.output
271 assert "─────┐" in result.output # Graphical table border
272 assert getsize(sb.proj_dir / "_build/default/hardware.pnr")
274 # -- 'apio graph -n'
275 args = ["graph", "-n"] + proj_arg
276 result = sb.invoke_apio_cmd(apio, args)
277 sb.assert_result_ok(result)
278 assert "SUCCESS" in result.output
279 assert "-DSYNTHESIZE" in result.output
280 assert "-DAPIO_SIM" not in result.output
281 assert getsize(sb.proj_dir / "_build/default/graph.dot")
282 assert getsize(sb.proj_dir / "_build/default/graph.svg")
284 # -- 'apio clean'
285 assert Path(sb.proj_dir / "_build/default").exists()
286 args = ["clean"] + proj_arg
287 result = sb.invoke_apio_cmd(apio, args)
288 sb.assert_result_ok(result)
289 assert "Cleanup completed" in result.output
290 assert not Path(sb.proj_dir / "_build/default").exists()
292 # -- Here we should have only the original project files.
293 assert set(os.listdir(sb.proj_dir)) == set(project_files)
296# NOTE: We use the alhambra-ii/bcd-counter example because it uses
297# subdirectories. This increases the test coverage.
298def test_project_ice40_local_dir(apio_runner: ApioRunner):
299 """Tests building and testing an ice40 project as the current working
300 dir."""
301 _test_project(
302 apio_runner,
303 remote_proj_dir=False,
304 example="alhambra-ii/bcd-counter",
305 testbench_file="main_tb.v",
306 bitstream="hardware.bin",
307 report_item="ICESTORM_LC",
308 )
311def test_project_ice40_remote_dir(apio_runner: ApioRunner):
312 """Tests building and testing an ice40 project from a remote dir, using
313 the -p option."""
314 _test_project(
315 apio_runner,
316 remote_proj_dir=True,
317 example="alhambra-ii/bcd-counter",
318 testbench_file="main_tb.v",
319 bitstream="hardware.bin",
320 report_item="ICESTORM_LC",
321 )
324def test_project_ice40_system_verilog(apio_runner: ApioRunner):
325 """Tests building and testing an ice40 project that contains system
326 verilog files."""
327 _test_project(
328 apio_runner,
329 remote_proj_dir=False,
330 example="alhambra-ii/bcd-counter-sv",
331 testbench_file="main_tb.sv",
332 bitstream="hardware.bin",
333 report_item="ICESTORM_LC",
334 )
337def test_project_ecp5_local_dir(apio_runner: ApioRunner):
338 """Tests building and testing an ecp5 project as the current working dir"""
339 _test_project(
340 apio_runner,
341 remote_proj_dir=False,
342 example="colorlight-5a-75b-v8/ledon",
343 testbench_file="ledon_tb.v",
344 bitstream="hardware.bit",
345 report_item="TRELLIS_IO",
346 )
349def test_project_ecp5_remote_dir(apio_runner: ApioRunner):
350 """Tests building and testing an ecp5 project from a remote directory."""
351 _test_project(
352 apio_runner,
353 remote_proj_dir=True,
354 example="colorlight-5a-75b-v8/ledon",
355 testbench_file="ledon_tb.v",
356 bitstream="hardware.bit",
357 report_item="TRELLIS_IO",
358 )
361def test_project_ecp5_system_verilog(apio_runner: ApioRunner):
362 """Tests building and testing an ecp5 project that contains system
363 verilog files."""
364 _test_project(
365 apio_runner,
366 remote_proj_dir=False,
367 example="colorlight-5a-75b-v8/ledon-sv",
368 testbench_file="ledon_tb.sv",
369 bitstream="hardware.bit",
370 report_item="TRELLIS_IO",
371 )
374def test_project_gowin_local_dir(apio_runner: ApioRunner):
375 """Tests building and testing a gowin project as the current working dir"""
376 _test_project(
377 apio_runner,
378 remote_proj_dir=False,
379 example="sipeed-tang-nano-9k/blinky",
380 testbench_file="blinky_tb.v",
381 bitstream="hardware.fs",
382 report_item="LUT4",
383 )
386def test_project_gowin_remote_dir(apio_runner: ApioRunner):
387 """Tests building and testing a gowin project from a remote directory."""
388 _test_project(
389 apio_runner,
390 remote_proj_dir=True,
391 example="sipeed-tang-nano-9k/blinky",
392 testbench_file="blinky_tb.v",
393 bitstream="hardware.fs",
394 report_item="LUT4",
395 )
398def test_project_gowin_system_verilog(apio_runner: ApioRunner):
399 """Tests building and testing an gowin project that contains system
400 verilog files."""
401 _test_project(
402 apio_runner,
403 remote_proj_dir=False,
404 example="sipeed-tang-nano-9k/blinky-sv",
405 testbench_file="blinky_tb.sv",
406 bitstream="hardware.fs",
407 report_item="LUT4",
408 )
411def test_project_xilinx_local_dir(apio_runner: ApioRunner):
412 """Tests building and testing a Xilinx project as the current working
413 dir."""
414 _test_project(
415 apio_runner,
416 remote_proj_dir=False,
417 example="basys3/ledon",
418 testbench_file="ledon_tb.v",
419 bitstream="hardware.bit",
420 report_item="PSEUDO_GND",
421 )
424def test_project_xilinx_remote_dir(apio_runner: ApioRunner):
425 """Tests building and testing an ice40 project from a remote dir, using
426 the -p option."""
427 _test_project(
428 apio_runner,
429 remote_proj_dir=True,
430 example="basys3/ledon",
431 testbench_file="ledon_tb.v",
432 bitstream="hardware.bit",
433 report_item="PSEUDO_GND",
434 )