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

73 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-08 02:47 +0000

1""" 

2Tests of project.py 

3""" 

4 

5from typing import Dict, Optional, Tuple 

6from pytest import LogCaptureFixture 

7import pytest 

8from tests.conftest import ApioRunner 

9from apio.managers.project import Project, ENV_OPTIONS_SPEC 

10from apio.common.apio_console import cunstyle 

11from apio.apio_context import ( 

12 ApioContext, 

13 PackagesPolicy, 

14 ProjectPolicy, 

15 RemoteConfigPolicy, 

16) 

17 

18# TODO: Add more tests. 

19 

20 

21def load_apio_ini( 

22 apio_ini: Dict[str, Dict[str, str]], 

23 env_arg: Optional[str], 

24 apio_runner: ApioRunner, 

25 capsys: LogCaptureFixture, 

26) -> Tuple[Project, str]: 

27 """A helper function load apio.ini. Returns (project, stdout)""" 

28 

29 with apio_runner.in_sandbox() as sb: 

30 # -- Create the apio.ini file 

31 sb.write_apio_ini(apio_ini) 

32 

33 # -- Try to create the context with the project info. 

34 capsys.readouterr() # Reset capture 

35 apio_ctx = ApioContext( 

36 project_policy=ProjectPolicy.PROJECT_REQUIRED, 

37 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

38 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

39 env_arg=env_arg, 

40 ) 

41 

42 # -- Return the values. 

43 return ( 

44 apio_ctx.project, 

45 cunstyle(capsys.readouterr().out), 

46 ) 

47 

48 

49def test_env_options_specs(): 

50 """Tests the option spec keys match options specs names""" 

51 

52 for name, env_options_spec in ENV_OPTIONS_SPEC.items(): 

53 assert name == env_options_spec.name 

54 

55 

56def test_all_options_env(apio_runner: ApioRunner, capsys: LogCaptureFixture): 

57 """Tests an apio.ini with all options""" 

58 

59 apio_ini = { 

60 "[env:default]": { 

61 "board": "alhambra-ii", # required. 

62 "default-testbench": "main_tb.v", 

63 "defines": "\n aaa=111\n bbb=222", 

64 "format-verible-options": "\n --aaa bbb\n --ccc ddd", 

65 "programmer-cmd": "iceprog ${VID}:${PID}", 

66 "top-module": "my_module", 

67 "yosys-synth-extra-options": "-dsp -xyz", 

68 "nextpnr-extra-options": "--freq 13", 

69 "gtkwave-extra-options": "--rcvar=do_initial_zoom_fit 1", 

70 "constraint-file": "pinout.lpf", 

71 } 

72 } 

73 

74 # -- Make sure we covered all the options. 

75 assert len(apio_ini["[env:default]"]) == len(ENV_OPTIONS_SPEC) 

76 

77 project, _ = load_apio_ini( 

78 apio_ini=apio_ini, 

79 env_arg=None, 

80 apio_runner=apio_runner, 

81 capsys=capsys, 

82 ) 

83 

84 assert project.env_name == "default" 

85 assert project.env_options == { 

86 "board": "alhambra-ii", 

87 "default-testbench": "main_tb.v", 

88 "defines": ["aaa=111", "bbb=222"], 

89 "format-verible-options": ["--aaa bbb", "--ccc ddd"], 

90 "programmer-cmd": "iceprog ${VID}:${PID}", 

91 "top-module": "my_module", 

92 "yosys-synth-extra-options": ["-dsp -xyz"], 

93 "nextpnr-extra-options": ["--freq 13"], 

94 "gtkwave-extra-options": ["--rcvar=do_initial_zoom_fit 1"], 

95 "constraint-file": "pinout.lpf", 

96 } 

97 

98 # -- Try a few as dict lookup on the project object. 

99 assert project.get_str_option("board") == "alhambra-ii" 

100 assert project.get_str_option("top-module") == "my_module" 

101 assert project.get_str_option("constraint-file") == "pinout.lpf" 

102 

103 

104def test_required_options_only_env( 

105 apio_runner: ApioRunner, capsys: LogCaptureFixture 

106): 

107 """Tests a minimal apio.ini with required only options.""" 

108 

109 project, _ = load_apio_ini( 

110 apio_ini={ 

111 "[env:default]": { 

112 "board": "alhambra-ii", 

113 "top-module": "main", 

114 } 

115 }, 

116 env_arg=None, 

117 apio_runner=apio_runner, 

118 capsys=capsys, 

119 ) 

120 

121 assert project.env_name == "default" 

122 assert project.env_options == { 

123 "board": "alhambra-ii", 

124 "top-module": "main", 

125 } 

126 

127 

128def test_list_options(apio_runner: ApioRunner, capsys: LogCaptureFixture): 

129 """Tests list optionss.""" 

130 

