Coverage for tests/unit_tests/managers/test_apio_definitions.py: 100%

132 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-23 03:53 +0000

1""" 

2Tests of apio_definitions.py 

3""" 

4 

5from pytest import raises 

6from google.protobuf.json_format import MessageToDict 

7from tests.conftest import ApioRunner 

8from apio.common import proto_util 

9from apio.common.proto.apio_definitions_pb2 import FpgaDefinition 

10from apio.apio_context import ( 

11 ApioContext, 

12 PackagesPolicy, 

13 ProjectPolicy, 

14 RemoteConfigPolicy, 

15) 

16 

17 

18def test_default_loading_no_project(apio_runner: ApioRunner): 

19 """Tests loading of apio standard definitions with no project.""" 

20 

21 with apio_runner.in_sandbox(): 

22 

23 apio_ctx = ApioContext( 

24 project_policy=ProjectPolicy.NO_PROJECT, 

25 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

26 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

27 ) 

28 

29 assert not apio_ctx.has_project 

30 assert apio_ctx.definitions is not None 

31 assert "alhambra-ii" in apio_ctx.definitions.boards 

32 assert "ice40hx4k-tq144-8k" in apio_ctx.definitions.fpgas 

33 assert "openfpgaloader" in apio_ctx.definitions.programmers 

34 

35 

36def test_default_loading_with_project(apio_runner: ApioRunner): 

37 """Tests loading of apio standard definitions with a project and 

38 without project's custom definition.""" 

39 

40 with apio_runner.in_sandbox() as sb: 

41 

42 # -- Fake apio project file. 

43 sb.write_default_apio_ini() 

44 

45 apio_ctx = ApioContext( 

46 project_policy=ProjectPolicy.PROJECT_REQUIRED, 

47 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

48 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

49 ) 

50 

51 assert apio_ctx.has_project 

52 assert apio_ctx.definitions is not None 

53 assert "alhambra-ii" in apio_ctx.definitions.boards 

54 assert "ice40hx4k-tq144-8k" in apio_ctx.definitions.fpgas 

55 assert "openfpgaloader" in apio_ctx.definitions.programmers 

56 

57 

58def test_loading_with_custom_boards(apio_runner: ApioRunner): 

59 """Tests loading of apio standard definitions with a with project's 

60 custom boards definitions. 

61 """ 

62 

63 with apio_runner.in_sandbox() as sb: 

64 

65 # -- Fake apio project file. 

66 sb.write_default_apio_ini() 

67 

68 # -- Write fake custom boards 

69 board_definition1 = { 

70 "description": "My Custom Alhambra II", 

71 "fpga-id": "ice40hx4k-tq144-8k", 

72 "programmer": { 

73 "id": "openfpgaloader", 

74 }, 

75 "usb": { 

76 "vid": "0403", 

77 "pid": "6010", 

78 }, 

79 } 

80 

81 board_definition2 = { 

82 "description": "My new board", 

83 "fpga-id": "lfe5u-45f-6bg381c", 

84 "programmer": { 

85 "id": "openfpgaloader", 

86 }, 

87 } 

88 

89 sb.write_json_file( 

90 "boards.jsonc", 

91 { 

92 # -- Overrides a standard definition 

93 "alhambra-ii": board_definition1, 

94 # -- Adds a new definition 

95 "my-custom-board": board_definition2, 

96 }, 

97 ) 

98 

99 # -- Load 

100 apio_ctx = ApioContext( 

101 project_policy=ProjectPolicy.PROJECT_REQUIRED, 

102 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

103 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

104 ) 

105 

106 # -- Verify 

107 definitions = apio_ctx.definitions 

108 assert definitions is not None 

109 

110 assert apio_ctx.has_project 

111 assert "ice40hx4k-tq144-8k" in definitions.fpgas 

112 assert "openfpgaloader" in definitions.programmers 

113 

114 assert ( 

115 MessageToDict(definitions.boards["alhambra-ii"]) 

116 == board_definition1 

117 ) 

