Coverage for tests / integration_tests / test_projects.py: 100%
192 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-25 02:31 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-25 02:31 +0000
1"""
2Test various "apio" commands.
3"""
5import os
6from os.path import getsize
7from pathlib import Path
8from tests.conftest import ApioRunner
9from apio.commands.apio import apio_top_cli as apio
12def test_project_with_legacy_board_id(apio_runner: ApioRunner):
13 """Test a project that uses a legacy board id."""
15 # -- We shared the apio home with the other tests in this file to speed
16 # -- up apio package installation. Tests should not mutate the shared home
17 # -- to avoid cross-interference between tests in this file.
18 with apio_runner.in_sandbox() as sb:
20 # -- Fetch an example of a board that has a legacy name.
21 result = sb.invoke_apio_cmd(
22 apio, ["examples", "fetch", "ice40-hx8k/leds"]
23 )
24 sb.assert_result_ok(result)
26 # -- Run 'apio build'
27 result = sb.invoke_apio_cmd(apio, ["build"])
28 sb.assert_result_ok(result)
30 # -- Modify the apio.ini to have the legacy board id
31 sb.write_apio_ini(
32 {
33 "[env:default]": {
34 "board": "iCE40-HX8K",
35 "top-module": "leds",
36 }
37 }
38 )
40 # -- Run 'apio clean'
41 result = sb.invoke_apio_cmd(apio, ["clean"])
42 sb.assert_result_ok(result)
44 # -- Run 'apio build' again. It should also succeed.
45 result = sb.invoke_apio_cmd(apio, ["build"])
46 sb.assert_result_ok(result)
49def _test_project(
50 apio_runner: ApioRunner,
51 *,
52 remote_proj_dir: bool,
53 example: str,
54 testbench_file: str,
55 bitstream: str,
56 report_item: str,
57):
58 """A common project integration test. Invoked per each tested
59 architecture.
60 """
62 # pylint: disable=too-many-arguments
63 # pylint: disable=too-many-statements
65 # -- Extract the base name of the testbench file
66 testbench, _ = os.path.splitext(testbench_file)
68 # -- We shared the apio home with the other tests in this file to speed
69 # -- up apio package installation. Tests should not mutate the shared home
70 # -- to avoid cross-interference between tests in this file.
71 with apio_runner.in_sandbox() as sb:
73 # -- If testing from a remote dir, step out of the proj dir, and
74 # -- use -p proj_dir arg.
75 if remote_proj_dir:
76 os.chdir(sb.sandbox_dir)
77 sb.proj_dir.rmdir()
78 proj_arg = ["-p", str(sb.proj_dir)]
79 dst_arg = ["-d", str(sb.proj_dir)]
80 else:
81 proj_arg = []
82 dst_arg = []
84 # -- If testing from a remote dir, the proj dir should not exist yet,
85 # -- else, the project dir should be empty.
86 if remote_proj_dir:
87 assert not sb.proj_dir.exists()
88 else:
89 assert not os.listdir(sb.proj_dir)
91 # -- 'apio examples fetch <example> -d <proj_dir>'
92 args = ["examples", "fetch", example] + dst_arg
93 result = sb.invoke_apio_cmd(apio, args)
94 sb.assert_result_ok(result)
95 assert f"Copying {example} example files" in result.output
96 assert "fetched successfully" in result.output
97 assert getsize(sb.proj_dir / "apio.ini")
99 # -- Remember the original list of project files.
100 project_files = os.listdir(sb.proj_dir)
102 # -- 'apio build'
103 args = ["build"] + proj_arg
104 result = sb.invoke_apio_cmd(apio, args)
105 sb.assert_result_ok(result)
106 assert "SUCCESS" in result.output
107 assert "yosys -p" in result.output
108 assert "-DSYNTHESIZE" in result.output
109 assert "-DAPIO_SIM" not in result.output
111 assert getsize(sb.proj_dir / "_build/default" / bitstream)
113 # -- 'apio build' (no change, yosys is not invoked)
114 args = ["build"] + proj_arg
115 result = sb.invoke_apio_cmd(apio, args)
116 sb.assert_result_ok(result)
117 assert "SUCCESS" in result.output
118 assert "yosys -p" not in result.output
120 # -- Modify apio.ini
121 apio_ini_lines = sb.read_file(
122 sb.proj_dir / "apio.ini", lines_mode=True
123 )
124 apio_ini_lines.append(" ")
125 sb.write_file(sb.proj_dir / "apio.ini", apio_ini_lines, exists_ok=True)
127 # -- 'apio build'
128 # -- Apio.ini modification should triggers a new build.
129 args = ["build"] + proj_arg
130 result = sb.invoke_apio_cmd(apio, args)
131 sb.assert_result_ok(result)
132 assert "SUCCESS" in result.output
133 assert "yosys -p" in result.output
134 assert "-DSYNTHESIZE" in result.output
135 assert "-DAPIO_SIM" not in result.output
137 # -- 'apio lint'
138 args = ["lint"] + proj_arg
139 result = sb.invoke_apio_cmd(apio, args)
140 sb.assert_result_ok(result)
141 assert "SUCCESS" in result.output
142 assert "-DSYNTHESIZE" in result.output
143 assert "-DAPIO_SIM=0" in result.output
144 assert getsize(sb.proj_dir / "_build/default/hardware.vlt")
146 # -- 'apio lint <testbench.v>'
147 args = ["lint", testbench_file] + proj_arg
148 result = sb.invoke_apio_cmd(apio, args)
149 sb.assert_result_ok(result)
150 assert "SUCCESS" in result.output
151 assert "TOP_MODULE" not in result.output
152 assert "-DAPIO_SIM=0" in result.output
154 # -- 'apio lint --nosynth'
155 args = ["lint", "--nosynth"] + proj_arg
156 result = sb.invoke_apio_cmd(apio, args)
157 sb.assert_result_ok(result)
158 assert "SUCCESS" in result.output
159 assert "-DSYNTHESIZE" not in result.output
160 assert "-DAPIO_SIM=0" in result.output
161 assert getsize(sb.proj_dir / "_build/default/hardware.vlt")
163 # -- 'apio format'
164 args = ["format"] + proj_arg
165 result = sb.invoke_apio_cmd(apio, args)
166 sb.assert_result_ok(result)
167 assert "-DSYNTHESIZE" not in result.output
168 assert "-DAPIO_SIM" not in result.output
170 # -- 'apio format <testbench-file>'
171 # -- This tests the project relative specification even when
172 # -- the option --project-dir is used.
173 args = ["format", testbench_file] + proj_arg
174 result = sb.invoke_apio_cmd(apio, args)
175 sb.assert_result_ok(result)
177 # -- 'apio test'
178 args = ["test"] + proj_arg
179 result = sb.invoke_apio_cmd(apio, args)
180 sb.assert_result_ok(result)
181 assert "SUCCESS" in result.output
182 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
183 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
184 # -- For issue https://github.com/FPGAwars/apio/issues/557
185 assert "warning: Timing checks are not supported" not in result.output
186 assert "-DSYNTHESIZE" not in result.output
187 assert "-DAPIO_SIM=0" in result.output
189 # -- 'apio clean'
190 args = ["clean"] + proj_arg
191 result = sb.invoke_apio_cmd(apio, args)
192 sb.assert_result_ok(result)
193 assert "Cleanup completed" in result.output
194 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
195 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
197 # -- 'apio test --default'
198 args = ["test", "--default"] + proj_arg
199 result = sb.invoke_apio_cmd(apio, args)
200 sb.assert_result_ok(result)
201 assert "SUCCESS" in result.output
202 assert "-DAPIO_SIM=0" in result.output
203 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
204 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
206 # -- 'apio clean'
207 args = ["clean"] + proj_arg
208 result = sb.invoke_apio_cmd(apio, args)
209 sb.assert_result_ok(result)
210 assert "Cleanup completed" in result.output
211 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
212 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
214 # -- 'apio sim --no-gtkwave'
215 args = ["sim", "--no-gtkwave"] + proj_arg
216 result = sb.invoke_apio_cmd(apio, args)
217 sb.assert_result_ok(result)
218 assert "SUCCESS" in result.output
219 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
220 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
221 # -- For issue https://github.com/FPGAwars/apio/issues/557
222 assert "warning: Timing checks are not supported" not in result.output
223 assert "-DSYNTHESIZE" not in result.output
224 assert "-DAPIO_SIM=1" 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 test <testbench-file>'
235 args = ["test", 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 sim --no-gtkw <testbench-file>'
245 args = ["sim", "--no-gtkwave", testbench_file] + proj_arg
246 result = sb.invoke_apio_cmd(apio, args)
247 sb.assert_result_ok(result)
248 assert "SUCCESS" in result.output
249 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
250 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
251 # -- For issue https://github.com/FPGAwars/apio/issues/557
252 assert "warning: Timing checks are not supported" not in result.output
254 # -- 'apio clean'
255 args = ["clean"] + proj_arg
256 result = sb.invoke_apio_cmd(apio, args)
257 sb.assert_result_ok(result)
258 assert "Cleanup completed" in result.output
259 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
260 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
262 # -- 'apio report'
263 args = ["report"] + proj_arg
264 result = sb.invoke_apio_cmd(apio, args)
265 sb.assert_result_ok(result)
266 assert "SUCCESS" in result.output
267 assert "-DSYNTHESIZE" in result.output
268 assert "-DAPIO_SIM" not in result.output
269 assert report_item in result.output
270 assert "─────┐" in result.output # Graphical table border
271 assert getsize(sb.proj_dir / "_build/default/hardware.pnr")
273 # -- 'apio graph -n'
274 args = ["graph", "-n"] + proj_arg
275 result = sb.invoke_apio_cmd(apio, args)
276 sb.assert_result_ok(result)
277 assert "SUCCESS" in result.output
278 assert "-DSYNTHESIZE" in result.output
279 assert "-DAPIO_SIM" not in result.output
280 assert getsize(sb.proj_dir / "_build/default/graph.dot")
281 assert getsize(sb.proj_dir / "_build/default/graph.svg")
283 # -- 'apio clean'
284 assert Path(sb.proj_dir / "_build/default").exists()
285 args = ["clean"] + proj_arg
286 result = sb.invoke_apio_cmd(apio, args)
287 sb.assert_result_ok(result)
288 assert "Cleanup completed" in result.output
289 assert not Path(sb.proj_dir / "_build/default").exists()
291 # -- Here we should have only the original project files.
292 assert set(os.listdir(sb.proj_dir)) == set(project_files)
295# NOTE: We use the alhambra-ii/bcd-counter example because it uses
296# subdirectories. This increases the test coverage.
297def test_project_ice40_local_dir(apio_runner: ApioRunner):
298 """Tests building and testing an ice40 project as the current working
299 dir."""
300 _test_project(
301 apio_runner,
302 remote_proj_dir=False,
303 example="alhambra-ii/bcd-counter",
304 testbench_file="main_tb.v",
305 bitstream="hardware.bin",
306 report_item="ICESTORM_LC",
307 )
310def test_project_ice40_remote_dir(apio_runner: ApioRunner):
311 """Tests building and testing an ice40 project from a remote dir, using
312 the -p option."""
313 _test_project(
314 apio_runner,
315 remote_proj_dir=True,
316 example="alhambra-ii/bcd-counter",
317 testbench_file="main_tb.v",
318 bitstream="hardware.bin",
319 report_item="ICESTORM_LC",
320 )
323def test_project_ice40_system_verilog(apio_runner: ApioRunner):
324 """Tests building and testing an ice40 project that contains system
325 verilog files."""
326 _test_project(
327 apio_runner,
328 remote_proj_dir=False,
329 example="alhambra-ii/bcd-counter-sv",
330 testbench_file="main_tb.sv",
331 bitstream="hardware.bin",
332 report_item="ICESTORM_LC",
333 )
336def test_project_ecp5_local_dir(apio_runner: ApioRunner):
337 """Tests building and testing an ecp5 project as the current working dir"""
338 _test_project(
339 apio_runner,
340 remote_proj_dir=False,
341 example="colorlight-5a-75b-v8/ledon",
342 testbench_file="ledon_tb.v",
343 bitstream="hardware.bit",
344 report_item="ALU54B",
345 )
348def test_project_ecp5_remote_dir(apio_runner: ApioRunner):
349 """Tests building and testing an ecp5 project from a remote directory."""
350 _test_project(
351 apio_runner,
352 remote_proj_dir=True,
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_system_verilog(apio_runner: ApioRunner):
361 """Tests building and testing an ecp5 project that contains system
362 verilog files."""
363 _test_project(
364 apio_runner,
365 remote_proj_dir=False,
366 example="colorlight-5a-75b-v8/ledon-sv",
367 testbench_file="ledon_tb.sv",
368 bitstream="hardware.bit",
369 report_item="ALU54B",
370 )
373def test_project_gowin_local_dir(apio_runner: ApioRunner):
374 """Tests building and testing a gowin project as the current working dir"""
375 _test_project(
376 apio_runner,
377 remote_proj_dir=False,
378 example="sipeed-tang-nano-9k/blinky",
379 testbench_file="blinky_tb.v",
380 bitstream="hardware.fs",
381 report_item="LUT4",
382 )
385def test_project_gowin_remote_dir(apio_runner: ApioRunner):
386 """Tests building and testing a gowin project from a remote directory."""
387 _test_project(
388 apio_runner,
389 remote_proj_dir=True,
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_system_verilog(apio_runner: ApioRunner):
398 """Tests building and testing an gowin project that contains system
399 verilog files."""
400 _test_project(
401 apio_runner,
402 remote_proj_dir=False,
403 example="sipeed-tang-nano-9k/blinky-sv",
404 testbench_file="blinky_tb.sv",
405 bitstream="hardware.fs",
406 report_item="LUT4",
407 )