Coverage for tests/integration_tests/test_projects.py: 100%
203 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-06-24 03:51 +0000
« prev ^ index » next coverage.py v7.14.3, created at 2026-06-24 03:51 +0000
1"""
2Test various "apio" commands.
3"""
5import sys
6import os
7from os.path import getsize
8from pathlib import Path
9from typing import cast
10import pytest
11from tests.conftest import ApioRunner
12from apio.commands.apio import apio_top_cli as apio
14# -- Message to show for the tests that only run on Linux
15ONLY_LINUX_MSG = (
16 "Currently, the Xilinx arch is only implemented for Linux, "
17 "so this test only run on linux platforms"
18)
20# -- For non-linux platfoms (windows and mac)
21is_not_linux = not sys.platform.startswith("linux")
24def test_project_with_legacy_board_id(apio_runner: ApioRunner):
25 """Test a project that uses a legacy board id."""
27 # -- We shared the apio home with the other tests in this file to speed
28 # -- up apio package installation. Tests should not mutate the shared home
29 # -- to avoid cross-interference between tests in this file.
30 with apio_runner.in_sandbox() as sb:
32 # -- Fetch an example of a board that has a legacy name.
33 result = sb.invoke_apio_cmd(
34 apio, ["examples", "fetch", "ice40-hx8k/leds"]
35 )
36 sb.assert_result_ok(result)
38 # -- Run 'apio build'
39 result = sb.invoke_apio_cmd(apio, ["build"])
40 sb.assert_result_ok(result)
42 # -- Modify the apio.ini to have the legacy board id
43 sb.write_apio_ini(
44 {
45 "[env:default]": {
46 "board": "iCE40-HX8K",
47 "top-module": "leds",
48 }
49 }
50 )
52 # -- Run 'apio clean'
53 result = sb.invoke_apio_cmd(apio, ["clean"])
54 sb.assert_result_ok(result)
56 # -- Run 'apio build' again. It should also succeed.
57 result = sb.invoke_apio_cmd(apio, ["build"])
58 sb.assert_result_ok(result)
61def _test_project(
62 apio_runner: ApioRunner,
63 *,
64 remote_proj_dir: bool,
65 example: str,
66 testbench_file: str,
67 bitstream: str,
68 report_item: str,
69):
70 """A common project integration test. Invoked per each tested
71 architecture.
72 """
74 # pylint: disable=too-many-arguments
75 # pylint: disable=too-many-statements
77 # -- Extract the base name of the testbench file
78 testbench, _ = os.path.splitext(testbench_file)
80 # -- We shared the apio home with the other tests in this file to speed
81 # -- up apio package installation. Tests should not mutate the shared home
82 # -- to avoid cross-interference between tests in this file.
83 with apio_runner.in_sandbox() as sb:
85 # -- If testing from a remote dir, step out of the proj dir, and
86 # -- use -p proj_dir arg.
87 if remote_proj_dir:
88 os.chdir(sb.sandbox_dir)
89 sb.proj_dir.rmdir()
90 proj_arg = ["-p", str(sb.proj_dir)]
91 dst_arg = ["-d", str(sb.proj_dir)]
92 else:
93 proj_arg = []
94 dst_arg = []
96 # -- If testing from a remote dir, the proj dir should not exist yet,
97 # -- else, the project dir should be empty.
98 if remote_proj_dir:
99 assert not sb.proj_dir.exists()
100 else:
101 assert not os.listdir(sb.proj_dir)
103 # -- 'apio examples fetch <example> -d <proj_dir>'
104 args = ["examples", "fetch", example] + dst_arg
105 result = sb.invoke_apio_cmd(apio, args)
106 sb.assert_result_ok(result)
107 assert f"Copying {example} example files" in result.output
108 assert "fetched successfully" in result.output
109 assert getsize(sb.proj_dir / "apio.ini")
111 # -- Remember the original list of project files.
112 project_files = os.listdir(sb.proj_dir)
114 # -- 'apio build'
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" in result.output
120 assert "-DSYNTHESIZE" in result.output
121 assert "-DAPIO_SIM" not in result.output
123 assert getsize(sb.proj_dir / "_build/default" / bitstream)
125 # -- 'apio build' (no change, yosys is not invoked)
126 args = ["build"] + proj_arg
127 result = sb.invoke_apio_cmd(apio, args)
128 sb.assert_result_ok(result)
129 assert "SUCCESS" in result.output
130 assert "yosys -p" not in result.output
132 # -- Modify apio.ini
133 apio_ini_lines = cast(
134 list[str], sb.read_file(sb.proj_dir / "apio.ini", lines_mode=True)
135 )
136 apio_ini_lines.append(" ")
137 sb.write_file(sb.proj_dir / "apio.ini", apio_ini_lines, exists_ok=True)
139 # -- 'apio build'
140 # -- Apio.ini modification should triggers a new build.
141 args = ["build"] + proj_arg
142 result = sb.invoke_apio_cmd(apio, args)
143 sb.assert_result_ok(result)
144 assert "SUCCESS" in result.output
145 assert "yosys -p" in result.output
146 assert "-DSYNTHESIZE" in result.output
147 assert "-DAPIO_SIM" not in result.output
149 # -- 'apio lint'
150 args = ["lint"] + proj_arg
151 result = sb.invoke_apio_cmd(apio, args)
152 sb.assert_result_ok(result)
153 assert "SUCCESS" in result.output
154 assert "-DSYNTHESIZE" in result.output
155 assert "-DAPIO_SIM=0" in result.output
156 assert getsize(sb.proj_dir / "_build/default/hardware.vlt")
158 # -- 'apio lint <testbench.v>'
159 args = ["lint", testbench_file] + proj_arg
160 result = sb.invoke_apio_cmd(apio, args)
161 sb.assert_result_ok(result)
162 assert "SUCCESS" in result.output
163 assert "TOP_MODULE" not in result.output
164 assert "-DAPIO_SIM=0" in result.output
166 # -- 'apio lint --nosynth'
167 args = ["lint", "--nosynth"] + proj_arg
168 result = sb.invoke_apio_cmd(apio, args)
169 sb.assert_result_ok(result)
170 assert "SUCCESS" in result.output
171 assert "-DSYNTHESIZE" not in result.output
172 assert "-DAPIO_SIM=0" in result.output
173 assert getsize(sb.proj_dir / "_build/default/hardware.vlt")
175 # -- 'apio format'
176 args = ["format"] + proj_arg
177 result = sb.invoke_apio_cmd(apio, args)
178 sb.assert_result_ok(result)
179 assert "-DSYNTHESIZE" not in result.output
180 assert "-DAPIO_SIM" not in result.output
182 # -- 'apio format <testbench-file>'
183 # -- This tests the project relative specification even when
184 # -- the option --project-dir is used.
185 args = ["format", testbench_file] + proj_arg
186 result = sb.invoke_apio_cmd(apio, args)
187 sb.assert_result_ok(result)
189 # -- 'apio test'
190 args = ["test"] + proj_arg
191 result = sb.invoke_apio_cmd(apio, args)
192 sb.assert_result_ok(result)
193 assert "SUCCESS" in result.output
194 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
195 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
196 # -- For issue https://github.com/FPGAwars/apio/issues/557
197 assert "warning: Timing checks are not supported" not in result.output
198 assert "-DSYNTHESIZE" not in result.output
199 assert "-DAPIO_SIM=0" in result.output
201 # -- 'apio clean'
202 args = ["clean"] + proj_arg
203 result = sb.invoke_apio_cmd(apio, args)
204 sb.assert_result_ok(result)
205 assert "Cleanup completed" in result.output
206 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
207 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
209 # -- 'apio test --default'
210 args = ["test", "--default"] + proj_arg
211 result = sb.invoke_apio_cmd(apio, args)
212 sb.assert_result_ok(result)
213 assert "SUCCESS" in result.output
214 assert "-DAPIO_SIM=0" in result.output
215 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
216 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
218 # -- 'apio clean'
219 args = ["clean"] + proj_arg
220 result = sb.invoke_apio_cmd(apio, args)
221 sb.assert_result_ok(result)
222 assert "Cleanup completed" in result.output
223 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
224 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
226 # -- 'apio sim --no-gtkwave'
227 args = ["sim", "--no-gtkwave"] + proj_arg
228 result = sb.invoke_apio_cmd(apio, args)
229 sb.assert_result_ok(result)
230 assert "SUCCESS" in result.output
231 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
232 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
233 # -- For issue https://github.com/FPGAwars/apio/issues/557
234 assert "warning: Timing checks are not supported" not in result.output
235 assert "-DSYNTHESIZE" not in result.output
236 assert "-DAPIO_SIM=1" in result.output
238 # -- 'apio clean'
239 args = ["clean"] + proj_arg
240 result = sb.invoke_apio_cmd(apio, args)
241 sb.assert_result_ok(result)
242 assert "Cleanup completed" in result.output
243 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
244 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
246 # -- 'apio test <testbench-file>'
247 args = ["test", testbench_file] + proj_arg
248 result = sb.invoke_apio_cmd(apio, args)
249 sb.assert_result_ok(result)
250 assert "SUCCESS" in result.output
251 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
252 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
253 # -- For issue https://github.com/FPGAwars/apio/issues/557
254 assert "warning: Timing checks are not supported" not in result.output
256 # -- 'apio sim --no-gtkw <testbench-file>'
257 args = ["sim", "--no-gtkwave", testbench_file] + proj_arg
258 result = sb.invoke_apio_cmd(apio, args)
259 sb.assert_result_ok(result)
260 assert "SUCCESS" in result.output
261 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
262 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
263 # -- For issue https://github.com/FPGAwars/apio/issues/557
264 assert "warning: Timing checks are not supported" not in result.output
266 # -- 'apio clean'
267 args = ["clean"] + proj_arg
268 result = sb.invoke_apio_cmd(apio, args)
269 sb.assert_result_ok(result)
270 assert "Cleanup completed" in result.output
271 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
272 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
274 # -- 'apio report'
275 args = ["report"] + 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 report_item in result.output
282 assert "─────┐" in result.output # Graphical table border
283 assert getsize(sb.proj_dir / "_build/default/hardware.pnr")
285 # -- 'apio graph -n'
286 args = ["graph", "-n"] + proj_arg
287 result = sb.invoke_apio_cmd(apio, args)
288 sb.assert_result_ok(result)
289 assert "SUCCESS" in result.output
290 assert "-DSYNTHESIZE" in result.output
291 assert "-DAPIO_SIM" not in result.output
292 assert getsize(sb.proj_dir / "_build/default/graph.dot")
293 assert getsize(sb.proj_dir / "_build/default/graph.svg")
295 # -- 'apio clean'
296 assert Path(sb.proj_dir / "_build/default").exists()
297 args = ["clean"] + proj_arg
298 result = sb.invoke_apio_cmd(apio, args)
299 sb.assert_result_ok(result)
300 assert "Cleanup completed" in result.output
301 assert not Path(sb.proj_dir / "_build/default").exists()
303 # -- Here we should have only the original project files.
304 assert set(os.listdir(sb.proj_dir)) == set(project_files)
307# NOTE: We use the alhambra-ii/bcd-counter example because it uses
308# subdirectories. This increases the test coverage.
309def test_project_ice40_local_dir(apio_runner: ApioRunner):
310 """Tests building and testing an ice40 project as the current working
311 dir."""
312 _test_project(
313 apio_runner,
314 remote_proj_dir=False,
315 example="alhambra-ii/bcd-counter",
316 testbench_file="main_tb.v",
317 bitstream="hardware.bin",
318 report_item="ICESTORM_LC",
319 )
322def test_project_ice40_remote_dir(apio_runner: ApioRunner):
323 """Tests building and testing an ice40 project from a remote dir, using
324 the -p option."""
325 _test_project(
326 apio_runner,
327 remote_proj_dir=True,
328 example="alhambra-ii/bcd-counter",
329 testbench_file="main_tb.v",
330 bitstream="hardware.bin",
331 report_item="ICESTORM_LC",
332 )
335def test_project_ice40_system_verilog(apio_runner: ApioRunner):
336 """Tests building and testing an ice40 project that contains system
337 verilog files."""
338 _test_project(
339 apio_runner,
340 remote_proj_dir=False,
341 example="alhambra-ii/bcd-counter-sv",
342 testbench_file="main_tb.sv",
343 bitstream="hardware.bin",
344 report_item="ICESTORM_LC",
345 )
348def test_project_ecp5_local_dir(apio_runner: ApioRunner):
349 """Tests building and testing an ecp5 project as the current working dir"""
350 _test_project(
351 apio_runner,
352 remote_proj_dir=False,
353 example="colorlight-5a-75b-v8/ledon",
354 testbench_file="ledon_tb.v",
355 bitstream="hardware.bit",
356 report_item="ALU54B",
357 )
360def test_project_ecp5_remote_dir(apio_runner: ApioRunner):
361 """Tests building and testing an ecp5 project from a remote directory."""
362 _test_project(
363 apio_runner,
364 remote_proj_dir=True,
365 example="colorlight-5a-75b-v8/ledon",
366 testbench_file="ledon_tb.v",
367 bitstream="hardware.bit",
368 report_item="ALU54B",
369 )
372def test_project_ecp5_system_verilog(apio_runner: ApioRunner):
373 """Tests building and testing an ecp5 project that contains system
374 verilog files."""
375 _test_project(
376 apio_runner,
377 remote_proj_dir=False,
378 example="colorlight-5a-75b-v8/ledon-sv",
379 testbench_file="ledon_tb.sv",
380 bitstream="hardware.bit",
381 report_item="ALU54B",
382 )
385def test_project_gowin_local_dir(apio_runner: ApioRunner):
386 """Tests building and testing a gowin project as the current working dir"""
387 _test_project(
388 apio_runner,
389 remote_proj_dir=False,
390 example="sipeed-tang-nano-9k/blinky",
391 testbench_file="blinky_tb.v",
392 bitstream="hardware.fs",
393 report_item="LUT4",
394 )
397def test_project_gowin_remote_dir(apio_runner: ApioRunner):
398 """Tests building and testing a gowin project from a remote directory."""
399 _test_project(
400 apio_runner,
401 remote_proj_dir=True,
402 example="sipeed-tang-nano-9k/blinky",
403 testbench_file="blinky_tb.v",
404 bitstream="hardware.fs",
405 report_item="LUT4",
406 )
409def test_project_gowin_system_verilog(apio_runner: ApioRunner):
410 """Tests building and testing an gowin project that contains system
411 verilog files."""
412 _test_project(
413 apio_runner,
414 remote_proj_dir=False,
415 example="sipeed-tang-nano-9k/blinky-sv",
416 testbench_file="blinky_tb.sv",
417 bitstream="hardware.fs",
418 report_item="LUT4",
419 )
422@pytest.mark.skipif(is_not_linux, reason=ONLY_LINUX_MSG)
423def test_project_xilinx_local_dir(apio_runner: ApioRunner):
424 """Tests building and testing a Xilinx project as the current working
425 dir."""
426 _test_project(
427 apio_runner,
428 remote_proj_dir=False,
429 example="basys3/ledon",
430 testbench_file="ledon_tb.v",
431 bitstream="hardware.bit",
432 report_item="PSEUDO_GND",
433 )
436@pytest.mark.skipif(is_not_linux, reason=ONLY_LINUX_MSG)
437def test_project_xilinx_remote_dir(apio_runner: ApioRunner):
438 """Tests building and testing an ice40 project from a remote dir, using
439 the -p option."""
440 _test_project(
441 apio_runner,
442 remote_proj_dir=True,
443 example="basys3/ledon",
444 testbench_file="ledon_tb.v",
445 bitstream="hardware.bit",
446 report_item="PSEUDO_GND",
447 )