Coverage for tests/unit_tests/commands/test_apio_api.py: 100%

206 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"""Test for the "apio api" command.""" 

10 

11# NOTE: 'apio api scan-devices' require apio packages (for libusb1 lib) 

12# and thus, is tested in the integration tests. 

13 

14import json 

15import os 

16from pathlib import Path 

17from tests.conftest import ApioRunner 

18from apio.commands.apio import apio_top_cli as apio 

19from apio.utils import env_options 

20 

21# TODO: Add more tests 

22 

23 

24def test_apio_api_get_boards(apio_runner: ApioRunner): 

25 """Test "apio api get-boards" """ 

26 

27 with apio_runner.in_sandbox() as sb: 

28 

29 # -- Execute "apio api get-boards -t xyz" (stdout) 

30 result = sb.invoke_apio_cmd(apio, ["api", "get-boards", "-t", "xyz"]) 

31 sb.assert_result_ok(result) 

32 assert "xyz" in result.output 

33 assert "alhambra-ii" in result.output 

34 

35 # -- Execute "apio api get-boards -t xyz -o <dir>" (file) 

36 path = sb.proj_dir / "apio.json" 

37 result = sb.invoke_apio_cmd( 

38 apio, ["api", "get-boards", "-t", "xyz", "-o", str(path)] 

39 ) 

40 sb.assert_result_ok(result) 

41 

42 # -- Read and verify the file. 

43 text = sb.read_file_text(path) 

44 data = json.loads(text) 

45 assert data["timestamp"] == "xyz" 

46 assert data["boards"]["alhambra-ii"] == { 

47 "description": "Alhambra II", 

48 "fpga": { 

49 "id": "ice40hx4k-tq144-8k", 

50 "definition": { 

51 "arch": "ice40", 

52 "vendor-part": "ICE40HX4K-TQ144", 

53 "size": "8k", 

54 "ice40-params": { 

55 "nextpnr-type": "hx8k", 

56 "nextpnr-package": "tq144:4k", 

57 }, 

58 }, 

59 }, 

60 "programmer": { 

61 "id": "openfpgaloader", 

62 "definition": { 

63 "command": "openFPGALoader", 

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

65 }, 

66 }, 

67 } 

68 

69 

70def test_apio_api_get_fpgas(apio_runner: ApioRunner): 

71 """Test "apio api get-fpgas" """ 

72 

73 with apio_runner.in_sandbox() as sb: 

74 

75 # -- Execute "apio api get-fpgas -t xyz" (stdout) 

76 result = sb.invoke_apio_cmd(apio, ["api", "get-fpgas", "-t", "xyz"]) 

77 sb.assert_result_ok(result) 

78 assert "xyz" in result.output 

79 assert "ice40hx4k-tq144-8k" in result.output 

80 

81 # -- Execute "apio api get-fpgas -t xyz -o <dir>" (file) 

82 path = sb.proj_dir / "apio.json" 

83 result = sb.invoke_apio_cmd( 

84 apio, ["api", "get-fpgas", "-t", "xyz", "-o", str(path)] 

85 ) 

86 sb.assert_result_ok(result) 

87 

88 # -- Read and verify the file. 

89 text = sb.read_file_text(path) 

90 data = json.loads(text) 

91 assert data["timestamp"] == "xyz" 

92 assert data["fpgas"]["ice40hx4k-tq144-8k"] == { 

93 "arch": "ice40", 

94 "vendor-part": "ICE40HX4K-TQ144", 

95 "size": "8k", 

96 "ice40-params": { 

97 "nextpnr-type": "hx8k", 

98 "nextpnr-package": "tq144:4k", 

99 }, 

100 } 

101 

102 

103def test_apio_api_get_programmers(apio_runner: ApioRunner): 

104 """Test "apio api get-programmers" """ 

105 

106 with apio_runner.in_sandbox() as sb: 

107 

108 # -- Execute "apio api get-programmers -t xyz" (stdout) 

109 result = sb.invoke_apio_cmd( 

110 apio, ["api", "get-programmers", "-t", "xyz"] 

111 ) 

112 sb.assert_result_ok(result) 

113 assert "xyz" in result.output 

