Coverage for tests/integration_tests/test_projects.py: 100%
196 statements
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-07 05:08 +0000
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-07 05:08 +0000
1# This file is part of the Apio project
2# (C) 2016-present FPGAwars
3# Project authors:
4# * Jesús Arroyo (Jesus89) (2016-2019)
5# * Juan Gonzalez (obijuan) (2019-2024)
6# * The Apio Team (2024-present)
7# License: GPLv2
9"""
10Test various "apio" commands.
11"""
13import os
14from os.path import getsize
15from pathlib import Path
16from typing import cast
17from tests.conftest import ApioRunner
18from apio.commands.apio import apio_top_cli as apio
21def _test_project(
22 apio_runner: ApioRunner,
23 *,
24 arch: str,
25 remote_proj_dir: bool,
26 example: str,
27 testbench_file: str,
28 bitstream: str,
29 report_item: str,
30):
31 """A common project integration test. Invoked per each tested
32 architecture.
33 """
35 # pylint: disable=too-many-arguments
36 # pylint: disable=too-many-statements
37 # pylint: disable=too-many-locals
39 # -- Extract the base name of the testbench file
40 testbench, _ = os.path.splitext(testbench_file)
42 # -- We shared the apio home with the other tests in this file to speed
43 # -- up apio package installation. Tests should not mutate the shared home
44 # -- to avoid cross-interference between tests in this file.
45 with apio_runner.in_sandbox() as sb:
47 # -- If testing from a remote dir, step out of the proj dir, and
48 # -- use -p proj_dir arg.
49 if remote_proj_dir:
50 os.chdir(sb.sandbox_dir)
51 sb.proj_dir.rmdir()
52 proj_arg = ["-p", str(sb.proj_dir)]
53 dst_arg = ["-d", str(sb.proj_dir)]
54 else:
55 proj_arg = []
56 dst_arg = []
58 # -- If testing from a remote dir, the proj dir should not exist yet,
59 # -- else, the project dir should be empty.
60 if remote_proj_dir:
61 assert not sb.proj_dir.exists()
62 else:
63 assert not os.listdir(sb.proj_dir)
65 # -- 'apio examples fetch <example> -d <proj_dir>'
66 args = ["examples", "fetch", example] + dst_arg
67 result = sb.invoke_apio_cmd(apio, args)
68 sb.assert_result_ok(result)
69 assert f"Copying {example} example files" in result.output
70 assert "fetched successfully" in result.output
71 assert getsize(sb.proj_dir / "apio.ini")
73 # -- 'apio packages list'.
74 # -- We perform it after the 'apio examples fetch' above to make sure
75 # -- it installed the packages if running in a clean environment.
76 args = ["packages", "list"]
77 result = sb.invoke_apio_cmd(apio, args)
78 sb.assert_result_ok(result)
79 print(result.output)
81 # -- Remember the original list of project files.
82 project_files = os.listdir(sb.proj_dir)
84 # -- 'apio build'
85 args = ["build"] + proj_arg
86 result = sb.invoke_apio_cmd(apio, args)
87 sb.assert_result_ok(result)
88 assert "SUCCESS" in result.output
89 assert "yosys -p" in result.output
90 assert "-DSYNTHESIZE" in result.output
91 assert "-DAPIO_SIM" not in result.output
93 assert getsize(sb.proj_dir / "_build/default" / bitstream)
95 # -- 'apio build' (no change, yosys is not invoked)
96 args = ["build"] + proj_arg
97 result = sb.invoke_apio_cmd(apio, args)
98 sb.assert_result_ok(result)
99 assert "SUCCESS" in result.output
100 assert "yosys -p" not in result.output
102 # -- Modify apio.ini
103 apio_ini_lines = cast(
104 list[str], sb.read_file_lines(sb.proj_dir / "apio.ini")
105 )
106 apio_ini_lines.append(" ")
107 sb.write_file(sb.proj_dir / "apio.ini", apio_ini_lines, exists_ok=True)
109 # -- 'apio build'
110 # -- Apio.ini modification should triggers a new build.
111 args = ["build"] + proj_arg
112 result = sb.invoke_apio_cmd(apio, args)
113 sb.assert_result_ok(result)
114 assert "SUCCESS" in result.output
115 assert "yosys -p" in result.output
116 assert "-DSYNTHESIZE" in result.output
117 assert "-DAPIO_SIM" not in result.output
119 # -- 'apio api get-build-report -o _build_report.json'
120 args = ["api", "get-build-report", "-o", "_build_report.json"]
121 result = sb.invoke_apio_cmd(apio, args)
122 sb.assert_result_ok(result)
123 build_report = sb.read_json_file("_build_report.json")
124 assert build_report["build-report"]["arch"] == arch
125 Path("_build_report.json").unlink()
127 # -- 'apio lint'
128 args = ["lint"] + proj_arg
129 result = sb.invoke_apio_cmd(apio, args)
130 sb.assert_result_ok(result)
131 assert "SUCCESS" in result.output
132 assert "-DSYNTHESIZE" in result.output
133 assert "-DAPIO_SIM=0" in result.output
134 assert getsize(sb.proj_dir / "_build/default/hardware.vlt")
136 # -- 'apio lint <testbench.v>'
137 args = ["lint", testbench_file] + proj_arg
138 result = sb.invoke_apio_cmd(apio, args)
139 sb.assert_result_ok(result)
140 assert "SUCCESS" in result.output
141 assert "TOP_MODULE" not in result.output
142 assert "-DAPIO_SIM=0" in result.output
144 # -- 'apio lint --nosynth'
145 args = ["lint", "--nosynth"] + proj_arg
146 result = sb.invoke_apio_cmd(apio, args)
147 sb.assert_result_ok(result)
148 assert "SUCCESS" in result.output
149 assert "-DSYNTHESIZE" not in result.output
150 assert "-DAPIO_SIM=0" in result.output
151 assert getsize(sb.proj_dir / "_build/default/hardware.vlt")
153 # -- 'apio format'
154 args = ["format"] + proj_arg
155 result = sb.invoke_apio_cmd(apio, args)
156 sb.assert_result_ok(result)
157 assert "-DSYNTHESIZE" not in result.output
158 assert "-DAPIO_SIM" not in result.output
160 # -- 'apio format <testbench-file>'
161 # -- This tests the project relative specification even when
162 # -- the option --project-dir is used.
163 args = ["format", testbench_file] + proj_arg
164 result = sb.invoke_apio_cmd(apio, args)
165 sb.assert_result_ok(result)
167 # -- 'apio test'
168 args = ["test"] + proj_arg
169 result = sb.invoke_apio_cmd(apio, args)
170 sb.assert_result_ok(result)
171 assert "SUCCESS" in result.output
172 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
173 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
174 # -- For issue https://github.com/FPGAwars/apio/issues/557
175 assert "warning: Timing checks are not supported" not in result.output
176 assert "-DSYNTHESIZE" not in result.output
177 assert "-DAPIO_SIM=0" in result.output
179 # -- 'apio clean'
180 args = ["clean"] + proj_arg
181 result = sb.invoke_apio_cmd(apio, args)
182 sb.assert_result_ok(result)
183 assert "Cleanup completed" in result.output
184 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
185 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
187 # -- 'apio test --default'
188 args = ["test", "--default"] + proj_arg
189 result = sb.invoke_apio_cmd(apio, args)
190 sb.assert_result_ok(result)
191 assert "SUCCESS" in result.output
192 assert "-DAPIO_SIM=0" in result.output
193 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
194 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
196 # -- 'apio clean'
197 args = ["clean"] + proj_arg
198 result = sb.invoke_apio_cmd(apio, args)
199 sb.assert_result_ok(result)
200 assert "Cleanup completed" in result.output
201 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
202 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
204 # -- 'apio sim --no-gtkwave'
205 args = ["sim", "--no-gtkwave"] + proj_arg
206 result = sb.invoke_apio_cmd(apio, args)
207 sb.assert_result_ok(result)
208 assert "SUCCESS" in result.output
209 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
210 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
211 # -- For issue https://github.com/FPGAwars/apio/issues/557
212 assert "warning: Timing checks are not supported" not in result.output
213 assert "-DSYNTHESIZE" not in result.output
214 assert "-DAPIO_SIM=1" in result.output
216 # -- 'apio clean'
217 args = ["clean"] + proj_arg
218 result = sb.invoke_apio_cmd(apio, args)
219 sb.assert_result_ok(result)
220 assert "Cleanup completed" in result.output
221 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
222 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
224 # -- 'apio test <testbench-file>'
225 args = ["test", testbench_file] + proj_arg
226 result = sb.invoke_apio_cmd(apio, args)
227 sb.assert_result_ok(result)
228 assert "SUCCESS" in result.output
229 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
230 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
231 # -- For issue https://github.com/FPGAwars/apio/issues/557
232 assert "warning: Timing checks are not supported" not in result.output
234 # -- 'apio sim --no-gtkw <testbench-file>'
235 args = ["sim", "--no-gtkwave", testbench_file] + proj_arg
236 result = sb.invoke_apio_cmd(apio, args)
237 sb.assert_result_ok(result)
238 assert "SUCCESS" in result.output
239 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
240 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
241 # -- For issue https://github.com/FPGAwars/apio/issues/557
242 assert "warning: Timing checks are not supported" not in result.output
244 # -- 'apio clean'
245 args = ["clean"] + proj_arg
246 result = sb.invoke_apio_cmd(apio, args)
247 sb.assert_result_ok(result)
248 assert "Cleanup completed" in result.output
249 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
250 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
252 # -- 'apio report'
253 args = ["report"] + proj_arg
254 result = sb.invoke_apio_cmd(apio, args)
255 sb.assert_result_ok(result)
256 assert "SUCCESS" in result.output
257 assert "-DSYNTHESIZE" in result.output
258 assert "-DAPIO_SIM" not in result.output
259 assert report_item in result.output
260 assert "─────┐" in result.output # Graphical table border
261 assert getsize(sb.proj_dir / "_build/default/hardware.pnr")
263 # -- 'apio graph -n'
264 args = ["graph", "-n"] + 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 getsize(sb.proj_dir / "_build/default/graph.dot")
271 assert getsize(sb.proj_dir / "_build/default/graph.svg")
273 # -- 'apio clean'
274 assert Path(sb.proj_dir / "_build/default").exists()
275 args = ["clean"] + proj_arg
276 result = sb.invoke_apio_cmd(apio, args)
277 sb.assert_result_ok(result)
278 assert "Cleanup completed" in result.output
279 assert not Path(sb.proj_dir / "_build/default").exists()
281 # -- Here we should have only the original project files.
282 assert set(os.listdir(sb.proj_dir)) == set(project_files)
285# NOTE: We use the alhambra-ii/bcd-counter example because it uses
286# subdirectories. This increases the test coverage.
287def test_project_ice40_local_dir(apio_runner: ApioRunner):
288 """Tests building and testing an ice40 project as the current working
289 dir."""
290 _test_project(
291 apio_runner,
292 arch="ice40",
293 remote_proj_dir=False,
294 example="alhambra-ii/bcd-counter",
295 testbench_file="main_tb.v",
296 bitstream="hardware.bin",
297 report_item="ICESTORM_LC",
298 )
301def test_project_ice40_remote_dir(apio_runner: ApioRunner):
302 """Tests building and testing an ice40 project from a remote dir, using
303 the -p option."""
304 _test_project(
305 apio_runner,
306 arch="ice40",
307 remote_proj_dir=True,
308 example="alhambra-ii/bcd-counter",
309 testbench_file="main_tb.v",
310 bitstream="hardware.bin",
311 report_item="ICESTORM_LC",
312 )
315def test_project_ice40_system_verilog(apio_runner: ApioRunner):
316 """Tests building and testing an ice40 project that contains system
317 verilog files."""
318 _test_project(
319 apio_runner,
320 arch="ice40",
321 remote_proj_dir=False,
322 example="alhambra-ii/bcd-counter-sv",
323 testbench_file="main_tb.sv",
324 bitstream="hardware.bin",
325 report_item="ICESTORM_LC",
326 )
329def test_project_ecp5_local_dir(apio_runner: ApioRunner):
330 """Tests building and testing an ecp5 project as the current working dir"""
331 _test_project(
332 apio_runner,
333 arch="ecp5",
334 remote_proj_dir=False,
335 example="colorlight-5a-75b-v8/ledon",
336 testbench_file="main_tb.v",
337 bitstream="hardware.bit",
338 report_item="TRELLIS_IO",
339 )
342def test_project_ecp5_remote_dir(apio_runner: ApioRunner):
343 """Tests building and testing an ecp5 project from a remote directory."""
344 _test_project(
345 apio_runner,
346 arch="ecp5",
347 remote_proj_dir=True,
348 example="colorlight-5a-75b-v8/ledon",
349 testbench_file="main_tb.v",
350 bitstream="hardware.bit",
351 report_item="TRELLIS_IO",
352 )
355def test_project_ecp5_system_verilog(apio_runner: ApioRunner):
356 """Tests building and testing an ecp5 project that contains system
357 verilog files."""
358 _test_project(
359 apio_runner,
360 arch="ecp5",
361 remote_proj_dir=False,
362 example="colorlight-5a-75b-v8/ledon-sv",
363 testbench_file="main_tb.sv",
364 bitstream="hardware.bit",
365 report_item="TRELLIS_IO",
366 )
369def test_project_gowin_local_dir(apio_runner: ApioRunner):
370 """Tests building and testing a gowin project as the current working dir"""
371 _test_project(
372 apio_runner,
373 arch="gowin",
374 remote_proj_dir=False,
375 example="sipeed-tang-nano-9k/blinky",
376 testbench_file="main_tb.v",
377 bitstream="hardware.fs",
378 report_item="LUT4",
379 )
382def test_project_gowin_remote_dir(apio_runner: ApioRunner):
383 """Tests building and testing a gowin project from a remote directory."""
384 _test_project(
385 apio_runner,
386 arch="gowin",
387 remote_proj_dir=True,
388 example="sipeed-tang-nano-9k/blinky",
389 testbench_file="main_tb.v",
390 bitstream="hardware.fs",
391 report_item="LUT4",
392 )
395def test_project_gowin_system_verilog(apio_runner: ApioRunner):
396 """Tests building and testing an gowin project that contains system
397 verilog files."""
398 _test_project(
399 apio_runner,
400 arch="gowin",
401 remote_proj_dir=False,
402 example="sipeed-tang-nano-9k/blinky-sv",
403 testbench_file="main_tb.sv",
404 bitstream="hardware.fs",
405 report_item="LUT4",
406 )
409def test_project_xilinx_local_dir(apio_runner: ApioRunner):
410 """Tests building and testing a Xilinx project as the current working
411 dir."""
412 _test_project(
413 apio_runner,
414 arch="xilinx",
415 remote_proj_dir=False,
416 example="basys3/ledon",
417 testbench_file="main_tb.v",
418 bitstream="hardware.bit",
419 report_item="PSEUDO_GND",
420 )
423def test_project_xilinx_remote_dir(apio_runner: ApioRunner):
424 """Tests building and testing an ice40 project from a remote dir, using
425 the -p option."""
426 _test_project(
427 apio_runner,
428 arch="xilinx",
429 remote_proj_dir=True,
430 example="basys3/ledon",
431 testbench_file="main_tb.v",
432 bitstream="hardware.bit",
433 report_item="PSEUDO_GND",
434 )