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

196 statements  

« prev     ^ index     » next       coverage.py v7.17.2, created at 2026-11-07 05:08 +0000

1# This file is part of the Apio project 

2# (C) 2016-present FPGAwars 

3# Project authors: 

4# * Jesús Arroyo (Jesus89) (2016-2019) 

5# * Juan Gonzalez (obijuan) (2019-2024) 

6# * The Apio Team (2024-present) 

7# License: GPLv2 

8 

9""" 

10Test various "apio" commands. 

11""" 

12 

13import os 

14from os.path import getsize 

15from pathlib import Path 

16from typing import cast 

17from tests.conftest import ApioRunner 

18from apio.commands.apio import apio_top_cli as apio 

19 

20 

21def _test_project( 

22 apio_runner: ApioRunner, 

23 *, 

24 arch: str, 

25 remote_proj_dir: bool, 

26 example: str, 

27 testbench_file: str, 

28 bitstream: str, 

29 report_item: str, 

30): 

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

32 architecture. 

33 """ 

34 

35 # pylint: disable=too-many-arguments 

36 # pylint: disable=too-many-statements 

37 # pylint: disable=too-many-locals 

38 

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

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

41 

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

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

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

45 with apio_runner.in_sandbox() as sb: 

46 

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

48 # -- use -p proj_dir arg. 

49 if remote_proj_dir: 

50 os.chdir(sb.sandbox_dir) 

51 sb.proj_dir.rmdir() 

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

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

54 else: 

55 proj_arg = [] 

56 dst_arg = [] 

57 

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

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

60 if remote_proj_dir: 

61 assert not sb.proj_dir.exists() 

62 else: 

63 assert not os.listdir(sb.proj_dir) 

64 

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

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

67 result = sb.invoke_apio_cmd(apio, args) 

68 sb.assert_result_ok(result) 

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

70 assert "fetched successfully" in result.output 

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

72 

73 # -- 'apio packages list'. 

74 # -- We perform it after the 'apio examples fetch' above to make sure 

75 # -- it installed the packages if running in a clean environment. 

76 args = ["packages", "list"] 

77 result = sb.invoke_apio_cmd(apio, args) 

78 sb.assert_result_ok(result) 

79 print(result.output) 

80 

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

82 project_files = os.listdir(sb.proj_dir) 

83 

84 # -- 'apio build' 

85 args = ["build"] + proj_arg 

86 result = sb.invoke_apio_cmd(apio, args) 

87 sb.assert_result_ok(result) 

88 assert "SUCCESS" in result.output 

89 assert "yosys -p" in result.output 

90 assert "-DSYNTHESIZE" in result.output 

91 assert "-DAPIO_SIM" not in result.output 

92 

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

94 

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

96 args = ["build"] + proj_arg 

97 result = sb.invoke_apio_cmd(apio, args) 

98 sb.assert_result_ok(result) 

99 assert "SUCCESS" in result.output 

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

101 

102 # -- Modify apio.ini 

103 apio_ini_lines = cast( 

104 list[str], sb.read_file_lines(sb.proj_dir / "apio.ini") 

105 ) 

106 apio_ini_lines.append(" ") 

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

108 

109 # -- 'apio build' 

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

111 args = ["build"] + proj_arg 

112 result = sb.invoke_apio_cmd(apio, args) 

113 sb.assert_result_ok(result) 

114 assert "SUCCESS" in result.output 

115 assert "yosys -p" in result.output 

116 assert "-DSYNTHESIZE" in result.output 

117 assert "-DAPIO_SIM" not in result.output 

118 

119 # -- 'apio api get-build-report -o _build_report.json' 

120 args = ["api", "get-build-report", "-o", "_build_report.json"] 

121 result = sb.invoke_apio_cmd(apio, args) 

122 sb.assert_result_ok(result) 

123 build_report = sb.read_json_file("_build_report.json") 

124 assert build_report["build-report"]["arch"] == arch 

125 Path("_build_report.json").unlink() 

126 

127 # -- 'apio lint' 

128 args = ["lint"] + proj_arg 

129 result = sb.invoke_apio_cmd(apio, args) 

130 sb.assert_result_ok(result) 

131 assert "SUCCESS" in result.output 

132 assert "-DSYNTHESIZE" in result.output 

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

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

135 

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

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

138 result = sb.invoke_apio_cmd(apio, args) 

139 sb.assert_result_ok(result) 

140 assert "SUCCESS" in result.output 

141 assert "TOP_MODULE" not in result.output 

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

143 

144 # -- 'apio lint --nosynth' 

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

146 result = sb.invoke_apio_cmd(apio, args) 

147 sb.assert_result_ok(result) 

148 assert "SUCCESS" in result.output 

149 assert "-DSYNTHESIZE" not in result.output 

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

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

152 

153 # -- 'apio format' 

154 args = ["format"] + proj_arg 

155 result = sb.invoke_apio_cmd(apio, args) 

156 sb.assert_result_ok(result) 

157 assert "-DSYNTHESIZE" not in result.output 

158 assert "-DAPIO_SIM" not in result.output 

159 

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

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

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

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

164 result = sb.invoke_apio_cmd(apio, args) 

165 sb.assert_result_ok(result) 

166 

167 # -- 'apio test' 

168 args = ["test"] + proj_arg 

169 result = sb.invoke_apio_cmd(apio, args) 

170 sb.assert_result_ok(result) 

171 assert "SUCCESS" in result.output 

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

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

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

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

176 assert "-DSYNTHESIZE" not in result.output 

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

178 

179 # -- 'apio clean' 

180 args = ["clean"] + proj_arg 

181 result = sb.invoke_apio_cmd(apio, args) 

182 sb.assert_result_ok(result) 

183 assert "Cleanup completed" in result.output 

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

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

186 

187 # -- 'apio test --default' 

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

189 result = sb.invoke_apio_cmd(apio, args) 

190 sb.assert_result_ok(result) 

191 assert "SUCCESS" in result.output 

192 assert "-DAPIO_SIM=0" 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 

196 # -- 'apio clean' 

197 args = ["clean"] + proj_arg 

198 result = sb.invoke_apio_cmd(apio, args) 

199 sb.assert_result_ok(result) 

200 assert "Cleanup completed" in result.output 

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

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

203 

204 # -- 'apio sim --no-gtkwave' 

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

206 result = sb.invoke_apio_cmd(apio, args) 

207 sb.assert_result_ok(result) 

208 assert "SUCCESS" in result.output 

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

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

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

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

213 assert "-DSYNTHESIZE" not in result.output 

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

215 

216 # -- 'apio clean' 

217 args = ["clean"] + proj_arg 

218 result = sb.invoke_apio_cmd(apio, args) 

219 sb.assert_result_ok(result) 

220 assert "Cleanup completed" in result.output 

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

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

223 

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

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

226 result = sb.invoke_apio_cmd(apio, args) 

227 sb.assert_result_ok(result) 

228 assert "SUCCESS" in result.output 

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

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

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

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

233 

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

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

243 

244 # -- 'apio clean' 

245 args = ["clean"] + proj_arg 

246 result = sb.invoke_apio_cmd(apio, args) 

247 sb.assert_result_ok(result) 

248 assert "Cleanup completed" in result.output 

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

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

251 

252 # -- 'apio report' 

253 args = ["report"] + proj_arg 

254 result = sb.invoke_apio_cmd(apio, args) 

255 sb.assert_result_ok(result) 

256 assert "SUCCESS" in result.output 

257 assert "-DSYNTHESIZE" in result.output 

258 assert "-DAPIO_SIM" not in result.output 

259 assert report_item in result.output 

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

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

262 

263 # -- 'apio graph -n' 

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

265 result = sb.invoke_apio_cmd(apio, args) 

266 sb.assert_result_ok(result) 

267 assert "SUCCESS" in result.output 

268 assert "-DSYNTHESIZE" in result.output 

269 assert "-DAPIO_SIM" not in result.output 

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

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

272 

273 # -- 'apio clean' 

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

275 args = ["clean"] + proj_arg 

276 result = sb.invoke_apio_cmd(apio, args) 

277 sb.assert_result_ok(result) 

278 assert "Cleanup completed" in result.output 

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

280 

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

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

283 

284 

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

286# subdirectories. This increases the test coverage. 

287def test_project_ice40_local_dir(apio_runner: ApioRunner): 

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

289 dir.""" 