114 assert "openfpgaloader" in result.output 

115 

116 # -- Execute "apio api get-programmers -t xyz -o <dir>" (file) 

117 path = sb.proj_dir / "apio.json" 

118 result = sb.invoke_apio_cmd( 

119 apio, ["api", "get-programmers", "-t", "xyz", "-o", str(path)] 

120 ) 

121 sb.assert_result_ok(result) 

122 

123 # -- Read and verify the file. 

124 text = sb.read_file_text(path) 

125 data = json.loads(text) 

126 assert data["timestamp"] == "xyz" 

127 assert data["programmers"]["openfpgaloader"] == { 

128 "command": "openFPGALoader", 

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

130 } 

131 

132 

133def test_apio_api_get_commands(apio_runner: ApioRunner): 

134 """Test "apio api get-commands" """ 

135 

136 with apio_runner.in_sandbox() as sb: 

137 

138 # -- Execute "apio api get-commands -t xyz" (stdout) 

139 result = sb.invoke_apio_cmd(apio, ["api", "get-commands", "-t", "xyz"]) 

140 sb.assert_result_ok(result) 

141 assert "xyz" in result.output 

142 assert '"apio"' in result.output 

143 assert '"api"' in result.output 

144 assert '"get-boards"' in result.output 

145 

146 # -- Execute "apio api get-commands -t xyz -o <dir>" (file) 

147 path = sb.proj_dir / "apio.json" 

148 result = sb.invoke_apio_cmd( 

149 apio, ["api", "get-commands", "-t", "xyz", "-o", str(path)] 

150 ) 

151 sb.assert_result_ok(result) 

152 

153 # -- Read and verify the file. 

154 text = sb.read_file_text(path) 

155 data = json.loads(text) 

156 assert data["timestamp"] == "xyz" 

157 assert ( 

158 data["commands"]["apio"]["commands"]["api"]["commands"][ 

159 "get-boards" 

160 ] 

161 == {} 

162 ) 

163 

164 

165def test_apio_api_get_packages(apio_runner: ApioRunner): 

166 """Test "apio api get-packages" """ 

167 

168 with apio_runner.in_sandbox() as sb: 

169 

170 # -- Execute "apio api get-packages -t xyz" (stdout) 

171 result = sb.invoke_apio_cmd(apio, ["api", "get-packages", "-t", "xyz"]) 

172 sb.assert_result_ok(result) 

173 assert '"packages":' in result.output 

174 assert '"oss-cad-suite":' in result.output 

175 

176 # -- Execute "apio api get-packages -t xyz -o <dir>" (file) 

177 path = sb.proj_dir / "apio.json" 

178 result = sb.invoke_apio_cmd( 

179 apio, ["api", "get-packages", "-t", "xyz", "-o", str(path)] 

180 ) 

181 sb.assert_result_ok(result) 

182 

183 # -- Read and verify the file. 

184 text = sb.read_file_text(path) 

185 data = json.loads(text) 

186 assert data["timestamp"] == "xyz" 

187 assert ( 

188 "yosys-release-tag" 

189 in data["packages"]["oss-cad-suite"]["build-info"] 

190 ) 

191 

192 

193def test_apio_api_get_config(apio_runner: ApioRunner): 

194 """Test "apio api get-config" """ 

195 

196 with apio_runner.in_sandbox() as sb: 

197 

198 # -- Execute "apio api get-config -t xyz" (stdout) 

199 result = sb.invoke_apio_cmd(apio, ["api", "get-config", "-t", "xyz"]) 

200 sb.assert_result_ok(result) 

201 assert '"config":' in result.output 

202 assert '"YOSYS_LIB":' in result.output 

203 

204 # -- Execute "apio api get-config -t xyz -o <dir>" (file) 

205 path = sb.proj_dir / "apio.json" 

206 result = sb.invoke_apio_cmd( 

207 apio, ["api", "get-config", "-t", "xyz", "-o", str(path)] 

208 ) 

209 sb.assert_result_ok(result) 

210 

211 # -- Read and verify the file. 

212 text = sb.read_file_text(path) 

213 data = json.loads(text) 

214 assert data["timestamp"] == "xyz" 