131 project, _ = load_apio_ini( 

132 apio_ini={ 

133 "[env:default]": { 

134 "board": "alhambra-ii", 

135 "top-module": "my_top_module", 

136 "yosys-synth-extra-options": " k1=v1 k2=v2 \n\n k3=v3 \n\n", 

137 "nextpnr-extra-options": " k5=v5 k6=v6 \n\n k7=v7 \n\n", 

138 } 

139 }, 

140 env_arg=None, 

141 apio_runner=apio_runner, 

142 capsys=capsys, 

143 ) 

144 

145 assert project.get_list_option("yosys-synth-extra-options") == [ 

146 "k1=v1 k2=v2", 

147 "k3=v3", 

148 ] 

149 

150 assert project.get_list_option("nextpnr-extra-options") == [ 

151 "k5=v5 k6=v6", 

152 "k7=v7", 

153 ] 

154 

155 

156def test_legacy_board_id(apio_runner: ApioRunner, capsys: LogCaptureFixture): 

157 """Tests with 'board' option having a legacy board id. It should 

158 be converted to the canonical board id""" 

159 

160 project, stdout = load_apio_ini( 

161 apio_ini={ 

162 "[env:default]": { 

163 "board": "iCE40-HX8K", 

164 "top-module": "my_top_module", 

165 } 

166 }, 

167 env_arg=None, 

168 apio_runner=apio_runner, 

169 capsys=capsys, 

170 ) 

171 

172 assert project.env_name == "default" 

173 assert project.env_options == { 

174 "board": "ice40-hx8k", 

175 "top-module": "my_top_module", 

176 } 

177 assert ( 

178 "Warning: 'Board iCE40-HX8K' was renamed to 'ice40-hx8k'. " 

179 "Please update apio.ini" in stdout 

180 ) 

181 

182 

183def test_legacy_apio_ini(apio_runner: ApioRunner, capsys: LogCaptureFixture): 

184 """Tests with an old style apio.ini that has a single [env] section.""" 

185 

186 project, stdout = load_apio_ini( 

187 apio_ini={ 

188 "[env]": { 

189 "board": "alhambra-ii", 

190 "top-module": "main", 

191 } 

192 }, 

193 env_arg=None, 

194 apio_runner=apio_runner, 

195 capsys=capsys, 

196 ) 

197 

198 assert project.env_name == "default" 

199 assert project.env_options == { 

200 "board": "alhambra-ii", 

201 "top-module": "main", 

202 } 

203 assert ( 

204 "Warning: Apio.ini has a legacy [env] section. " 

205 "Please rename it to [env:default]" in stdout 

206 ) 

207 

208 

209def test_first_env_is_default( 

210 apio_runner: ApioRunner, capsys: LogCaptureFixture 

211): 

212 """Tests that with no --env and no default-env, the first env in 

213 apio.ini is selected""" 

214 

215 project, _ = load_apio_ini( 

216 apio_ini={ 

217 "[common]": { 

218 "default-testbench": "main_tb.v", 

219 }, 

220 "[env:env1]": { 

221 "board": "alhambra-ii", 

222 "top-module": "module1", 

223 }, 

224 "[env:env2]": { 

225 "board": "ice40-hx8k", 

226 "top-module": "module2", 

227 }, 

228 }, 

229 env_arg=None, 

230 apio_runner=apio_runner, 

231 capsys=capsys, 

232 ) 

233 

234 assert project.env_name == "env1" 

235 assert project.env_options == { 

236 "default-testbench": "main_tb.v", 

237 "board": "alhambra-ii", 

238 "top-module": "module1", 

239 } 

240 

241 

242def test_env_selection_from_apio_ini( 

243 apio_runner: ApioRunner, capsys: LogCaptureFixture 

244): 

245 """Tests that with no --env, and with default env defined in apio.ini 

246 using default-env option.""" 

247 

248 project, _ = load_apio_ini( 

249 apio_ini={ 

250 "[apio]": { 

251 "default-env": "env2", 

252 }, 

253 "[common]": { 

254 "default-testbench": "main_tb.v", 

255 }, 

256 "[env:env1]": { 

257 "board": "alhambra-ii", 

258 "top-module": "module1", 

259 }, 

260 "[env:env2]": { 

261 "board": "ice40-hx8k", 

262 "top-module": "module2", 

263 }, 

264 }, 

265 env_arg=None, 

266 apio_runner=apio_runner, 

267 capsys=capsys, 

268 ) 

269 

270 assert project.env_name == "env2" 

271 assert project.env_options == { 

272 "default-testbench": "main_tb.v", 

273 "board": "ice40-hx8k", 

274 "top-module": "module2", 

275 } 

276 

277 

278def test_env_selection_from_env_arg( 

279 apio_runner: ApioRunner, capsys: LogCaptureFixture 

280): 

281 """Tests that with --env overriding default-env in apio.ini.""" 

282 

283 project, _ = load_apio_ini( 

284 apio_ini={ 

285 "[apio]": { 

286 "default-env": "env1", 

287 }, 

288 "[common]": { 

289 "default-testbench": "main_tb.v", 

290 }, 

291 "[env:env1]": { 

292 "board": "alhambra-ii", 

293 "top-module": "module1", 

294 }, 

295 "[env:env2]": { 

296 "board": "ice40-hx8k", 

297 "top-module": "module2", 

298 }, 

299 }, 

300 env_arg="env2", # --env env2 

301 apio_runner=apio_runner, 

302 capsys=capsys, 

303 ) 

