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

125 statements  

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

1""" 

2Tests of remote_config.py and it's integration with ApioContext. 

3""" 

4 

5# TODO: Add test coverage of the "refresh-failure-on" logic. 

6 

7import json 

8from datetime import datetime, timedelta 

9from tests.conftest import ApioRunner 

10from apio.managers.remote_config import ( 

11 get_datetime_stamp, 

12 days_between_datetime_stamps, 

13) 

14from apio.utils import util 

15from apio.apio_context import ( 

16 ApioContext, 

17 PackagesPolicy, 

18 ProjectPolicy, 

19 RemoteConfigPolicy, 

20) 

21 

22 

23def get_test_data( 

24 loaded_by_apio_version: str, 

25 loaded_at_days: int, 

26 loaded_from: str, 

27): 

28 """Returns a fake cached-remote-config.json content. 'loaded_at_days' is 

29 the value of the remote config "loaded-at" relative to today in days.""" 

30 loaded_at_datetime = datetime.now() + timedelta(days=loaded_at_days) 

31 loaded_at_stamp = get_datetime_stamp(loaded_at_datetime) 

32 assert isinstance(loaded_at_stamp, str) 

33 

34 return { 

35 "remote-config": { 

36 "packages": { 

37 "drivers": { 

38 "repository": { 

39 "name": "tools-drivers", 

40 "organization": "fpgawars", 

41 }, 

42 "release": { 

43 "tag": "2026-08-07", 

44 "package": "apio-drivers-${PLATFORM}-${YYYYMMDD}.tgz", 

45 }, 

46 }, 

47 "examples": { 

48 "repository": { 

49 "name": "apio-examples", 

50 "organization": "fpgawars", 

51 }, 

52 "release": { 

53 "tag": "2026-08-06", 

54 "package": "apio-examples-${YYYYMMDD}.tgz", 

55 }, 

56 }, 

57 }, 

58 }, 

59 "metadata": { 

60 "loaded-by": loaded_by_apio_version, 

61 "loaded-at": loaded_at_stamp, 

62 "loaded-from": loaded_from, 

63 }, 

64 } 

65 

66 

67def test_datetime_stamp_diff_days(): 

68 """Test the datetime timestamp diff.""" 

69 

70 assert ( 

71 get_datetime_stamp( 

72 datetime(year=2025, month=6, day=30, hour=14, minute=45) 

73 ) 

74 == "2025-06-30-14-45" 

75 ) 

76 

77 ts_now = get_datetime_stamp() 

78 assert ( 

79 days_between_datetime_stamps( 

80 ts_now, 

81 ts_now, 

82 default=9999, 

83 ) 

84 == 0 

85 ) 

86 

87 assert ( 

88 days_between_datetime_stamps( 

89 "2025-06-15-07-30", 

90 "2025-06-16-00-01", 

91 default=9999, 

92 ) 

93 == 1 

94 ) 

95 

96 assert ( 

97 days_between_datetime_stamps( 

98 "2025-06-16-00-01", 

99 "2025-06-15-07-30", 

100 default=9999, 

101 ) 

102 == -1 

103 ) 

104 

105 assert ( 

106 days_between_datetime_stamps( 

107 "2025-06-15-00-00", 

108 "2025-06-15-23-59", 

109 default=9999, 

110 ) 

111 == 0 

112 ) 

113 

114 assert ( 

115 days_between_datetime_stamps( 

116 "2025-06-15-00-0x", 

117 "2025-06-15-23-59", 

118 default=9999, 

119 ) 

120 == 9999 

121 ) 

122 

123 assert ( 

124 days_between_datetime_stamps( 

125 "2025-06-15-20-15", 

126 "2025-06-20-00-01", 

127 default=9999, 

128 ) 

129 == 5 

130 ) 

131 

132 

133def test_cached_config_ok(apio_runner: ApioRunner): 

134 """Tests remote config resolution when the cached config is ok.""" 

135 

136 with apio_runner.in_sandbox() as sb: 

137 

138 # -- Get an actual fresh remote config. 

139 with apio_runner.with_logger() as log: 

140 base_apio_ctx = ApioContext( 

141 project_policy=ProjectPolicy.NO_PROJECT, 

142 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

143 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

144 ) 

145 assert "Fetching" in log.out 

146 remote_config_url = base_apio_ctx.remote_config.metadata["loaded-from"] 

147 

148 # -- Write a test cached remote config. 

149 path = sb.home_dir / "cached-remote-config.json" 

150 test_data = get_test_data( 

151 util.get_apio_version_str(), 0, remote_config_url 

152 ) 

153 sb.write_file( 

154 path, 

155 json.dumps( 

156 test_data, 

157 indent=2, 

158 ), 

159 exists_ok=True, 

160 ) 

161 

162 # -- Init an apio context that should return the test cached 

163 # -- remote config. 

164 with apio_runner.with_logger() as log: 

165 apio_ctx = ApioContext( 

166 project_policy=ProjectPolicy.NO_PROJECT, 

167 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

168 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

169 ) 

