Skip to content

Commit fe4f5c4

Browse files
committed
tests: lib: timeutil: add timespec util testsuite
Add a timespec util testsuite. This should have reasonably high enough coverage to be useful. I would have preferred to add this as an architecture-independent unit test (for the unit_testing platform) under tests/unit/timeutil but there is an inconsistency about the size of time_t on the unit_testing and native_sim/native platforms. On every other platform supported by Zephyr, time_t is 64-bits. However, on those platforms, time_t is only 32-bits. Signed-off-by: Chris Friedt <[email protected]>
1 parent 0eef8cb commit fe4f5c4

File tree

5 files changed

+582
-0
lines changed

5 files changed

+582
-0
lines changed
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
5+
project(timespec_util)
6+
7+
FILE(GLOB app_sources src/main.c)
8+
target_sources(app PRIVATE ${app_sources})

tests/lib/timespec_util/prj.conf

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
CONFIG_ZTEST=y
2+
3+
CONFIG_NO_OPTIMIZATIONS=y
4+
CONFIG_DEBUG_INFO=y

tests/lib/timespec_util/src/main.c

+271
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
/*
2+
* Copyright 2025 Tenstorrent AI ULC
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <stdbool.h>
8+
#include <time.h>
9+
10+
#include <zephyr/ztest.h>
11+
#include <zephyr/sys_clock.h>
12+
#include <zephyr/sys/timeutil.h>
13+
#include <zephyr/sys/util.h>
14+
15+
/* need this to be signed for the purposes of this testsuite */
16+
#undef NSEC_PER_SEC
17+
#define NSEC_PER_SEC 1000000000L
18+
19+
#undef CORRECTABLE
20+
#define CORRECTABLE true
21+
22+
#undef UNCORRECTABLE
23+
#define UNCORRECTABLE false
24+
25+
/*
26+
* test spec for simple timespec validation
27+
*
28+
* If a timespec is expected to be valid, then invalid_ts and valid_ts are equal.
29+
*
30+
* If a timespec is expected to be invalid, then invalid_ts and valid_ts are not equal.
31+
*/
32+
struct ts_test_spec {
33+
struct timespec invalid_ts;
34+
struct timespec valid_ts;
35+
bool expect_valid;
36+
bool correctable;
37+
};
38+
39+
#define DECL_VALID_TS_TEST(sec, nsec) \
40+
{ \
41+
.invalid_ts = {(sec), (nsec)}, \
42+
.valid_ts = {(sec), (nsec)}, \
43+
.expect_valid = true, \
44+
.correctable = false, \
45+
}
46+
47+
/*
48+
* Invalid timespecs can usually be converted to valid ones by adding or subtracting
49+
* multiples of `NSEC_PER_SEC` from tv_nsec, and incrementing or decrementing tv_sec
50+
* proportionately, unless doing so would result in signed integer overflow.
51+
*
52+
* There are two particular corner cases:
53+
* 1. making tv_sec more negative would overflow the tv_sec field in the negative direction
54+
* 1. making tv_sec more positive would overflow the tv_sec field in the positive direction
55+
*/
56+
#define DECL_INVALID_TS_TEST(invalid_sec, invalid_nsec, valid_sec, valid_nsec, is_correctable) \
57+
{ \
58+
.valid_ts = {(valid_sec), (valid_nsec)}, \
59+
.invalid_ts = {(invalid_sec), (invalid_nsec)}, \
60+
.expect_valid = false, \
61+
.correctable = (is_correctable), \
62+
}
63+
64+
/*
65+
* Easily verifiable tests for both valid and invalid timespecs.
66+
*/
67+
static const struct ts_test_spec ts_tests[] = {
68+
/* Valid cases */
69+
DECL_VALID_TS_TEST(0, 0),
70+
DECL_VALID_TS_TEST(0, 1),
71+
DECL_VALID_TS_TEST(1, 0),
72+
DECL_VALID_TS_TEST(1, 1),
73+
DECL_VALID_TS_TEST(1, NSEC_PER_SEC - 1),
74+
DECL_VALID_TS_TEST(-1, 0),
75+
DECL_VALID_TS_TEST(-1, 1),
76+
DECL_VALID_TS_TEST(-1, 0),
77+
DECL_VALID_TS_TEST(-1, 1),
78+
DECL_VALID_TS_TEST(-1, NSEC_PER_SEC - 1),
79+
DECL_VALID_TS_TEST(INT64_MIN, 0),
80+
DECL_VALID_TS_TEST(INT64_MIN, NSEC_PER_SEC - 1),
81+
DECL_VALID_TS_TEST(INT64_MAX, 0),
82+
DECL_VALID_TS_TEST(INT64_MAX, NSEC_PER_SEC - 1),
83+
84+
/* Correctable, invalid cases */
85+
DECL_INVALID_TS_TEST(0, -1, -1, NSEC_PER_SEC - 1, CORRECTABLE),
86+
DECL_INVALID_TS_TEST(0, NSEC_PER_SEC, 1, 0, CORRECTABLE),
87+
DECL_INVALID_TS_TEST(1, -1, 0, NSEC_PER_SEC - 1, CORRECTABLE),
88+
DECL_INVALID_TS_TEST(1, NSEC_PER_SEC, 2, 0, CORRECTABLE),
89+
DECL_INVALID_TS_TEST(-1, -1, -2, NSEC_PER_SEC - 1, CORRECTABLE),
90+
DECL_INVALID_TS_TEST(0, NSEC_PER_SEC, 1, 0, CORRECTABLE),
91+
DECL_INVALID_TS_TEST(1, -1, 0, NSEC_PER_SEC - 1, CORRECTABLE),
92+
DECL_INVALID_TS_TEST(1, NSEC_PER_SEC, 2, 0, CORRECTABLE),
93+
DECL_INVALID_TS_TEST(INT64_MIN, NSEC_PER_SEC, INT64_MIN + 1, 0, CORRECTABLE),
94+
DECL_INVALID_TS_TEST(INT64_MAX, -1, INT64_MAX - 1, NSEC_PER_SEC - 1, CORRECTABLE),
95+
96+
/* Uncorrectable, invalid cases */
97+
DECL_INVALID_TS_TEST(INT64_MIN + 1, -NSEC_PER_SEC - 1, 0, 0, UNCORRECTABLE),
98+
DECL_INVALID_TS_TEST(INT64_MIN, -1, 0, 0, UNCORRECTABLE),
99+
DECL_INVALID_TS_TEST(INT64_MAX, NSEC_PER_SEC, 0, 0, UNCORRECTABLE),
100+
DECL_INVALID_TS_TEST(INT64_MAX - 1, 2 * NSEC_PER_SEC, 0, 0, UNCORRECTABLE),
101+
};
102+
103+
ZTEST(timeutil_api, test_timespac_is_valid)
104+
{
105+
ARRAY_FOR_EACH(ts_tests, i) {
106+
const struct ts_test_spec *const tspec = &ts_tests[i];
107+
bool valid = timespec_is_valid(&tspec->invalid_ts);
108+
109+
zexpect_equal(valid, tspec->expect_valid,
110+
"%d: timespec_is_valid({%ld, %ld}) = %s, expected true", i,
111+
tspec->valid_ts.tv_sec, tspec->valid_ts.tv_nsec,
112+
tspec->expect_valid ? "false" : "true");
113+
}
114+
}
115+
116+
ZTEST(timeutil_api, test_timespac_normalize_overflow)
117+
{
118+
ARRAY_FOR_EACH(ts_tests, i) {
119+
bool different;
120+
bool overflow;
121+
const struct ts_test_spec *const tspec = &ts_tests[i];
122+
struct timespec norm = tspec->invalid_ts;
123+
124+
if (i == 24) {
125+
printk("");
126+
}
127+
128+
overflow = timespec_normalize_overflow(&norm);
129+
zexpect_not_equal(tspec->expect_valid || tspec->correctable, overflow,
130+
"%d: timespec_normalize_overflow({%ld, %ld}) %s, unexpectedly", i,
131+
tspec->invalid_ts.tv_sec, tspec->invalid_ts.tv_nsec,
132+
tspec->correctable ? "failed" : "succeeded");
133+
134+
if (!tspec->expect_valid && tspec->correctable) {
135+
different = !timespec_equal(&tspec->invalid_ts, &norm);
136+
zexpect_true(different, "%d: {%ld, %ld} and {%ld, %ld} are unexpectedly %s",
137+
i, tspec->invalid_ts.tv_sec, tspec->invalid_ts.tv_nsec,
138+
norm.tv_sec, tspec->valid_ts.tv_sec,
139+
(tspec->expect_valid || tspec->correctable) ? "different"
140+
: "equal");
141+
}
142+
}
143+
}
144+
145+
ZTEST(timeutil_api, test_timespec_add_overflow)
146+
{
147+
bool overflow;
148+
struct timespec actual;
149+
const struct atspec {
150+
struct timespec a;
151+
struct timespec b;
152+
struct timespec result;
153+
bool expect_overflow;
154+
} tspecs[] = {
155+
/* non-overflow cases */
156+
{.a = {0, 0}, .b = {0, 0}, .result = {0, 0}, .expect_overflow = false},
157+
{.a = {1, 1}, .b = {1, 1}, .result = {2, 2}, .expect_overflow = false},
158+
{.a = {-1, 1}, .b = {-1, 1}, .result = {-2, 2}, .expect_overflow = false},
159+
{.a = {-1, NSEC_PER_SEC - 1},
160+
.b = {0, 1},
161+
.result = {0, 0},
162+
.expect_overflow = false},
163+
/* overflow cases */
164+
{.a = {INT64_MAX, 0}, .b = {1, 0}, .result = {0}, .expect_overflow = true},
165+
{.a = {INT64_MIN, 0}, .b = {-1, 0}, .result = {0}, .expect_overflow = true},
166+
{.a = {INT64_MAX, NSEC_PER_SEC - 1},
167+
.b = {1, 1},
168+
.result = {0},
169+
.expect_overflow = true},
170+
{.a = {INT64_MIN, NSEC_PER_SEC - 1},
171+
.b = {-1, 0},
172+
.result = {0},
173+
.expect_overflow = true},
174+
};
175+
176+
ARRAY_FOR_EACH(tspecs, i) {
177+
const struct atspec *const tspec = &tspecs[i];
178+
179+
actual = tspec->a;
180+
overflow = timespec_add_overflow(&actual, &tspec->b);
181+
182+
zexpect_equal(overflow, tspec->expect_overflow,
183+
"%d: timespec_add_overflow({%ld, %ld}, {%ld, %ld}) %s, unexpectedly",
184+
i, tspec->a.tv_sec, tspec->a.tv_nsec, tspec->b.tv_sec,
185+
tspec->b.tv_nsec, tspec->expect_overflow ? "succeeded" : "failed");
186+
187+
if (!tspec->expect_overflow) {
188+
zexpect_equal(timespec_equal(&actual, &tspec->result), true,
189+
"%d: {%ld, %ld} and {%ld, %ld} are unexpectedly different", i,
190+
actual.tv_sec, actual.tv_nsec, tspec->result.tv_sec,
191+
tspec->result.tv_nsec);
192+
}
193+
}
194+
}
195+
196+
ZTEST(timeutil_api, test_timespac_negate_overflow)
197+
{
198+
struct ntspec {
199+
struct timespec ts;
200+
struct timespec result;
201+
bool expect_overflow;
202+
} tspecs[] = {
203+
/* non-overflow cases */
204+
{.ts = {0, 0}, .result = {0, 0}, .expect_overflow = false},
205+
{.ts = {1, 1}, .result = {-2, NSEC_PER_SEC - 1}, .expect_overflow = false},
206+
{.ts = {-1, 1}, .result = {0, NSEC_PER_SEC - 1}, .expect_overflow = false},
207+
{.ts = {INT64_MAX, 0}, .result = {INT64_MIN + 1, 0}, .expect_overflow = false},
208+
/* overflow cases */
209+
{.ts = {INT64_MIN, 0}, .result = {0}, .expect_overflow = true},
210+
};
211+
212+
ARRAY_FOR_EACH(tspecs, i) {
213+
const struct ntspec *const tspec = &tspecs[i];
214+
struct timespec actual = tspec->ts;
215+
bool overflow = timespec_negate_overflow(&actual);
216+
217+
zexpect_equal(overflow, tspec->expect_overflow,
218+
"%d: timespec_negate_overflow({%ld, %ld}) %s, unexpectedly", i,
219+
tspec->ts.tv_sec, tspec->ts.tv_nsec,
220+
tspec->expect_overflow ? "did not overflow" : "overflowed");
221+
222+
if (!tspec->expect_overflow) {
223+
zexpect_true(timespec_equal(&actual, &tspec->result),
224+
"%d: {%ld, %ld} and {%ld, %ld} are unexpectedly different", i,
225+
actual.tv_sec, actual.tv_nsec, tspec->result.tv_sec,
226+
tspec->result.tv_nsec);
227+
}
228+
}
229+
}
230+
231+
ZTEST(timeutil_api, test_timespec_sub_overflow)
232+
{
233+
struct timespec a = {0};
234+
struct timespec b = {0};
235+
236+
zexpect_false(timespec_sub_overflow(&a, &b));
237+
}
238+
239+
ZTEST(timeutil_api, test_timespac_compare)
240+
{
241+
struct timespec a;
242+
struct timespec b;
243+
244+
a = (struct timespec){0};
245+
b = (struct timespec){0};
246+
zexpect_equal(timespec_compare(&a, &b), 0);
247+
248+
a = (struct timespec){-1};
249+
b = (struct timespec){0};
250+
zexpect_equal(timespec_compare(&a, &b), -1);
251+
252+
a = (struct timespec){1};
253+
b = (struct timespec){0};
254+
zexpect_equal(timespec_compare(&a, &b), 1);
255+
}
256+
257+
ZTEST(timeutil_api, test_timespac_equal)
258+
{
259+
struct timespec a;
260+
struct timespec b;
261+
262+
a = (struct timespec){0};
263+
b = (struct timespec){0};
264+
zexpect_true(timespec_equal(&a, &b));
265+
266+
a = (struct timespec){-1};
267+
b = (struct timespec){0};
268+
zexpect_false(timespec_equal(&a, &b));
269+
}
270+
271+
ZTEST_SUITE(timeutil_api, NULL, NULL, NULL, NULL, NULL);

