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

190 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-23 03:53 +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( 

14 apio_runner: ApioRunner, 

15 *, 

16 remote_proj_dir: bool, 

17 example: str, 

18 testbench_file: str, 

19 bitstream: str, 

20 report_item: str, 

21): 

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

23 architecture. 

24 """ 

25 

26 # pylint: disable=too-many-arguments 

27 # pylint: disable=too-many-statements 

28 

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

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

31 

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

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

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

35 with apio_runner.in_sandbox() as sb: 

36 

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

38 # -- use -p proj_dir arg. 

39 if remote_proj_dir: 

40 os.chdir(sb.sandbox_dir) 

41 sb.proj_dir.rmdir() 

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

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

44 else: 

45 proj_arg = [] 

46 dst_arg = [] 

47 

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

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

50 if remote_proj_dir: 

51 assert not sb.proj_dir.exists() 

52 else: 

53 assert not os.listdir(sb.proj_dir) 

54 

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

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

57 result = sb.invoke_apio_cmd(apio, args) 

58 sb.assert_result_ok(result) 

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

60 assert "fetched successfully" in result.output 

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

62 

63 # -- 'apio packages list'. 

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

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

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

67 result = sb.invoke_apio_cmd(apio, args) 

68 sb.assert_result_ok(result) 

69 print(result.output) 

70 

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

72 project_files = os.listdir(sb.proj_dir) 

73 

74 # -- 'apio build' 

75 args = ["build"] + proj_arg 

76 result = sb.invoke_apio_cmd(apio, args) 

77 sb.assert_result_ok(result) 

78 assert "SUCCESS" in result.output 

79 assert "yosys -p" in result.output 

80 assert "-DSYNTHESIZE" in result.output 

81 assert "-DAPIO_SIM" not in result.output 

82 

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

84 

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

86 args = ["build"] + proj_arg 

87 result = sb.invoke_apio_cmd(apio, args) 

88 sb.assert_result_ok(result) 

89 assert "SUCCESS" in result.output 

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

91 

92 # -- Modify apio.ini 

93 apio_ini_lines = cast( 

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

95 ) 

96 apio_ini_lines.append(" ") 

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

98 

99 # -- 'apio build' 

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

101 args = ["build"] + proj_arg 

102 result = sb.invoke_apio_cmd(apio, args) 

103 sb.assert_result_ok(result) 

104 assert "SUCCESS" in result.output 

105 assert "yosys -p" in result.output 

106 assert "-DSYNTHESIZE" in result.output 

107 assert "-DAPIO_SIM" not in result.output 

108 

109 # -- 'apio lint' 

110 args = ["lint"] + proj_arg 

111 result = sb.invoke_apio_cmd(apio, args) 

112 sb.assert_result_ok(result) 

113 assert "SUCCESS" in result.output 

114 assert "-DSYNTHESIZE" in result.output 

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

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

117 

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

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

120 result = sb.invoke_apio_cmd(apio, args) 

121 sb.assert_result_ok(result) 

122 assert "SUCCESS" in result.output 

123 assert "TOP_MODULE" not in result.output 

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

125 

126 # -- 'apio lint --nosynth' 

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

128 result = sb.invoke_apio_cmd(apio, args) 

129 sb.assert_result_ok(result) 

130 assert "SUCCESS" in result.output 

131 assert "-DSYNTHESIZE" not in result.output 

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

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

134 

135 # -- 'apio format' 

136 args = ["format"] + proj_arg 

137 result = sb.invoke_apio_cmd(apio, args) 

138 sb.assert_result_ok(result) 

139 assert "-DSYNTHESIZE" not in result.output 

140 assert "-DAPIO_SIM" not in result.output 

141 

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

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

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

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

146 result = sb.invoke_apio_cmd(apio, args) 

147 sb.assert_result_ok(result) 

148 

149 # -- 'apio test' 

150 args = ["test"] + proj_arg 

151 result = sb.invoke_apio_cmd(apio, args) 

152 sb.assert_result_ok(result) 

153 assert "SUCCESS" in result.output 

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

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

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

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

158 assert "-DSYNTHESIZE" not in result.output 

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

160 

161 # -- 'apio clean' 

162 args = ["clean"] + proj_arg 

163 result = sb.invoke_apio_cmd(apio, args) 

164 sb.assert_result_ok(result) 

165 assert "Cleanup completed" in result.output 

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

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

168 

169 # -- 'apio test --default' 

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

171 result = sb.invoke_apio_cmd(apio, args) 

172 sb.assert_result_ok(result) 

173 assert "SUCCESS" in result.output 

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

178 # -- 'apio clean' 

179 args = ["clean"] + proj_arg 

180 result = sb.invoke_apio_cmd(apio, args) 

181 sb.assert_result_ok(result) 

182 assert "Cleanup completed" in result.output 

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

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

185 

186 # -- 'apio sim --no-gtkwave' 

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

188 result = sb.invoke_apio_cmd(apio, args) 

189 sb.assert_result_ok(result) 

190 assert "SUCCESS" in result.output 

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

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

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

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

195 assert "-DSYNTHESIZE" not in result.output 

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

197 

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

205 

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

207 args = ["test", testbench_file] + 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 

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

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

218 result = sb.invoke_apio_cmd(apio, args) 

219 sb.assert_result_ok(result) 

220 assert "SUCCESS" in result.output 

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

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

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

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

225 

226 # -- 'apio clean' 

227 args = ["clean"] + proj_arg 

228 result = sb.invoke_apio_cmd(apio, args) 

229 sb.assert_result_ok(result) 

230 assert "Cleanup completed" in result.output 

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

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

233 

234 # -- 'apio report' 

235 args = ["report"] + proj_arg 

236 result = sb.invoke_apio_cmd(apio, args) 

237 sb.assert_result_ok(result) 

238 assert "SUCCESS" in result.output 

239 assert "-DSYNTHESIZE" in result.output 

240 assert "-DAPIO_SIM" not in result.output 

241 assert report_item in result.output 

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

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

244 

245 # -- 'apio graph -n' 

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

247 result = sb.invoke_apio_cmd(apio, args) 

248 sb.assert_result_ok(result) 

249 assert "SUCCESS" in result.output 

250 assert "-DSYNTHESIZE" in result.output 

251 assert "-DAPIO_SIM" not in result.output 

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

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

254 

255 # -- 'apio clean' 

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

257 args = ["clean"] + proj_arg 

258 result = sb.invoke_apio_cmd(apio, args) 

259 sb.assert_result_ok(result) 

260 assert "Cleanup completed" in result.output 

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

262 

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

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

265 

266 

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

268# subdirectories. This increases the test coverage. 

269def test_project_ice40_local_dir(apio_runner: ApioRunner): 

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

271 dir.""" 