290 _test_project( 

291 apio_runner, 

292 arch="ice40", 

293 remote_proj_dir=False, 

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

295 testbench_file="main_tb.v", 

296 bitstream="hardware.bin", 

297 report_item="ICESTORM_LC", 

298 ) 

299 

300 

301def test_project_ice40_remote_dir(apio_runner: ApioRunner): 

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

303 the -p option.""" 

304 _test_project( 

305 apio_runner, 

306 arch="ice40", 

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 ) 

313 

314 

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 arch="ice40", 

321 remote_proj_dir=False, 

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

323 testbench_file="main_tb.sv", 

324 bitstream="hardware.bin", 

325 report_item="ICESTORM_LC", 

326 ) 

327 

328 

329def test_project_ecp5_local_dir(apio_runner: ApioRunner): 

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

331 _test_project( 

332 apio_runner, 

333 arch="ecp5", 

334 remote_proj_dir=False, 

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

336 testbench_file="main_tb.v", 

337 bitstream="hardware.bit", 

338 report_item="TRELLIS_IO", 

339 ) 

340 

341 

342def test_project_ecp5_remote_dir(apio_runner: ApioRunner): 

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

344 _test_project( 

345 apio_runner, 

346 arch="ecp5", 

347 remote_proj_dir=True, 

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

349 testbench_file="main_tb.v", 

350 bitstream="hardware.bit", 

351 report_item="TRELLIS_IO", 

352 ) 

353 

354 

355def test_project_ecp5_system_verilog(apio_runner: ApioRunner): 

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

357 verilog files.""" 

