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

197 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-09 01:55 +0000

1""" 

2Test various "apio" commands. 

3""" 

4 

5import os 

6from os.path import getsize 

7from pathlib import Path 

8from typing import cast 

9from tests.conftest import ApioRunner 

10from apio.commands.apio import apio_top_cli as apio 

11 

12 

13def test_project_with_legacy_board_id(apio_runner: ApioRunner): 

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

15 

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

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

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

19 with apio_runner.in_sandbox() as sb: 

20 

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

22 result = sb.invoke_apio_cmd( 

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

24 ) 

25 sb.assert_result_ok(result) 

26 

27 # -- Run 'apio build' 

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

29 sb.assert_result_ok(result) 

30 

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

32 sb.write_apio_ini( 

33 { 

34 "[env:default]": { 

35 "board": "iCE40-HX8K", 

36 "top-module": "leds", 

37 } 

38 } 

39 ) 

40 

41 # -- Run 'apio clean' 

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

43 sb.assert_result_ok(result) 

44 

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

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

47 sb.assert_result_ok(result) 

48 

49 

50def _test_project( 

51 apio_runner: ApioRunner, 

52 *, 

53 remote_proj_dir: bool, 

54 example: str, 

55 testbench_file: str, 

56 bitstream: str, 

57 report_item: str, 

58): 

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

60 architecture. 

61 """ 

62 

63 # pylint: disable=too-many-arguments 

64 # pylint: disable=too-many-statements 

65 

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

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

68 

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

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

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

72 with apio_runner.in_sandbox() as sb: 

73 

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

75 # -- use -p proj_dir arg. 

76 if remote_proj_dir: 

77 os.chdir(sb.sandbox_dir) 

78 sb.proj_dir.rmdir() 

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

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

81 else: 

82 proj_arg = [] 

83 dst_arg = [] 

84 

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

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

87 if remote_proj_dir: 

88 assert not sb.proj_dir.exists() 

89 else: 

90 assert not os.listdir(sb.proj_dir) 

91 

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

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

94 result = sb.invoke_apio_cmd(apio, args) 

95 sb.assert_result_ok(result) 

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

97 assert "fetched successfully" in result.output 

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

99 

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

101 project_files = os.listdir(sb.proj_dir) 

102 

103 # -- 'apio build' 

104 args = ["build"] + proj_arg 

105 result = sb.invoke_apio_cmd(apio, args) 

106 sb.assert_result_ok(result) 

107 assert "SUCCESS" in result.output 

108 assert "yosys -p" in result.output 

109 assert "-DSYNTHESIZE" in result.output 

110 assert "-DAPIO_SIM" not in result.output 

111 

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

113 

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

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" not in result.output 

120 

121 # -- Modify apio.ini 

122 apio_ini_lines = cast( 

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

124 ) 

125 apio_ini_lines.append(" ") 

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

127 

128 # -- 'apio build' 

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

130 args = ["build"] + proj_arg 

131 result = sb.invoke_apio_cmd(apio, args) 

132 sb.assert_result_ok(result) 

133 assert "SUCCESS" in result.output 

134 assert "yosys -p" in result.output 

135 assert "-DSYNTHESIZE" in result.output 

136 assert "-DAPIO_SIM" not in result.output 

137 

138 # -- 'apio lint' 

139 args = ["lint"] + proj_arg 

140 result = sb.invoke_apio_cmd(apio, args) 

141 sb.assert_result_ok(result) 

142 assert "SUCCESS" in result.output 

143 assert "-DSYNTHESIZE" in result.output 

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

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

146 

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

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

149 result = sb.invoke_apio_cmd(apio, args) 

150 sb.assert_result_ok(result) 

151 assert "SUCCESS" in result.output 

152 assert "TOP_MODULE" not in result.output 

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

154 

155 # -- 'apio lint --nosynth' 

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

157 result = sb.invoke_apio_cmd(apio, args) 

158 sb.assert_result_ok(result) 

159 assert "SUCCESS" in result.output 

160 assert "-DSYNTHESIZE" not in result.output 

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

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

163 

164 # -- 'apio format' 

165 args = ["format"] + proj_arg 

166 result = sb.invoke_apio_cmd(apio, args) 

167 sb.assert_result_ok(result) 

168 assert "-DSYNTHESIZE" not in result.output 

169 assert "-DAPIO_SIM" not in result.output 

170 

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

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

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

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

175 result = sb.invoke_apio_cmd(apio, args) 

176 sb.assert_result_ok(result) 

177 

178 # -- 'apio test' 

179 args = ["test"] + proj_arg 

180 result = sb.invoke_apio_cmd(apio, args) 

181 sb.assert_result_ok(result) 

182 assert "SUCCESS" in result.output 

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

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

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

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

187 assert "-DSYNTHESIZE" not in result.output 

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

189 

190 # -- 'apio clean' 

191 args = ["clean"] + proj_arg 

192 result = sb.invoke_apio_cmd(apio, args) 

193 sb.assert_result_ok(result) 

194 assert "Cleanup completed" in result.output 

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

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

197 

198 # -- 'apio test --default' 

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

200 result = sb.invoke_apio_cmd(apio, args) 

201 sb.assert_result_ok(result) 

202 assert "SUCCESS" in result.output 

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

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

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

206 

207 # -- 'apio clean' 

208 args = ["clean"] + proj_arg 

209 result = sb.invoke_apio_cmd(apio, args) 

210 sb.assert_result_ok(result) 

211 assert "Cleanup completed" in result.output 

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

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

214 

215 # -- 'apio sim --no-gtkwave' 

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

217 result = sb.invoke_apio_cmd(apio, args) 

218 sb.assert_result_ok(result) 

219 assert "SUCCESS" in result.output 

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

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

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

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

224 assert "-DSYNTHESIZE" not in result.output 

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

226 

227 # -- 'apio clean' 

228 args = ["clean"] + proj_arg 

229 result = sb.invoke_apio_cmd(apio, args) 

230 sb.assert_result_ok(result) 

231 assert "Cleanup completed" in result.output 

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

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

234 

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

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

237 result = sb.invoke_apio_cmd(apio, args) 

238 sb.assert_result_ok(result) 

239 assert "SUCCESS" in result.output 

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

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

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

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

244 

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

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

247 result = sb.invoke_apio_cmd(apio, args) 

248 sb.assert_result_ok(result) 

249 assert "SUCCESS" in result.output 

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

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

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

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

254 

255 # -- 'apio clean' 

256 args = ["clean"] + proj_arg 

257 result = sb.invoke_apio_cmd(apio, args) 

258 sb.assert_result_ok(result) 

259 assert "Cleanup completed" in result.output 

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

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

262 

263 # -- 'apio report' 

264 args = ["report"] + 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 report_item in result.output 

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

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

273 

274 # -- 'apio graph -n' 

275 args = ["graph", "-n"] + 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 getsize(sb.proj_dir / "_build/default/graph.dot") 

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

283 

284 # -- 'apio clean' 

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

286 args = ["clean"] + proj_arg 

287 result = sb.invoke_apio_cmd(apio, args) 

288 sb.assert_result_ok(result) 

289 assert "Cleanup completed" in result.output 

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

291 

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

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

294 

295 

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

297# subdirectories. This increases the test coverage. 

298def test_project_ice40_local_dir(apio_runner: ApioRunner): 

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

300 dir.""" 

