Coverage for tests/integration_tests/test_projects.py: 100%

203 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-06-24 03:51 +0000

1""" 

2Test various "apio" commands. 

3""" 

4 

5import sys 

6import os 

7from os.path import getsize 

8from pathlib import Path 

9from typing import cast 

10import pytest 

11from tests.conftest import ApioRunner 

12from apio.commands.apio import apio_top_cli as apio 

13 

14# -- Message to show for the tests that only run on Linux 

15ONLY_LINUX_MSG = ( 

16 "Currently, the Xilinx arch is only implemented for Linux, " 

17 "so this test only run on linux platforms" 

18) 

19 

20# -- For non-linux platfoms (windows and mac) 

21is_not_linux = not sys.platform.startswith("linux") 

22 

23 

24def test_project_with_legacy_board_id(apio_runner: ApioRunner): 

25 """Test a project that uses a legacy board id.""" 

26 

27 # -- We shared the apio home with the other tests in this file to speed 

28 # -- up apio package installation. Tests should not mutate the shared home 

29 # -- to avoid cross-interference between tests in this file. 

30 with apio_runner.in_sandbox() as sb: 

31 

32 # -- Fetch an example of a board that has a legacy name. 

33 result = sb.invoke_apio_cmd( 

34 apio, ["examples", "fetch", "ice40-hx8k/leds"] 

35 ) 

36 sb.assert_result_ok(result) 

37 

38 # -- Run 'apio build' 

39 result = sb.invoke_apio_cmd(apio, ["build"]) 

40 sb.assert_result_ok(result) 

41 

42 # -- Modify the apio.ini to have the legacy board id 

43 sb.write_apio_ini( 

44 { 

45 "[env:default]": { 

46 "board": "iCE40-HX8K", 

47 "top-module": "leds", 

48 } 

49 } 

50 ) 

51 

52 # -- Run 'apio clean' 

53 result = sb.invoke_apio_cmd(apio, ["clean"]) 

54 sb.assert_result_ok(result) 

55 

56 # -- Run 'apio build' again. It should also succeed. 

57 result = sb.invoke_apio_cmd(apio, ["build"]) 

58 sb.assert_result_ok(result) 

59 

60 

61def _test_project( 

62 apio_runner: ApioRunner, 

63 *, 

64 remote_proj_dir: bool, 

65 example: str, 

66 testbench_file: str, 

67 bitstream: str, 

68 report_item: str, 

69): 

70 """A common project integration test. Invoked per each tested 

71 architecture. 

72 """ 

73 

74 # pylint: disable=too-many-arguments 

75 # pylint: disable=too-many-statements 

76 

77 # -- Extract the base name of the testbench file 

78 testbench, _ = os.path.splitext(testbench_file) 

79 

80 # -- We shared the apio home with the other tests in this file to speed 

81 # -- up apio package installation. Tests should not mutate the shared home 

82 # -- to avoid cross-interference between tests in this file. 

83 with apio_runner.in_sandbox() as sb: 

84 

85 # -- If testing from a remote dir, step out of the proj dir, and 

86 # -- use -p proj_dir arg. 

87 if remote_proj_dir: 

88 os.chdir(sb.sandbox_dir) 

89 sb.proj_dir.rmdir() 

90 proj_arg = ["-p", str(sb.proj_dir)] 

91 dst_arg = ["-d", str(sb.proj_dir)] 

92 else: 

93 proj_arg = [] 

94 dst_arg = [] 

95 

96 # -- If testing from a remote dir, the proj dir should not exist yet, 

97 # -- else, the project dir should be empty. 

98 if remote_proj_dir: 

99 assert not sb.proj_dir.exists() 

100 else: 

101 assert not os.listdir(sb.proj_dir) 

102 

103 # -- 'apio examples fetch <example> -d <proj_dir>' 

104 args = ["examples", "fetch", example] + dst_arg 

105 result = sb.invoke_apio_cmd(apio, args) 

106 sb.assert_result_ok(result) 

107 assert f"Copying {example} example files" in result.output 

108 assert "fetched successfully" in result.output 

109 assert getsize(sb.proj_dir / "apio.ini") 

110 

111 # -- Remember the original list of project files. 

112 project_files = os.listdir(sb.proj_dir) 

113 

114 # -- 'apio build' 

115 args = ["build"] + proj_arg 

116 result = sb.invoke_apio_cmd(apio, args) 

117 sb.assert_result_ok(result) 

118 assert "SUCCESS" in result.output 