118 assert ( 

119 MessageToDict(definitions.boards["my-custom-board"]) 

120 == board_definition2 

121 ) 

122 

123 assert definitions.is_custom_board("alhambra-ii") 

124 assert definitions.is_custom_board("my-custom-board") 

125 assert not definitions.is_custom_board("sipeed-tang-nano-9k") 

126 

127 

128def test_loading_with_custom_fpgas(apio_runner: ApioRunner): 

129 """Tests loading of apio standard definitions with a with project's 

130 custom fpgas definitions. 

131 """ 

132 

133 with apio_runner.in_sandbox() as sb: 

134 

135 # -- Fake apio project file. 

136 sb.write_default_apio_ini() 

137 

138 # -- Write fake custom boards 

139 fpga_json1 = { 

140 "part-num": "ICE40HX4K-TQ144", 

141 "arch": "ice40", 

142 "size": "99k", # Overriding 8k 

143 "ice40-params": {"type": "hx8k", "package": "tq144:4k"}, 

144 } 

145 

146 fpga_json2 = { 

147 "part-num": "MY-CUSTOM-FPGA", 

148 "arch": "ice40", 

149 "size": "4k", 

150 "ice40-params": {"type": "hx4k", "package": "bg121"}, 

151 } 

152 

153 sb.write_json_file( 

154 "fpgas.jsonc", 

155 { 

156 # -- Overrides a standard definition 

157 "ice40hx4k-tq144-8k": fpga_json1, 

158 # -- Adds a new definition 

159 "my-custom-fpga": fpga_json2, 

160 }, 

161 ) 

162 

163 # -- Load 

164 apio_ctx = ApioContext( 

165 project_policy=ProjectPolicy.PROJECT_REQUIRED, 

166 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

167 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

168 ) 

169 

170 # -- Verify 

171 definitions = apio_ctx.definitions 

172 assert definitions is not None 

173 

174 assert apio_ctx.has_project 

175 assert "alhambra-ii" in definitions.boards 

176 assert "openfpgaloader" in definitions.programmers 

177 

178 # TODO: Use proto ascii literals instead of converting from json 

179 fpga_proto1 = proto_util.proto_from_json_dict( 

180 fpga_json1, FpgaDefinition, "Failed to parse fpga_json1" 

181 ) 

182 fpga_proto2 = proto_util.proto_from_json_dict( 

183 fpga_json2, FpgaDefinition, "Failed to parse fpga_json2" 

184 ) 

185 

186 assert definitions.fpgas["ice40hx4k-tq144-8k"] == fpga_proto1 

187 assert definitions.fpgas["my-custom-fpga"] == fpga_proto2 

188 

189 assert definitions.is_custom_fpga("ice40hx4k-tq144-8k") 

190 assert definitions.is_custom_fpga("my-custom-fpga") 

191 assert not definitions.is_custom_fpga("ice40lp1k-cm36") 

192 

193 

194def test_loading_with_custom_programmer(apio_runner: ApioRunner): 

195 """Tests loading of apio standard definitions with a with project's 

196 custom programmer definitions. 

197 """ 

198 

199 with apio_runner.in_sandbox() as sb: 

200 

201 # -- Fake apio project file. 

202 sb.write_default_apio_ini() 

203 

204 # -- Write fake custom programmers 

205 programmer_info1 = { 

206 "command": "custom-iceprog", 

207 "args": "-d d:${BUS}/${DEV}", 

208 } 

209 programmer_info2 = {"command": "iceprog", "args": "my custom args"} 

210 

211 sb.write_json_file( 

212 "programmers.jsonc", 

213 { 

214 # -- Overrides a standard definition 

215 "iceprog": programmer_info1, 

216 # -- Adds a new definition 

217 "my-custom-programmer": programmer_info2, 

218 }, 

219 ) 

220 

221 # -- Load 

