Coverage for tests / integration_tests / test_projects.py: 100%
150 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-24 01:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-24 01:53 +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
109 assert getsize(sb.proj_dir / "_build/default" / bitstream)
111 # -- 'apio build' (no change)
112 args = ["build"] + proj_arg
113 result = sb.invoke_apio_cmd(apio, args)
114 sb.assert_result_ok(result)
115 assert "SUCCESS" in result.output
116 assert "yosys -p" not in result.output
118 # -- Modify apio.ini
119 apio_ini_lines = sb.read_file(
120 sb.proj_dir / "apio.ini", lines_mode=True
121 )
122 apio_ini_lines.append(" ")
123 sb.write_file(sb.proj_dir / "apio.ini", apio_ini_lines, exists_ok=True)
125 # -- 'apio build'
126 # -- Apio.ini modification should triggers a new build.
127 args = ["build"] + proj_arg
128 result = sb.invoke_apio_cmd(apio, args)
129 sb.assert_result_ok(result)
130 assert "SUCCESS" in result.output
131 assert "yosys -p" in result.output
133 # -- 'apio lint'
134 args = ["lint"] + proj_arg
135 result = sb.invoke_apio_cmd(apio, args)
136 sb.assert_result_ok(result)
137 assert "SUCCESS" in result.output
138 assert getsize(sb.proj_dir / "_build/default/hardware.vlt")
140 # -- 'apio format'
141 args = ["format"] + proj_arg
142 result = sb.invoke_apio_cmd(apio, args)
143 sb.assert_result_ok(result)
145 # -- 'apio format <testbench-file>'
146 # -- This tests the project relative specification even when
147 # -- the option --project-dir is used.
148 args = ["format", testbench_file] + proj_arg
149 result = sb.invoke_apio_cmd(apio, args)
150 sb.assert_result_ok(result)
152 # -- 'apio test'
153 args = ["test"] + proj_arg
154 result = sb.invoke_apio_cmd(apio, args)
155 sb.assert_result_ok(result)
156 assert "SUCCESS" in result.output
157 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out")
158 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd")
159 # -- For issue https://github.com/FPGAwars/apio/issues/557
160 assert "warning: Timing checks are not supported" not in result.output
162 # -- 'apio clean'
163 args = ["clean"] + proj_arg
164 result = sb.invoke_apio_cmd(apio, args)
165 sb.assert_result_ok(result)
166 assert "Cleanup completed" in result.output
167 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
168 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
170 # -- 'apio sim --no-gtkwave'
171 args = ["sim", "--no-gtkwave"] + proj_arg
172 result = sb.invoke_apio_cmd(apio, args)
173 sb.assert_result_ok(result)
174 assert "SUCCESS" 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")
177 # -- For issue https://github.com/FPGAwars/apio/issues/557
178 assert "warning: Timing checks are not supported" not in result.output
180 # -- 'apio clean'
181 args = ["clean"] + proj_arg
182 result = sb.invoke_apio_cmd(apio, args)
183 sb.assert_result_ok(result)
184 assert "Cleanup completed" in result.output
185 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
186 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
188 # -- 'apio test <testbench-file>'
189 args = ["test", testbench_file] + proj_arg
190 result = sb.invoke_apio_cmd(apio, args)
191 sb.assert_result_ok(result)
192 assert "SUCCESS" 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")
195 # -- For issue https://github.com/FPGAwars/apio/issues/557
196 assert "warning: Timing checks are not supported" not in result.output
198 # -- 'apio sim --no-gtkw <testbench-file>'
199 args = ["sim", "--no-gtkwave", testbench_file] + proj_arg
200 result = sb.invoke_apio_cmd(apio, args)
201 sb.assert_result_ok(result)
202 assert "SUCCESS" 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")
205 # -- For issue https://github.com/FPGAwars/apio/issues/557
206 assert "warning: Timing checks are not supported" not in result.output
208 # -- 'apio clean'
209 args = ["clean"] + proj_arg
210 result = sb.invoke_apio_cmd(apio, args)
211 sb.assert_result_ok(result)
212 assert "Cleanup completed" in result.output
213 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists()
214 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists()
216 # -- 'apio report'
217 args = ["report"] + proj_arg
218 result = sb.invoke_apio_cmd(apio, args)
219 sb.assert_result_ok(result)
220 assert "SUCCESS" in result.output
221 assert report_item in result.output
222 assert "─────┐" in result.output # Graphical table border
223 assert getsize(sb.proj_dir / "_build/default/hardware.pnr")
225 # -- 'apio graph -n'
226 args = ["graph", "-n"] + proj_arg
227 result = sb.invoke_apio_cmd(apio, args)
228 sb.assert_result_ok(result)
229 assert "SUCCESS" in result.output
230 assert getsize(sb.proj_dir / "_build/default/graph.dot")
231 assert getsize(sb.proj_dir / "_build/default/graph.svg")
233 # -- 'apio clean'
234 assert Path(sb.proj_dir / "_build/default").exists()
235 args = ["clean"] + proj_arg
236 result = sb.invoke_apio_cmd(apio, args)
237 sb.assert_result_ok(result)
238 assert "Cleanup completed" in result.output
239 assert not Path(sb.proj_dir / "_build/default").exists()
241 # -- Here we should have only the original project files.
242 assert set(os.listdir(sb.proj_dir)) == set(project_files)
245# NOTE: We use the alhambra-ii/bcd-counter example because it uses
246# subdirectories. This increases the test coverage.
247def test_project_ice40_local_dir(apio_runner: ApioRunner):
248 """Tests building and testing an ice40 project as the current working
249 dir."""
250 _test_project(
251 apio_runner,
252 remote_proj_dir=False,
253 example="alhambra-ii/bcd-counter",
254 testbench_file="main_tb.v",
255 bitstream="hardware.bin",
256 report_item="ICESTORM_LC",
257 )
260def test_project_ice40_remote_dir(apio_runner: ApioRunner):
261 """Tests building and testing an ice40 project from a remote dir, using
262 the -p option."""
263 _test_project(
264 apio_runner,
265 remote_proj_dir=True,
266 example="alhambra-ii/bcd-counter",
267 testbench_file="main_tb.v",
268 bitstream="hardware.bin",
269 report_item="ICESTORM_LC",
270 )
273def test_project_ice40_system_verilog(apio_runner: ApioRunner):
274 """Tests building and testing an ice40 project that contains system
275 verilog files."""
276 _test_project(
277 apio_runner,
278 remote_proj_dir=False,
279 example="alhambra-ii/bcd-counter-sv",
280 testbench_file="main_tb.sv",
281 bitstream="hardware.bin",
282 report_item="ICESTORM_LC",
283 )
286def test_project_ecp5_local_dir(apio_runner: ApioRunner):
287 """Tests building and testing an ecp5 project as the current working dir"""
288 _test_project(
289 apio_runner,
290 remote_proj_dir=False,
291 example="colorlight-5a-75b-v8/ledon",
292 testbench_file="ledon_tb.v",
293 bitstream="hardware.bit",
294 report_item="ALU54B",
295 )
298def test_project_ecp5_remote_dir(apio_runner: ApioRunner):
299 """Tests building and testing an ecp5 project from a remote directory."""
300 _test_project(
301 apio_runner,
302 remote_proj_dir=True,
303 example="colorlight-5a-75b-v8/ledon",
304 testbench_file="ledon_tb.v",
305 bitstream="hardware.bit",
306 report_item="ALU54B",
307 )
310def test_project_ecp5_system_verilog(apio_runner: ApioRunner):
311 """Tests building and testing an ecp5 project that contains system
312 verilog files."""
313 _test_project(
314 apio_runner,
315 remote_proj_dir=False,
316 example="colorlight-5a-75b-v8/ledon-sv",
317 testbench_file="ledon_tb.sv",
318 bitstream="hardware.bit",
319 report_item="ALU54B",
320 )
323def test_project_gowin_local_dir(apio_runner: ApioRunner):
324 """Tests building and testing a gowin project as the current working dir"""
325 _test_project(
326 apio_runner,
327 remote_proj_dir=False,
328 example="sipeed-tang-nano-9k/blinky",
329 testbench_file="blinky_tb.v",
330 bitstream="hardware.fs",
331 report_item="LUT4",
332 )
335def test_project_gowin_remote_dir(apio_runner: ApioRunner):
336 """Tests building and testing a gowin project from a remote directory."""
337 _test_project(
338 apio_runner,
339 remote_proj_dir=True,
340 example="sipeed-tang-nano-9k/blinky",
341 testbench_file="blinky_tb.v",
342 bitstream="hardware.fs",
343 report_item="LUT4",
344 )
347def test_project_gowin_system_verilog(apio_runner: ApioRunner):
348 """Tests building and testing an gowin project that contains system
349 verilog files."""
350 _test_project(
351 apio_runner,
352 remote_proj_dir=False,
353 example="sipeed-tang-nano-9k/blinky-sv",
354 testbench_file="blinky_tb.sv",
355 bitstream="hardware.fs",
356 report_item="LUT4",
357 )