119 assert "yosys -p" in result.output 

120 assert "-DSYNTHESIZE" in result.output 

121 assert "-DAPIO_SIM" not in result.output 

122 

123 assert getsize(sb.proj_dir / "_build/default" / bitstream) 

124 

125 # -- 'apio build' (no change, yosys is not invoked) 

126 args = ["build"] + proj_arg 

127 result = sb.invoke_apio_cmd(apio, args) 

128 sb.assert_result_ok(result) 

129 assert "SUCCESS" in result.output 

130 assert "yosys -p" not in result.output 

131 

132 # -- Modify apio.ini 

133 apio_ini_lines = cast( 

134 list[str], sb.read_file(sb.proj_dir / "apio.ini", lines_mode=True) 

135 ) 

136 apio_ini_lines.append(" ") 

137 sb.write_file(sb.proj_dir / "apio.ini", apio_ini_lines, exists_ok=True) 

138 

139 # -- 'apio build' 

140 # -- Apio.ini modification should triggers a new build. 

141 args = ["build"] + proj_arg 

142 result = sb.invoke_apio_cmd(apio, args) 

143 sb.assert_result_ok(result) 

144 assert "SUCCESS" in result.output 

145 assert "yosys -p" in result.output 

146 assert "-DSYNTHESIZE" in result.output 

147 assert "-DAPIO_SIM" not in result.output 

148 

149 # -- 'apio lint' 

150 args = ["lint"] + proj_arg 

151 result = sb.invoke_apio_cmd(apio, args) 

152 sb.assert_result_ok(result) 

153 assert "SUCCESS" in result.output 

154 assert "-DSYNTHESIZE" in result.output 

155 assert "-DAPIO_SIM=0" in result.output 

156 assert getsize(sb.proj_dir / "_build/default/hardware.vlt") 

157 

158 # -- 'apio lint <testbench.v>' 

159 args = ["lint", testbench_file] + proj_arg 

160 result = sb.invoke_apio_cmd(apio, args) 

161 sb.assert_result_ok(result) 

162 assert "SUCCESS" in result.output 

163 assert "TOP_MODULE" not in result.output 

164 assert "-DAPIO_SIM=0" in result.output 

165 

166 # -- 'apio lint --nosynth' 

167 args = ["lint", "--nosynth"] + proj_arg 

168 result = sb.invoke_apio_cmd(apio, args) 

169 sb.assert_result_ok(result) 

170 assert "SUCCESS" in result.output 

171 assert "-DSYNTHESIZE" not in result.output 

172 assert "-DAPIO_SIM=0" in result.output 

173 assert getsize(sb.proj_dir / "_build/default/hardware.vlt") 

174 

175 # -- 'apio format' 

176 args = ["format"] + proj_arg 

177 result = sb.invoke_apio_cmd(apio, args) 

178 sb.assert_result_ok(result) 

179 assert "-DSYNTHESIZE" not in result.output 

180 assert "-DAPIO_SIM" not in result.output 

181 

182 # -- 'apio format <testbench-file>' 

183 # -- This tests the project relative specification even when 

184 # -- the option --project-dir is used. 

185 args = ["format", testbench_file] + proj_arg 

186 result = sb.invoke_apio_cmd(apio, args) 

187 sb.assert_result_ok(result) 

188 

189 # -- 'apio test' 

190 args = ["test"] + proj_arg 

191 result = sb.invoke_apio_cmd(apio, args) 

192 sb.assert_result_ok(result) 

193 assert "SUCCESS" in result.output 

194 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out") 

195 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd") 

196 # -- For issue https://github.com/FPGAwars/apio/issues/557 

197 assert "warning: Timing checks are not supported" not in result.output 

198 assert "-DSYNTHESIZE" not in result.output 

199 assert "-DAPIO_SIM=0" in result.output 

200 

201 # -- 'apio clean' 

202 args = ["clean"] + proj_arg 

203 result = sb.invoke_apio_cmd(apio, args) 

204 sb.assert_result_ok(result) 

205 assert "Cleanup completed" in result.output 

206 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists() 

207 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists() 

208 

209 # -- 'apio test --default' 

210 args = ["test", "--default"] + proj_arg 

211 result = sb.invoke_apio_cmd(apio, args) 

212 sb.assert_result_ok(result) 

213 assert "SUCCESS" in result.output 

214 assert "-DAPIO_SIM=0" in result.output 

215 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out") 

216 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd") 

