Skip to content

Commit 6f0e153

Browse files
committed
tests: timerfd: add tests
This will test the timerfd interface and behavior similar to the popular interface available on other POSIX OS. The tests are based on the eventfd plus the POSIX timer tests. Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
1 parent 51f9b00 commit 6f0e153

File tree

11 files changed

+743
-0
lines changed

11 files changed

+743
-0
lines changed

tests/posix/timerfd/CMakeLists.txt

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(eventfd)
6+
7+
FILE(GLOB app_sources src/*.c)
8+
target_sources(app PRIVATE ${app_sources})

tests/posix/timerfd/Kconfig

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Copyright (c) 2025 Atym, Inc
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
source "Kconfig.zephyr"
6+
7+
config TEST_DURATION_S
8+
int "Number of seconds to run the test"
9+
range 1 21600
10+
default 5
11+
help
12+
Duration for the test, in seconds. The range has a reblatively high
13+
upper bound because we should expect that timerfd_read() and
14+
the timer are stable enough to run for an arbitrarily long
15+
period of time without encountering any race conditions.
16+
17+
config TEST_TIMEOUT_S
18+
int "Number of seconds to run the test"
19+
range 1 21600
20+
default 10
21+
22+
config TEST_STACK_SIZE
23+
int "Size of each thread stack in this test"
24+
default 2048
25+
help
26+
The minimal stack size required to run a no-op thread.
27+
28+
config TEST_EXTRA_ASSERTIONS
29+
bool "Add extra assertions into the hot path"
30+
help
31+
In order to get a true benchmark, there should be as few branches
32+
as possible on the hot path. Say 'y' here to add extra assertions
33+
on the hot path as well to verify functionality.
34+
35+
config TEST_EXTRA_QUIET
36+
bool "Do not print out regular reports"
37+
help
38+
In order to get a true benchmark, there should be as few branches
39+
as possible on the hot path. Say 'y' here to skip reporting.
40+
41+
module = TIMERFD_TEST
42+
module-str = timerfd
43+
source "subsys/logging/Kconfig.template.log_config"

tests/posix/timerfd/prj.conf

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CONFIG_ZTEST=y
2+
3+
CONFIG_POSIX_API=y
4+
CONFIG_XOPEN_STREAMS=y
5+
CONFIG_TIMERFD=y
6+
7+
CONFIG_ZVFS_OPEN_MAX=4
8+
9+
CONFIG_TEST_STACK_SIZE=5200

tests/posix/timerfd/src/_main.c

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright (c) 2020 Tobias Svehagen
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include "_main.h"
8+
9+
void reopen(int *fd, int clockid, int flags)
10+
{
11+
zassert_not_null(fd);
12+
zassert_ok(close(*fd));
13+
*fd = timerfd_create(clockid, flags);
14+
zassert_true(*fd >= 0, "timerfd(%d, %d) failed: %d", clockid, flags, errno);
15+
}
16+
17+
int is_blocked(int fd, short *event)
18+
{
19+
struct pollfd pfd;
20+
int ret;
21+
22+
pfd.fd = fd;
23+
pfd.events = *event;
24+
25+
ret = poll(&pfd, 1, 0);
26+
zassert_true(ret >= 0, "poll failed %d", ret);
27+
28+
*event = pfd.revents;
29+
30+
return ret == 0;
31+
}
32+
33+
void timerfd_poll_unset_common(int fd)
34+
{
35+
uint64_t val = 0;
36+
37+
short event;
38+
int ret;
39+
40+
event = POLLIN;
41+
ret = is_blocked(fd, &event);
42+
zassert_equal(ret, 1, "timerfd not blocked with initval == 0");
43+
44+
zassert_ok(ioctl(fd, TFD_IOC_SET_TICKS, (uint64_t)TESTVAL));
45+
46+
event = POLLIN;
47+
ret = is_blocked(fd, &event);
48+
zassert_equal(ret, 0, "timerfd blocked after write");
49+
zassert_equal(event, POLLIN, "POLLIN not set");
50+
51+
zassert_equal(read(fd, &val, sizeof(val)), sizeof(val), "read failed");
52+
zassert_equal(val, TESTVAL, "val == %lld, expected %d", val, TESTVAL);
53+
54+
/* timerfd shall block on subsequent reads before next interval expires */
55+
56+
event = POLLIN;
57+
ret = is_blocked(fd, &event);
58+
zassert_equal(ret, 1, "timerfd not blocked after read");
59+
}
60+
61+
void timerfd_poll_set_common(int fd)
62+
{
63+
timerfd_t val = 0;
64+
short event;
65+
int ret;
66+
67+
event = POLLIN;
68+
ret = is_blocked(fd, &event);
69+
zassert_equal(ret, 0, "timerfd is blocked with initval != 0");
70+
71+
ret = read(fd, &val, sizeof(val));
72+
zassert_equal(ret, sizeof(val), "read ret %d", ret);
73+
zassert_equal(val, TESTVAL, "val == %d", (int)val);
74+
75+
event = POLLIN;
76+
ret = is_blocked(fd, &event);
77+
zassert_equal(ret, 1, "timerfd is not blocked after read");
78+
}
79+
80+
static struct timerfd_fixture tfd_fixture;
81+
82+
static void *setup(void)
83+
{
84+
tfd_fixture.fd = -1;
85+
return &tfd_fixture;
86+
}
87+
88+
static void before(void *arg)
89+
{
90+
struct timerfd_fixture *fixture = arg;
91+
92+
fixture->fd = timerfd_create(0, 0);
93+
zassert_true(fixture->fd >= 0, "timerfd(0, 0) failed: %d", errno);
94+
}
95+
96+
static void after(void *arg)
97+
{
98+
struct timerfd_fixture *fixture = arg;
99+
100+
if (fixture->fd != -1) {
101+
close(fixture->fd);
102+
fixture->fd = -1;
103+
}
104+
}
105+
106+
ZTEST_SUITE(timerfd, NULL, setup, before, after, NULL);

