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

192 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-25 02:31 +0000

1""" 

2Test various "apio" commands. 

3""" 

4 

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 

10 

11 

12def test_project_with_legacy_board_id(apio_runner: ApioRunner): 

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

14 

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: 

19 

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) 

25 

26 # -- Run 'apio build' 

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

28 sb.assert_result_ok(result) 

29 

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 ) 

39 

40 # -- Run 'apio clean' 

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

42 sb.assert_result_ok(result) 

43 

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

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

46 sb.assert_result_ok(result) 

47 

48 

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 """ 

61 

62 # pylint: disable=too-many-arguments 

63 # pylint: disable=too-many-statements 

64 

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

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

67 

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: 

72 

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 = [] 

83 

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) 

90 

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

98 

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

100 project_files = os.listdir(sb.proj_dir) 

101 

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 

108 assert "-DSYNTHESIZE" in result.output 

109 assert "-DAPIO_SIM" not in result.output 

110 

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

112 

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

114 args = ["build"] + proj_arg 

115 result = sb.invoke_apio_cmd(apio, args) 

116 sb.assert_result_ok(result) 

117 assert "SUCCESS" in result.output 

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

119 

120 # -- Modify apio.ini 

121 apio_ini_lines = sb.read_file( 

122 sb.proj_dir / "apio.ini", lines_mode=True 

123 ) 

124 apio_ini_lines.append(" ") 

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

126 

127 # -- 'apio build' 

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

129 args = ["build"] + proj_arg 

130 result = sb.invoke_apio_cmd(apio, args) 

131 sb.assert_result_ok(result) 

132 assert "SUCCESS" in result.output 

133 assert "yosys -p" in result.output 

134 assert "-DSYNTHESIZE" in result.output 

135 assert "-DAPIO_SIM" not in result.output 

136 

137 # -- 'apio lint' 

138 args = ["lint"] + proj_arg 

139 result = sb.invoke_apio_cmd(apio, args) 

140 sb.assert_result_ok(result) 

141 assert "SUCCESS" in result.output 

142 assert "-DSYNTHESIZE" in result.output 

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

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

145 

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

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

148 result = sb.invoke_apio_cmd(apio, args) 

149 sb.assert_result_ok(result) 

150 assert "SUCCESS" in result.output 

151 assert "TOP_MODULE" not in result.output 

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

153 

154 # -- 'apio lint --nosynth' 

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

156 result = sb.invoke_apio_cmd(apio, args) 

157 sb.assert_result_ok(result) 

158 assert "SUCCESS" in result.output 

159 assert "-DSYNTHESIZE" not in result.output 

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

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

162 

163 # -- 'apio format' 

164 args = ["format"] + proj_arg 

165 result = sb.invoke_apio_cmd(apio, args) 

166 sb.assert_result_ok(result) 

167 assert "-DSYNTHESIZE" not in result.output 

168 assert "-DAPIO_SIM" not in result.output 

169 

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

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

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

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

174 result = sb.invoke_apio_cmd(apio, args) 

175 sb.assert_result_ok(result) 

176 

177 # -- 'apio test' 

178 args = ["test"] + proj_arg 

179 result = sb.invoke_apio_cmd(apio, args) 

180 sb.assert_result_ok(result) 

181 assert "SUCCESS" in result.output 

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

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

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

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

186 assert "-DSYNTHESIZE" not in result.output 

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

188 

189 # -- 'apio clean' 

190 args = ["clean"] + proj_arg 

191 result = sb.invoke_apio_cmd(apio, args) 

192 sb.assert_result_ok(result) 

193 assert "Cleanup completed" in result.output 

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

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

196 

197 # -- 'apio test --default' 

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

199 result = sb.invoke_apio_cmd(apio, args) 

200 sb.assert_result_ok(result) 

201 assert "SUCCESS" in result.output 

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

206 # -- 'apio clean' 

207 args = ["clean"] + proj_arg 

208 result = sb.invoke_apio_cmd(apio, args) 

209 sb.assert_result_ok(result) 

210 assert "Cleanup completed" in result.output 

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

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

213 

214 # -- 'apio sim --no-gtkwave' 

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

216 result = sb.invoke_apio_cmd(apio, args) 

217 sb.assert_result_ok(result) 

218 assert "SUCCESS" in result.output 

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

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

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

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

223 assert "-DSYNTHESIZE" not in result.output 

224 assert "-DAPIO_SIM=1" 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 test <testbench-file>' 

235 args = ["test", 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 sim --no-gtkw <testbench-file>' 

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

246 result = sb.invoke_apio_cmd(apio, args) 

247 sb.assert_result_ok(result) 

248 assert "SUCCESS" in result.output 

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

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

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

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

253 

254 # -- 'apio clean' 

255 args = ["clean"] + proj_arg 

256 result = sb.invoke_apio_cmd(apio, args) 

257 sb.assert_result_ok(result) 

258 assert "Cleanup completed" in result.output 

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

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

261 

262 # -- 'apio report' 

263 args = ["report"] + proj_arg 

264 result = sb.invoke_apio_cmd(apio, args) 

265 sb.assert_result_ok(result) 

266 assert "SUCCESS" in result.output 

267 assert "-DSYNTHESIZE" in result.output 

268 assert "-DAPIO_SIM" not in result.output 

269 assert report_item in result.output 

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

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

272 

273 # -- 'apio graph -n' 

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

275 result = sb.invoke_apio_cmd(apio, args) 

276 sb.assert_result_ok(result) 

277 assert "SUCCESS" in result.output 

278 assert "-DSYNTHESIZE" in result.output 

279 assert "-DAPIO_SIM" not in result.output 

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

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

282 

283 # -- 'apio clean' 

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

285 args = ["clean"] + proj_arg 

286 result = sb.invoke_apio_cmd(apio, args) 

287 sb.assert_result_ok(result) 

288 assert "Cleanup completed" in result.output 

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

290 

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

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

293 

294 

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

296# subdirectories. This increases the test coverage. 

297def test_project_ice40_local_dir(apio_runner: ApioRunner): 

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

299 dir.""" 

