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

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

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

11""" 

12 

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

14 

15import json 

16from datetime import datetime, timedelta 

17from tests.conftest import ApioRunner 

18from apio.common.proto_util import proto_to_json_dict 

19from apio.managers.remote_config import ( 

20 get_datetime_stamp, 

21 days_between_datetime_stamps, 

22) 

23from apio.utils import util 

24from apio.apio_context import ( 

25 ApioContext, 

26 PackagesPolicy, 

27 ProjectPolicy, 

28 RemoteConfigPolicy, 

29) 

30 

31 

32def get_test_data( 

33 loaded_by_apio_version: str, 

34 loaded_at_days: int, 

35 loaded_from: str, 

36): 

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

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

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

40 loaded_at_stamp = get_datetime_stamp(loaded_at_datetime) 

41 assert isinstance(loaded_at_stamp, str) 

42 

43 return { 

44 "remote-config": { 

45 "packages": { 

46 "drivers": { 

47 "repository": { 

48 "name": "tools-drivers", 

49 "organization": "fpgawars", 

50 }, 

51 "release": { 

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

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

54 }, 

55 }, 

56 "examples": { 

57 "repository": { 

58 "name": "apio-examples", 

59 "organization": "fpgawars", 

60 }, 

61 "release": { 

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

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

64 }, 

65 }, 

66 }, 

67 }, 

68 "metadata": { 

69 "loaded-by": loaded_by_apio_version, 

70 "loaded-at": loaded_at_stamp, 

71 "loaded-from": loaded_from, 

72 }, 

73 } 

74 

75 

76def test_datetime_stamp_diff_days(): 

77 """Test the datetime timestamp diff.""" 

78 

79 assert ( 

80 get_datetime_stamp( 

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

82 ) 

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

84 ) 

85 

86 ts_now = get_datetime_stamp() 

87 assert ( 

88 days_between_datetime_stamps( 

89 ts_now, 

90 ts_now, 

91 default=9999, 

92 ) 

93 == 0 

94 ) 

95 

96 assert ( 

97 days_between_datetime_stamps( 

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

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

100 default=9999, 

101 ) 

102 == 1 

103 ) 

104 

105 assert ( 

106 days_between_datetime_stamps( 

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

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

109 default=9999, 

110 ) 

111 == -1 

112 ) 

113 

114 assert ( 

115 days_between_datetime_stamps( 

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

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

118 default=9999, 

119 ) 

120 == 0 

121 ) 

122 

123 assert ( 

124 days_between_datetime_stamps( 

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

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

127 default=9999, 

128 ) 

129 == 9999 

130 ) 

131 

132 assert ( 

133 days_between_datetime_stamps( 

134 "2025-06-15-20-15", 

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

136 default=9999, 

137 ) 

138 == 5 

139 ) 

140 

141 

142def test_cached_config_ok(apio_runner: ApioRunner): 

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

144 

145 with apio_runner.in_sandbox() as sb: 

146 

147 # -- Get an actual fresh remote config. 

148 with apio_runner.with_logger() as log: 

149 base_apio_ctx = ApioContext( 

150 project_policy=ProjectPolicy.NO_PROJECT, 

151 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

152 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

153 ) 

154 assert "Fetching" in log.out 

155 remote_config_url = base_apio_ctx.remote_config.metadata.loaded_from 

156 

157 # -- Write a test cached remote config. 

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

159 test_data = get_test_data( 

160 util.get_apio_version_str(), 0, remote_config_url 

161 ) 

162 sb.write_file( 

163 path, 

164 json.dumps( 

165 test_data, 

166 indent=2, 

167 ), 

168 exists_ok=True, 

169 ) 

170 

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

172 # -- remote config. 

173 with apio_runner.with_logger() as log: 

174 apio_ctx = ApioContext( 

175 project_policy=ProjectPolicy.NO_PROJECT, 

176 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

177 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

178 ) 

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

180 assert "Fetching" not in log.out 

181 assert ( 

182 proto_to_json_dict(apio_ctx.remote_config.data) 

183 == test_data["remote-config"] 

184 ) 

185 

186 

187def test_cached_config_different_apio_version(apio_runner: ApioRunner): 

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

189 different apio version.""" 