215 assert "YOSYS_LIB" in data["config"]["platform-env"]["define-consts"] 

216 

217 

218def test_apio_api_get_system(apio_runner: ApioRunner): 

219 """Test "apio api get-system" """ 

220 

221 with apio_runner.in_sandbox() as sb: 

222 

223 # -- Execute "apio api get-system -t xyz" (stdout) 

224 result = sb.invoke_apio_cmd(apio, ["api", "get-system", "-t", "xyz"]) 

225 sb.assert_result_ok(result) 

226 assert "xyz" in result.output 

227 assert '"apio-cli-version"' in result.output 

228 assert '"python-version"' in result.output 

229 

230 # -- Execute "apio api get-system -t xyz -o <dir>" (file) 

231 path = sb.proj_dir / "apio.json" 

232 result = sb.invoke_apio_cmd( 

233 apio, ["api", "get-system", "-t", "xyz", "-o", str(path)] 

234 ) 

235 sb.assert_result_ok(result) 

236 

237 # -- Read and verify the file. 

238 text = sb.read_file_text(path) 

239 data = json.loads(text) 

240 assert data["timestamp"] == "xyz" 

241 assert data["system"]["remote-config-file-url"].endswith(".jsonc") 

242 # -- APIO_DEFINITIONS may be predefined by the developer. 

243 expected_dir = env_options.get( 

244 env_options.APIO_DEFINITIONS, str(sb.packages_dir / "definitions") 

245 ) 

246 assert Path(data["system"]["apio-definitions-dir"]) == Path( 

247 str(expected_dir) 

248 ) 

249 

250 

251def test_apio_api_get_project(apio_runner: ApioRunner): 

252 """Test "apio api get-project" """ 

253 

254 with apio_runner.in_sandbox() as sb: 

255 

256 # -- Create a fake apio project 

257 sb.write_default_apio_ini() 

258 

259 sb.write_file("synth0.v", "") 

260 sb.write_file("tb_0.sv", "") 

261 

262 os.makedirs("src1") 

263 sb.write_file("src1/synth1.sv", "") 

264 sb.write_file("src1/synth2.sv", "") 

265 sb.write_file("src1/tb1_tb.sv", "") 

266 

267 # -- Execute "apio api get-project -t xyz" (stdout) 

268 result = sb.invoke_apio_cmd(apio, ["api", "get-project", "-t", "xyz"]) 

269 sb.assert_result_ok(result) 

270 assert '"default"' in result.output 

271 assert '"envs"' in result.output 

272 

273 # -- Execute "apio api get-project -t xyz -o <dir>" (file) 

274 path = sb.proj_dir / "apio.json" 

275 result = sb.invoke_apio_cmd( 

276 apio, ["api", "get-project", "-t", "xyz", "-o", str(path)] 

277 ) 

278 sb.assert_result_ok(result) 

279 

280 # -- Read and verify the file. 

281 text = sb.read_file_text(path) 

282 data = json.loads(text) 

283 

284 print("\nActual data:") 

285 print(json.dumps(data, indent=2)) 

286 print() 

287 

288 print(data) 

289 

290 assert data == { 

291 "timestamp": "xyz", 

292 "project": { 

293 "active-env": { 

294 "name": "default", 

295 "options": {"board": "alhambra-ii", "top-module": "main"}, 

296 }, 

297 "envs": ["default"], 

298 "synth-files": [ 

299 "synth0.v", 

300 "tb_0.sv", 

301 f"src1{os.sep}synth1.sv", 

302 f"src1{os.sep}synth2.sv", 

303 ], 

304 "test-benches": [f"src1{os.sep}tb1_tb.sv"], 

305 "board": { 

306 "id": "alhambra-ii", 

307 "is-custom": False, 

308 "definition": { 

309 "description": "Alhambra II", 

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

311 "programmer": { 

312 "id": "openfpgaloader", 

313 "extra-args": "-b ice40_generic --vid ${VID} " 

314 + "--pid ${PID} --busdev-num ${BUS}:${DEV}", 

315 }, 

316 "usb": { 

317 "vid": "0403", 

318 "pid": "6010", 

319 "product-regex": "^Alhambra II.*", 

320 }, 

321 }, 

322 }, 

323 "fpga": { 

324 "id": "ice40hx4k-tq144-8k", 

325 "is-custom": False, 

326 "definition": { 

327 "arch": "ice40", 

328 "vendor-part": "ICE40HX4K-TQ144", 

329 "size": "8k", 

330 "ice40-params": { 

331 "nextpnr-type": "hx8k", 

332 "nextpnr-package": "tq144:4k", 

333 }, 

334 }, 

335 }, 

336 "programmer": { 

337 "id": "openfpgaloader", 

338 "is-custom": False, 

339 "definition": { 

340 "command": "openFPGALoader", 

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

342 }, 

343 }, 

344 }, 

345 } 