358 _test_project( 

359 apio_runner, 

360 arch="ecp5", 

361 remote_proj_dir=False, 

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

363 testbench_file="main_tb.sv", 

364 bitstream="hardware.bit", 

365 report_item="TRELLIS_IO", 

366 ) 

367 

368 

369def test_project_gowin_local_dir(apio_runner: ApioRunner): 

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

371 _test_project( 

372 apio_runner, 

373 arch="gowin", 

374 remote_proj_dir=False, 

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

376 testbench_file="main_tb.v", 

377 bitstream="hardware.fs", 

378 report_item="LUT4", 

379 ) 

380 

381 

382def test_project_gowin_remote_dir(apio_runner: ApioRunner): 

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

384 _test_project( 

385 apio_runner, 

386 arch="gowin", 

387 remote_proj_dir=True, 

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

389 testbench_file="main_tb.v", 

390 bitstream="hardware.fs", 

391 report_item="LUT4", 

392 ) 

393 

394 

395def test_project_gowin_system_verilog(apio_runner: ApioRunner): 

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

397 verilog files.""" 

398 _test_project( 

399 apio_runner, 

400 arch="gowin", 

401 remote_proj_dir=False, 

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

403 testbench_file="main_tb.sv", 

404 bitstream="hardware.fs", 

405 report_item="LUT4", 

406 ) 

407 

408 

409def test_project_xilinx_local_dir(apio_runner: ApioRunner): 

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

411 dir.""" 

412 _test_project( 

413 apio_runner, 

414 arch="xilinx", 

415 remote_proj_dir=False, 

416 example="basys3/ledon", 

417 testbench_file="main_tb.v", 

418 bitstream="hardware.bit", 

419 report_item="PSEUDO_GND", 

420 ) 

421 

422 

423def test_project_xilinx_remote_dir(apio_runner: ApioRunner): 

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

425 the -p option.""" 

426 _test_project( 

427 apio_runner, 

428 arch="xilinx", 

429 remote_proj_dir=True, 

430 example="basys3/ledon", 

431 testbench_file="main_tb.v", 

432 bitstream="hardware.bit", 

433 report_item="PSEUDO_GND", 

434 )