272 _test_project( 

273 apio_runner, 

274 remote_proj_dir=False, 

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

276 testbench_file="main_tb.v", 

277 bitstream="hardware.bin", 

278 report_item="ICESTORM_LC", 

279 ) 

280 

281 

282def test_project_ice40_remote_dir(apio_runner: ApioRunner): 

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

284 the -p option.""" 

285 _test_project( 

286 apio_runner, 

287 remote_proj_dir=True, 

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

289 testbench_file="main_tb.v", 

290 bitstream="hardware.bin", 

291 report_item="ICESTORM_LC", 

292 ) 

293 

294 

295def test_project_ice40_system_verilog(apio_runner: ApioRunner): 

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

297 verilog files.""" 

298 _test_project( 

299 apio_runner, 

300 remote_proj_dir=False, 

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

302 testbench_file="main_tb.sv", 

303 bitstream="hardware.bin", 

304 report_item="ICESTORM_LC", 

305 ) 

306 

307 

308def test_project_ecp5_local_dir(apio_runner: ApioRunner): 

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

310 _test_project( 

311 apio_runner, 

312 remote_proj_dir=False, 

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

314 testbench_file="ledon_tb.v", 

315 bitstream="hardware.bit", 

316 report_item="TRELLIS_IO", 

317 ) 

318 

319 

320def test_project_ecp5_remote_dir(apio_runner: ApioRunner): 

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

322 _test_project( 

323 apio_runner, 

324 remote_proj_dir=True, 

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

326 testbench_file="ledon_tb.v", 

327 bitstream="hardware.bit", 

328 report_item="TRELLIS_IO", 

329 ) 

330 

331 

332def test_project_ecp5_system_verilog(apio_runner: ApioRunner): 

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

334 verilog files.""" 

335 _test_project( 

336 apio_runner, 

337 remote_proj_dir=False, 

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

339 testbench_file="ledon_tb.sv", 

340 bitstream="hardware.bit", 

341 report_item="TRELLIS_IO", 

342 ) 

343 

344 

345def test_project_gowin_local_dir(apio_runner: ApioRunner): 

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

347 _test_project( 

348 apio_runner, 

349 remote_proj_dir=False, 

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

351 testbench_file="blinky_tb.v", 

352 bitstream="hardware.fs", 

353 report_item="LUT4", 

354 ) 

355 

356 

357def test_project_gowin_remote_dir(apio_runner: ApioRunner): 

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

359 _test_project( 

360 apio_runner, 

361 remote_proj_dir=True, 

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

363 testbench_file="blinky_tb.v", 

364 bitstream="hardware.fs", 

365 report_item="LUT4", 

366 ) 

367 

368 

369def test_project_gowin_system_verilog(apio_runner: ApioRunner): 

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

371 verilog files.""" 

372 _test_project( 

373 apio_runner, 

374 remote_proj_dir=False, 

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

376 testbench_file="blinky_tb.sv", 

377 bitstream="hardware.fs", 

378 report_item="LUT4", 

379 ) 

380 

381 

382def test_project_xilinx_local_dir(apio_runner: ApioRunner): 

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

384 dir.""" 

385 _test_project( 

386 apio_runner, 

387 remote_proj_dir=False, 

388 example="basys3/ledon", 

389 testbench_file="ledon_tb.v", 

390 bitstream="hardware.bit", 

391 report_item="PSEUDO_GND", 

392 ) 

393 

394 

395def test_project_xilinx_remote_dir(apio_runner: ApioRunner): 

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

397 the -p option.""" 

398 _test_project( 

399 apio_runner, 

400 remote_proj_dir=True, 

401 example="basys3/ledon", 

402 testbench_file="ledon_tb.v", 

403 bitstream="hardware.bit", 

404 report_item="PSEUDO_GND", 

405 )