346 

347 

348def test_apio_api_get_build_report(apio_runner: ApioRunner): 

349 """Test "apio api get-project" """ 

350 

351 with apio_runner.in_sandbox() as sb: 

352 

353 # -- Execute "apio examples fetch alhambra-ii/blinky" 

354 result = sb.invoke_apio_cmd( 

355 apio, ["examples", "fetch", "alhambra-ii/blinky"] 

356 ) 

357 sb.assert_result_ok(result) 

358 

359 # -- Execute "apio build" 

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

361 sb.assert_result_ok(result) 

362 

363 # -- Execute "apio api get-build-report -t xyz" (stdout) 

364 result = sb.invoke_apio_cmd( 

365 apio, ["api", "get-build-report", "-t", "xyz"] 

366 ) 

367 sb.assert_result_ok(result) 

368 

369 # -- Sanity check the output 

370 assert '"env": "default"' in result.output 

371 assert '"ICESTORM_LC":' in result.output 

372 assert '"sys_clk":' in result.output 

373 assert '"fmax_mhz":' in result.output 

374 

375 # -- Execute "apio api get-build-report -t xyz -o <dir>" (file) 

376 path = sb.proj_dir / "apio.json" 

377 result = sb.invoke_apio_cmd( 

378 apio, ["api", "get-build-report", "-t", "xyz", "-o", str(path)] 

379 ) 

380 sb.assert_result_ok(result) 

381 

382 # -- Read and sanity check the file. 

383 text = sb.read_file_text(path) 

384 data = json.loads(text) 

385 print(data) 

386 

387 assert data["timestamp"] == "xyz" 

388 assert data["build-report"]["env"] == "default" 

389 assert ( 

390 data["build-report"]["resources"]["ICESTORM_LC"]["available"] 

391 == 7680 

392 ) 

393 assert "fmax_mhz" in data["build-report"]["clocks"]["sys_clk"] 

394 

395 

396def test_apio_api_get_build_report_invalid_utilization( 

397 apio_runner: ApioRunner, 

398): 

399 """Test "apio api get-build-report" with invalid utilization values in 

400 hardware.pnr. Percentages that can't be computed or are out of range 

401 should be reported as null.""" 

402 

403 with apio_runner.in_sandbox() as sb: 

404 

405 sb.write_default_apio_ini() 

406 sb.write_json_file( 

407 "_build/default/hardware.pnr", 

408 { 

409 "utilization": { 

410 "ICESTORM_LC": {"available": 7680, "used": 768}, 

411 "ICESTORM_PLL": {"available": 0, "used": 2}, 

412 "SB_IO": {"available": 10, "used": 20}, 

413 }, 

414 "fmax": {}, 

415 }, 

416 ) 

417 

418 # -- Execute "apio api get-build-report -o <file>" 

419 path = sb.proj_dir / "apio.json" 

420 result = sb.invoke_apio_cmd( 

421 apio, ["api", "get-build-report", "-o", str(path)] 

422 ) 

423 sb.assert_result_ok(result) 

424 

425 # -- Check the percentages. 

426 resources = json.loads(sb.read_file_text(path))["build-report"][ 

427 "resources" 

428 ] 

429 assert resources["ICESTORM_LC"]["percentage"] == 10.0 

430 assert resources["ICESTORM_PLL"]["percentage"] is None 

431 assert resources["SB_IO"]["percentage"] is None 

432 

433 

434def test_apio_api_get_examples(apio_runner: ApioRunner): 

435 """Test "apio api get-examples" """ 

436 