190 

191 with apio_runner.in_sandbox() as sb: 

192 

193 # -- Get an actual fresh remote config. 

194 with apio_runner.with_logger() as log: 

195 base_apio_ctx = ApioContext( 

196 project_policy=ProjectPolicy.NO_PROJECT, 

197 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

198 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

199 ) 

200 assert "Fetching" in log.out 

201 remote_config_url = base_apio_ctx.remote_config.metadata.loaded_from 

202 

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

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

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

206 sb.write_file( 

207 path, 

208 json.dumps( 

209 test_data, 

210 indent=2, 

211 ), 

212 exists_ok=True, 

213 ) 

214 

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

216 # -- remote config. 

217 with apio_runner.with_logger() as log: 

218 apio_ctx = ApioContext( 

219 project_policy=ProjectPolicy.NO_PROJECT, 

220 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

221 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

222 ) 

223 assert ( 

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

225 in log.out 

226 ) 

227 assert "Fetching" in log.out 

228 assert apio_ctx.remote_config.data == base_apio_ctx.remote_config.data 

229 

230 

231def test_cached_config_different_apio_src_url( 

232 apio_runner: ApioRunner, 

233): 

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

235 different URL.""" 

236 

237 with apio_runner.in_sandbox() as sb: 

238 

239 # -- Get an actual fresh remote config. 

240 with apio_runner.with_logger() as log: 

241 base_apio_ctx = ApioContext( 

242 project_policy=ProjectPolicy.NO_PROJECT, 

243 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

244 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

245 ) 

246 assert "Fetching" in log.out 

247 

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

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

250 test_data = get_test_data( 

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

252 ) 

253 sb.write_file( 

254 path, 

255 json.dumps( 

256 test_data, 

257 indent=2, 

258 ), 

259 exists_ok=True, 

260 ) 

261 

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

263 # -- remote config. 

264 with apio_runner.with_logger() as log: 

265 apio_ctx = ApioContext( 

266 project_policy=ProjectPolicy.NO_PROJECT, 

267 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

268 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

269 ) 

270 assert ( 

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

272 in log.out 

273 ) 

274 assert "Fetching" in log.out 

275 assert apio_ctx.remote_config.data == base_apio_ctx.remote_config.data 

276 

277 

278def test_cached_remote_config_too_old(apio_runner: ApioRunner): 

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

280 

281 with apio_runner.in_sandbox() as sb: 

282 

283 # -- Get an actual fresh remote config. 

284 with apio_runner.with_logger() as log: 

285 base_apio_ctx = ApioContext( 

286 project_policy=ProjectPolicy.NO_PROJECT, 

287 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

288 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

289 ) 

290 assert "Fetching" in log.out 

291 remote_config_url = base_apio_ctx.remote_config.metadata.loaded_from 

292 

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

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

295 test_data = get_test_data( 

296 util.get_apio_version_str(), -10, remote_config_url 

297 ) 

298 sb.write_file( 

299 path, 

300 json.dumps( 

301 test_data, 

302 indent=2, 

303 ), 

304 exists_ok=True, 

305 ) 

306 

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

308 with apio_runner.with_logger() as log: 

309 apio_ctx = ApioContext( 

310 project_policy=ProjectPolicy.NO_PROJECT, 

311 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

312 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

313 ) 

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

315 assert "Fetching" in log.out 

316 assert apio_ctx.remote_config.data == base_apio_ctx.remote_config.data 

317 

318 

319def test_cached_remote_config_too_new(apio_runner: ApioRunner): 

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

321 from the future.""" 

322 

323 with apio_runner.in_sandbox() as sb: 

324 

325 # -- Get an actual fresh remote config. 

326 with apio_runner.with_logger() as log: 

