Browse Source

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 <cfriedt@tenstorrent.com>
pull/90365/head
Chris Friedt 2 months ago committed by Benjamin Cabé
parent
commit
13fe95b51d
  1. 8
      tests/lib/timespec_util/CMakeLists.txt
  2. 1
      tests/lib/timespec_util/prj.conf
  3. 380
      tests/lib/timespec_util/src/main.c
  4. 36
      tests/lib/timespec_util/testcase.yaml

8
tests/lib/timespec_util/CMakeLists.txt

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(timespec_util)
FILE(GLOB app_sources src/main.c)
target_sources(app PRIVATE ${app_sources})

1
tests/lib/timespec_util/prj.conf

@ -0,0 +1 @@ @@ -0,0 +1 @@
CONFIG_ZTEST=y

380
tests/lib/timespec_util/src/main.c

@ -0,0 +1,380 @@ @@ -0,0 +1,380 @@
/*
* Copyright 2025 Tenstorrent AI ULC
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <time.h>
#include <zephyr/ztest.h>
#include <zephyr/sys_clock.h>
#include <zephyr/sys/timeutil.h>
#include <zephyr/sys/util.h>
BUILD_ASSERT(sizeof(time_t) == sizeof(int64_t), "time_t must be 64-bit");
BUILD_ASSERT(sizeof(((struct timespec *)0)->tv_sec) == sizeof(int64_t), "tv_sec must be 64-bit");
/* need NSEC_PER_SEC to be signed for the purposes of this testsuite */
#undef NSEC_PER_SEC
#define NSEC_PER_SEC 1000000000L
#undef CORRECTABLE
#define CORRECTABLE true
#undef UNCORRECTABLE
#define UNCORRECTABLE false
/*
* test spec for simple timespec validation
*
* If a timespec is expected to be valid, then invalid_ts and valid_ts are equal.
*
* If a timespec is expected to be invalid, then invalid_ts and valid_ts are not equal.
*/
struct ts_test_spec {
struct timespec invalid_ts;
struct timespec valid_ts;
bool expect_valid;
bool correctable;
};
#define DECL_VALID_TS_TEST(sec, nsec) \
{ \
.invalid_ts = {(sec), (nsec)}, \
.valid_ts = {(sec), (nsec)}, \
.expect_valid = true, \
.correctable = false, \
}
/*
* Invalid timespecs can usually be converted to valid ones by adding or subtracting
* multiples of `NSEC_PER_SEC` from tv_nsec, and incrementing or decrementing tv_sec
* proportionately, unless doing so would result in signed integer overflow.
*
* There are two particular corner cases:
* 1. making tv_sec more negative would overflow the tv_sec field in the negative direction
* 1. making tv_sec more positive would overflow the tv_sec field in the positive direction
*/
#define DECL_INVALID_TS_TEST(invalid_sec, invalid_nsec, valid_sec, valid_nsec, is_correctable) \
{ \
.valid_ts = {(valid_sec), (valid_nsec)}, \
.invalid_ts = {(invalid_sec), (invalid_nsec)}, \
.expect_valid = false, \
.correctable = (is_correctable), \
}
/*
* Easily verifiable tests for both valid and invalid timespecs.
*/
static const struct ts_test_spec ts_tests[] = {
/* Valid cases */
DECL_VALID_TS_TEST(0, 0),
DECL_VALID_TS_TEST(0, 1),
DECL_VALID_TS_TEST(1, 0),
DECL_VALID_TS_TEST(1, 1),
DECL_VALID_TS_TEST(1, NSEC_PER_SEC - 1),
DECL_VALID_TS_TEST(-1, 0),
DECL_VALID_TS_TEST(-1, 1),
DECL_VALID_TS_TEST(-1, 0),
DECL_VALID_TS_TEST(-1, 1),
DECL_VALID_TS_TEST(-1, NSEC_PER_SEC - 1),
DECL_VALID_TS_TEST(INT64_MIN, 0),
DECL_VALID_TS_TEST(INT64_MIN, NSEC_PER_SEC - 1),
DECL_VALID_TS_TEST(INT64_MAX, 0),
DECL_VALID_TS_TEST(INT64_MAX, NSEC_PER_SEC - 1),
/* Correctable, invalid cases */
DECL_INVALID_TS_TEST(0, -2 * NSEC_PER_SEC + 1, -2, 1, CORRECTABLE),
DECL_INVALID_TS_TEST(0, -2 * NSEC_PER_SEC - 1, -3, NSEC_PER_SEC - 1, CORRECTABLE),
DECL_INVALID_TS_TEST(0, -NSEC_PER_SEC - 1, -2, NSEC_PER_SEC - 1, CORRECTABLE),
DECL_INVALID_TS_TEST(0, -1, -1, NSEC_PER_SEC - 1, CORRECTABLE),
DECL_INVALID_TS_TEST(0, NSEC_PER_SEC, 1, 0, CORRECTABLE),
DECL_INVALID_TS_TEST(0, NSEC_PER_SEC + 1, 1, 0, CORRECTABLE),
DECL_INVALID_TS_TEST(1, -1, 0, NSEC_PER_SEC - 1, CORRECTABLE),
DECL_INVALID_TS_TEST(1, NSEC_PER_SEC, 2, 0, CORRECTABLE),
DECL_INVALID_TS_TEST(-1, -1, -2, NSEC_PER_SEC - 1, CORRECTABLE),
DECL_INVALID_TS_TEST(0, NSEC_PER_SEC, 1, 0, CORRECTABLE),
DECL_INVALID_TS_TEST(1, -1, 0, NSEC_PER_SEC - 1, CORRECTABLE),
DECL_INVALID_TS_TEST(1, NSEC_PER_SEC, 2, 0, CORRECTABLE),
DECL_INVALID_TS_TEST(INT64_MIN, NSEC_PER_SEC, INT64_MIN + 1, 0, CORRECTABLE),
DECL_INVALID_TS_TEST(INT64_MAX, -1, INT64_MAX - 1, NSEC_PER_SEC - 1, CORRECTABLE),
DECL_INVALID_TS_TEST(0, LONG_MIN, LONG_MAX / NSEC_PER_SEC, 145224192, CORRECTABLE),
DECL_INVALID_TS_TEST(0, LONG_MAX, LONG_MAX / NSEC_PER_SEC, LONG_MAX % NSEC_PER_SEC,
CORRECTABLE),
/* Uncorrectable, invalid cases */
DECL_INVALID_TS_TEST(INT64_MIN + 2, -2 * NSEC_PER_SEC - 1, 0, 0, UNCORRECTABLE),
DECL_INVALID_TS_TEST(INT64_MIN + 1, -NSEC_PER_SEC - 1, 0, 0, UNCORRECTABLE),
DECL_INVALID_TS_TEST(INT64_MIN + 1, -NSEC_PER_SEC - 1, 0, 0, UNCORRECTABLE),
DECL_INVALID_TS_TEST(INT64_MIN, -1, 0, 0, UNCORRECTABLE),
DECL_INVALID_TS_TEST(INT64_MAX, NSEC_PER_SEC, 0, 0, UNCORRECTABLE),
DECL_INVALID_TS_TEST(INT64_MAX - 1, 2 * NSEC_PER_SEC, 0, 0, UNCORRECTABLE),
};
ZTEST(timeutil_api, test_timespec_is_valid)
{
ARRAY_FOR_EACH(ts_tests, i) {
const struct ts_test_spec *const tspec = &ts_tests[i];
bool valid = timespec_is_valid(&tspec->invalid_ts);
zexpect_equal(valid, tspec->expect_valid,
"%d: timespec_is_valid({%ld, %ld}) = %s, expected true", i,
tspec->valid_ts.tv_sec, tspec->valid_ts.tv_nsec,
tspec->expect_valid ? "false" : "true");
}
}
ZTEST(timeutil_api, test_timespec_normalize)
{
ARRAY_FOR_EACH(ts_tests, i) {
bool different;
bool overflow;
const struct ts_test_spec *const tspec = &ts_tests[i];
struct timespec norm = tspec->invalid_ts;
overflow = !timespec_normalize(&norm);
zexpect_not_equal(tspec->expect_valid || tspec->correctable, overflow,
"%d: timespec_normalize({%ld, %ld}) %s, unexpectedly", i,
(long)tspec->invalid_ts.tv_sec, (long)tspec->invalid_ts.tv_nsec,
tspec->correctable ? "failed" : "succeeded");
if (!tspec->expect_valid && tspec->correctable) {
different = !timespec_equal(&tspec->invalid_ts, &norm);
zexpect_true(different, "%d: {%ld, %ld} and {%ld, %ld} are unexpectedly %s",
i, tspec->invalid_ts.tv_sec, tspec->invalid_ts.tv_nsec,
norm.tv_sec, tspec->valid_ts.tv_sec,
(tspec->expect_valid || tspec->correctable) ? "different"
: "equal");
}
}
}
ZTEST(timeutil_api, test_timespec_add)
{
bool overflow;
struct timespec actual;
const struct atspec {
struct timespec a;
struct timespec b;
struct timespec result;
bool expect;
} tspecs[] = {
/* non-overflow cases */
{.a = {0, 0}, .b = {0, 0}, .result = {0, 0}, .expect = false},
{.a = {1, 1}, .b = {1, 1}, .result = {2, 2}, .expect = false},
{.a = {-1, 1}, .b = {-1, 1}, .result = {-2, 2}, .expect = false},
{.a = {-1, NSEC_PER_SEC - 1}, .b = {0, 1}, .result = {0, 0}, .expect = false},
/* overflow cases */
{.a = {INT64_MAX, 0}, .b = {1, 0}, .result = {0}, .expect = true},
{.a = {INT64_MIN, 0}, .b = {-1, 0}, .result = {0}, .expect = true},
{.a = {INT64_MAX, NSEC_PER_SEC - 1}, .b = {1, 1}, .result = {0}, .expect = true},
{.a = {INT64_MIN, NSEC_PER_SEC - 1}, .b = {-1, 0}, .result = {0}, .expect = true},
};
ARRAY_FOR_EACH(tspecs, i) {
const struct atspec *const tspec = &tspecs[i];
actual = tspec->a;
overflow = !timespec_add(&actual, &tspec->b);
zexpect_equal(overflow, tspec->expect,
"%d: timespec_add({%ld, %ld}, {%ld, %ld}) %s, unexpectedly", i,
tspec->a.tv_sec, tspec->a.tv_nsec, tspec->b.tv_sec, tspec->b.tv_nsec,
tspec->expect ? "succeeded" : "failed");
if (!tspec->expect) {
zexpect_equal(timespec_equal(&actual, &tspec->result), true,
"%d: {%ld, %ld} and {%ld, %ld} are unexpectedly different", i,
actual.tv_sec, actual.tv_nsec, tspec->result.tv_sec,
tspec->result.tv_nsec);
}
}
}
ZTEST(timeutil_api, test_timespec_negate)
{
struct ntspec {
struct timespec ts;
struct timespec result;
bool expect_failure;
} tspecs[] = {
/* non-overflow cases */
{.ts = {0, 0}, .result = {0, 0}, .expect_failure = false},
{.ts = {1, 1}, .result = {-2, NSEC_PER_SEC - 1}, .expect_failure = false},
{.ts = {-1, 1}, .result = {0, NSEC_PER_SEC - 1}, .expect_failure = false},
{.ts = {INT64_MAX, 0}, .result = {INT64_MIN + 1, 0}, .expect_failure = false},
/* overflow cases */
{.ts = {INT64_MIN, 0}, .result = {0}, .expect_failure = true},
};
ARRAY_FOR_EACH(tspecs, i) {
bool overflow;
const struct ntspec *const tspec = &tspecs[i];
struct timespec actual = tspec->ts;
overflow = !timespec_negate(&actual);
zexpect_equal(overflow, tspec->expect_failure,
"%d: timespec_negate({%ld, %ld}) %s, unexpectedly", i,
tspec->ts.tv_sec, tspec->ts.tv_nsec,
tspec->expect_failure ? "did not overflow" : "overflowed");
if (!tspec->expect_failure) {
zexpect_true(timespec_equal(&actual, &tspec->result),
"%d: {%ld, %ld} and {%ld, %ld} are unexpectedly different", i,
actual.tv_sec, actual.tv_nsec, tspec->result.tv_sec,
tspec->result.tv_nsec);
}
}
}
ZTEST(timeutil_api, test_timespec_sub)
{
struct timespec a = {0};
struct timespec b = {0};
zexpect_true(timespec_sub(&a, &b));
}
ZTEST(timeutil_api, test_timespec_compare)
{
struct timespec a;
struct timespec b;
a = (struct timespec){0};
b = (struct timespec){0};
zexpect_equal(timespec_compare(&a, &b), 0);
a = (struct timespec){-1};
b = (struct timespec){0};
zexpect_equal(timespec_compare(&a, &b), -1);
a = (struct timespec){1};
b = (struct timespec){0};
zexpect_equal(timespec_compare(&a, &b), 1);
}
ZTEST(timeutil_api, test_timespec_equal)
{
struct timespec a;
struct timespec b;
a = (struct timespec){0};
b = (struct timespec){0};
zexpect_true(timespec_equal(&a, &b));
a = (struct timespec){-1};
b = (struct timespec){0};
zexpect_false(timespec_equal(&a, &b));
}
#define K_TICK_MAX ((uint64_t)(CONFIG_TIMEOUT_64BIT ? (INT64_MAX) : (UINT32_MAX)))
#define NS_PER_TICK (NSEC_PER_SEC / CONFIG_SYS_CLOCK_TICKS_PER_SEC)
/* 0 := lower limit, 2 := upper limit */
static const struct timespec k_timeout_limits[] = {
/* K_NO_WAIT + 1 tick */
{
.tv_sec = 0,
.tv_nsec = NS_PER_TICK,
},
/* K_FOREVER - 1 tick */
{
.tv_sec = CLAMP((NS_PER_TICK * K_TICK_MAX) / NSEC_PER_SEC, 0, INT64_MAX),
.tv_nsec = CLAMP((NS_PER_TICK * K_TICK_MAX) % NSEC_PER_SEC, 0, NSEC_PER_SEC - 1),
},
};
static const struct tospec {
k_timeout_t timeout;
struct timespec tspec;
int saturation;
} tospecs[] = {
{K_NO_WAIT, {INT64_MIN, 0}, -1},
{K_NO_WAIT, {-1, 0}, -1},
{K_NO_WAIT, {-1, NSEC_PER_SEC - 1}, -1},
{K_NO_WAIT, {0, 0}, 0},
{K_NSEC(0), {0, 0}, 0},
{K_NSEC(2000000000), {2, 0}, 0},
{K_USEC(0), {0, 0}, 0},
{K_USEC(2000000), {2, 0}, 0},
{K_MSEC(100), {0, 100000000}, 0},
{K_MSEC(2000), {2, 0}, 0},
{K_SECONDS(0), {0, 0}, 0},
{K_SECONDS(1), {1, 0}, 0},
{K_SECONDS(100), {100, 0}, 0},
{K_FOREVER, {INT64_MAX, NSEC_PER_SEC - 1}, 0},
};
ZTEST(timeutil_api, test_timespec_from_timeout)
{
ARRAY_FOR_EACH(tospecs, i) {
const struct tospec *const tspec = &tospecs[i];
struct timespec actual;
if (tspec->saturation != 0) {
/* saturation cases are only checked in test_timespec_to_timeout */
continue;
}
timespec_from_timeout(tspec->timeout, &actual);
zexpect_true(timespec_equal(&actual, &tspec->tspec),
"%d: {%ld, %ld} and {%ld, %ld} are unexpectedly different", i,
actual.tv_sec, actual.tv_nsec, tspec->tspec.tv_sec,
tspec->tspec.tv_nsec);
}
}
ZTEST(timeutil_api, test_timespec_to_timeout)
{
ARRAY_FOR_EACH(tospecs, i) {
const struct tospec *const tspec = &tospecs[i];
k_timeout_t actual;
if (tspec->saturation == 0) {
/* no saturation / exact match */
actual = timespec_to_timeout(&tspec->tspec);
zexpect_equal(actual.ticks, tspec->timeout.ticks,
"%d: {%" PRId64 "} and {%" PRId64
"} are unexpectedly different",
i, (int64_t)actual.ticks, (int64_t)tspec->timeout.ticks);
continue;
}
if ((tspec->saturation < 0) ||
(timespec_compare(&tspec->tspec, &k_timeout_limits[0]) < 0)) {
/* K_NO_WAIT saturation */
actual = timespec_to_timeout(&tspec->tspec);
zexpect_equal(actual.ticks, K_NO_WAIT.ticks,
"%d: {%" PRId64 "} and {%" PRId64
"} are unexpectedly different",
i, (int64_t)actual.ticks, (int64_t)K_NO_WAIT.ticks);
continue;
}
if ((tspec->saturation > 0) ||
(timespec_compare(&tspec->tspec, &k_timeout_limits[1]) > 0)) {
/* K_FOREVER saturation */
actual = timespec_to_timeout(&tspec->tspec);
zexpect_equal(actual.ticks, K_TICKS_FOREVER,
"%d: {%" PRId64 "} and {%" PRId64
"} are unexpectedly different",
i, (int64_t)actual.ticks, (int64_t)K_TICKS_FOREVER);
continue;
}
}
}
static void *setup(void)
{
printk("CONFIG_TIMEOUT_64BIT=%c\n", CONFIG_TIMEOUT_64BIT ? 'y' : 'n');
printk("K_TICK_MAX: %lld\n", (long long)K_TICK_MAX);
printk("minimum timeout: {%lld, %lld}\n", (long long)k_timeout_limits[0].tv_sec,
(long long)k_timeout_limits[0].tv_nsec);
printk("maximum timeout: {%lld, %lld}\n", (long long)k_timeout_limits[1].tv_sec,
(long long)k_timeout_limits[1].tv_nsec);
return NULL;
}
ZTEST_SUITE(timeutil_api, NULL, setup, NULL, NULL, NULL);

