The FreeRADIUS server $Id: f3670dba8951ca10eb4948feb3dc3db9423a334f $
Loading...
Searching...
No Matches
time_tests.c
Go to the documentation of this file.
1/*
2 * This library is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU Lesser General Public
4 * License as published by the Free Software Foundation; either
5 * version 2.1 of the License, or (at your option) any later version.
6 *
7 * This library is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * Lesser General Public License for more details.
11 *
12 * You should have received a copy of the GNU Lesser General Public
13 * License along with this library; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/** Tests for a generic string buffer structure for string printing and parsing
18 *
19 * @file src/lib/util/test//time_tests.c
20 *
21 * @copyright 2022 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
22 */
23#include "acutest.h"
24#include"acutest_helpers.h"
25#include <freeradius-devel/util/time.h>
26
27#define ROUNDS (100000)
28
29DIAG_OFF(unused-but-set-variable)
30static void time_benchmark(void)
31{
32 int i;
33 fr_time_t start, stop;
34 uint64_t rate;
35
36 start = fr_time();
37 for (i = 0; i < ROUNDS; i++) {
38 volatile fr_time_t now;
39
40 now = fr_time();
41 }
42 stop = fr_time();
43
44 rate = (uint64_t)((float)NSEC / (fr_time_delta_unwrap(fr_time_sub(stop, start)) / ROUNDS));
45 printf("fr_time rate %" PRIu64 "\n", rate);
46
47 /* shared runners are terrible for performance tests */
48 if (!getenv("NO_PERFORMANCE_TESTS")) TEST_CHECK(rate > (ROUNDS * 10));
49}
50DIAG_ON(unused-but-set-variable)
51
52/** Format the most negative delta possible
53 *
54 * fr_time_delta_to_str() takes the magnitude of the delta with a MOD()
55 * macro. INT64_MIN is the awkward case: negating it in a signed type is
56 * undefined (its positive counterpart, 2^63, isn't representable in
57 * int64_t), so the magnitude has to be taken in the unsigned domain.
58 * This checks the boundary formats correctly rather than tripping UBSan.
59 */
61{
62 char buf[64];
63 fr_sbuff_t sbuff;
64 fr_slen_t slen;
65 fr_time_delta_t delta = fr_time_delta_wrap(INT64_MIN);
66
67 TEST_CASE("signed INT64_MIN, seconds resolution");
68 sbuff = FR_SBUFF_OUT(buf, sizeof(buf));
69 slen = fr_time_delta_to_str(&sbuff, delta, FR_TIME_RES_SEC, false);
70 TEST_CHECK(slen > 0);
71 TEST_MSG("slen %zd", slen);
72 TEST_CHECK(strcmp(buf, "-9223372036.854775808") == 0);
73 TEST_MSG("got \"%s\"", buf);
74
75 TEST_CASE("unsigned INT64_MIN clamps to zero");
76 sbuff = FR_SBUFF_OUT(buf, sizeof(buf));
77 slen = fr_time_delta_to_str(&sbuff, delta, FR_TIME_RES_SEC, true);
78 TEST_CHECK(slen > 0);
79 TEST_MSG("slen %zd", slen);
80 TEST_CHECK(strcmp(buf, "0") == 0);
81 TEST_MSG("got \"%s\"", buf);
82}
83
84/*
85 * Helpers for fr_time_delta_from_str().
86 */
87static void check_delta_from_str(char const *in, fr_time_res_t hint, int64_t expected)
88{
90 fr_slen_t slen;
91
92 slen = fr_time_delta_from_str(&delta, in, strlen(in), hint);
93 TEST_CHECK(slen > 0);
94 TEST_MSG("\"%s\": slen %zd", in, (ssize_t)slen);
95 TEST_CHECK(fr_time_delta_unwrap(delta) == expected);
96 TEST_MSG("\"%s\": got %" PRIi64 ", expected %" PRIi64,
97 in, fr_time_delta_unwrap(delta), expected);
98}
99
100static void check_delta_from_str_fail(char const *in, fr_time_res_t hint)
101{
103 fr_slen_t slen;
104
105 slen = fr_time_delta_from_str(&delta, in, strlen(in), hint);
106 TEST_CHECK(slen < 0);
107 TEST_MSG("\"%s\": expected failure, got %" PRIi64, in, fr_time_delta_unwrap(delta));
108}
109
110/** Parse time deltas from strings: integers, floats, scale suffixes, timestamps and errors. */
111static void time_delta_from_str(void)
112{
113 TEST_CASE("plain integers with a resolution hint");
116 check_delta_from_str("-2", FR_TIME_RES_SEC, -2 * (int64_t)NSEC);
117
118 TEST_CASE("explicit scale suffixes override the hint");
120 check_delta_from_str("500ms", FR_TIME_RES_SEC, 500 * (NSEC / MSEC));
121 check_delta_from_str("250us", FR_TIME_RES_SEC, 250 * (NSEC / USEC));
123
124 TEST_CASE("floating point is pre-scaled to nanoseconds");
125 check_delta_from_str("1.5", FR_TIME_RES_SEC, 1500 * (NSEC / MSEC));
127
128 TEST_CASE("[hours:]minutes:seconds timestamps");
129 check_delta_from_str("1:30", FR_TIME_RES_SEC, 90 * (int64_t)NSEC);
130 check_delta_from_str("01:02:03", FR_TIME_RES_SEC, 3723 * (int64_t)NSEC);
131
132 /*
133 * A leading '-' negates the whole timestamp. The first number
134 * carries the sign (integer == -1 for "-1:30"), the mm:ss / hh:mm:ss
135 * branches take its magnitude for the component, and the assembled
136 * total is negated once at the end.
137 */
138 TEST_CASE("negative [hours:]minutes:seconds timestamps");
139 check_delta_from_str("-1:30", FR_TIME_RES_SEC, -90 * (int64_t)NSEC);
140 check_delta_from_str("-0:30", FR_TIME_RES_SEC, -30 * (int64_t)NSEC); /* negative, zero minutes */
141 check_delta_from_str("-1:02:03", FR_TIME_RES_SEC, -3723 * (int64_t)NSEC);
142
143 /*
144 * The do_timestamp range guard caps the first (hours) component at
145 * the int16_t bounds. The negative boundary (INT16_MIN) exercises
146 * the "negative ? -integer : integer" magnitude step, which must
147 * not trip signed-negation UB.
148 */
149 TEST_CASE("hours component at the int16_t guard boundary");
150 check_delta_from_str("32767:00:00", FR_TIME_RES_SEC, (int64_t)32767 * 3600 * NSEC); /* INT16_MAX */
151 check_delta_from_str("-32768:00:00", FR_TIME_RES_SEC, (int64_t)-32768 * 3600 * NSEC); /* INT16_MIN */
152 check_delta_from_str_fail("32768:00:00", FR_TIME_RES_SEC); /* > INT16_MAX */
153 check_delta_from_str_fail("-32769:00:00", FR_TIME_RES_SEC); /* < INT16_MIN */
154
155 TEST_CASE("invalid input is rejected");
159 check_delta_from_str_fail("99:30", FR_TIME_RES_SEC); /* minutes >= 60 (mm:ss) */
160 check_delta_from_str_fail("1:99", FR_TIME_RES_SEC); /* seconds >= 60 (mm:ss) */
161 check_delta_from_str_fail("01:02:99", FR_TIME_RES_SEC); /* seconds >= 60 (hh:mm:ss) */
162 check_delta_from_str_fail("-99:30", FR_TIME_RES_SEC); /* minutes >= 60 (negative mm:ss) */
163 check_delta_from_str_fail("-1:02:99", FR_TIME_RES_SEC); /* seconds >= 60 (negative hh:mm:ss) */
164
165 /*
166 * Extreme first-component magnitudes must be rejected, and taking
167 * their magnitude (the "negative ? -integer : integer" step) must
168 * never overflow or, for INT64_MIN, trigger signed-negation UB.
169 * These are the raw int64_t bounds fr_sbuff_out() can parse for the
170 * first number. They must always be rejected in do_timestamp,
171 * regardless of the exact range-guard threshold.
172 */
173 TEST_CASE("extreme first-component magnitudes are rejected");
174 check_delta_from_str_fail("9223372036854775807:00", FR_TIME_RES_SEC); /* INT64_MAX, mm:ss */
175 check_delta_from_str_fail("-9223372036854775808:00", FR_TIME_RES_SEC); /* INT64_MIN, mm:ss (would-be negation UB) */
176 check_delta_from_str_fail("9223372036854775807:00:00", FR_TIME_RES_SEC); /* INT64_MAX, hh:mm:ss */
177 check_delta_from_str_fail("-9223372036854775808:00:00", FR_TIME_RES_SEC); /* INT64_MIN, hh:mm:ss (would-be negation UB) */
178
179 TEST_CASE("overflow is detected");
180 check_delta_from_str_fail("10000000000s", FR_TIME_RES_SEC); /* 1e10s * 1e9 > INT64_MAX */
181}
182
183/** Format time deltas to strings across sign, fraction and the unsigned flag. */
184static void time_delta_to_str(void)
185{
186 char buf[64];
187 fr_sbuff_t sbuff;
188 struct {
189 int64_t delta; /* nanoseconds */
190 fr_time_res_t res;
191 bool is_unsigned;
192 char const *expected;
193 } tests[] = {
194 { 5 * (int64_t)NSEC, FR_TIME_RES_SEC, false, "5" },
195 { 1500 * (NSEC / MSEC), FR_TIME_RES_SEC, false, "1.5" },
196 { -1 * (int64_t)NSEC, FR_TIME_RES_SEC, false, "-1" },
197 { -100 * (NSEC / MSEC), FR_TIME_RES_SEC, false, "-0.1" }, /* lhs == 0 but still negative */
198 { -5 * (int64_t)NSEC, FR_TIME_RES_SEC, true, "0" }, /* unsigned clamps negatives */
199 };
200
201 for (size_t i = 0; i < NUM_ELEMENTS(tests); i++) {
202 fr_slen_t slen;
203
204 sbuff = FR_SBUFF_OUT(buf, sizeof(buf));
205 slen = fr_time_delta_to_str(&sbuff, fr_time_delta_wrap(tests[i].delta),
206 tests[i].res, tests[i].is_unsigned);
207 TEST_CHECK(slen > 0);
208 TEST_CHECK(strcmp(buf, tests[i].expected) == 0);
209 TEST_MSG("delta %" PRIi64 ": got \"%s\", expected \"%s\"",
210 tests[i].delta, buf, tests[i].expected);
211 }
212}
213
214/** Test fr_time_scale: per-resolution multipliers, clamping and invalid hints. */
215static void time_scale(void)
216{
217 TEST_CASE("each resolution scales to nanoseconds");
218 TEST_CHECK(fr_time_scale(5, FR_TIME_RES_SEC) == 5 * (int64_t)NSEC);
222
223 TEST_CASE("hints the function does not handle return 0");
226
227 TEST_CASE("overflow and underflow clamp");
228 TEST_CHECK(fr_time_scale(INT64_MAX, FR_TIME_RES_SEC) == INT64_MAX);
229 TEST_CHECK(fr_time_scale(INT64_MIN, FR_TIME_RES_SEC) == INT64_MIN);
231}
232
233/** Test fr_unix_time_from_tm against well-known UTC epochs. */
234static void unix_time_from_tm(void)
235{
236 struct tm tm;
237
238 TEST_CASE("the unix epoch");
239 memset(&tm, 0, sizeof(tm));
240 tm.tm_year = 1970 - 1900;
241 tm.tm_mon = 0;
242 tm.tm_mday = 1;
244
245 TEST_CASE("2000-01-01T00:00:00Z == 946684800");
246 memset(&tm, 0, sizeof(tm));
247 tm.tm_year = 2000 - 1900;
248 tm.tm_mon = 0;
249 tm.tm_mday = 1;
250 TEST_CHECK(fr_unix_time_unwrap(fr_unix_time_from_tm(&tm)) == (int64_t)946684800 * NSEC);
251
252 TEST_CASE("the Y2038 boundary, 2038-01-19T03:14:07Z == INT32_MAX");
253 memset(&tm, 0, sizeof(tm));
254 tm.tm_year = 2038 - 1900;
255 tm.tm_mon = 0;
256 tm.tm_mday = 19;
257 tm.tm_hour = 3;
258 tm.tm_min = 14;
259 tm.tm_sec = 7;
260 TEST_CHECK(fr_unix_time_unwrap(fr_unix_time_from_tm(&tm)) == (int64_t)2147483647 * NSEC);
261
262 TEST_CASE("a positive gmtoff is removed to give UTC");
263 memset(&tm, 0, sizeof(tm));
264 tm.tm_year = 2000 - 1900;
265 tm.tm_mon = 0;
266 tm.tm_mday = 1;
267 tm.tm_gmtoff = 3600; /* one hour east of UTC */
268 TEST_CHECK(fr_unix_time_unwrap(fr_unix_time_from_tm(&tm)) == ((int64_t)946684800 - 3600) * NSEC);
269}
270
271/** Round-trip fr_unix_time_from_str() and fr_unix_time_to_str() in UTC. */
272static void unix_time_str(void)
273{
275 char buf[128];
276 fr_sbuff_t sbuff;
277
278 TEST_CASE("a bare unix timestamp scales by the hint");
279 TEST_CHECK(fr_unix_time_from_str(&t, "946684800", FR_TIME_RES_SEC) == 0);
280 TEST_CHECK(fr_unix_time_unwrap(t) == (int64_t)946684800 * NSEC);
281
282 TEST_CASE("RFC 3339 in UTC round-trips");
283 TEST_CHECK(fr_unix_time_from_str(&t, "2000-01-01T00:00:00Z", FR_TIME_RES_SEC) == 0);
284 TEST_CHECK(fr_unix_time_unwrap(t) == (int64_t)946684800 * NSEC);
285 sbuff = FR_SBUFF_OUT(buf, sizeof(buf));
286 TEST_CHECK(fr_unix_time_to_str(&sbuff, t, FR_TIME_RES_SEC, true) > 0);
287 TEST_CHECK(strcmp(buf, "2000-01-01T00:00:00Z") == 0);
288 TEST_MSG("got \"%s\"", buf);
289
290 TEST_CASE("RFC 3339 numeric timezone offsets are removed to give UTC");
291 TEST_CHECK(fr_unix_time_from_str(&t, "2000-01-01T00:00:00+01:00", FR_TIME_RES_SEC) == 0);
292 TEST_CHECK(fr_unix_time_unwrap(t) == ((int64_t)946684800 - 3600) * NSEC); /* +01:00 is one hour ahead of UTC */
293 TEST_CHECK(fr_unix_time_from_str(&t, "2000-01-01T00:00:00-01:00", FR_TIME_RES_SEC) == 0);
294 TEST_CHECK(fr_unix_time_unwrap(t) == ((int64_t)946684800 + 3600) * NSEC); /* -01:00 is one hour behind UTC */
295
296 TEST_CASE("sub-second parsing and millisecond formatting");
297 TEST_CHECK(fr_unix_time_from_str(&t, "2000-01-01T00:00:00.5Z", FR_TIME_RES_SEC) == 0);
298 TEST_CHECK(fr_unix_time_unwrap(t) == ((int64_t)946684800 * NSEC) + (NSEC / 2));
299 sbuff = FR_SBUFF_OUT(buf, sizeof(buf));
300 TEST_CHECK(fr_unix_time_to_str(&sbuff, t, FR_TIME_RES_MSEC, true) > 0);
301 TEST_CHECK(strcmp(buf, "2000-01-01T00:00:00.500Z") == 0);
302 TEST_MSG("got \"%s\"", buf);
303
304 TEST_CASE("malformed input is rejected");
306 TEST_CHECK(fr_unix_time_from_str(&t, "2000-13-01T00:00:00Z", FR_TIME_RES_SEC) < 0); /* month 13 */
307}
308
309/** Test fr_time_elapsed_update sorts delays into the correct histogram bins. */
310static void time_elapsed_update(void)
311{
312 fr_time_elapsed_t elapsed;
313 static int64_t const delays[8] = {
314 500, /* < 1us -> [0] */
315 5000, /* < 10us -> [1] */
316 50000, /* < 100us -> [2] */
317 500000, /* < 1ms -> [3] */
318 5000000, /* < 10ms -> [4] */
319 50000000, /* < 100ms -> [5] */
320 500000000, /* < 1s -> [6] */
321 5000000000 /* >= 1s -> [7] */
322 };
323
324 memset(&elapsed, 0, sizeof(elapsed));
325
326 TEST_CASE("one delay lands in each bin");
327 for (int i = 0; i < 8; i++) {
328 fr_time_elapsed_update(&elapsed, fr_time_wrap(0), fr_time_wrap(delays[i]));
329 }
330 for (int i = 0; i < 8; i++) {
331 TEST_CHECK(elapsed.array[i] == 1);
332 TEST_MSG("bin %d: got %" PRIu64, i, elapsed.array[i]);
333 }
334
335 TEST_CASE("start >= end is treated as a zero delay");
337 TEST_CHECK(elapsed.array[0] == 2);
338 TEST_MSG("bin 0: got %" PRIu64, elapsed.array[0]);
339}
340
342 { "time_const_benchmark", time_benchmark },
343 { "time_delta_to_str_int64_min", time_delta_to_str_int64_min },
344 { "time_delta_from_str", time_delta_from_str },
345 { "time_delta_to_str", time_delta_to_str },
346 { "time_scale", time_scale },
347 { "unix_time_from_tm", unix_time_from_tm },
348 { "unix_time_str", unix_time_str },
349 { "time_elapsed_update", time_elapsed_update },
350
351 { 0 }
352};
#define TEST_CHECK(cond)
Definition acutest.h:87
#define TEST_CASE(name)
Definition acutest.h:186
#define TEST_MSG(...)
Definition acutest.h:217
static bool stop
Definition radmin.c:68
#define DIAG_ON(_x)
Definition build.h:535
#define NUM_ELEMENTS(_t)
Definition build.h:406
#define DIAG_OFF(_x)
Definition build.h:534
static fr_slen_t in
Definition dict.h:882
#define fr_time()
Definition event.c:60
long int ssize_t
ssize_t fr_slen_t
#define FR_SBUFF_OUT(_start, _len_or_end)
void fr_time_elapsed_update(fr_time_elapsed_t *elapsed, fr_time_t start, fr_time_t end)
Definition time.c:570
fr_unix_time_t fr_unix_time_from_tm(struct tm *tm)
Definition time.c:651
fr_slen_t fr_time_delta_from_str(fr_time_delta_t *out, char const *in, size_t inlen, fr_time_res_t hint)
Create fr_time_delta_t from a string.
Definition time.c:419
int fr_unix_time_from_str(fr_unix_time_t *date, char const *date_str, fr_time_res_t hint)
Convert string in various formats to a fr_unix_time_t.
Definition time.c:817
int64_t fr_time_scale(int64_t t, fr_time_res_t hint)
Scale an input time to NSEC, clamping it at max / min.
Definition time.c:706
fr_slen_t fr_time_delta_to_str(fr_sbuff_t *out, fr_time_delta_t delta, fr_time_res_t res, bool is_unsigned)
Print fr_time_delta_t to a string with an appropriate suffix.
Definition time.c:447
fr_slen_t fr_unix_time_to_str(fr_sbuff_t *out, fr_unix_time_t time, fr_time_res_t res, bool utc)
Convert unix time to string.
Definition time.c:1152
#define MSEC
Definition time.h:381
static int64_t fr_time_delta_unwrap(fr_time_delta_t time)
Definition time.h:154
#define fr_time_delta_wrap(_time)
Definition time.h:152
#define fr_time_wrap(_time)
Definition time.h:145
fr_time_res_t
The base resolution for print parse operations.
Definition time.h:48
@ FR_TIME_RES_MSEC
Definition time.h:58
@ FR_TIME_RES_MIN
Definition time.h:51
@ FR_TIME_RES_NSEC
Definition time.h:60
@ FR_TIME_RES_USEC
Definition time.h:59
@ FR_TIME_RES_SEC
Definition time.h:50
@ FR_TIME_RES_INVALID
Definition time.h:49
#define NSEC
Definition time.h:379
static uint64_t fr_unix_time_unwrap(fr_unix_time_t time)
Definition time.h:161
uint64_t array[8]
100ns to 100s
Definition time.h:376
#define USEC
Definition time.h:380
#define fr_time_sub(_a, _b)
Subtract one time from another.
Definition time.h:229
A time delta, a difference in time measured in nanoseconds.
Definition time.h:80
"server local" time.
Definition time.h:69
"Unix" time.
Definition time.h:95
static void unix_time_str(void)
Round-trip fr_unix_time_from_str() and fr_unix_time_to_str() in UTC.
Definition time_tests.c:272
TEST_LIST
Definition time_tests.c:341
static void time_delta_from_str(void)
Parse time deltas from strings: integers, floats, scale suffixes, timestamps and errors.
Definition time_tests.c:111
static void time_delta_to_str_int64_min(void)
Format the most negative delta possible.
Definition time_tests.c:60
#define ROUNDS
Definition time_tests.c:27
static void time_delta_to_str(void)
Format time deltas to strings across sign, fraction and the unsigned flag.
Definition time_tests.c:184
static void unix_time_from_tm(void)
Test fr_unix_time_from_tm against well-known UTC epochs.
Definition time_tests.c:234
static void time_elapsed_update(void)
Test fr_time_elapsed_update sorts delays into the correct histogram bins.
Definition time_tests.c:310
static void time_benchmark(void)
Definition time_tests.c:30
static void time_scale(void)
Test fr_time_scale: per-resolution multipliers, clamping and invalid hints.
Definition time_tests.c:215
static void check_delta_from_str(char const *in, fr_time_res_t hint, int64_t expected)
Definition time_tests.c:87
static void check_delta_from_str_fail(char const *in, fr_time_res_t hint)
Definition time_tests.c:100