Skip to content

Commit 4f98277

Browse files
committed
tests/extmod: Make test time_res.py more deterministic.
Replace sample-counting approach with direct monotonicity and resolution tests. The previous method mixed ticks_ms() and RTC clock sources which could drift, causing non-deterministic results across platforms. New approach directly tests that time functions advance at their documented rates. Signed-off-by: Andrew Leech <[email protected]>
1 parent 4efc5e1 commit 4f98277

File tree

1 file changed

+59
-43
lines changed

1 file changed

+59
-43
lines changed

tests/extmod/time_res.py

Lines changed: 59 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,48 @@
77
raise SystemExit
88

99

10+
def test_monotonic_advance(func_name, min_advance_s, sleep_ms):
11+
"""Test that a time function advances by at least min_advance_s seconds."""
12+
try:
13+
if func_name.endswith("_time"):
14+
# Helper functions defined below
15+
time_func = globals()[func_name]
16+
else:
17+
time_func = getattr(time, func_name)
18+
except AttributeError:
19+
return None # Function not available
20+
21+
t1 = time_func()
22+
time.sleep_ms(sleep_ms)
23+
t2 = time_func()
24+
25+
# For tuple return values (gmtime, localtime), compare as tuples
26+
if isinstance(t1, tuple) and isinstance(t2, tuple):
27+
# Should have changed
28+
return t2 != t1
29+
# For numeric return values (time, ticks_*)
30+
else:
31+
# Should have advanced by at least min_advance_s
32+
# Use appropriate diff function for ticks
33+
if func_name.startswith("ticks_"):
34+
diff = time.ticks_diff(t2, t1)
35+
if func_name == "ticks_ms":
36+
# Expect at least 80% of sleep time (accounting for overhead)
37+
return diff >= sleep_ms * 0.8
38+
elif func_name == "ticks_us":
39+
# Expect at least 80% of sleep time in microseconds
40+
return diff >= sleep_ms * 1000 * 0.8
41+
elif func_name == "ticks_ns":
42+
# Expect at least 80% of sleep time in nanoseconds
43+
return diff >= sleep_ms * 1000000 * 0.8
44+
elif func_name == "ticks_cpu":
45+
# ticks_cpu may return 0 on some ports, just check it advanced
46+
return diff > 0 or t2 == 0
47+
else:
48+
# For time() and other float/int returns
49+
return t2 >= t1 + min_advance_s
50+
51+
1052
def gmtime_time():
1153
return time.gmtime(time.time())
1254

@@ -16,53 +58,27 @@ def localtime_time():
1658

1759

1860
def test():
19-
TEST_TIME = 2500
20-
EXPECTED_MAP = (
21-
# (function name, min. number of results in 2.5 sec)
22-
("time", 3),
23-
("gmtime", 3),
24-
("localtime", 3),
25-
("gmtime_time", 3),
26-
("localtime_time", 3),
27-
("ticks_ms", 15),
28-
("ticks_us", 15),
29-
("ticks_ns", 15),
30-
("ticks_cpu", 15),
61+
# Test configuration: (function name, minimum advance in seconds, sleep time in ms)
62+
TEST_CONFIG = (
63+
("time", 1, 1200),
64+
("gmtime", 0, 1200), # gmtime returns tuple, just check it changes
65+
("localtime", 0, 1200),
66+
("gmtime_time", 0, 1200),
67+
("localtime_time", 0, 1200),
68+
("ticks_ms", 0, 150), # Test millisecond resolution
69+
("ticks_us", 0, 150), # Test microsecond resolution
70+
("ticks_ns", 0, 150), # Test nanosecond resolution
71+
("ticks_cpu", 0, 150),
3172
)
3273

33-
# call time functions
34-
results_map = {}
35-
end_time = time.ticks_ms() + TEST_TIME
36-
while time.ticks_diff(end_time, time.ticks_ms()) > 0:
37-
time.sleep_ms(100)
38-
for func_name, _ in EXPECTED_MAP:
39-
try:
40-
if func_name.endswith("_time"):
41-
time_func = globals()[func_name]
42-
else:
43-
time_func = getattr(time, func_name)
44-
now = time_func() # may raise AttributeError
45-
except AttributeError:
46-
continue
47-
try:
48-
results_map[func_name].add(now)
49-
except KeyError:
50-
results_map[func_name] = {now}
51-
52-
# check results
53-
for func_name, min_len in EXPECTED_MAP:
74+
for func_name, min_advance, sleep_ms in TEST_CONFIG:
5475
print("Testing %s" % func_name)
55-
results = results_map.get(func_name)
56-
if results is None:
57-
pass
58-
elif func_name == "ticks_cpu" and results == {0}:
59-
# ticks_cpu() returns 0 on some ports (e.g. unix)
76+
result = test_monotonic_advance(func_name, min_advance, sleep_ms)
77+
if result is None:
78+
# Function not available, skip silently
6079
pass
61-
elif len(results) < min_len:
62-
print(
63-
"%s() returns %s result%s in %s ms, expecting >= %s"
64-
% (func_name, len(results), "s"[: len(results) != 1], TEST_TIME, min_len)
65-
)
80+
elif not result:
81+
print("%s() did not advance as expected" % func_name)
6682

6783

6884
test()

0 commit comments

Comments
 (0)