tests/lib/timespec_util/testcase.yaml

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# FIXME: this should be under tests/unit/timeutil but will not work due to #90029
2+
common:
3+
tags:
4+
- timeutils
5+
integration_platforms:
6+
- native_sim/native/64
7+
tests:
8+
libraries.timespec_utils: {}
9+
libraries.timespec_utils.speed:
10+
extra_configs:
11+
- CONFIG_SPEED_OPTIMIZATIONS=y
12+
libraries.timespec_utils.armclang_std_libc:
13+
toolchain_allow: armclang
14+
extra_configs:
15+
- CONFIG_ARMCLANG_STD_LIBC=y
16+
libraries.timespec_utils.arcmwdtlib:
17+
toolchain_allow: arcmwdt
18+
extra_configs:
19+
- CONFIG_ARCMWDT_LIBC=y
20+
libraries.timespec_utils.minimal:
21+
extra_configs:
22+
- CONFIG_MINIMAL_LIBC=y
23+
libraries.timespec_utils.newlib:
24+
filter: TOOLCHAIN_HAS_NEWLIB == 1
25+
extra_configs:
26+
- CONFIG_NEWLIB_LIBC=y
27+
libraries.timespec_utils.picolibc:
28+
tags: picolibc
29+
filter: CONFIG_PICOLIBC_SUPPORTED
30+
extra_configs:
31+
- CONFIG_PICOLIBC=y

0 commit comments

Comments
 (0)