304 

305 assert project.env_name == "env2" 

306 assert project.env_options == { 

307 "default-testbench": "main_tb.v", 

308 "board": "ice40-hx8k", 

309 "top-module": "module2", 

310 } 

311 

312 

313def error_tester( 

314 env_arg: Optional[str], 

315 apio_ini: Dict[str, Dict[str, str]], 

316 expected_error: str, 

317 apio_runner: ApioRunner, 

318 capsys: LogCaptureFixture, 

319): 

320 """A helper function to tests apio.ini content that is expected to 

321 exit with an error.""" 

322 

323 with apio_runner.in_sandbox() as sb: 

324 # -- Create the apio.ini file 

325 sb.write_apio_ini(apio_ini) 

326 

327 # -- Try to create the context with the project info. 

328 capsys.readouterr() # Reset capture 

329 with pytest.raises(SystemExit) as e: 

330 ApioContext( 

331 project_policy=ProjectPolicy.PROJECT_REQUIRED, 

332 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

333 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

334 env_arg=env_arg, 

335 ) 

336 

337 # -- Check the errors. 

338 capture = cunstyle(capsys.readouterr().out) 

339 assert e.value.code == 1 

340 assert expected_error in capture 

341 

342 

343def test_validation_errors(apio_runner: ApioRunner, capsys: LogCaptureFixture): 

344 """Tests the validation of apio.ini errors.""" 

345 

346 # -- No [env:name] section. 

347 error_tester( 

348 env_arg=None, 

349 apio_ini={ 

350 "[common]": { 

351 "board": "alhambra-ii", 

352 "top-module": "main", 

353 } 

354 }, 

355 expected_error=( 

356 "Error: Project file 'apio.ini' should have at " 

357 "least one [env:name] section." 

358 ), 

359 apio_runner=apio_runner, 

360 capsys=capsys, 

361 ) 

362 

363 # -- Unknown board id. 

364 error_tester( 

365 env_arg=None, 

366 apio_ini={ 

367 "[env:default]": { 

368 "board": "no-such-board", 

369 } 

370 }, 

371 expected_error="Error: Unknown board id 'no-such-board' in apio.ini", 

372 apio_runner=apio_runner, 

373 capsys=capsys, 

374 ) 

375 

376 # -- Env name has an invalid char (Uppercase). 

377 error_tester( 

378 env_arg=None, 

379 apio_ini={ 

380 "[env:Default]": { 

381 "board": "alhambra-ii", 

382 "top-module": "main", 

383 } 

384 }, 

385 expected_error="Error: Invalid env name 'Default'", 

386 apio_runner=apio_runner, 

387 capsys=capsys, 

388 ) 

389 

390 # -- Env name has an extra space. 

391 error_tester( 

392 env_arg=None, 

393 apio_ini={ 

394 "[env: default]": { 

395 "board": "alhambra-ii", 

396 "top-module": "main", 

397 } 

398 }, 

399 expected_error="Error: Invalid env name ' default'", 

400 apio_runner=apio_runner, 

401 capsys=capsys, 

402 ) 

403 

404 # -- default-env points to a non existing env. 

405 error_tester( 

406 env_arg=None, 

407 apio_ini={ 

408 "[apio]": { 

409 "default-env": "no-such-env", 

410 }, 

411 "[env:default]": { 

412 "board": "alhambra-ii", 

413 "top-module": "main", 

414 }, 

415 }, 

416 expected_error="Error: Env 'no-such-env' not found in apio.ini", 

417 apio_runner=apio_runner, 

418 capsys=capsys, 

419 ) 

420 

421 # -- Missing required option. 

422 error_tester( 

423 env_arg=None, 

424 apio_ini={ 

425 "[env:default]": {"top-module": "main"}, 

426 }, 

427 expected_error="Error: Missing required option 'board' for " 

428 "env 'default'.", 

429 apio_runner=apio_runner, 

430 capsys=capsys, 

431 ) 

432 

433 # -- env_arg has a non existing env name. 

434 error_tester( 

435 env_arg="no-such-env", 

436 apio_ini={ 

437 "[env:default]": { 

438 "board": "alhambra-ii", 

439 "top-module": "main", 

440 }, 

441 }, 

442 expected_error="Error: Env 'no-such-env' not found in apio.ini", 

443 apio_runner=apio_runner, 

444 capsys=capsys, 

445 ) 

446 

447 # -- env_arg has an env name with an invalid char (upper case). 

448 error_tester( 

449 env_arg="Default", 

450 apio_ini={ 

451 "[env:default]": { 

452 "board": "alhambra-ii", 

453 "top-module": "main", 

454 }, 

455 }, 

456 expected_error="Error: Invalid --env value 'Default'", 

457 apio_runner=apio_runner, 

458 capsys=capsys, 

459 )