diff options
Diffstat (limited to 'lib/libc/tests')
-rw-r--r-- | lib/libc/tests/gen/Makefile | 1 | ||||
-rw-r--r-- | lib/libc/tests/gen/opendir_test.c | 145 | ||||
-rw-r--r-- | lib/libc/tests/gen/scandir_test.c | 12 | ||||
-rw-r--r-- | lib/libc/tests/gen/wordexp_test.c | 26 | ||||
-rw-r--r-- | lib/libc/tests/stdtime/Makefile | 5 | ||||
-rw-r--r-- | lib/libc/tests/stdtime/detect_tz_changes_test.c | 281 | ||||
-rw-r--r-- | lib/libc/tests/sys/Makefile | 4 | ||||
-rw-r--r-- | lib/libc/tests/sys/swapcontext_test.c | 63 |
8 files changed, 527 insertions, 10 deletions
diff --git a/lib/libc/tests/gen/Makefile b/lib/libc/tests/gen/Makefile index a967ad5ddf91..8c2151105209 100644 --- a/lib/libc/tests/gen/Makefile +++ b/lib/libc/tests/gen/Makefile @@ -20,6 +20,7 @@ ATF_TESTS_C+= glob2_test ATF_TESTS_C+= glob_blocks_test .endif ATF_TESTS_C+= makecontext_test +ATF_TESTS_C+= opendir_test ATF_TESTS_C+= popen_test ATF_TESTS_C+= posix_spawn_test ATF_TESTS_C+= realpath2_test diff --git a/lib/libc/tests/gen/opendir_test.c b/lib/libc/tests/gen/opendir_test.c new file mode 100644 index 000000000000..b7481255654f --- /dev/null +++ b/lib/libc/tests/gen/opendir_test.c @@ -0,0 +1,145 @@ +/*- + * Copyright (c) 2025 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/stat.h> + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> + +#include <atf-c.h> + +/* + * Create a directory with a single subdirectory. + */ +static void +opendir_prepare(const struct atf_tc *tc) +{ + ATF_REQUIRE_EQ(0, mkdir("dir", 0755)); + ATF_REQUIRE_EQ(0, mkdir("dir/subdir", 0755)); +} + +/* + * Assuming dirp represents the directory created by opendir_prepare(), + * verify that readdir() returns what we expected to see there. + */ +static void +opendir_check(const struct atf_tc *tc, DIR *dirp) +{ + struct dirent *ent; + + ATF_REQUIRE((ent = readdir(dirp)) != NULL); + ATF_CHECK_EQ(1, ent->d_namlen); + ATF_CHECK_STREQ(".", ent->d_name); + ATF_CHECK_EQ(DT_DIR, ent->d_type); + ATF_REQUIRE((ent = readdir(dirp)) != NULL); + ATF_CHECK_EQ(2, ent->d_namlen); + ATF_CHECK_STREQ("..", ent->d_name); + ATF_CHECK_EQ(DT_DIR, ent->d_type); + ATF_REQUIRE((ent = readdir(dirp)) != NULL); + ATF_CHECK_EQ(sizeof("subdir") - 1, ent->d_namlen); + ATF_CHECK_STREQ("subdir", ent->d_name); + ATF_CHECK_EQ(DT_DIR, ent->d_type); + ATF_CHECK(readdir(dirp) == NULL); + ATF_CHECK(readdir(dirp) == NULL); +} + +ATF_TC(opendir_ok); +ATF_TC_HEAD(opendir_ok, tc) +{ + atf_tc_set_md_var(tc, "descr", "Open a directory."); +} +ATF_TC_BODY(opendir_ok, tc) +{ + DIR *dirp; + + opendir_prepare(tc); + ATF_REQUIRE((dirp = opendir("dir")) != NULL); + opendir_check(tc, dirp); + ATF_CHECK_EQ(0, closedir(dirp)); +} + +ATF_TC(opendir_fifo); +ATF_TC_HEAD(opendir_fifo, tc) +{ + atf_tc_set_md_var(tc, "descr", "Do not hang if given a named pipe."); +} +ATF_TC_BODY(opendir_fifo, tc) +{ + DIR *dirp; + int fd; + + ATF_REQUIRE((fd = mkfifo("fifo", 0644)) >= 0); + ATF_REQUIRE_EQ(0, close(fd)); + ATF_REQUIRE((dirp = opendir("fifo")) == NULL); + ATF_CHECK_EQ(ENOTDIR, errno); +} + +ATF_TC(fdopendir_ok); +ATF_TC_HEAD(fdopendir_ok, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Open a directory from a directory descriptor."); +} +ATF_TC_BODY(fdopendir_ok, tc) +{ + DIR *dirp; + int dd; + + opendir_prepare(tc); + ATF_REQUIRE((dd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0); + ATF_REQUIRE((dirp = fdopendir(dd)) != NULL); + opendir_check(tc, dirp); + ATF_CHECK_EQ(dd, fdclosedir(dirp)); + ATF_CHECK_EQ(0, close(dd)); +} + +ATF_TC(fdopendir_ebadf); +ATF_TC_HEAD(fdopendir_ebadf, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Open a directory from an invalid descriptor."); +} +ATF_TC_BODY(fdopendir_ebadf, tc) +{ + DIR *dirp; + int dd; + + ATF_REQUIRE_EQ(0, mkdir("dir", 0755)); + ATF_REQUIRE((dd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0); + ATF_CHECK_EQ(0, close(dd)); + ATF_REQUIRE((dirp = fdopendir(dd)) == NULL); + ATF_CHECK_EQ(EBADF, errno); +} + +ATF_TC(fdopendir_enotdir); +ATF_TC_HEAD(fdopendir_enotdir, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Open a directory from a non-directory descriptor."); +} +ATF_TC_BODY(fdopendir_enotdir, tc) +{ + DIR *dirp; + int fd; + + ATF_REQUIRE((fd = open("file", O_CREAT | O_RDWR, 0644)) >= 0); + ATF_REQUIRE((dirp = fdopendir(fd)) == NULL); + ATF_CHECK_EQ(ENOTDIR, errno); + ATF_CHECK_EQ(0, close(fd)); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, opendir_ok); + ATF_TP_ADD_TC(tp, fdopendir_ok); + ATF_TP_ADD_TC(tp, fdopendir_ebadf); + ATF_TP_ADD_TC(tp, fdopendir_enotdir); + ATF_TP_ADD_TC(tp, opendir_fifo); + return (atf_no_error()); +} diff --git a/lib/libc/tests/gen/scandir_test.c b/lib/libc/tests/gen/scandir_test.c index f7b52b5e3616..afd25bf7c0b2 100644 --- a/lib/libc/tests/gen/scandir_test.c +++ b/lib/libc/tests/gen/scandir_test.c @@ -157,7 +157,7 @@ ATF_TC_BODY(scandir_error, tc) { char path[16]; struct dirent **namelist = NULL; - int fd, i, ret; + int fd, i; ATF_REQUIRE_EQ(0, mkdir("dir", 0755)); for (i = 0; i < 1024; i++) { @@ -170,9 +170,8 @@ ATF_TC_BODY(scandir_error, tc) scandir_error_count = 0; scandir_error_fd = fd; scandir_error_select_return = 0; - ret = fdscandir(fd, &namelist, scandir_error_select, NULL); - ATF_CHECK_EQ(-1, ret); - ATF_CHECK_ERRNO(EBADF, ret < 0); + ATF_CHECK_ERRNO(EBADF, + fdscandir(fd, &namelist, scandir_error_select, NULL) < 0); ATF_CHECK_EQ(NULL, namelist); /* second pass, select everything */ @@ -180,9 +179,8 @@ ATF_TC_BODY(scandir_error, tc) scandir_error_count = 0; scandir_error_fd = fd; scandir_error_select_return = 1; - ret = fdscandir(fd, &namelist, scandir_error_select, NULL); - ATF_CHECK_EQ(-1, ret); - ATF_CHECK_ERRNO(EBADF, ret < 0); + ATF_CHECK_ERRNO(EBADF, + fdscandir(fd, &namelist, scandir_error_select, NULL) < 0); ATF_CHECK_EQ(NULL, namelist); } diff --git a/lib/libc/tests/gen/wordexp_test.c b/lib/libc/tests/gen/wordexp_test.c index 909097fbf51e..a8b9d5509633 100644 --- a/lib/libc/tests/gen/wordexp_test.c +++ b/lib/libc/tests/gen/wordexp_test.c @@ -292,6 +292,31 @@ ATF_TC_BODY(with_SIGCHILD_handler_test, tc) ATF_REQUIRE(r == 0); } +ATF_TC_WITHOUT_HEAD(with_SIGCHILD_ignore_test); +ATF_TC_BODY(with_SIGCHILD_ignore_test, tc) +{ + struct sigaction sa; + wordexp_t we; + int r; + + /* With SIGCHLD set to ignore so that the kernel auto-reaps zombies. */ + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_IGN; + r = sigaction(SIGCHLD, &sa, NULL); + ATF_REQUIRE(r == 0); + r = wordexp("hello world", &we, 0); + ATF_REQUIRE(r == 0); + ATF_REQUIRE(we.we_wordc == 2); + ATF_REQUIRE(strcmp(we.we_wordv[0], "hello") == 0); + ATF_REQUIRE(strcmp(we.we_wordv[1], "world") == 0); + ATF_REQUIRE(we.we_wordv[2] == NULL); + wordfree(&we); + sa.sa_handler = SIG_DFL; + r = sigaction(SIGCHLD, &sa, NULL); + ATF_REQUIRE(r == 0); +} + ATF_TC_WITHOUT_HEAD(with_unused_non_default_IFS_test); ATF_TC_BODY(with_unused_non_default_IFS_test, tc) { @@ -350,6 +375,7 @@ ATF_TP_ADD_TCS(tp) ATF_TP_ADD_TC(tp, WRDE_BADCHAR_test); ATF_TP_ADD_TC(tp, WRDE_SYNTAX_test); ATF_TP_ADD_TC(tp, with_SIGCHILD_handler_test); + ATF_TP_ADD_TC(tp, with_SIGCHILD_ignore_test); ATF_TP_ADD_TC(tp, with_unused_non_default_IFS_test); ATF_TP_ADD_TC(tp, with_used_non_default_IFS_test); diff --git a/lib/libc/tests/stdtime/Makefile b/lib/libc/tests/stdtime/Makefile index c7a7f5b9436f..adb883cc5b9a 100644 --- a/lib/libc/tests/stdtime/Makefile +++ b/lib/libc/tests/stdtime/Makefile @@ -1,6 +1,9 @@ -.include <bsd.own.mk> +.include <src.opts.mk> ATF_TESTS_C+= strptime_test +.if ${MK_DETECT_TZ_CHANGES} != "no" +ATF_TESTS_C+= detect_tz_changes_test +.endif TESTSDIR:= ${TESTSBASE}/${RELDIR:C/libc\/tests/libc/} diff --git a/lib/libc/tests/stdtime/detect_tz_changes_test.c b/lib/libc/tests/stdtime/detect_tz_changes_test.c new file mode 100644 index 000000000000..9722546747fd --- /dev/null +++ b/lib/libc/tests/stdtime/detect_tz_changes_test.c @@ -0,0 +1,281 @@ +/*- + * Copyright (c) 2025 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/stat.h> +#include <sys/wait.h> + +#include <dlfcn.h> +#include <fcntl.h> +#include <limits.h> +#include <poll.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +#include <atf-c.h> + +static const time_t then = 1751328000; /* 2025-07-01 00:00:00 UTC */ +static const char *tz_change_interval_sym = "__tz_change_interval"; +static int *tz_change_interval_p; +static const int tz_change_interval = 3; +static int tz_change_timeout = 90; + +static bool debugging; + +static void +debug(const char *fmt, ...) +{ + va_list ap; + + if (debugging) { + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); + } +} + +static void +change_tz(const char *tzn) +{ + static const char *zfn = "/usr/share/zoneinfo"; + static const char *tfn = "root/etc/.localtime"; + static const char *dfn = "root/etc/localtime"; + ssize_t clen; + int zfd, sfd, dfd; + + ATF_REQUIRE((zfd = open(zfn, O_DIRECTORY | O_SEARCH)) >= 0); + ATF_REQUIRE((sfd = openat(zfd, tzn, O_RDONLY)) >= 0); + ATF_REQUIRE((dfd = open(tfn, O_CREAT | O_TRUNC | O_WRONLY)) >= 0); + do { + clen = copy_file_range(sfd, NULL, dfd, NULL, SSIZE_MAX, 0); + ATF_REQUIRE_MSG(clen != -1, "failed to copy %s/%s: %m", + zfn, tzn); + } while (clen > 0); + ATF_CHECK_EQ(0, close(dfd)); + ATF_CHECK_EQ(0, close(sfd)); + ATF_CHECK_EQ(0, close(zfd)); + ATF_REQUIRE_EQ(0, rename(tfn, dfn)); + debug("time zone %s installed", tzn); +} + +/* + * Test time zone change detection. + * + * The parent creates a chroot containing only /etc/localtime, initially + * set to UTC. It then forks a child which enters the chroot, repeatedly + * checks the current time zone, and prints it to stdout if it changes + * (including once on startup). Meanwhile, the parent waits for output + * from the child. Every time it receives a line of text from the child, + * it checks that it is as expected, then changes /etc/localtime within + * the chroot to the next case in the list. Once it reaches the end of + * the list, it closes a pipe to notify the child, which terminates. + * + * Note that ATF and / or Kyua may have set the timezone before the test + * case starts (even unintentionally). Therefore, we start the test only + * after we've received and discarded the first report from the child, + * which should come almost immediately on startup. + */ +ATF_TC(detect_tz_changes); +ATF_TC_HEAD(detect_tz_changes, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test timezone change detection"); + atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "timeout", "600"); +} +ATF_TC_BODY(detect_tz_changes, tc) +{ + static const struct tzcase { + const char *tzfn; + const char *expect; + } tzcases[] = { + /* + * A handful of time zones and the expected result of + * strftime("%z (%Z)", tm) when that time zone is active + * and tm represents a date in the summer of 2025. + */ + { "America/Vancouver", "-0700 (PDT)" }, + { "America/New_York", "-0400 (EDT)" }, + { "Europe/London", "+0100 (BST)" }, + { "Europe/Paris", "+0200 (CEST)" }, + { "Asia/Kolkata", "+0530 (IST)" }, + { "Asia/Tokyo", "+0900 (JST)" }, + { "Australia/Canberra", "+1000 (AEST)" }, + { "UTC", "+0000 (UTC)" }, + { 0 }, + }; + char obuf[1024] = ""; + char ebuf[1024] = ""; + struct pollfd fds[3]; + int opd[2], epd[2], spd[2]; + time_t changed, now; + const struct tzcase *tzcase = NULL; + struct tm *tm; + size_t olen = 0, elen = 0; + ssize_t rlen; + long curoff = LONG_MIN; + pid_t pid; + int nfds, status; + + /* speed up the test if possible */ + tz_change_interval_p = dlsym(RTLD_SELF, tz_change_interval_sym); + if (tz_change_interval_p != NULL && + *tz_change_interval_p > tz_change_interval) { + debug("reducing detection interval from %d to %d", + *tz_change_interval_p, tz_change_interval); + *tz_change_interval_p = tz_change_interval; + tz_change_timeout = tz_change_interval * 3; + } + /* prepare chroot */ + ATF_REQUIRE_EQ(0, mkdir("root", 0755)); + ATF_REQUIRE_EQ(0, mkdir("root/etc", 0755)); + change_tz("UTC"); + time(&changed); + /* output, error, sync pipes */ + if (pipe(opd) != 0 || pipe(epd) != 0 || pipe(spd) != 0) + atf_tc_fail("failed to pipe"); + /* fork child */ + if ((pid = fork()) < 0) + atf_tc_fail("failed to fork"); + if (pid == 0) { + /* child */ + dup2(opd[1], STDOUT_FILENO); + close(opd[0]); + close(opd[1]); + dup2(epd[1], STDERR_FILENO); + close(epd[0]); + close(epd[1]); + close(spd[0]); + unsetenv("TZ"); + ATF_REQUIRE_EQ(0, chroot("root")); + ATF_REQUIRE_EQ(0, chdir("/")); + fds[0].fd = spd[1]; + fds[0].events = POLLIN; + for (;;) { + ATF_REQUIRE(poll(fds, 1, 100) >= 0); + if (fds[0].revents & POLLHUP) { + /* parent closed sync pipe */ + _exit(0); + } + ATF_REQUIRE((tm = localtime(&then)) != NULL); + if (tm->tm_gmtoff == curoff) + continue; + olen = strftime(obuf, sizeof(obuf), "%z (%Z)", tm); + ATF_REQUIRE(olen > 0); + fprintf(stdout, "%s\n", obuf); + fflush(stdout); + curoff = tm->tm_gmtoff; + } + _exit(2); + } + /* parent */ + close(opd[1]); + close(epd[1]); + close(spd[1]); + /* receive output until child terminates */ + fds[0].fd = opd[0]; + fds[0].events = POLLIN; + fds[1].fd = epd[0]; + fds[1].events = POLLIN; + fds[2].fd = spd[0]; + fds[2].events = POLLIN; + nfds = 3; + for (;;) { + ATF_REQUIRE(poll(fds, 3, 1000) >= 0); + time(&now); + if (fds[0].revents & POLLIN && olen < sizeof(obuf)) { + rlen = read(opd[0], obuf + olen, sizeof(obuf) - olen); + ATF_REQUIRE(rlen >= 0); + olen += rlen; + } + if (olen > 0) { + ATF_REQUIRE_EQ('\n', obuf[olen - 1]); + obuf[--olen] = '\0'; + /* tzcase will be NULL at first */ + if (tzcase != NULL) { + debug("%s", obuf); + ATF_REQUIRE_STREQ(tzcase->expect, obuf); + debug("change to %s detected after %d s", + tzcase->tzfn, (int)(now - changed)); + if (tz_change_interval_p != NULL) { + ATF_CHECK((int)(now - changed) >= + *tz_change_interval_p - 1); + ATF_CHECK((int)(now - changed) <= + *tz_change_interval_p + 1); + } + } + olen = 0; + /* first / next test case */ + if (tzcase == NULL) + tzcase = tzcases; + else + tzcase++; + if (tzcase->tzfn == NULL) { + /* test is over */ + break; + } + change_tz(tzcase->tzfn); + changed = now; + } + if (fds[1].revents & POLLIN && elen < sizeof(ebuf)) { + rlen = read(epd[0], ebuf + elen, sizeof(ebuf) - elen); + ATF_REQUIRE(rlen >= 0); + elen += rlen; + } + if (elen > 0) { + ATF_REQUIRE_EQ(elen, fwrite(ebuf, 1, elen, stderr)); + elen = 0; + } + if (nfds > 2 && fds[2].revents & POLLHUP) { + /* child closed sync pipe */ + break; + } + /* + * The timeout for this test case is set to 10 minutes, + * because it can take that long to run with the default + * 61-second interval. However, each individual tzcase + * entry should not take much longer than the detection + * interval to test, so we can detect a problem long + * before Kyua terminates us. + */ + if ((now - changed) > tz_change_timeout) { + close(spd[0]); + if (tz_change_interval_p == NULL && + tzcase == tzcases) { + /* + * The most likely explanation in this + * case is that libc was built without + * time zone change detection. + */ + atf_tc_skip("time zone change detection " + "does not appear to be enabled"); + } + atf_tc_fail("timed out waiting for change to %s " + "to be detected", tzcase->tzfn); + } + } + close(opd[0]); + close(epd[0]); + close(spd[0]); /* this will wake up and terminate the child */ + if (olen > 0) + ATF_REQUIRE_EQ(olen, fwrite(obuf, 1, olen, stdout)); + if (elen > 0) + ATF_REQUIRE_EQ(elen, fwrite(ebuf, 1, elen, stderr)); + ATF_REQUIRE_EQ(pid, waitpid(pid, &status, 0)); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE_EQ(0, WEXITSTATUS(status)); +} + +ATF_TP_ADD_TCS(tp) +{ + debugging = !getenv("__RUNNING_INSIDE_ATF_RUN") && + isatty(STDERR_FILENO); + ATF_TP_ADD_TC(tp, detect_tz_changes); + return (atf_no_error()); +} diff --git a/lib/libc/tests/sys/Makefile b/lib/libc/tests/sys/Makefile index 89d341ff400a..88f8191a16eb 100644 --- a/lib/libc/tests/sys/Makefile +++ b/lib/libc/tests/sys/Makefile @@ -7,11 +7,11 @@ ATF_TESTS_C+= brk_test .endif ATF_TESTS_C+= cpuset_test ATF_TESTS_C+= errno_test +ATF_TESTS_C+= swapcontext_test ATF_TESTS_C+= queue_test ATF_TESTS_C+= sendfile_test -# TODO: clone, lwp_create, lwp_ctl, posix_fadvise, recvmmsg, -# swapcontext +# TODO: clone, lwp_create, lwp_ctl, posix_fadvise, recvmmsg NETBSD_ATF_TESTS_C+= access_test NETBSD_ATF_TESTS_C+= bind_test NETBSD_ATF_TESTS_C+= chroot_test diff --git a/lib/libc/tests/sys/swapcontext_test.c b/lib/libc/tests/sys/swapcontext_test.c new file mode 100644 index 000000000000..f341a746e515 --- /dev/null +++ b/lib/libc/tests/sys/swapcontext_test.c @@ -0,0 +1,63 @@ +/*- + * Copyright (c) 2025 Raptor Computing Systems, LLC + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <stdio.h> +#include <stdlib.h> +#include <ucontext.h> +#include <errno.h> + +#include <atf-c.h> + +#define STACK_SIZE (64ull << 10) + +static volatile int callback_reached = 0; + +static ucontext_t uctx_save, uctx_switch; + +static void swapcontext_callback() +{ + // Increment callback reached variable + // If this is called multiple times, we will fail the test + // If this is not called at all, we will fail the test + callback_reached++; +} + +ATF_TC(swapcontext_basic); +ATF_TC_HEAD(swapcontext_basic, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Verify basic functionality of swapcontext"); +} + +ATF_TC_BODY(swapcontext_basic, tc) +{ + char *stack; + int res; + + stack = malloc(STACK_SIZE); + ATF_REQUIRE_MSG(stack != NULL, "malloc failed: %s", strerror(errno)); + res = getcontext(&uctx_switch); + ATF_REQUIRE_MSG(res == 0, "getcontext failed: %s", strerror(errno)); + + uctx_switch.uc_stack.ss_sp = stack; + uctx_switch.uc_stack.ss_size = STACK_SIZE; + uctx_switch.uc_link = &uctx_save; + makecontext(&uctx_switch, swapcontext_callback, 0); + + res = swapcontext(&uctx_save, &uctx_switch); + + ATF_REQUIRE_MSG(res == 0, "swapcontext failed: %s", strerror(errno)); + ATF_REQUIRE_MSG(callback_reached == 1, + "callback failed, reached %d times", callback_reached); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, swapcontext_basic); + + return (atf_no_error()); +} + |