300 _test_project( 

301 apio_runner, 

302 remote_proj_dir=False, 

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

304 testbench_file="main_tb.v", 

305 bitstream="hardware.bin", 

306 report_item="ICESTORM_LC", 

307 ) 

308 

309 

310def test_project_ice40_remote_dir(apio_runner: ApioRunner): 

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

312 the -p option.""" 

313 _test_project( 

314 apio_runner, 

315 remote_proj_dir=True, 

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

317 testbench_file="main_tb.v", 

318 bitstream="hardware.bin", 

319 report_item="ICESTORM_LC", 

320 ) 

321 

322 

323def test_project_ice40_system_verilog(apio_runner: ApioRunner): 

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

325 verilog files.""" 

326 _test_project( 

327 apio_runner, 

328 remote_proj_dir=False, 

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

330 testbench_file="main_tb.sv", 

331 bitstream="hardware.bin", 

332 report_item="ICESTORM_LC", 

333 ) 

334 

335 

336def test_project_ecp5_local_dir(apio_runner: ApioRunner): 

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

338 _test_project( 

339 apio_runner, 

340 remote_proj_dir=False, 

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

342 testbench_file="ledon_tb.v", 

343 bitstream="hardware.bit", 

344 report_item="ALU54B", 

345 ) 

346 

347 

348def test_project_ecp5_remote_dir(apio_runner: ApioRunner): 

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

350 _test_project( 

351 apio_runner, 

352 remote_proj_dir=True, 

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_system_verilog(apio_runner: ApioRunner): 

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

362 verilog files.""" 

363 _test_project( 

364 apio_runner, 

365 remote_proj_dir=False, 

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

367 testbench_file="ledon_tb.sv", 

368 bitstream="hardware.bit", 

369 report_item="ALU54B", 

370 ) 

371 

372 

373def test_project_gowin_local_dir(apio_runner: ApioRunner): 

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

375 _test_project( 

376 apio_runner, 

377 remote_proj_dir=False, 

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

379 testbench_file="blinky_tb.v", 

380 bitstream="hardware.fs", 

381 report_item="LUT4", 

382 ) 

383 

384 

385def test_project_gowin_remote_dir(apio_runner: ApioRunner): 

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

387 _test_project( 

388 apio_runner, 

389 remote_proj_dir=True, 

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_system_verilog(apio_runner: ApioRunner): 

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

399 verilog files.""" 

400 _test_project( 

401 apio_runner, 

402 remote_proj_dir=False, 

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

404 testbench_file="blinky_tb.sv", 

405 bitstream="hardware.fs", 

406 report_item="LUT4", 

407 )