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
« 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
9"""
10Tests of remote_config.py and it's integration with ApioContext.
11"""
13# TODO: Add test coverage of the "refresh-failure-on" logic.
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)
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)
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 }
76def test_datetime_stamp_diff_days():
77 """Test the datetime timestamp diff."""
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 )
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 )
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 )
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 )
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 )
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 )
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 )
142def test_cached_config_ok(apio_runner: ApioRunner):
143 """Tests remote config resolution when the cached config is ok."""
145 with apio_runner.in_sandbox() as sb:
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
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 )
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 )
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."""
191 with apio_runner.in_sandbox() as sb:
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
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 )
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
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."""
237 with apio_runner.in_sandbox() as sb:
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
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 )
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
278def test_cached_remote_config_too_old(apio_runner: ApioRunner):
279 """Tests remote config resolution when the cached config is too old."""
281 with apio_runner.in_sandbox() as sb:
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
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 )
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
319def test_cached_remote_config_too_new(apio_runner: ApioRunner):
320 """Tests remote config resolution when the cached config is
321 from the future."""
323 with apio_runner.in_sandbox() as sb:
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
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 )
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
362def test_corrupt_cached_remote_config(apio_runner: ApioRunner):
363 """Tests remote config resolution when the cached config file is
364 corrupt."""
366 with apio_runner.in_sandbox() as sb:
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
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 )
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
404def test_no_cached_remote_config(apio_runner: ApioRunner):
405 """Tests remote config resolution when there is no cached remote
406 config file."""
408 with apio_runner.in_sandbox() as sb:
410 # -- Check that the cache file doesn't exist.
411 path = sb.home_dir / "cached-remote-config.json"
412 assert not path.exists()
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
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."""
432 with apio_runner.in_sandbox() as sb:
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
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 )
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