Coverage for tests / integration_tests / test_projects.py: 100%
186 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 02:47 +0000
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 02:47 +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 --nosynth'
147 args = ["lint", "--nosynth"] + proj_arg
148 result = sb.invoke_apio_cmd(apio, args)
149 sb.assert_result_ok(result)
150 assert "SUCCESS" in result.output
151 assert "-DSYNTHESIZE" not in result.output
152 assert "-DAPIO_SIM=0" in result.output
153 assert getsize(sb.proj_dir / "_build/default/hardware.vlt")
155 # -- 'apio format'
156 args = ["format"] + proj_arg
157 result = sb.invoke_apio_cmd(apio, args)
158 sb.assert_result_ok(result)
159 assert "-DSYNTHESIZE" not in result.output
160 assert "-DAPIO_SIM" not in result.output
162 # -- 'apio format <testbench-file>'
163 # -- This tests the project relative specification even when
164 # -- the option --project-dir is used.
165 args = ["format", testbench_file] + proj_arg
166 result = sb.invoke_apio_cmd(apio, args)
167 sb.assert_result_ok(result)
169 # -- 'apio test'
170 args = ["test"] + proj_arg
171 result = sb.invoke_apio_cmd(apio, args)
172 sb.assert_result_ok(result)
173 assert "SUCCESS" in result.output
174 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
175 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
176 # -- For issue https://github.com/FPGAwars/apio/issues/557
177 assert "warning: Timing checks are not supported" not in result.output
178 assert "-DSYNTHESIZE" not in result.output
179 assert "-DAPIO_SIM=0" in result.output
181 # -- 'apio clean'
182 args = ["clean"] + proj_arg
183 result = sb.invoke_apio_cmd(apio, args)
184 sb.assert_result_ok(result)
185 assert "Cleanup completed" in result.output
186 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
187 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
189 # -- 'apio test --default'
190 args = ["test", "--default"] + proj_arg
191 result = sb.invoke_apio_cmd(apio, args)
192 sb.assert_result_ok(result)
193 assert "SUCCESS" in result.output
194 assert "-DAPIO_SIM=0" in result.output
195 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
196 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
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 sim --no-gtkwave'
207 args = ["sim", "--no-gtkwave"] + 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
215 assert "-DSYNTHESIZE" not in result.output
216 assert "-DAPIO_SIM=1" in result.output
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 test <testbench-file>'
227 args = ["test", testbench_file] + 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
236 # -- 'apio sim --no-gtkw <testbench-file>'
237 args = ["sim", "--no-gtkwave", testbench_file] + proj_arg
238 result = sb.invoke_apio_cmd(apio, args)
239 sb.assert_result_ok(result)
240 assert "SUCCESS" in result.output
241 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
242 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
243 # -- For issue https://github.com/FPGAwars/apio/issues/557
244 assert "warning: Timing checks are not supported" not in result.output
246 # -- 'apio clean'
247 args = ["clean"] + proj_arg
248 result = sb.invoke_apio_cmd(apio, args)
249 sb.assert_result_ok(result)
250 assert "Cleanup completed" in result.output
251 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
252 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
254 # -- 'apio report'
255 args = ["report"] + proj_arg
256 result = sb.invoke_apio_cmd(apio, args)
257 sb.assert_result_ok(result)
258 assert "SUCCESS" in result.output
259 assert "-DSYNTHESIZE" in result.output
260 assert "-DAPIO_SIM" not in result.output
261 assert report_item in result.output
262 assert "─────┐" in result.output # Graphical table border
263 assert getsize(sb.proj_dir / "_build/default/hardware.pnr")
265 # -- 'apio graph -n'
266 args = ["graph", "-n"] + proj_arg
267 result = sb.invoke_apio_cmd(apio, args)
268 sb.assert_result_ok(result)
269 assert "SUCCESS" in result.output
270 assert "-DSYNTHESIZE" in result.output
271 assert "-DAPIO_SIM" not in result.output
272 assert getsize(sb.proj_dir / "_build/default/graph.dot")
273 assert getsize(sb.proj_dir / "_build/default/graph.svg")
275 # -- 'apio clean'
276 assert Path(sb.proj_dir / "_build/default").exists()
277 args = ["clean"] + proj_arg
278 result = sb.invoke_apio_cmd(apio, args)
279 sb.assert_result_ok(result)
280 assert "Cleanup completed" in result.output
281 assert not Path(sb.proj_dir / "_build/default").exists()
283 # -- Here we should have only the original project files.
284 assert set(os.listdir(sb.proj_dir)) == set(project_files)
287# NOTE: We use the alhambra-ii/bcd-counter example because it uses
288# subdirectories. This increases the test coverage.
289def test_project_ice40_local_dir(apio_runner: ApioRunner):
290 """Tests building and testing an ice40 project as the current working
291 dir."""
292 _test_project(
293 apio_runner,
294 remote_proj_dir=False,
295 example="alhambra-ii/bcd-counter",
296 testbench_file="main_tb.v",
297 bitstream="hardware.bin",
298 report_item="ICESTORM_LC",
299 )
302def test_project_ice40_remote_dir(apio_runner: ApioRunner):
303 """Tests building and testing an ice40 project from a remote dir, using
304 the -p option."""
305 _test_project(
306 apio_runner,
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 remote_proj_dir=False,
321 example="alhambra-ii/bcd-counter-sv",
322 testbench_file="main_tb.sv",
323 bitstream="hardware.bin",
324 report_item="ICESTORM_LC",
325 )
328def test_project_ecp5_local_dir(apio_runner: ApioRunner):
329 """Tests building and testing an ecp5 project as the current working dir"""
330 _test_project(
331 apio_runner,
332 remote_proj_dir=False,
333 example="colorlight-5a-75b-v8/ledon",
334 testbench_file="ledon_tb.v",
335 bitstream="hardware.bit",
336 report_item="ALU54B",
337 )
340def test_project_ecp5_remote_dir(apio_runner: ApioRunner):
341 """Tests building and testing an ecp5 project from a remote directory."""
342 _test_project(
343 apio_runner,
344 remote_proj_dir=True,
345 example="colorlight-5a-75b-v8/ledon",
346 testbench_file="ledon_tb.v",
347 bitstream="hardware.bit",
348 report_item="ALU54B",
349 )
352def test_project_ecp5_system_verilog(apio_runner: ApioRunner):
353 """Tests building and testing an ecp5 project that contains system
354 verilog files."""
355 _test_project(
356 apio_runner,
357 remote_proj_dir=False,
358 example="colorlight-5a-75b-v8/ledon-sv",
359 testbench_file="ledon_tb.sv",
360 bitstream="hardware.bit",
361 report_item="ALU54B",
362 )
365def test_project_gowin_local_dir(apio_runner: ApioRunner):
366 """Tests building and testing a gowin project as the current working dir"""
367 _test_project(
368 apio_runner,
369 remote_proj_dir=False,
370 example="sipeed-tang-nano-9k/blinky",
371 testbench_file="blinky_tb.v",
372 bitstream="hardware.fs",
373 report_item="LUT4",
374 )
377def test_project_gowin_remote_dir(apio_runner: ApioRunner):
378 """Tests building and testing a gowin project from a remote directory."""
379 _test_project(
380 apio_runner,
381 remote_proj_dir=True,
382 example="sipeed-tang-nano-9k/blinky",
383 testbench_file="blinky_tb.v",
384 bitstream="hardware.fs",
385 report_item="LUT4",
386 )
389def test_project_gowin_system_verilog(apio_runner: ApioRunner):
390 """Tests building and testing an gowin project that contains system
391 verilog files."""
392 _test_project(
393 apio_runner,
394 remote_proj_dir=False,
395 example="sipeed-tang-nano-9k/blinky-sv",
396 testbench_file="blinky_tb.sv",
397 bitstream="hardware.fs",
398 report_item="LUT4",
399 )