36
tests/lib/timespec_util/testcase.yaml

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
# FIXME: this should be under tests/unit/timeutil but will not work due to #90029
common:
filter: not CONFIG_NATIVE_LIBC
tags:
- timeutils
# 1 tier0 platform per supported architecture
platform_key:
- arch
- simulation
integration_platforms:
- native_sim/native/64
tests:
libraries.timespec_utils: {}
libraries.timespec_utils.speed:
extra_configs:
- CONFIG_SPEED_OPTIMIZATIONS=y
libraries.timespec_utils.armclang_std_libc:
toolchain_allow: armclang
extra_configs:
- CONFIG_ARMCLANG_STD_LIBC=y
libraries.timespec_utils.arcmwdtlib:
toolchain_allow: arcmwdt
extra_configs:
- CONFIG_ARCMWDT_LIBC=y
libraries.timespec_utils.minimal:
extra_configs:
- CONFIG_MINIMAL_LIBC=y
libraries.timespec_utils.newlib:
filter: TOOLCHAIN_HAS_NEWLIB == 1
extra_configs:
- CONFIG_NEWLIB_LIBC=y
libraries.timespec_utils.picolibc:
tags: picolibc
filter: CONFIG_PICOLIBC_SUPPORTED
extra_configs:
- CONFIG_PICOLIBC=y
Loading…
Cancel
Save