301 _test_project( 

302 apio_runner, 

303 remote_proj_dir=False, 

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

305 testbench_file="main_tb.v", 

306 bitstream="hardware.bin", 

307 report_item="ICESTORM_LC", 

308 ) 

309 

310 

311def test_project_ice40_remote_dir(apio_runner: ApioRunner): 

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

313 the -p option.""" 

314 _test_project( 

315 apio_runner, 

316 remote_proj_dir=True, 

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

318 testbench_file="main_tb.v", 

319 bitstream="hardware.bin", 

320 report_item="ICESTORM_LC", 

321 ) 

322 

323 

324def test_project_ice40_system_verilog(apio_runner: ApioRunner): 

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

326 verilog files.""" 

327 _test_project( 

328 apio_runner, 

329 remote_proj_dir=False, 

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

331 testbench_file="main_tb.sv", 

332 bitstream="hardware.bin", 

333 report_item="ICESTORM_LC", 

334 ) 

335 

336 

337def test_project_ecp5_local_dir(apio_runner: ApioRunner): 

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

339 _test_project( 

340 apio_runner, 

341 remote_proj_dir=False, 

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

343 testbench_file="ledon_tb.v", 

344 bitstream="hardware.bit", 

345 report_item="TRELLIS_IO", 

346 ) 

347 

348 

349def test_project_ecp5_remote_dir(apio_runner: ApioRunner): 

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

351 _test_project( 

352 apio_runner, 

353 remote_proj_dir=True, 

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

355 testbench_file="ledon_tb.v", 

356 bitstream="hardware.bit", 

357 report_item="TRELLIS_IO", 

358 ) 

359 

360 

361def test_project_ecp5_system_verilog(apio_runner: ApioRunner): 

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

363 verilog files.""" 

364 _test_project( 

365 apio_runner, 

366 remote_proj_dir=False, 

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

368 testbench_file="ledon_tb.sv", 

369 bitstream="hardware.bit", 

370 report_item="TRELLIS_IO", 

371 ) 

372 

373 

374def test_project_gowin_local_dir(apio_runner: ApioRunner): 

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

376 _test_project( 

377 apio_runner, 

378 remote_proj_dir=False, 

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

380 testbench_file="blinky_tb.v", 

381 bitstream="hardware.fs", 

382 report_item="LUT4", 

383 ) 

384 

385 

386def test_project_gowin_remote_dir(apio_runner: ApioRunner): 

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

388 _test_project( 

389 apio_runner, 

390 remote_proj_dir=True, 

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

392 testbench_file="blinky_tb.v", 

393 bitstream="hardware.fs", 

394 report_item="LUT4", 

395 ) 

396 

397 

398def test_project_gowin_system_verilog(apio_runner: ApioRunner): 

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

400 verilog files.""" 

401 _test_project( 

402 apio_runner, 

403 remote_proj_dir=False, 

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

405 testbench_file="blinky_tb.sv", 

406 bitstream="hardware.fs", 

407 report_item="LUT4", 

408 ) 

409 

410 

411def test_project_xilinx_local_dir(apio_runner: ApioRunner): 

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

413 dir.""" 

414 _test_project( 

415 apio_runner, 

416 remote_proj_dir=False, 

417 example="basys3/ledon", 

418 testbench_file="ledon_tb.v", 

419 bitstream="hardware.bit", 

420 report_item="PSEUDO_GND", 

421 ) 

422 

423 

424def test_project_xilinx_remote_dir(apio_runner: ApioRunner): 

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

426 the -p option.""" 

427 _test_project( 

428 apio_runner, 

429 remote_proj_dir=True, 

430 example="basys3/ledon", 

431 testbench_file="ledon_tb.v", 

432 bitstream="hardware.bit", 

433 report_item="PSEUDO_GND", 

434 )