Skip to content

Commit 0144e45

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 0144e45

File tree

4 files changed

+310
-0
lines changed

4 files changed

+310
-0
lines changed
Lines changed: 8 additions & 0 deletions
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

Lines changed: 4 additions & 0 deletions
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

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
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+
overflow = timespec_normalize_overflow(&norm);
125+
zexpect_not_equal(tspec->expect_valid || tspec->correctable, overflow,
126+
"%d: timespec_normalize_overflow({%ld, %ld}) %s, unexpectedly", i,
127+
tspec->invalid_ts.tv_sec, tspec->invalid_ts.tv_nsec,
128+
tspec->correctable ? "failed" : "succeeded");
129+
130+
if (!tspec->expect_valid && tspec->correctable) {
131+
different = !timespec_equal(&tspec->invalid_ts, &norm);
132+
zexpect_true(different, "%d: {%ld, %ld} and {%ld, %ld} are unexpectedly %s",
133+
i, tspec->invalid_ts.tv_sec, tspec->invalid_ts.tv_nsec,
134+
norm.tv_sec, tspec->valid_ts.tv_sec,
135+
(tspec->expect_valid || tspec->correctable) ? "different"
136+
: "equal");
137+
}
138+
}
139+
}
140+
141+
ZTEST(timeutil_api, test_timespec_add_overflow)
142+
{
143+
bool overflow;
144+
struct timespec actual;
145+
const struct atspec {
146+
struct timespec a;
147+
struct timespec b;
148+
struct timespec result;
149+
bool expect_overflow;
150+
} tspecs[] = {
151+
/* non-overflow cases */
152+
{.a = {0, 0}, .b = {0, 0}, .result = {0, 0}, .expect_overflow = false},
153+
{.a = {1, 1}, .b = {1, 1}, .result = {2, 2}, .expect_overflow = false},
154+
{.a = {-1, 1}, .b = {-1, 1}, .result = {-2, 2}, .expect_overflow = false},
155+
{.a = {-1, NSEC_PER_SEC - 1},
156+
.b = {0, 1},
157+
.result = {0, 0},
158+
.expect_overflow = false},
159+
/* overflow cases */
160+
{.a = {INT64_MAX, 0}, .b = {1, 0}, .result = {0}, .expect_overflow = true},
161+
{.a = {INT64_MIN, 0}, .b = {-1, 0}, .result = {0}, .expect_overflow = true},
162+
{.a = {INT64_MAX, NSEC_PER_SEC - 1},
163+
.b = {1, 1},
164+
.result = {0},
165+
.expect_overflow = true},
166+
{.a = {INT64_MIN, NSEC_PER_SEC - 1},
167+
.b = {-1, 0},
168+
.result = {0},
169+
.expect_overflow = true},
170+
};
171+
172+
ARRAY_FOR_EACH(tspecs, i) {
173+
const struct atspec *const tspec = &tspecs[i];
174+
175+
actual = tspec->a;
176+
overflow = timespec_add_overflow(&actual, &tspec->b);
177+
178+
zexpect_equal(overflow, tspec->expect_overflow,
179+
"%d: timespec_add_overflow({%ld, %ld}, {%ld, %ld}) %s, unexpectedly",
180+
i, tspec->a.tv_sec, tspec->a.tv_nsec, tspec->b.tv_sec,
181+
tspec->b.tv_nsec, tspec->expect_overflow ? "succeeded" : "failed");
182+
183+
if (!tspec->expect_overflow) {
184+
zexpect_equal(timespec_equal(&actual, &tspec->result), true,
185+
"%d: {%ld, %ld} and {%ld, %ld} are unexpectedly different", i,
186+
actual.tv_sec, actual.tv_nsec, tspec->result.tv_sec,
187+
tspec->result.tv_nsec);
188+
}
189+
}
190+
}
191+
192+
ZTEST(timeutil_api, test_timespac_negate_overflow)
193+
{
194+
struct ntspec {
195+
struct timespec ts;
196+
struct timespec result;
197+
bool expect_overflow;
198+
} tspecs[] = {
199+
/* non-overflow cases */
200+
{.ts = {0, 0}, .result = {0, 0}, .expect_overflow = false},
201+
{.ts = {1, 1}, .result = {-2, NSEC_PER_SEC - 1}, .expect_overflow = false},
202+
{.ts = {-1, 1}, .result = {0, NSEC_PER_SEC - 1}, .expect_overflow = false},
203+
{.ts = {INT64_MAX, 0}, .result = {INT64_MIN + 1, 0}, .expect_overflow = false},
204+
/* overflow cases */
205+
{.ts = {INT64_MIN, 0}, .result = {0}, .expect_overflow = true},
206+
};
207+
208+
ARRAY_FOR_EACH(tspecs, i) {
209+
const struct ntspec *const tspec = &tspecs[i];
210+
struct timespec actual = tspec->ts;
211+
bool overflow = timespec_negate_overflow(&actual);
212+
213+
zexpect_equal(overflow, tspec->expect_overflow,
214+
"%d: timespec_negate_overflow({%ld, %ld}) %s, unexpectedly", i,
215+
tspec->ts.tv_sec, tspec->ts.tv_nsec,
216+
tspec->expect_overflow ? "did not overflow" : "overflowed");
217+
218+
if (!tspec->expect_overflow) {
219+
zexpect_true(timespec_equal(&actual, &tspec->result),
220+
"%d: {%ld, %ld} and {%ld, %ld} are unexpectedly different", i,
221+
actual.tv_sec, actual.tv_nsec, tspec->result.tv_sec,
222+
tspec->result.tv_nsec);
223+
}
224+
}
225+
}
226+
227+
ZTEST(timeutil_api, test_timespec_sub_overflow)
228+
{
229+
struct timespec a = {0};
230+
struct timespec b = {0};
231+
232+
zexpect_false(timespec_sub_overflow(&a, &b));
233+
}
234+
235+
ZTEST(timeutil_api, test_timespac_compare)
236+
{
237+
struct timespec a;
238+
struct timespec b;
239+
240+
a = (struct timespec){0};
241+
b = (struct timespec){0};
242+
zexpect_equal(timespec_compare(&a, &b), 0);
243+
244+
a = (struct timespec){-1};
245+
b = (struct timespec){0};
246+
zexpect_equal(timespec_compare(&a, &b), -1);
247+
248+
a = (struct timespec){1};
249+
b = (struct timespec){0};
250+
zexpect_equal(timespec_compare(&a, &b), 1);
251+
}
252+
253+
ZTEST(timeutil_api, test_timespac_equal)
254+
{
255+
struct timespec a;
256+
struct timespec b;
257+
258+
a = (struct timespec){0};
259+
b = (struct timespec){0};
260+
zexpect_true(timespec_equal(&a, &b));
261+
262+
a = (struct timespec){-1};
263+
b = (struct timespec){0};
264+
zexpect_false(timespec_equal(&a, &b));
265+
}
266+
267+
ZTEST_SUITE(timeutil_api, NULL, NULL, NULL, NULL, NULL);

tests/lib/timespec_util/testcase.yaml

Lines changed: 31 additions & 0 deletions
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)