|
7 | 7 | raise SystemExit |
8 | 8 |
|
9 | 9 |
|
| 10 | +def test_monotonic_advance(func_name, min_advance_s, sleep_ms): |
| 11 | + """Test that a time function advances within expected bounds.""" |
| 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 | + try: |
| 22 | + t1 = time_func() |
| 23 | + time.sleep_ms(sleep_ms) |
| 24 | + t2 = time_func() |
| 25 | + except AttributeError: |
| 26 | + # Function exists but calls unavailable time functions (e.g., gmtime_time) |
| 27 | + return None |
| 28 | + |
| 29 | + # For tuple return values (gmtime, localtime), compare as tuples |
| 30 | + if isinstance(t1, tuple) and isinstance(t2, tuple): |
| 31 | + # Should have changed (checking resolution) |
| 32 | + return t2 != t1 |
| 33 | + # For numeric return values (time, ticks_*) |
| 34 | + else: |
| 35 | + # Use appropriate diff function for ticks |
| 36 | + if func_name.startswith("ticks_"): |
| 37 | + diff = time.ticks_diff(t2, t1) |
| 38 | + if func_name == "ticks_ms": |
| 39 | + # Expect 80%-200% of sleep time (tolerance for overhead and loaded CI) |
| 40 | + expected = sleep_ms |
| 41 | + return (expected * 0.8) <= diff <= (expected * 2.0) |
| 42 | + elif func_name == "ticks_us": |
| 43 | + # Expect 80%-200% of sleep time in microseconds |
| 44 | + expected = sleep_ms * 1000 |
| 45 | + return (expected * 0.8) <= diff <= (expected * 2.0) |
| 46 | + elif func_name == "ticks_ns": |
| 47 | + # Expect 80%-200% of sleep time in nanoseconds |
| 48 | + expected = sleep_ms * 1000000 |
| 49 | + return (expected * 0.8) <= diff <= (expected * 2.0) |
| 50 | + elif func_name == "ticks_cpu": |
| 51 | + # ticks_cpu may return 0 on some ports, just check it advanced |
| 52 | + return diff > 0 or t2 == 0 |
| 53 | + else: |
| 54 | + # For time() and other float/int returns, check both bounds |
| 55 | + # 2x tolerance for overhead and loaded CI systems |
| 56 | + min_expected = min_advance_s |
| 57 | + max_expected = (sleep_ms / 1000.0) * 2.0 |
| 58 | + actual_diff = t2 - t1 |
| 59 | + return min_expected <= actual_diff <= max_expected |
| 60 | + |
| 61 | + |
10 | 62 | def gmtime_time(): |
| 63 | + # May raise AttributeError if gmtime not available |
11 | 64 | return time.gmtime(time.time()) |
12 | 65 |
|
13 | 66 |
|
14 | 67 | def localtime_time(): |
| 68 | + # May raise AttributeError if localtime not available |
15 | 69 | return time.localtime(time.time()) |
16 | 70 |
|
17 | 71 |
|
18 | 72 | 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), |
| 73 | + # Test configuration: (function name, minimum advance in seconds, sleep time in ms) |
| 74 | + TEST_CONFIG = ( |
| 75 | + ("time", 1, 1200), |
| 76 | + ("gmtime", 0, 1200), # gmtime returns tuple, just check it changes |
| 77 | + ("localtime", 0, 1200), |
| 78 | + ("gmtime_time", 0, 1200), |
| 79 | + ("localtime_time", 0, 1200), |
| 80 | + ("ticks_ms", 0, 150), # Test millisecond resolution |
| 81 | + ("ticks_us", 0, 150), # Test microsecond resolution |
| 82 | + ("ticks_ns", 0, 150), # Test nanosecond resolution |
| 83 | + ("ticks_cpu", 0, 150), |
31 | 84 | ) |
32 | 85 |
|
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: |
| 86 | + for func_name, min_advance, sleep_ms in TEST_CONFIG: |
54 | 87 | 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) |
| 88 | + result = test_monotonic_advance(func_name, min_advance, sleep_ms) |
| 89 | + if result is None: |
| 90 | + # Function not available, skip silently |
60 | 91 | 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 | | - ) |
| 92 | + elif not result: |
| 93 | + print("%s() did not advance as expected" % func_name) |
66 | 94 |
|
67 | 95 |
|
68 | 96 | test() |
0 commit comments