217 

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() 

225 

226 # -- 'apio sim --no-gtkwave' 

227 args = ["sim", "--no-gtkwave"] + 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 

235 assert "-DSYNTHESIZE" not in result.output 

236 assert "-DAPIO_SIM=1" in result.output 

237 

238 # -- 'apio clean' 

239 args = ["clean"] + proj_arg 

240 result = sb.invoke_apio_cmd(apio, args) 

241 sb.assert_result_ok(result) 

242 assert "Cleanup completed" in result.output 

243 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists() 

244 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists() 

245 

246 # -- 'apio test <testbench-file>' 

247 args = ["test", testbench_file] + proj_arg 

248 result = sb.invoke_apio_cmd(apio, args) 

249 sb.assert_result_ok(result) 

250 assert "SUCCESS" in result.output 

251 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out") 

252 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd") 

253 # -- For issue https://github.com/FPGAwars/apio/issues/557 

254 assert "warning: Timing checks are not supported" not in result.output 

255 

256 # -- 'apio sim --no-gtkw <testbench-file>' 

257 args = ["sim", "--no-gtkwave", testbench_file] + proj_arg 

258 result = sb.invoke_apio_cmd(apio, args) 

259 sb.assert_result_ok(result) 

260 assert "SUCCESS" in result.output 

261 assert getsize(sb.proj_dir / f"_build/default/{testbench}.out") 

262 assert getsize(sb.proj_dir / f"_build/default/{testbench}.vcd") 

263 # -- For issue https://github.com/FPGAwars/apio/issues/557 

264 assert "warning: Timing checks are not supported" not in result.output 

265 

266 # -- 'apio clean' 

267 args = ["clean"] + proj_arg 

268 result = sb.invoke_apio_cmd(apio, args) 

269 sb.assert_result_ok(result) 

270 assert "Cleanup completed" in result.output 

271 assert not (sb.proj_dir / f"_build/default/{testbench}.out").exists() 

272 assert not (sb.proj_dir / f"_build/default/{testbench}.vcd").exists() 

273 

274 # -- 'apio report' 

275 args = ["report"] + proj_arg 

276 result = sb.invoke_apio_cmd(apio, args) 

277 sb.assert_result_ok(result) 

278 assert "SUCCESS" in result.output 

279 assert "-DSYNTHESIZE" in result.output 

280 assert "-DAPIO_SIM" not in result.output 

281 assert report_item in result.output 

282 assert "─────┐" in result.output # Graphical table border 

283 assert getsize(sb.proj_dir / "_build/default/hardware.pnr") 

284 

285 # -- 'apio graph -n' 

286 args = ["graph", "-n"] + proj_arg 

287 result = sb.invoke_apio_cmd(apio, args) 

288 sb.assert_result_ok(result) 

289 assert "SUCCESS" in result.output 

290 assert "-DSYNTHESIZE" in result.output 

291 assert "-DAPIO_SIM" not in result.output 

292 assert getsize(sb.proj_dir / "_build/default/graph.dot") 

293 assert getsize(sb.proj_dir / "_build/default/graph.svg") 

294 

295 # -- 'apio clean' 

296 assert Path(sb.proj_dir / "_build/default").exists() 

297 args = ["clean"] + proj_arg 

298 result = sb.invoke_apio_cmd(apio, args) 

299 sb.assert_result_ok(result) 

300 assert "Cleanup completed" in result.output 

301 assert not Path(sb.proj_dir / "_build/default").exists() 

302 

303 # -- Here we should have only the original project files. 

304 assert set(os.listdir(sb.proj_dir)) == set(project_files) 

305 

306 

307# NOTE: We use the alhambra-ii/bcd-counter example because it uses 

308# subdirectories. This increases the test coverage. 

309def test_project_ice40_local_dir(apio_runner: ApioRunner): 

310 """Tests building and testing an ice40 project as the current working 

311 dir.""" 

312 _test_project( 

313 apio_runner, 

314 remote_proj_dir=False, 

315 example="alhambra-ii/bcd-counter", 

316 testbench_file="main_tb.v", 

317 bitstream="hardware.bin", 

318 report_item="ICESTORM_LC", 

319 ) 

320 

321 

322def test_project_ice40_remote_dir(apio_runner: ApioRunner): 

323 """Tests building and testing an ice40 project from a remote dir, using 

324 the -p option.""" 