222 apio_ctx = ApioContext( 

223 project_policy=ProjectPolicy.PROJECT_REQUIRED, 

224 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

225 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

226 ) 

227 

228 # -- Verify 

229 definitions = apio_ctx.definitions 

230 assert definitions is not None 

231 

232 assert apio_ctx.has_project 

233 assert "alhambra-ii" in definitions.boards 

234 assert "ice40hx4k-tq144-8k" in definitions.fpgas 

235 

236 programmer_definition1 = definitions.programmers["iceprog"] 

237 assert ( 

238 proto_util.proto_to_json_dict(programmer_definition1) 

239 == programmer_info1 

240 ) 

241 

242 programmer_definition2 = definitions.programmers[ 

243 "my-custom-programmer" 

244 ] 

245 assert ( 

246 proto_util.proto_to_json_dict(programmer_definition2) 

247 == programmer_info2 

248 ) 

249 

250 assert definitions.is_custom_programmer("iceprog") 

251 assert definitions.is_custom_programmer("my-custom-programmer") 

252 assert not definitions.is_custom_programmer("openfpgaloader") 

253 

254 

255def test_loading_invalid_custom_board(apio_runner: ApioRunner): 

256 """Tests loading attempt of an invalid board custom definition.""" 

257 

258 with apio_runner.in_sandbox() as sb: 

259 

260 # -- Fake apio project file. 

261 sb.write_default_apio_ini() 

262 

263 # -- Write a custom definition file with an invalid definition 

264 # -- Missing description field. 

265 sb.write_json_file( 

266 "boards.jsonc", 

267 { 

268 "alhambra-ii": { 

269 "fpga-id": "ice40hx4k-tq144-8k", 

270 "programmer": { 

271 "id": "openfpgaloader", 

272 }, 

273 "usb": { 

274 "vid": "0403", 

275 "pid": "6010", 

276 }, 

277 } 

278 }, 

279 ) 

280 

281 # -- Test 

282 with apio_runner.with_logger() as log: 

283 with raises(SystemExit) as e: 

284 _ = ApioContext( 

285 project_policy=ProjectPolicy.PROJECT_REQUIRED, 

286 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

287 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

288 ) 

289 

290 # -- Verify 

291 assert e.value.code == 1 

292 assert "Missing required field 'description'" in log.out 

293 

294 

295def test_loading_invalid_custom_fpga(apio_runner: ApioRunner): 

296 """Tests loading attempt of an invalid board custom definition.""" 

297 

298 with apio_runner.in_sandbox() as sb: 

299 

300 # -- Fake apio project file. 

301 sb.write_default_apio_ini() 

302 

303 # -- Write a custom definition file with an invalid definition 

304 # -- Missing part-num field. 

305 sb.write_json_file( 

306 "fpgas.jsonc", 

307 { 

308 "ice40lp384-cm36": { 

309 "arch": "ice40", 

310 "size": "384", 

311 "ice40-params": {"type": "lp384", "package": "cm36"}, 

312 } 

313 }, 

314 ) 

315 

316 # -- Test 

317 with apio_runner.with_logger() as log: 

318 with raises(SystemExit) as e: 

319 _ = ApioContext( 

320 project_policy=ProjectPolicy.PROJECT_REQUIRED, 

321 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

322 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

323 ) 

324 

325 # -- Verify 

326 assert e.value.code == 1 

327 assert "Missing required field 'part-num'" in log.out 

328 

329 

330def test_loading_invalid_custom_programmer(apio_runner: ApioRunner): 

331 """Tests loading attempt of an invalid programmer custom definition.""" 

332 

333 with apio_runner.in_sandbox() as sb: 

334 

335 # -- Fake apio project file. 

336 sb.write_default_apio_ini() 

337 

338 # -- Write a custom definition file with an invalid definition 

339 # -- Missing command field. 

340 sb.write_json_file( 

341 "programmers.jsonc", 

342 { 

343 "openfpgaloader": { 

344 "args": "--force-terminal-mode --verify", 

345 } 

346 }, 

347 ) 