327 base_apio_ctx = ApioContext( 

328 project_policy=ProjectPolicy.NO_PROJECT, 

329 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

330 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

331 ) 

332 assert "Fetching" in log.out 

333 remote_config_url = base_apio_ctx.remote_config.metadata.loaded_from 

334 

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

336 # -- in the future. 

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

338 test_data = get_test_data( 

339 util.get_apio_version_str(), 10, remote_config_url 

340 ) 

341 sb.write_file( 

342 path, 

343 json.dumps( 

344 test_data, 

345 indent=2, 

346 ), 

347 exists_ok=True, 

348 ) 

349 

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

351 with apio_runner.with_logger() as log: 

352 apio_ctx = ApioContext( 

353 project_policy=ProjectPolicy.NO_PROJECT, 

354 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

355 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

356 ) 

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

358 assert "Fetching" in log.out 

359 assert apio_ctx.remote_config.data == base_apio_ctx.remote_config.data 

360 

361 

362def test_corrupt_cached_remote_config(apio_runner: ApioRunner): 

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

364 corrupt.""" 

365 

366 with apio_runner.in_sandbox() as sb: 

367 

368 # -- Get an actual fresh remote config. 

369 with apio_runner.with_logger() as log: 

370 base_apio_ctx = ApioContext( 

371 project_policy=ProjectPolicy.NO_PROJECT, 

372 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

373 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

374 ) 

375 assert "Fetching" in log.out 

376 

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

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

379 test_data = "{ corrupt json file }" 

380 sb.write_file( 

381 path, 

382 json.dumps( 

383 test_data, 

384 indent=2, 

385 ), 

386 exists_ok=True, 

387 ) 

388 

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

390 with apio_runner.with_logger() as log: 

391 apio_ctx = ApioContext( 

392 project_policy=ProjectPolicy.NO_PROJECT, 

393 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

394 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

395 ) 

396 assert ( 

397 "No suitable cached remote config file (couldn't proto parse)" 

398 in log.out 

399 ) 

400 assert "Fetching" in log.out 

401 assert apio_ctx.remote_config.data == base_apio_ctx.remote_config.data 

402 

403 

404def test_no_cached_remote_config(apio_runner: ApioRunner): 

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

406 config file.""" 

407 

408 with apio_runner.in_sandbox() as sb: 

409 

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

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

412 assert not path.exists() 

413 

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

415 with apio_runner.with_logger() as log: 

416 apio_ctx = ApioContext( 

417 project_policy=ProjectPolicy.NO_PROJECT, 

418 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

419 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

420 ) 

421 assert ( 

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

423 ) 

424 assert "Fetching" in log.out 

425 assert "oss-cad-suite" in apio_ctx.remote_config.data.packages 

426 

427 

428def test_forced_fresh_remote_config_ok(apio_runner: ApioRunner): 

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

430 because a fresh config was requested.""" 

431 

432 with apio_runner.in_sandbox() as sb: 

433 

434 # -- Get an actual fresh remote config. 

435 with apio_runner.with_logger() as log: 

436 base_apio_ctx = ApioContext( 

437 project_policy=ProjectPolicy.NO_PROJECT, 

438 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

439 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

440 ) 

441 assert "Fetching" in log.out 

442 remote_config_url = base_apio_ctx.remote_config.metadata.loaded_from 

443 

444 # -- Write a test cached remote config. 

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

446 test_data = get_test_data( 

447 util.get_apio_version_str(), 0, remote_config_url 

448 ) 

449 sb.write_file( 

450 path, 

451 json.dumps( 

452 test_data, 

453 indent=2, 

454 ), 

455 exists_ok=True, 

456 ) 

457 

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

459 with apio_runner.with_logger() as log: 

460 apio_ctx = ApioContext( 

461 project_policy=ProjectPolicy.NO_PROJECT, 

462 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

463 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

464 ) 

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

466 assert "Fetching" in log.out 

467 assert apio_ctx.remote_config.data == base_apio_ctx.remote_config.data