170 assert "Cached remote config is unsuitable" not in log.out 

171 assert "Fetching" not in log.out 

172 assert apio_ctx.remote_config.data == test_data["remote-config"] 

173 

174 

175def test_cached_config_different_apio_version(apio_runner: ApioRunner): 

176 """Tests remote config resolution when the cached config is from a 

177 different apio version.""" 

178 

179 with apio_runner.in_sandbox() as sb: 

180 

181 # -- Get an actual fresh remote config. 

182 with apio_runner.with_logger() as log: 

183 base_apio_ctx = ApioContext( 

184 project_policy=ProjectPolicy.NO_PROJECT, 

185 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

186 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

187 ) 

188 assert "Fetching" in log.out 

189 remote_config_url = base_apio_ctx.remote_config.metadata["loaded-from"] 

190 

191 # -- Write a test cached remote config. Using a fake apio version. 

192 path = sb.home_dir / "cached-remote-config.json" 

193 test_data = get_test_data("1.0.0", 0, remote_config_url) 

194 sb.write_file( 

195 path, 

196 json.dumps( 

197 test_data, 

198 indent=2, 

199 ), 

200 exists_ok=True, 

201 ) 

202 

203 # -- Init an apio context that should return the test cached 

204 # -- remote config. 

205 with apio_runner.with_logger() as log: 

206 apio_ctx = ApioContext( 

207 project_policy=ProjectPolicy.NO_PROJECT, 

208 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

209 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

210 ) 

211 assert ( 

212 "No suitable cached remote config file (Apio version mismatch)" 

213 in log.out 

214 ) 

215 assert "Fetching" in log.out 

216 assert apio_ctx.remote_config.data == base_apio_ctx.remote_config.data 

217 

218 

219def test_cached_config_different_apio_src_url( 

220 apio_runner: ApioRunner, 

221): 

222 """Tests remote config resolution when the cached config is from a 

223 different URL.""" 

224 

225 with apio_runner.in_sandbox() as sb: 

226 

227 # -- Get an actual fresh remote config. 

228 with apio_runner.with_logger() as log: 

229 base_apio_ctx = ApioContext( 

230 project_policy=ProjectPolicy.NO_PROJECT, 

231 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

232 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

233 ) 

234 assert "Fetching" in log.out 

235 

236 # -- Write a test cached remote config. Using a fake loaded-from url. 

237 path = sb.home_dir / "cached-remote-config.json" 

238 test_data = get_test_data( 

239 util.get_apio_version_str(), 0, "https://github.com/fake/url.json" 

240 ) 

241 sb.write_file( 

242 path, 

243 json.dumps( 

244 test_data, 

245 indent=2, 

246 ), 

247 exists_ok=True, 

248 ) 

249 

250 # -- Init an apio context that should return the test cached 

251 # -- remote config. 

252 with apio_runner.with_logger() as log: 

253 apio_ctx = ApioContext( 

254 project_policy=ProjectPolicy.NO_PROJECT, 

255 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

256 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

257 ) 

258 assert ( 

259 "No suitable cached remote config file (source URL mismatch)" 

260 in log.out 

261 ) 

262 assert "Fetching" in log.out 

263 assert apio_ctx.remote_config.data == base_apio_ctx.remote_config.data 

264 

265 

266def test_cached_remote_config_too_old(apio_runner: ApioRunner): 

267 """Tests remote config resolution when the cached config is too old.""" 

268 

269 with apio_runner.in_sandbox() as sb: 

270 

271 # -- Get an actual fresh remote config. 

272 with apio_runner.with_logger() as log: 

273 base_apio_ctx = ApioContext( 

274 project_policy=ProjectPolicy.NO_PROJECT, 

275 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

276 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

277 ) 

278 assert "Fetching" in log.out 

279 remote_config_url = base_apio_ctx.remote_config.metadata["loaded-from"] 

280 

281 # -- Write a test cached remote config that is 10 days old. 

282 path = sb.home_dir / "cached-remote-config.json" 

283 test_data = get_test_data( 

284 util.get_apio_version_str(), -10, remote_config_url 

285 ) 

286 sb.write_file( 

287 path, 

288 json.dumps( 

289 test_data, 

290 indent=2, 

291 ), 

292 exists_ok=True, 

293 ) 

294 

295 # -- Init an apio context that should return a fresh remote config. 

296 with apio_runner.with_logger() as log: 

297 apio_ctx = ApioContext( 

298 project_policy=ProjectPolicy.NO_PROJECT, 

299 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

300 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

301 ) 

302 assert "No suitable cached remote config file (stale)" in log.out 

303 assert "Fetching" in log.out 

304 assert apio_ctx.remote_config.data == base_apio_ctx.remote_config.data 

305 

306 

307def test_cached_remote_config_too_new(apio_runner: ApioRunner): 

308 """Tests remote config resolution when the cached config is 

309 from the future.""" 

310 

311 with apio_runner.in_sandbox() as sb: 

312 