325 _test_project( 

326 apio_runner, 

327 remote_proj_dir=True, 

328 example="alhambra-ii/bcd-counter", 

329 testbench_file="main_tb.v", 

330 bitstream="hardware.bin", 

331 report_item="ICESTORM_LC", 

332 ) 

333 

334 

335def test_project_ice40_system_verilog(apio_runner: ApioRunner): 

336 """Tests building and testing an ice40 project that contains system 

337 verilog files.""" 

338 _test_project( 

339 apio_runner, 

340 remote_proj_dir=False, 

341 example="alhambra-ii/bcd-counter-sv", 

342 testbench_file="main_tb.sv", 

343 bitstream="hardware.bin", 

344 report_item="ICESTORM_LC", 

345 ) 

346 

347 

348def test_project_ecp5_local_dir(apio_runner: ApioRunner): 

349 """Tests building and testing an ecp5 project as the current working dir""" 

350 _test_project( 

351 apio_runner, 

352 remote_proj_dir=False, 

353 example="colorlight-5a-75b-v8/ledon", 

354 testbench_file="ledon_tb.v", 

355 bitstream="hardware.bit", 

356 report_item="ALU54B", 

357 ) 

358 

359 

360def test_project_ecp5_remote_dir(apio_runner: ApioRunner): 

361 """Tests building and testing an ecp5 project from a remote directory.""" 

362 _test_project( 

363 apio_runner, 

364 remote_proj_dir=True, 

365 example="colorlight-5a-75b-v8/ledon", 

366 testbench_file="ledon_tb.v", 

367 bitstream="hardware.bit", 

368 report_item="ALU54B", 

369 ) 

370 

371 

372def test_project_ecp5_system_verilog(apio_runner: ApioRunner): 

373 """Tests building and testing an ecp5 project that contains system 

374 verilog files.""" 

375 _test_project( 

376 apio_runner, 

377 remote_proj_dir=False, 

378 example="colorlight-5a-75b-v8/ledon-sv", 

379 testbench_file="ledon_tb.sv", 

380 bitstream="hardware.bit", 

381 report_item="ALU54B", 

382 ) 

383 

384 

385def test_project_gowin_local_dir(apio_runner: ApioRunner): 

386 """Tests building and testing a gowin project as the current working dir""" 

387 _test_project( 

388 apio_runner, 

389 remote_proj_dir=False, 

390 example="sipeed-tang-nano-9k/blinky", 

391 testbench_file="blinky_tb.v", 

392 bitstream="hardware.fs", 

393 report_item="LUT4", 

394 ) 

395 

396 

397def test_project_gowin_remote_dir(apio_runner: ApioRunner): 

398 """Tests building and testing a gowin project from a remote directory.""" 

399 _test_project( 

400 apio_runner, 

401 remote_proj_dir=True, 

402 example="sipeed-tang-nano-9k/blinky", 

403 testbench_file="blinky_tb.v", 

404 bitstream="hardware.fs", 

405 report_item="LUT4", 

406 ) 

407 

408 

409def test_project_gowin_system_verilog(apio_runner: ApioRunner): 

410 """Tests building and testing an gowin project that contains system 

411 verilog files.""" 

412 _test_project( 

413 apio_runner, 

414 remote_proj_dir=False, 

415 example="sipeed-tang-nano-9k/blinky-sv", 

416 testbench_file="blinky_tb.sv", 

417 bitstream="hardware.fs", 

418 report_item="LUT4", 

419 ) 

420 

421 

422@pytest.mark.skipif(is_not_linux, reason=ONLY_LINUX_MSG) 

423def test_project_xilinx_local_dir(apio_runner: ApioRunner): 

424 """Tests building and testing a Xilinx project as the current working 

425 dir.""" 

426 _test_project( 

427 apio_runner, 

428 remote_proj_dir=False, 

429 example="basys3/ledon", 

430 testbench_file="ledon_tb.v", 

431 bitstream="hardware.bit", 

432 report_item="PSEUDO_GND", 

433 ) 

434 

435 

436@pytest.mark.skipif(is_not_linux, reason=ONLY_LINUX_MSG) 

437def test_project_xilinx_remote_dir(apio_runner: ApioRunner): 

438 """Tests building and testing an ice40 project from a remote dir, using 

439 the -p option.""" 

440 _test_project( 

441 apio_runner, 

442 remote_proj_dir=True, 

443 example="basys3/ledon", 

444 testbench_file="ledon_tb.v", 

445 bitstream="hardware.bit", 

446 report_item="PSEUDO_GND", 

447 )