Skip to content

Commit cc6aa2d

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 4611b36 commit cc6aa2d

File tree

4 files changed

+308
-0
lines changed

4 files changed

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

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

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