313 # -- Get an actual fresh remote config. 

314 with apio_runner.with_logger() as log: 

315 base_apio_ctx = ApioContext( 

316 project_policy=ProjectPolicy.NO_PROJECT, 

317 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

318 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

319 ) 

320 assert "Fetching" in log.out 

321 remote_config_url = base_apio_ctx.remote_config.metadata["loaded-from"] 

322 

323 # -- Write a test cached remote config that was downloaded 10 days 

324 # -- in the future. 

325 path = sb.home_dir / "cached-remote-config.json" 

326 test_data = get_test_data( 

327 util.get_apio_version_str(), 10, remote_config_url 

328 ) 

329 sb.write_file( 

330 path, 

331 json.dumps( 

332 test_data, 

333 indent=2, 

334 ), 

335 exists_ok=True, 

336 ) 

337 

338 # -- Init an apio context that should return a fresh remote config. 

339 with apio_runner.with_logger() as log: 

340 apio_ctx = ApioContext( 

341 project_policy=ProjectPolicy.NO_PROJECT, 

342 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

343 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

344 ) 

345 assert "No suitable cached remote config file (stale)" in log.out 

346 assert "Fetching" in log.out 

347 assert apio_ctx.remote_config.data == base_apio_ctx.remote_config.data 

348 

349 

350def test_corrupt_cached_remote_config(apio_runner: ApioRunner): 

351 """Tests remote config resolution when the cached config file is 

352 corrupt.""" 

353 

354 with apio_runner.in_sandbox() as sb: 

355 

356 # -- Get an actual fresh remote config. 

357 with apio_runner.with_logger() as log: 

358 base_apio_ctx = ApioContext( 

359 project_policy=ProjectPolicy.NO_PROJECT, 

360 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

361 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

362 ) 

363 assert "Fetching" in log.out 

364 

365 # -- Write a test cached remote config that is corrupt. 

366 path = sb.home_dir / "cached-remote-config.json" 

367 test_data = "{ corrupt json file }" 

368 sb.write_file( 

369 path, 

370 json.dumps( 

371 test_data, 

372 indent=2, 

373 ), 

374 exists_ok=True, 

375 ) 

376 

377 # -- Init an apio context that should return a fresh remote config. 

378 with apio_runner.with_logger() as log: 

379 apio_ctx = ApioContext( 

380 project_policy=ProjectPolicy.NO_PROJECT, 

381 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

382 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

383 ) 

384 assert ( 

385 "No suitable cached remote config file (couldn't parse)" in log.out 

386 ) 

387 assert "Fetching" in log.out 

388 assert apio_ctx.remote_config.data == base_apio_ctx.remote_config.data 

389 

390 

391def test_no_cached_remote_config(apio_runner: ApioRunner): 

392 """Tests remote config resolution when there is no cached remote 

393 config file.""" 

394 

395 with apio_runner.in_sandbox() as sb: 

396 

397 # -- Check that the cache file doesn't exist. 

398 path = sb.home_dir / "cached-remote-config.json" 

399 assert not path.exists() 

400 

401 # -- Init an apio context that should return a fresh remote config. 

402 with apio_runner.with_logger() as log: 

403 apio_ctx = ApioContext( 

404 project_policy=ProjectPolicy.NO_PROJECT, 

405 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

406 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

407 ) 

408 assert ( 

409 "No suitable cached remote config file (no cache file)" in log.out 

410 ) 

411 assert "Fetching" in log.out 

412 assert "oss-cad-suite" in apio_ctx.remote_config.data["packages"] 

413 

414 

415def test_forced_fresh_remote_config_ok(apio_runner: ApioRunner): 

416 """Tests remote config resolution when the cached config is ignored 

417 because a fresh config was requested.""" 

418 

419 with apio_runner.in_sandbox() as sb: 

420 

421 # -- Get an actual fresh remote config. 

422 with apio_runner.with_logger() as log: 

423 base_apio_ctx = ApioContext( 

424 project_policy=ProjectPolicy.NO_PROJECT, 

425 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

426 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

427 ) 

428 assert "Fetching" in log.out 

429 remote_config_url = base_apio_ctx.remote_config.metadata["loaded-from"] 

430 

431 # -- Write a test cached remote config. 

432 path = sb.home_dir / "cached-remote-config.json" 

433 test_data = get_test_data( 

434 util.get_apio_version_str(), 0, remote_config_url 

435 ) 

436 sb.write_file( 

437 path, 

438 json.dumps( 

439 test_data, 

440 indent=2, 

441 ), 

442 exists_ok=True, 

443 ) 

444 

445 # -- Init an apio context that should fetch a fresh config. 

446 with apio_runner.with_logger() as log: 

447 apio_ctx = ApioContext( 

448 project_policy=ProjectPolicy.NO_PROJECT, 

449 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

450 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

451 ) 

452 assert "Cached remote config is unsuitable" not in log.out 

453 assert "Fetching" in log.out 

454 assert apio_ctx.remote_config.data == base_apio_ctx.remote_config.data