tests/posix/timerfd/src/_main.h

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright (c) 2020 Tobias Svehagen
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#ifndef TESTS_POSIX_TIMERFD_SRC__MAIN_H_
8+
#define TESTS_POSIX_TIMERFD_SRC__MAIN_H_
9+
10+
#include <errno.h>
11+
#include <stdio.h>
12+
13+
#include <zephyr/net/socket.h>
14+
#include <zephyr/posix/fcntl.h>
15+
#include <zephyr/posix/sys/ioctl.h>
16+
#include <zephyr/posix/poll.h>
17+
#include <zephyr/posix/sys/timerfd.h>
18+
#include <zephyr/posix/unistd.h>
19+
#include <zephyr/ztest.h>
20+
21+
#define TESTVAL 10
22+
23+
#ifdef __cplusplus
24+
extern "C" {
25+
#endif
26+
27+
struct timerfd_fixture {
28+
int fd;
29+
};
30+
31+
void reopen(int *fd, int clockid, int flags);
32+
int is_blocked(int fd, short *event);
33+
void timerfd_poll_set_common(int fd);
34+
void timerfd_poll_unset_common(int fd);
35+
36+
#ifdef __cplusplus
37+
}
38+
#endif
39+
40+
#endif

tests/posix/timerfd/src/blocking.c

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright (c) 2020 Tobias Svehagen
3+
* Copyright (c) 2025 Atym, Inc.
4+
*
5+
* SPDX-License-Identifier: Apache-2.0
6+
*/
7+
#include "_main.h"
8+
9+
ZTEST_F(timerfd, test_expire_then_read)
10+
{
11+
struct itimerspec ts = {
12+
.it_interval = {0, 100 * NSEC_PER_MSEC},
13+
.it_value = {0, 100 * NSEC_PER_MSEC},
14+
};
15+
timerfd_t val;
16+
int ret;
17+
18+
zassert_ok(timerfd_settime(fixture->fd, 0, &ts, NULL));
19+
k_sleep(K_MSEC(550));
20+
21+
ret = read(fixture->fd, &val, sizeof(val));
22+
zassert_true(ret == 8, "read ret %d", ret);
23+
zassert_true(val == 5, "val == %lld", val);
24+
}
25+
26+
ZTEST_F(timerfd, test_not_started_shall_not_unblock)
27+
{
28+
short event;
29+
int ret;
30+
31+
event = POLLIN;
32+
ret = is_blocked(fixture->fd, &event);
33+
zassert_equal(ret, 1, "timerfd unblocked by expiry");
34+
}
35+
36+
ZTEST_F(timerfd, test_poll_timeout)
37+
{
38+
struct pollfd pfd;
39+
int ret;
40+
41+
pfd.fd = fixture->fd;
42+
pfd.events = POLLIN;
43+
44+
ret = poll(&pfd, 1, 500);
45+
zassert_true(ret == 0, "poll ret %d", ret);
46+
}
47+
48+
ZTEST_F(timerfd, test_set_poll_event_block)
49+
{
50+
reopen(&fixture->fd, CLOCK_MONOTONIC, 0);
51+
zassert_ok(ioctl(fixture->fd, TFD_IOC_SET_TICKS, (uint64_t)TESTVAL));
52+
timerfd_poll_set_common(fixture->fd);
53+
}
54+
55+
ZTEST_F(timerfd, test_unset_poll_event_block)
56+
{
57+
timerfd_poll_unset_common(fixture->fd);
58+
}
59+
60+
K_THREAD_STACK_DEFINE(thread_stack, CONFIG_TEST_STACK_SIZE);
61+
static struct k_thread thread;
62+
63+
static void thread_timerfd_read_1(void *arg1, void *arg2, void *arg3)
64+
{
65+
uint64_t value;
66+
struct timerfd_fixture *fixture = arg1;
67+
68+
zassert_equal(read(fixture->fd, &value, sizeof(value)), sizeof(value));
69+
zassert_equal(value, 1);
70+
}
71+
72+
ZTEST_F(timerfd, test_read_then_expire_block)
73+
{
74+
struct itimerspec ts = {
75+
.it_interval = {0, 100 * NSEC_PER_MSEC},
76+
.it_value = {0, 100 * NSEC_PER_MSEC},
77+
};
78+
79+
k_thread_create(&thread, thread_stack, K_THREAD_STACK_SIZEOF(thread_stack),
80+
thread_timerfd_read_1, fixture, NULL, NULL, 0, 0, K_NO_WAIT);
81+
82+
zassert_ok(timerfd_settime(fixture->fd, 0, &ts, NULL));
83+
84+
k_thread_join(&thread, K_FOREVER);
85+
}
86+
87+
static void thread_timerfd_close(void *arg1, void *arg2, void *arg3)
88+
{
89+
struct timerfd_fixture *fixture = arg1;
90+
91+
zassert_ok(close(fixture->fd));
92+
}
93+
94+
ZTEST_F(timerfd, test_read_then_close_block)
95+
{
96+
timerfd_t value;
97+
98+
struct itimerspec ts = {
99+
.it_interval = {1, 0},
100+
.it_value = {1, 0},
101+
};
102+
103+
k_thread_create(&thread, thread_stack, K_THREAD_STACK_SIZEOF(thread_stack),
104+
thread_timerfd_close, fixture, NULL, NULL, 0, 0, K_MSEC(100));
105+
106+
zassert_ok(timerfd_settime(fixture->fd, 0, &ts, NULL));
107+
108+
zassert_equal(read(fixture->fd, &value, sizeof(value)), -1);
109+
110+
k_thread_join(&thread, K_FOREVER);
111+
}
112+
113+
ZTEST_F(timerfd, test_expire_while_pollin)
114+
{
115+
struct itimerspec ts = {
116+
.it_value = {0, 100 * NSEC_PER_MSEC},
117+
};
118+
119+
struct zsock_pollfd fds[] = {
120+
{
121+
.fd = fixture->fd,
122+
.events = ZSOCK_POLLIN,
123+
},
124+
};
125+
timerfd_t value;
126+
int ret;
127+
128+
zassert_ok(timerfd_settime(fixture->fd, 0, &ts, NULL));
129+
130+
/* Expect 1 event */
131+
ret = zsock_poll(fds, ARRAY_SIZE(fds), 200);
132+
zassert_equal(ret, 1);
133+
134+
zassert_equal(fds[0].revents, ZSOCK_POLLIN);
135+
136+
/* Check value */
137+
zassert_equal(read(fixture->fd, &value, sizeof(value)), sizeof(value));
138+
zassert_equal(value, 1);
139+
}

0 commit comments

Comments
 (0)