Coverage for tests/integration_tests/test_projects.py: 100%
190 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-23 03:53 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-23 03:53 +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(
14 apio_runner: ApioRunner,
15 *,
16 remote_proj_dir: bool,
17 example: str,
18 testbench_file: str,
19 bitstream: str,
20 report_item: str,
21):
22 """A common project integration test. Invoked per each tested
23 architecture.
24 """
26 # pylint: disable=too-many-arguments
27 # pylint: disable=too-many-statements
29 # -- Extract the base name of the testbench file
30 testbench, _ = os.path.splitext(testbench_file)
32 # -- We shared the apio home with the other tests in this file to speed
33 # -- up apio package installation. Tests should not mutate the shared home
34 # -- to avoid cross-interference between tests in this file.
35 with apio_runner.in_sandbox() as sb:
37 # -- If testing from a remote dir, step out of the proj dir, and
38 # -- use -p proj_dir arg.
39 if remote_proj_dir:
40 os.chdir(sb.sandbox_dir)
41 sb.proj_dir.rmdir()
42 proj_arg = ["-p", str(sb.proj_dir)]
43 dst_arg = ["-d", str(sb.proj_dir)]
44 else:
45 proj_arg = []
46 dst_arg = []
48 # -- If testing from a remote dir, the proj dir should not exist yet,
49 # -- else, the project dir should be empty.
50 if remote_proj_dir:
51 assert not sb.proj_dir.exists()
52 else:
53 assert not os.listdir(sb.proj_dir)
55 # -- 'apio examples fetch <example> -d <proj_dir>'
56 args = ["examples", "fetch", example] + dst_arg
57 result = sb.invoke_apio_cmd(apio, args)
58 sb.assert_result_ok(result)
59 assert f"Copying {example} example files" in result.output
60 assert "fetched successfully" in result.output
61 assert getsize(sb.proj_dir / "apio.ini")
63 # -- 'apio packages list'.
64 # -- We perform it after the 'apio examples fetch' above to make sure
65 # -- it installed the packages if running in a clean environment.
66 args = ["packages", "list"]
67 result = sb.invoke_apio_cmd(apio, args)
68 sb.assert_result_ok(result)
69 print(result.output)
71 # -- Remember the original list of project files.
72 project_files = os.listdir(sb.proj_dir)
74 # -- 'apio build'
75 args = ["build"] + proj_arg
76 result = sb.invoke_apio_cmd(apio, args)
77 sb.assert_result_ok(result)
78 assert "SUCCESS" in result.output
79 assert "yosys -p" in result.output
80 assert "-DSYNTHESIZE" in result.output
81 assert "-DAPIO_SIM" not in result.output
83 assert getsize(sb.proj_dir / "_build/default" / bitstream)
85 # -- 'apio build' (no change, yosys is not invoked)
86 args = ["build"] + proj_arg
87 result = sb.invoke_apio_cmd(apio, args)
88 sb.assert_result_ok(result)
89 assert "SUCCESS" in result.output
90 assert "yosys -p" not in result.output
92 # -- Modify apio.ini
93 apio_ini_lines = cast(
94 list[str], sb.read_file_lines(sb.proj_dir / "apio.ini")
95 )
96 apio_ini_lines.append(" ")
97 sb.write_file(sb.proj_dir / "apio.ini", apio_ini_lines, exists_ok=True)
99 # -- 'apio build'
100 # -- Apio.ini modification should triggers a new build.
101 args = ["build"] + proj_arg
102 result = sb.invoke_apio_cmd(apio, args)
103 sb.assert_result_ok(result)
104 assert "SUCCESS" in result.output
105 assert "yosys -p" in result.output
106 assert "-DSYNTHESIZE" in result.output
107 assert "-DAPIO_SIM" not in result.output
109 # -- 'apio lint'
110 args = ["lint"] + proj_arg
111 result = sb.invoke_apio_cmd(apio, args)
112 sb.assert_result_ok(result)
113 assert "SUCCESS" in result.output
114 assert "-DSYNTHESIZE" in result.output
115 assert "-DAPIO_SIM=0" in result.output
116 assert getsize(sb.proj_dir / "_build/default/hardware.vlt")
118 # -- 'apio lint <testbench.v>'
119 args = ["lint", testbench_file] + proj_arg
120 result = sb.invoke_apio_cmd(apio, args)
121 sb.assert_result_ok(result)
122 assert "SUCCESS" in result.output
123 assert "TOP_MODULE" not in result.output
124 assert "-DAPIO_SIM=0" in result.output
126 # -- 'apio lint --nosynth'
127 args = ["lint", "--nosynth"] + proj_arg
128 result = sb.invoke_apio_cmd(apio, args)
129 sb.assert_result_ok(result)
130 assert "SUCCESS" in result.output
131 assert "-DSYNTHESIZE" not in result.output
132 assert "-DAPIO_SIM=0" in result.output
133 assert getsize(sb.proj_dir / "_build/default/hardware.vlt")
135 # -- 'apio format'
136 args = ["format"] + proj_arg
137 result = sb.invoke_apio_cmd(apio, args)
138 sb.assert_result_ok(result)
139 assert "-DSYNTHESIZE" not in result.output
140 assert "-DAPIO_SIM" not in result.output
142 # -- 'apio format <testbench-file>'
143 # -- This tests the project relative specification even when
144 # -- the option --project-dir is used.
145 args = ["format", testbench_file] + proj_arg
146 result = sb.invoke_apio_cmd(apio, args)
147 sb.assert_result_ok(result)
149 # -- 'apio test'
150 args = ["test"] + proj_arg
151 result = sb.invoke_apio_cmd(apio, args)
152 sb.assert_result_ok(result)
153 assert "SUCCESS" in result.output
154 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
155 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
156 # -- For issue https://github.com/FPGAwars/apio/issues/557
157 assert "warning: Timing checks are not supported" not in result.output
158 assert "-DSYNTHESIZE" not in result.output
159 assert "-DAPIO_SIM=0" in result.output
161 # -- 'apio clean'
162 args = ["clean"] + proj_arg
163 result = sb.invoke_apio_cmd(apio, args)
164 sb.assert_result_ok(result)
165 assert "Cleanup completed" in result.output
166 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
167 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
169 # -- 'apio test --default'
170 args = ["test", "--default"] + proj_arg
171 result = sb.invoke_apio_cmd(apio, args)
172 sb.assert_result_ok(result)
173 assert "SUCCESS" in result.output
174 assert "-DAPIO_SIM=0" in result.output
175 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
176 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
178 # -- 'apio clean'
179 args = ["clean"] + proj_arg
180 result = sb.invoke_apio_cmd(apio, args)
181 sb.assert_result_ok(result)
182 assert "Cleanup completed" in result.output
183 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
184 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
186 # -- 'apio sim --no-gtkwave'
187 args = ["sim", "--no-gtkwave"] + proj_arg
188 result = sb.invoke_apio_cmd(apio, args)
189 sb.assert_result_ok(result)
190 assert "SUCCESS" in result.output
191 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
192 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
193 # -- For issue https://github.com/FPGAwars/apio/issues/557
194 assert "warning: Timing checks are not supported" not in result.output
195 assert "-DSYNTHESIZE" not in result.output
196 assert "-DAPIO_SIM=1" in result.output
198 # -- 'apio clean'
199 args = ["clean"] + proj_arg
200 result = sb.invoke_apio_cmd(apio, args)
201 sb.assert_result_ok(result)
202 assert "Cleanup completed" in result.output
203 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
204 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
206 # -- 'apio test <testbench-file>'
207 args = ["test", testbench_file] + proj_arg
208 result = sb.invoke_apio_cmd(apio, args)
209 sb.assert_result_ok(result)
210 assert "SUCCESS" in result.output
211 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
212 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
213 # -- For issue https://github.com/FPGAwars/apio/issues/557
214 assert "warning: Timing checks are not supported" not in result.output
216 # -- 'apio sim --no-gtkw <testbench-file>'
217 args = ["sim", "--no-gtkwave", testbench_file] + proj_arg
218 result = sb.invoke_apio_cmd(apio, args)
219 sb.assert_result_ok(result)
220 assert "SUCCESS" in result.output
221 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
222 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
223 # -- For issue https://github.com/FPGAwars/apio/issues/557
224 assert "warning: Timing checks are not supported" not in result.output
226 # -- 'apio clean'
227 args = ["clean"] + proj_arg
228 result = sb.invoke_apio_cmd(apio, args)
229 sb.assert_result_ok(result)
230 assert "Cleanup completed" in result.output
231 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
232 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
234 # -- 'apio report'
235 args = ["report"] + proj_arg
236 result = sb.invoke_apio_cmd(apio, args)
237 sb.assert_result_ok(result)
238 assert "SUCCESS" in result.output
239 assert "-DSYNTHESIZE" in result.output
240 assert "-DAPIO_SIM" not in result.output
241 assert report_item in result.output
242 assert "─────┐" in result.output # Graphical table border
243 assert getsize(sb.proj_dir / "_build/default/hardware.pnr")
245 # -- 'apio graph -n'
246 args = ["graph", "-n"] + proj_arg
247 result = sb.invoke_apio_cmd(apio, args)
248 sb.assert_result_ok(result)
249 assert "SUCCESS" in result.output
250 assert "-DSYNTHESIZE" in result.output
251 assert "-DAPIO_SIM" not in result.output
252 assert getsize(sb.proj_dir / "_build/default/graph.dot")
253 assert getsize(sb.proj_dir / "_build/default/graph.svg")
255 # -- 'apio clean'
256 assert Path(sb.proj_dir / "_build/default").exists()
257 args = ["clean"] + proj_arg
258 result = sb.invoke_apio_cmd(apio, args)
259 sb.assert_result_ok(result)
260 assert "Cleanup completed" in result.output
261 assert not Path(sb.proj_dir / "_build/default").exists()
263 # -- Here we should have only the original project files.
264 assert set(os.listdir(sb.proj_dir)) == set(project_files)
267# NOTE: We use the alhambra-ii/bcd-counter example because it uses
268# subdirectories. This increases the test coverage.
269def test_project_ice40_local_dir(apio_runner: ApioRunner):
270 """Tests building and testing an ice40 project as the current working
271 dir."""
272 _test_project(
273 apio_runner,
274 remote_proj_dir=False,
275 example="alhambra-ii/bcd-counter",
276 testbench_file="main_tb.v",
277 bitstream="hardware.bin",
278 report_item="ICESTORM_LC",
279 )
282def test_project_ice40_remote_dir(apio_runner: ApioRunner):
283 """Tests building and testing an ice40 project from a remote dir, using
284 the -p option."""
285 _test_project(
286 apio_runner,
287 remote_proj_dir=True,
288 example="alhambra-ii/bcd-counter",
289 testbench_file="main_tb.v",
290 bitstream="hardware.bin",
291 report_item="ICESTORM_LC",
292 )
295def test_project_ice40_system_verilog(apio_runner: ApioRunner):
296 """Tests building and testing an ice40 project that contains system
297 verilog files."""
298 _test_project(
299 apio_runner,
300 remote_proj_dir=False,
301 example="alhambra-ii/bcd-counter-sv",
302 testbench_file="main_tb.sv",
303 bitstream="hardware.bin",
304 report_item="ICESTORM_LC",
305 )
308def test_project_ecp5_local_dir(apio_runner: ApioRunner):
309 """Tests building and testing an ecp5 project as the current working dir"""
310 _test_project(
311 apio_runner,
312 remote_proj_dir=False,
313 example="colorlight-5a-75b-v8/ledon",
314 testbench_file="ledon_tb.v",
315 bitstream="hardware.bit",
316 report_item="TRELLIS_IO",
317 )
320def test_project_ecp5_remote_dir(apio_runner: ApioRunner):
321 """Tests building and testing an ecp5 project from a remote directory."""
322 _test_project(
323 apio_runner,
324 remote_proj_dir=True,
325 example="colorlight-5a-75b-v8/ledon",
326 testbench_file="ledon_tb.v",
327 bitstream="hardware.bit",
328 report_item="TRELLIS_IO",
329 )
332def test_project_ecp5_system_verilog(apio_runner: ApioRunner):
333 """Tests building and testing an ecp5 project that contains system
334 verilog files."""
335 _test_project(
336 apio_runner,
337 remote_proj_dir=False,
338 example="colorlight-5a-75b-v8/ledon-sv",
339 testbench_file="ledon_tb.sv",
340 bitstream="hardware.bit",
341 report_item="TRELLIS_IO",
342 )
345def test_project_gowin_local_dir(apio_runner: ApioRunner):
346 """Tests building and testing a gowin project as the current working dir"""
347 _test_project(
348 apio_runner,
349 remote_proj_dir=False,
350 example="sipeed-tang-nano-9k/blinky",
351 testbench_file="blinky_tb.v",
352 bitstream="hardware.fs",
353 report_item="LUT4",
354 )
357def test_project_gowin_remote_dir(apio_runner: ApioRunner):
358 """Tests building and testing a gowin project from a remote directory."""
359 _test_project(
360 apio_runner,
361 remote_proj_dir=True,
362 example="sipeed-tang-nano-9k/blinky",
363 testbench_file="blinky_tb.v",
364 bitstream="hardware.fs",
365 report_item="LUT4",
366 )
369def test_project_gowin_system_verilog(apio_runner: ApioRunner):
370 """Tests building and testing an gowin project that contains system
371 verilog files."""
372 _test_project(
373 apio_runner,
374 remote_proj_dir=False,
375 example="sipeed-tang-nano-9k/blinky-sv",
376 testbench_file="blinky_tb.sv",
377 bitstream="hardware.fs",
378 report_item="LUT4",
379 )
382def test_project_xilinx_local_dir(apio_runner: ApioRunner):
383 """Tests building and testing a Xilinx project as the current working
384 dir."""
385 _test_project(
386 apio_runner,
387 remote_proj_dir=False,
388 example="basys3/ledon",
389 testbench_file="ledon_tb.v",
390 bitstream="hardware.bit",
391 report_item="PSEUDO_GND",
392 )
395def test_project_xilinx_remote_dir(apio_runner: ApioRunner):
396 """Tests building and testing an ice40 project from a remote dir, using
397 the -p option."""
398 _test_project(
399 apio_runner,
400 remote_proj_dir=True,
401 example="basys3/ledon",
402 testbench_file="ledon_tb.v",
403 bitstream="hardware.bit",
404 report_item="PSEUDO_GND",
405 )