437 with apio_runner.in_sandbox() as sb: 

438 

439 # -- Execute "apio api get-examples -t xyz" (stdout) 

440 result = sb.invoke_apio_cmd(apio, ["api", "get-examples", "-t", "xyz"]) 

441 sb.assert_result_ok(result) 

442 assert "xyz" in result.output 

443 assert '"alhambra-ii"' in result.output 

444 assert '"blinky"' in result.output 

445 

446 # -- Execute "apio api get-examples -t xyz -s boards -o <dir>" (file) 

447 path = sb.proj_dir / "apio.json" 

448 result = sb.invoke_apio_cmd( 

449 apio, ["api", "get-examples", "-t", "xyz", "-o", str(path)] 

450 ) 

451 sb.assert_result_ok(result) 

452 

453 # -- Read and verify the file. 

454 text = sb.read_file_text(path) 

455 data = json.loads(text) 

456 assert data["timestamp"] == "xyz" 

457 assert ( 

458 data["examples"]["alhambra-ii"]["blinky"]["description"] 

459 == "Blinking led" 

460 ) 

461 

462 

463def test_apio_api_scan_path(apio_runner: ApioRunner): 

464 """Test "apio api scan-path" """ 

465 

466 with apio_runner.in_sandbox() as sb: 

467 # -- Execute "apio api scan-path -t xyz" (stdout) 

468 result = sb.invoke_apio_cmd(apio, ["api", "scan-path", "-t", "xyz"]) 

469 sb.assert_result_ok(result) 

470 assert "xyz" in result.output 

471 assert '"conflicts"' in result.output 

472 assert '"duplicates"' in result.output 

473 

474 # -- Execute "apio api scan-path -t xyz -o <path>" 

475 path = sb.proj_dir / "apio.json" 

476 result = sb.invoke_apio_cmd( 

477 apio, ["api", "scan-path", "-t", "xyz", "-o", str(path)] 

478 ) 

479 sb.assert_result_ok(result) 

480 

481 # -- Read and verify the file. 

482 text = sb.read_file_text(path) 

483 data = json.loads(text) 

484 assert data["timestamp"] == "xyz" 

485 assert len(data["apio-path-scan"]["path-dirs"]) > 0 

486 

487 

488def test_apio_api_scan_devices(apio_runner: ApioRunner): 

489 """Test "apio api scan-devices" """ 

490 

491 with apio_runner.in_sandbox() as sb: 

492 

493 # -- Execute "apio api scan-devices -t xyz". We run it in a 

494 # -- subprocess such that it releases the libusb1 file it uses. 

495 # -- This also means that it's not included in the pytest test 

496 # -- coverage report. 

497 result = sb.invoke_apio_cmd( 

498 apio, ["api", "scan-devices", "-t", "xyz"], in_subprocess=True 

499 ) 

500 sb.assert_result_ok(result) 

501 

502 assert "xyz" in result.output 

503 assert "usb-devices" in result.output 

504 assert "serial-devices" in result.output 

505 

506 # -- Execute "apio api get-boards -t xyz -s boards -o <dir>" (file) 

507 path = sb.proj_dir / "apio.json" 

508 

509 result = sb.invoke_apio_cmd( 

510 apio, 

511 ["api", "scan-devices", "-t", "xyz", "-o", str(path)], 

512 in_subprocess=True, 

513 ) 

514 sb.assert_result_ok(result) 

515 

516 # -- Read and verify the output file. Since we don't know what 

517 # -- devices the platform has, we just check for the section keys. 

518 text = sb.read_file_text(path) 

519 data = json.loads(text) 

520 assert data["timestamp"] == "xyz" 

521 assert "usb-devices" in data 

522 assert "serial-devices" in data 

523 

524 

525def test_apio_api_echo(apio_runner: ApioRunner): 

526 """Test "apio api echo" """ 

527 

528 with apio_runner.in_sandbox() as sb: 

529 result = sb.invoke_apio_cmd( 

530 apio, 

531 ["api", "echo", "-t", "Hello world", "-s", "OK"], 

532 in_subprocess=True, 

533 ) 

534 sb.assert_result_ok(result) 

535 

536 assert "Hello world" in result.output