Skip to content

Commit 4a55cd8

Browse files
committed
tests: lib: timespec_util: 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 b1ac3f2 commit 4a55cd8

File tree

4 files changed

+299
-0
lines changed

4 files changed

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

tests/lib/timespec_utils/prj.conf

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Copyright (c) 2022 Meta
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
CONFIG_ZTEST=y

tests/lib/timespec_utils/src/main.c

+256
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
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/timespec_util.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)
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(&norm);
125+
zexpect_not_equal(tspec->expect_valid || tspec->correctable, overflow,
126+
"%d: timespec_normalize({%ld, %ld}) %s, unexpectedly",
127+
i, tspec->invalid_ts.tv_sec, tspec->invalid_ts.tv_nsec,
128+
tspec->correctable ? "failed" : "succeeded");
129+
130+
different = !timespec_equal(&tspec->invalid_ts, &norm);
131+
zexpect_equal(!tspec->expect_valid && tspec->correctable, different,
132+
"%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" : "equal");
136+
}
137+
}
138+
139+
ZTEST(timeutil_api, test_timespac_add)
140+
{
141+
bool overflow;
142+
struct timespec actual;
143+
const struct atspec {
144+
struct timespec a;
145+
struct timespec b;
146+
struct timespec result;
147+
bool expect_overflow;
148+
} tspecs[] = {
149+
/* non-overflow cases */
150+
{.a = {0, 0}, .b = {0, 0}, .result = {0, 0}, .expect_overflow = false},
151+
{.a = {1, 1}, .b = {1, 1}, .result = {2, 2}, .expect_overflow = false},
152+
{.a = {-1, 1}, .b = {-1, 1}, .result = {-2, 2}, .expect_overflow = false},
153+
{.a = {-1, NSEC_PER_SEC - 1}, .b = {0, 1}, .result = {0, 0}, .expect_overflow = false},
154+
/* overflow cases */
155+
{.a = {INT64_MAX, 0}, .b = {1, 0}, .result = {0}, .expect_overflow = true},
156+
{.a = {INT64_MIN, 0}, .b = {-1, 0}, .result = {0}, .expect_overflow = true},
157+
{.a = {INT64_MAX, NSEC_PER_SEC - 1}, .b = {1, 1}, .result = {0}, .expect_overflow = true},
158+
{.a = {INT64_MIN, NSEC_PER_SEC - 1}, .b = {-1, 0}, .result = {0}, .expect_overflow = true},
159+
};
160+
161+
ARRAY_FOR_EACH(tspecs, i) {
162+
const struct atspec *const tspec = &tspecs[i];
163+
164+
actual = tspec->a;
165+
overflow = timespec_add(&actual, &tspec->b);
166+
167+
zexpect_equal(overflow, tspec->expect_overflow,
168+
"%d: timespec_add({%ld, %ld}, {%ld, %ld}) %s, unexpectedly",
169+
i, tspec->a.tv_sec, tspec->a.tv_nsec, tspec->b.tv_sec,
170+
tspec->b.tv_nsec, tspec->expect_overflow ? "succeeded" : "failed");
171+
172+
if (!tspec->expect_overflow) {
173+
zexpect_equal(timespec_equal(&actual, &tspec->result), true,
174+
"%d: {%ld, %ld} and {%ld, %ld} are unexpectedly different", i,
175+
actual.tv_sec, actual.tv_nsec, tspec->result.tv_sec,
176+
tspec->result.tv_nsec);
177+
}
178+
}
179+
}
180+
181+
ZTEST(timeutil_api, test_timespac_negate)
182+
{
183+
struct ntspec {
184+
struct timespec ts;
185+
struct timespec result;
186+
bool expect_overflow;
187+
} tspecs[] = {
188+
/* non-overflow cases */
189+
{.ts = {0, 0}, .result = {0, 0}, .expect_overflow = false},
190+
{.ts = {1, 1}, .result = {-2, NSEC_PER_SEC - 1}, .expect_overflow = false},
191+
{.ts = {-1, 1}, .result = {0, NSEC_PER_SEC - 1}, .expect_overflow = false},
192+
{.ts = {INT64_MAX, 0}, .result = {INT64_MIN + 1, 0}, .expect_overflow = false},
193+
/* overflow cases */
194+
{.ts = {INT64_MIN, 0}, .result = {0}, .expect_overflow = true},
195+
};
196+
197+
ARRAY_FOR_EACH(tspecs, i) {
198+
const struct ntspec *const tspec = &tspecs[i];
199+
struct timespec actual = tspec->ts;
200+
bool overflow = timespec_negate(&actual);
201+
202+
zexpect_equal(overflow, tspec->expect_overflow,
203+
"%d: timespec_negate({%ld, %ld}) %s, unexpectedly", i,
204+
tspec->ts.tv_sec, tspec->ts.tv_nsec,
205+
tspec->expect_overflow ? "did not overflow" : "overflowed");
206+
207+
if (!tspec->expect_overflow) {
208+
zexpect_true(timespec_equal(&actual, &tspec->result),
209+
"%d: {%ld, %ld} and {%ld, %ld} are unexpectedly different",
210+
i, actual.tv_sec, actual.tv_nsec, tspec->result.tv_sec,
211+
tspec->result.tv_nsec);
212+
}
213+
}
214+
}
215+
216+
ZTEST(timeutil_api, test_timespac_sub)
217+
{
218+
struct timespec a = {0};
219+
struct timespec b = {0};
220+
221+
zexpect_false(timespec_sub(&a, &b));
222+
}
223+
224+
ZTEST(timeutil_api, test_timespac_compare)
225+
{
226+
struct timespec a;
227+
struct timespec b;
228+
229+
a = (struct timespec){0};
230+
b = (struct timespec){0};
231+
zexpect_equal(timespec_compare(&a, &b), 0);
232+
233+
a = (struct timespec){-1};
234+
b = (struct timespec){0};
235+
zexpect_equal(timespec_compare(&a, &b), -1);
236+
237+
a = (struct timespec){1};
238+
b = (struct timespec){0};
239+
zexpect_equal(timespec_compare(&a, &b), 1);
240+
}
241+
242+
ZTEST(timeutil_api, test_timespac_equal)
243+
{
244+
struct timespec a;
245+
struct timespec b;
246+
247+
a = (struct timespec){0};
248+
b = (struct timespec){0};
249+
zexpect_true(timespec_equal(&a, &b));
250+
251+
a = (struct timespec){-1};
252+
b = (struct timespec){0};
253+
zexpect_false(timespec_equal(&a, &b));
254+
}
255+
256+
ZTEST_SUITE(timeutil_api, NULL, NULL, NULL, NULL, NULL);
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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/64
7+
tests:
8+
libraries.timespec_utils.size: {}
9+
libraries.timespec_utils.size.armclang_std_libc:
10+
toolchain_allow: armclang
11+
extra_configs:
12+
- CONFIG_ARMCLANG_STD_LIBC=y
13+
libraries.timespec_utils.size.arcmwdtlib:
14+
toolchain_allow: arcmwdt
15+
extra_configs:
16+
- CONFIG_ARCMWDT_LIBC=y
17+
libraries.timespec_utils.size.minimal:
18+
extra_configs:
19+
- CONFIG_MINIMAL_LIBC=y
20+
libraries.timespec_utils.size.newlib:
21+
filter: TOOLCHAIN_HAS_NEWLIB == 1
22+
extra_configs:
23+
- CONFIG_NEWLIB_LIBC=y
24+
libraries.timespec_utils.size.picolibc:
25+
tags: picolibc
26+
filter: CONFIG_PICOLIBC_SUPPORTED
27+
extra_configs:
28+
- CONFIG_PICOLIBC=y

0 commit comments

Comments
 (0)