348 

349 # -- Test 

350 with apio_runner.with_logger() as log: 

351 with raises(SystemExit) as e: 

352 _ = ApioContext( 

353 project_policy=ProjectPolicy.PROJECT_REQUIRED, 

354 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

355 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

356 ) 

357 

358 # -- Verify 

359 assert e.value.code == 1 

360 assert "Missing required field 'command'" in log.out 

361 

362 

363def test_loading_invalid_custom_board_id(apio_runner: ApioRunner): 

364 """Tests loading attempt of an invalid board id.""" 

365 

366 with apio_runner.in_sandbox() as sb: 

367 

368 # -- Fake apio project file. 

369 sb.write_default_apio_ini() 

370 

371 # -- Write a custom definition file with an invalid definition 

372 # -- Missing description field. 

373 sb.write_json_file( 

374 "boards.jsonc", 

375 { 

376 "invalid-ID": { 

377 "description": "BlackIce MX", 

378 "fpga-id": "ice40hx4k-tq144-8k", 

379 "programmer": {"id": "blackiceprog"}, 

380 "usb": {"vid": "0483", "pid": "5740"}, 

381 } 

382 }, 

383 ) 

384 

385 # -- Test 

386 with apio_runner.with_logger() as log: 

387 with raises(SystemExit) as e: 

388 _ = ApioContext( 

389 project_policy=ProjectPolicy.PROJECT_REQUIRED, 

390 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

391 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

392 ) 

393 

394 # -- Verify 

395 assert e.value.code == 1 

396 assert "Board id `invalid-ID` has an invalid format" in log.out 

397 

398 

399def test_loading_invalid_custom_fpga_id(apio_runner: ApioRunner): 

400 """Tests loading attempt of an invalid fpga id.""" 

401 

402 with apio_runner.in_sandbox() as sb: 

403 

404 # -- Fake apio project file. 

405 sb.write_default_apio_ini() 

406 

407 # -- Write a custom definition file with an invalid definition 

408 # -- Missing description field. 

409 sb.write_json_file( 

410 "fpgas.jsonc", 

411 { 

412 "invalid-ID": { 

413 "part-num": "ICE40LP1K-SWG16TR", 

414 "arch": "ice40", 

415 "size": "1k", 

416 "ice40-params": {"type": "lp1k", "package": "swg16tr"}, 

417 } 

418 }, 

419 ) 

420 

421 # -- Test 

422 with apio_runner.with_logger() as log: 

423 

424 with raises(SystemExit) as e: 

425 _ = ApioContext( 

426 project_policy=ProjectPolicy.PROJECT_REQUIRED, 

427 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

428 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

429 ) 

430 

431 # -- Verify 

432 assert e.value.code == 1 

433 assert "FPGA id has an invalid format: invalid-ID" in log.out 

434 

435 

436def test_loading_invalid_custom_programmer_id(apio_runner: ApioRunner): 

437 """Tests loading attempt of an invalid programmer id.""" 

438 

439 with apio_runner.in_sandbox() as sb: 

440 

441 # -- Fake apio project file. 

442 sb.write_default_apio_ini() 

443 

444 # -- Write a custom definition file with an invalid definition 

445 # -- Missing description field. 

446 sb.write_json_file( 

447 "programmers.jsonc", 

448 { 

449 "invalid-ID": { 

450 "command": "openFPGALoader", 

451 "args": "--force-terminal-mode --verify", 

452 } 

453 }, 

454 ) 

455 

456 # -- Test 

457 with apio_runner.with_logger() as log: 

458 with raises(SystemExit) as e: 

459 _ = ApioContext( 

460 project_policy=ProjectPolicy.PROJECT_REQUIRED, 

461 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

462 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

463 ) 

464 

465 # -- Verify 

466 assert e.value.code == 1 

467 assert "Programmer id has an invalid format: invalid-ID" in log.out