diff --git a/lib/libc/tests/gen/Makefile b/lib/libc/tests/gen/Makefile index b7df4b1d037b..a967ad5ddf91 100644 --- a/lib/libc/tests/gen/Makefile +++ b/lib/libc/tests/gen/Makefile @@ -1,126 +1,135 @@ .include ATF_TESTS_C+= arc4random_test ATF_TESTS_C+= dir2_test ATF_TESTS_C+= dlopen_empty_test ATF_TESTS_C+= fmtcheck2_test ATF_TESTS_C+= fmtmsg_test ATF_TESTS_C+= fnmatch2_test ATF_TESTS_C+= fpclassify2_test .if ${COMPILER_FEATURES:Mblocks} ATF_TESTS_C+= fts_blocks_test .endif +ATF_TESTS_C+= fts_misc_test ATF_TESTS_C+= fts_options_test ATF_TESTS_C+= ftw_test ATF_TESTS_C+= getentropy_test ATF_TESTS_C+= getmntinfo_test ATF_TESTS_C+= glob2_test .if ${COMPILER_FEATURES:Mblocks} ATF_TESTS_C+= glob_blocks_test .endif ATF_TESTS_C+= makecontext_test ATF_TESTS_C+= popen_test ATF_TESTS_C+= posix_spawn_test ATF_TESTS_C+= realpath2_test ATF_TESTS_C+= scandir_test .if ${COMPILER_FEATURES:Mblocks} ATF_TESTS_C+= scandir_blocks_test .endif ATF_TESTS_C+= sig2str_test ATF_TESTS_C+= sigsetops_test ATF_TESTS_C+= wordexp_test # TODO: t_closefrom, t_fmtcheck, t_randomid, # TODO: t_siginfo (fixes require further inspection) # TODO: t_sethostname_test (consistently screws up the hostname) FILESGROUPS+= posix_spawn_test_FILES posix_spawn_test_FILES= spawnp_enoexec.sh posix_spawn_test_FILESDIR= ${TESTSDIR} posix_spawn_test_FILESMODE= 0755 posix_spawn_test_FILESOWN= root posix_spawn_test_FILESGRP= wheel posix_spawn_test_FILESPACKAGE= ${PACKAGE} CFLAGS+= -DTEST_LONG_DOUBLE # Define __HAVE_LONG_DOUBLE for architectures whose long double has greater # precision than their double. .if ${MACHINE_CPUARCH} == "aarch64" || \ ${MACHINE_CPUARCH} == "amd64" || \ ${MACHINE_CPUARCH} == "i386" || \ ${MACHINE_CPUARCH} == "riscv" CFLAGS+= -D__HAVE_LONG_DOUBLE .endif NETBSD_ATF_TESTS_C= alarm_test NETBSD_ATF_TESTS_C+= assert_test NETBSD_ATF_TESTS_C+= basedirname_test NETBSD_ATF_TESTS_C+= cpuset_test NETBSD_ATF_TESTS_C+= dir_test NETBSD_ATF_TESTS_C+= floatunditf_test NETBSD_ATF_TESTS_C+= fnmatch_test NETBSD_ATF_TESTS_C+= fpclassify_test NETBSD_ATF_TESTS_C+= fpsetmask_test NETBSD_ATF_TESTS_C+= fpsetround_test NETBSD_ATF_TESTS_C+= ftok_test NETBSD_ATF_TESTS_C+= getcwd_test NETBSD_ATF_TESTS_C+= getgrent_test NETBSD_ATF_TESTS_C+= glob_test NETBSD_ATF_TESTS_C+= humanize_number_test NETBSD_ATF_TESTS_C+= isnan_test NETBSD_ATF_TESTS_C+= nice_test NETBSD_ATF_TESTS_C+= pause_test NETBSD_ATF_TESTS_C+= raise_test NETBSD_ATF_TESTS_C+= realpath_test NETBSD_ATF_TESTS_C+= setdomainname_test NETBSD_ATF_TESTS_C+= sethostname_test NETBSD_ATF_TESTS_C+= sleep_test NETBSD_ATF_TESTS_C+= syslog_test NETBSD_ATF_TESTS_C+= time_test NETBSD_ATF_TESTS_C+= ttyname_test NETBSD_ATF_TESTS_C+= vis_test .include "../Makefile.netbsd-tests" CFLAGS.getentropy_test+= -I${SRCTOP}/include LIBADD.getentropy_test+= c LIBADD.humanize_number_test+= util LIBADD.fpclassify_test+=m LIBADD.fpsetround_test+=m LIBADD.siginfo_test+= m LIBADD.nice_test+= pthread LIBADD.syslog_test+= pthread CFLAGS+= -I${.CURDIR} SRCS.fmtcheck2_test= fmtcheck_test.c SRCS.fnmatch2_test= fnmatch_test.c TEST_METADATA.setdomainname_test+= is_exclusive=true TESTS_SUBDIRS= execve TESTS_SUBDIRS+= posix_spawn +# Tests that require address sanitizer +.if ${COMPILER_FEATURES:Masan} +.for t in scandir_test realpath2_test +CFLAGS.${t}.c+= -fsanitize=address +LDFLAGS.${t}+= -fsanitize=address +.endfor +.endif + # Tests that require blocks support .for t in fts_blocks_test glob_blocks_test scandir_blocks_test CFLAGS.${t}.c+= -fblocks LIBADD.${t}+= BlocksRuntime .endfor # The old testcase name TEST_FNMATCH= test-fnmatch CLEANFILES+= ${GEN_SH_CASE_TESTCASES} sh-tests: .PHONY .for target in clean obj depend all @cd ${.CURDIR} && ${MAKE} PROG=${TEST_FNMATCH} \ -DNO_SUBDIR ${target} .endfor @cd ${.OBJDIR} && ./${TEST_FNMATCH} -s 1 > \ ${SRCTOP}/bin/sh/tests/builtins/case2.0 @cd ${.OBJDIR} && ./${TEST_FNMATCH} -s 2 > \ ${SRCTOP}/bin/sh/tests/builtins/case3.0 .include diff --git a/lib/libc/tests/gen/fts_misc_test.c b/lib/libc/tests/gen/fts_misc_test.c new file mode 100644 index 000000000000..91640078f63c --- /dev/null +++ b/lib/libc/tests/gen/fts_misc_test.c @@ -0,0 +1,78 @@ +/*- + * Copyright (c) 2025 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "fts_test.h" + +ATF_TC(fts_unrdir); +ATF_TC_HEAD(fts_unrdir, tc) +{ + atf_tc_set_md_var(tc, "descr", "unreadable directories"); + atf_tc_set_md_var(tc, "require.user", "unprivileged"); +} +ATF_TC_BODY(fts_unrdir, tc) +{ + ATF_REQUIRE_EQ(0, mkdir("dir", 0755)); + ATF_REQUIRE_EQ(0, mkdir("dir/unr", 0100)); + ATF_REQUIRE_EQ(0, mkdir("dir/unx", 0400)); + fts_test(tc, &(struct fts_testcase){ + (char *[]){ "dir", NULL }, + FTS_PHYSICAL, + (struct fts_expect[]){ + { FTS_D, "dir", "dir" }, + { FTS_D, "unr", "unr" }, + { FTS_DNR, "unr", "unr" }, + { FTS_D, "unx", "unx" }, + { FTS_DP, "unx", "unx" }, + { FTS_DP, "dir", "dir" }, + { 0 } + }, + }); +} + +ATF_TC(fts_unrdir_nochdir); +ATF_TC_HEAD(fts_unrdir_nochdir, tc) +{ + atf_tc_set_md_var(tc, "descr", "unreadable directories (nochdir)"); + atf_tc_set_md_var(tc, "require.user", "unprivileged"); +} +ATF_TC_BODY(fts_unrdir_nochdir, tc) +{ + ATF_REQUIRE_EQ(0, mkdir("dir", 0755)); + ATF_REQUIRE_EQ(0, mkdir("dir/unr", 0100)); + ATF_REQUIRE_EQ(0, mkdir("dir/unx", 0400)); + fts_test(tc, &(struct fts_testcase){ + (char *[]){ "dir", NULL }, + FTS_PHYSICAL | FTS_NOCHDIR, + (struct fts_expect[]){ + { FTS_D, "dir", "dir" }, + { FTS_D, "unr", "dir/unr" }, + { FTS_DNR, "unr", "dir/unr" }, + { FTS_D, "unx", "dir/unx" }, + { FTS_DP, "unx", "dir/unx" }, + { FTS_DP, "dir", "dir" }, + { 0 } + }, + }); +} + +ATF_TP_ADD_TCS(tp) +{ + fts_check_debug(); + ATF_TP_ADD_TC(tp, fts_unrdir); + ATF_TP_ADD_TC(tp, fts_unrdir_nochdir); + return (atf_no_error()); +} diff --git a/lib/libc/tests/gen/fts_options_test.c b/lib/libc/tests/gen/fts_options_test.c index c80474b70ac7..fc3015138a49 100644 --- a/lib/libc/tests/gen/fts_options_test.c +++ b/lib/libc/tests/gen/fts_options_test.c @@ -1,454 +1,394 @@ /*- * Copyright (c) 2025 Klara, Inc. * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include -struct fts_expect { - int fts_info; - const char *fts_name; - const char *fts_accpath; -}; - -struct fts_testcase { - char **paths; - int fts_options; - struct fts_expect *fts_expect; -}; +#include "fts_test.h" static char *all_paths[] = { "dir", "dirl", "file", "filel", "dead", "noent", NULL }; -/* shorter name for dead links */ -#define FTS_DL FTS_SLNONE - -/* are we being debugged? */ -static bool debug; - /* * Prepare the files and directories we will be inspecting. */ static void fts_options_prepare(const struct atf_tc *tc) { - debug = !getenv("__RUNNING_INSIDE_ATF_RUN") && - isatty(STDERR_FILENO); ATF_REQUIRE_EQ(0, mkdir("dir", 0755)); ATF_REQUIRE_EQ(0, close(creat("file", 0644))); ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644))); ATF_REQUIRE_EQ(0, symlink("..", "dir/up")); ATF_REQUIRE_EQ(0, symlink("dir", "dirl")); ATF_REQUIRE_EQ(0, symlink("file", "filel")); ATF_REQUIRE_EQ(0, symlink("noent", "dead")); } -/* - * Lexical order for reproducability. - */ -static int -fts_options_compar(const FTSENT * const *a, const FTSENT * const *b) -{ - return (strcmp((*a)->fts_name, (*b)->fts_name)); -} - -/* - * Run FTS with the specified paths and options and verify that it - * produces the expected result in the correct order. - */ -static void -fts_options_test(const struct atf_tc *tc, const struct fts_testcase *fts_tc) -{ - FTS *fts; - FTSENT *ftse; - const struct fts_expect *expect = fts_tc->fts_expect; - long level = 0; - - fts = fts_open(fts_tc->paths, fts_tc->fts_options, fts_options_compar); - ATF_REQUIRE_MSG(fts != NULL, "fts_open(): %m"); - while ((ftse = fts_read(fts)) != NULL && expect->fts_name != NULL) { - if (expect->fts_info == FTS_DP) - level--; - if (debug) { - fprintf(stderr, "%2ld %2d %s\n", level, - ftse->fts_info, ftse->fts_name); - } - ATF_CHECK_STREQ(expect->fts_name, ftse->fts_name); - ATF_CHECK_STREQ(expect->fts_accpath, ftse->fts_accpath); - ATF_CHECK_INTEQ(expect->fts_info, ftse->fts_info); - ATF_CHECK_INTEQ(level, ftse->fts_level); - if (expect->fts_info == FTS_D) - level++; - expect++; - } - ATF_CHECK_EQ(NULL, ftse); - ATF_CHECK_EQ(NULL, expect->fts_name); - ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m"); -} - ATF_TC(fts_options_logical); ATF_TC_HEAD(fts_options_logical, tc) { atf_tc_set_md_var(tc, "descr", "FTS_LOGICAL"); } ATF_TC_BODY(fts_options_logical, tc) { fts_options_prepare(tc); - fts_options_test(tc, &(struct fts_testcase){ + fts_test(tc, &(struct fts_testcase){ all_paths, FTS_LOGICAL, (struct fts_expect[]){ { FTS_DL, "dead", "dead" }, { FTS_D, "dir", "dir" }, { FTS_F, "file", "dir/file" }, { FTS_D, "up", "dir/up" }, { FTS_DL, "dead", "dir/up/dead" }, { FTS_DC, "dir", "dir/up/dir" }, { FTS_DC, "dirl", "dir/up/dirl" }, { FTS_F, "file", "dir/up/file" }, { FTS_F, "filel", "dir/up/filel" }, { FTS_DP, "up", "dir/up" }, { FTS_DP, "dir", "dir" }, { FTS_D, "dirl", "dirl" }, { FTS_F, "file", "dirl/file" }, { FTS_D, "up", "dirl/up" }, { FTS_DL, "dead", "dirl/up/dead" }, { FTS_DC, "dir", "dirl/up/dir" }, { FTS_DC, "dirl", "dirl/up/dirl" }, { FTS_F, "file", "dirl/up/file" }, { FTS_F, "filel", "dirl/up/filel" }, { FTS_DP, "up", "dirl/up" }, { FTS_DP, "dirl", "dirl" }, { FTS_F, "file", "file" }, { FTS_F, "filel", "filel" }, { FTS_NS, "noent", "noent" }, { 0 } }, }); } ATF_TC(fts_options_logical_nostat); ATF_TC_HEAD(fts_options_logical_nostat, tc) { atf_tc_set_md_var(tc, "descr", "FTS_LOGICAL | FTS_NOSTAT"); } ATF_TC_BODY(fts_options_logical_nostat, tc) { /* * While FTS_LOGICAL is not documented as being incompatible with * FTS_NOSTAT, and FTS does not clear FTS_NOSTAT if FTS_LOGICAL is * set, FTS_LOGICAL effectively nullifies FTS_NOSTAT by overriding * the follow check in fts_stat(). In theory, FTS could easily be * changed to only stat links (to check what they point to) in the * FTS_LOGICAL | FTS_NOSTAT case, which would produce a different * result here, so keep the test around in case that ever happens. */ atf_tc_expect_fail("FTS_LOGICAL nullifies FTS_NOSTAT"); fts_options_prepare(tc); - fts_options_test(tc, &(struct fts_testcase){ + fts_test(tc, &(struct fts_testcase){ all_paths, FTS_LOGICAL | FTS_NOSTAT, (struct fts_expect[]){ { FTS_DL, "dead", "dead" }, { FTS_D, "dir", "dir" }, { FTS_NSOK, "file", "dir/file" }, { FTS_D, "up", "dir/up" }, { FTS_DL, "dead", "dir/up/dead" }, { FTS_DC, "dir", "dir/up/dir" }, { FTS_DC, "dirl", "dir/up/dirl" }, { FTS_NSOK, "file", "dir/up/file" }, { FTS_NSOK, "filel", "dir/up/filel" }, { FTS_DP, "up", "dir/up" }, { FTS_DP, "dir", "dir" }, { FTS_D, "dirl", "dirl" }, { FTS_NSOK, "file", "dirl/file" }, { FTS_D, "up", "dirl/up" }, { FTS_DL, "dead", "dirl/up/dead" }, { FTS_DC, "dir", "dirl/up/dir" }, { FTS_DC, "dirl", "dirl/up/dirl" }, { FTS_NSOK, "file", "dirl/up/file" }, { FTS_NSOK, "filel", "dirl/up/filel" }, { FTS_DP, "up", "dirl/up" }, { FTS_DP, "dirl", "dirl" }, { FTS_F, "file", "file" }, { FTS_F, "filel", "filel" }, { FTS_NS, "noent", "noent" }, { 0 } }, }); } ATF_TC(fts_options_logical_seedot); ATF_TC_HEAD(fts_options_logical_seedot, tc) { atf_tc_set_md_var(tc, "descr", "FTS_LOGICAL | FTS_SEEDOT"); } ATF_TC_BODY(fts_options_logical_seedot, tc) { fts_options_prepare(tc); - fts_options_test(tc, &(struct fts_testcase){ + fts_test(tc, &(struct fts_testcase){ all_paths, FTS_LOGICAL | FTS_SEEDOT, (struct fts_expect[]){ { FTS_DL, "dead", "dead" }, { FTS_D, "dir", "dir" }, { FTS_DOT, ".", "dir/." }, { FTS_DOT, "..", "dir/.." }, { FTS_F, "file", "dir/file" }, { FTS_D, "up", "dir/up" }, { FTS_DOT, ".", "dir/up/." }, { FTS_DOT, "..", "dir/up/.." }, { FTS_DL, "dead", "dir/up/dead" }, { FTS_DC, "dir", "dir/up/dir" }, { FTS_DC, "dirl", "dir/up/dirl" }, { FTS_F, "file", "dir/up/file" }, { FTS_F, "filel", "dir/up/filel" }, { FTS_DP, "up", "dir/up" }, { FTS_DP, "dir", "dir" }, { FTS_D, "dirl", "dirl" }, { FTS_DOT, ".", "dirl/." }, { FTS_DOT, "..", "dirl/.." }, { FTS_F, "file", "dirl/file" }, { FTS_D, "up", "dirl/up" }, { FTS_DOT, ".", "dirl/up/." }, { FTS_DOT, "..", "dirl/up/.." }, { FTS_DL, "dead", "dirl/up/dead" }, { FTS_DC, "dir", "dirl/up/dir" }, { FTS_DC, "dirl", "dirl/up/dirl" }, { FTS_F, "file", "dirl/up/file" }, { FTS_F, "filel", "dirl/up/filel" }, { FTS_DP, "up", "dirl/up" }, { FTS_DP, "dirl", "dirl" }, { FTS_F, "file", "file" }, { FTS_F, "filel", "filel" }, { FTS_NS, "noent", "noent" }, { 0 } }, }); } ATF_TC(fts_options_physical); ATF_TC_HEAD(fts_options_physical, tc) { atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL"); } ATF_TC_BODY(fts_options_physical, tc) { fts_options_prepare(tc); - fts_options_test(tc, &(struct fts_testcase){ + fts_test(tc, &(struct fts_testcase){ all_paths, FTS_PHYSICAL, (struct fts_expect[]){ { FTS_SL, "dead", "dead" }, { FTS_D, "dir", "dir" }, { FTS_F, "file", "file" }, { FTS_SL, "up", "up" }, { FTS_DP, "dir", "dir" }, { FTS_SL, "dirl", "dirl" }, { FTS_F, "file", "file" }, { FTS_SL, "filel", "filel" }, { FTS_NS, "noent", "noent" }, { 0 } }, }); } ATF_TC(fts_options_physical_nochdir); ATF_TC_HEAD(fts_options_physical_nochdir, tc) { atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_NOCHDIR"); } ATF_TC_BODY(fts_options_physical_nochdir, tc) { fts_options_prepare(tc); - fts_options_test(tc, &(struct fts_testcase){ + fts_test(tc, &(struct fts_testcase){ all_paths, FTS_PHYSICAL | FTS_NOCHDIR, (struct fts_expect[]){ { FTS_SL, "dead", "dead" }, { FTS_D, "dir", "dir" }, { FTS_F, "file", "dir/file" }, { FTS_SL, "up", "dir/up" }, { FTS_DP, "dir", "dir" }, { FTS_SL, "dirl", "dirl" }, { FTS_F, "file", "file" }, { FTS_SL, "filel", "filel" }, { FTS_NS, "noent", "noent" }, { 0 } }, }); } ATF_TC(fts_options_physical_comfollow); ATF_TC_HEAD(fts_options_physical_comfollow, tc) { atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_COMFOLLOW"); } ATF_TC_BODY(fts_options_physical_comfollow, tc) { fts_options_prepare(tc); - fts_options_test(tc, &(struct fts_testcase){ + fts_test(tc, &(struct fts_testcase){ all_paths, FTS_PHYSICAL | FTS_COMFOLLOW, (struct fts_expect[]){ { FTS_DL, "dead", "dead" }, { FTS_D, "dir", "dir" }, { FTS_F, "file", "file" }, { FTS_SL, "up", "up" }, { FTS_DP, "dir", "dir" }, { FTS_D, "dirl", "dirl" }, { FTS_F, "file", "file" }, { FTS_SL, "up", "up" }, { FTS_DP, "dirl", "dirl" }, { FTS_F, "file", "file" }, { FTS_F, "filel", "filel" }, { FTS_NS, "noent", "noent" }, { 0 } }, }); } ATF_TC(fts_options_physical_comfollowdir); ATF_TC_HEAD(fts_options_physical_comfollowdir, tc) { atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_COMFOLLOWDIR"); } ATF_TC_BODY(fts_options_physical_comfollowdir, tc) { fts_options_prepare(tc); - fts_options_test(tc, &(struct fts_testcase){ + fts_test(tc, &(struct fts_testcase){ all_paths, FTS_PHYSICAL | FTS_COMFOLLOWDIR, (struct fts_expect[]){ { FTS_DL, "dead", "dead" }, { FTS_D, "dir", "dir" }, { FTS_F, "file", "file" }, { FTS_SL, "up", "up" }, { FTS_DP, "dir", "dir" }, { FTS_D, "dirl", "dirl" }, { FTS_F, "file", "file" }, { FTS_SL, "up", "up" }, { FTS_DP, "dirl", "dirl" }, { FTS_F, "file", "file" }, { FTS_SL, "filel", "filel" }, { FTS_NS, "noent", "noent" }, { 0 } }, }); } ATF_TC(fts_options_physical_nostat); ATF_TC_HEAD(fts_options_physical_nostat, tc) { atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_NOSTAT"); } ATF_TC_BODY(fts_options_physical_nostat, tc) { fts_options_prepare(tc); - fts_options_test(tc, &(struct fts_testcase){ + fts_test(tc, &(struct fts_testcase){ all_paths, FTS_PHYSICAL | FTS_NOSTAT, (struct fts_expect[]){ { FTS_SL, "dead", "dead" }, { FTS_D, "dir", "dir" }, { FTS_NSOK, "file", "file" }, { FTS_NSOK, "up", "up" }, { FTS_DP, "dir", "dir" }, { FTS_SL, "dirl", "dirl" }, { FTS_F, "file", "file" }, { FTS_SL, "filel", "filel" }, { FTS_NS, "noent", "noent" }, { 0 } }, }); } ATF_TC(fts_options_physical_nostat_type); ATF_TC_HEAD(fts_options_physical_nostat_type, tc) { atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_NOSTAT_TYPE"); } ATF_TC_BODY(fts_options_physical_nostat_type, tc) { fts_options_prepare(tc); - fts_options_test(tc, &(struct fts_testcase){ + fts_test(tc, &(struct fts_testcase){ all_paths, FTS_PHYSICAL | FTS_NOSTAT_TYPE, (struct fts_expect[]){ { FTS_SL, "dead", "dead" }, { FTS_D, "dir", "dir" }, { FTS_F, "file", "file" }, { FTS_SL, "up", "up" }, { FTS_DP, "dir", "dir" }, { FTS_SL, "dirl", "dirl" }, { FTS_F, "file", "file" }, { FTS_SL, "filel", "filel" }, { FTS_NS, "noent", "noent" }, { 0 } }, }); } ATF_TC(fts_options_physical_seedot); ATF_TC_HEAD(fts_options_physical_seedot, tc) { atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_SEEDOT"); } ATF_TC_BODY(fts_options_physical_seedot, tc) { fts_options_prepare(tc); - fts_options_test(tc, &(struct fts_testcase){ + fts_test(tc, &(struct fts_testcase){ all_paths, FTS_PHYSICAL | FTS_SEEDOT, (struct fts_expect[]){ { FTS_SL, "dead", "dead" }, { FTS_D, "dir", "dir" }, { FTS_DOT, ".", "." }, { FTS_DOT, "..", ".." }, { FTS_F, "file", "file" }, { FTS_SL, "up", "up" }, { FTS_DP, "dir", "dir" }, { FTS_SL, "dirl", "dirl" }, { FTS_F, "file", "file" }, { FTS_SL, "filel", "filel" }, { FTS_NS, "noent", "noent" }, { 0 } }, }); } /* * TODO: Add tests for FTS_XDEV and FTS_WHITEOUT */ ATF_TP_ADD_TCS(tp) { + fts_check_debug(); ATF_TP_ADD_TC(tp, fts_options_logical); ATF_TP_ADD_TC(tp, fts_options_logical_nostat); ATF_TP_ADD_TC(tp, fts_options_logical_seedot); ATF_TP_ADD_TC(tp, fts_options_physical); ATF_TP_ADD_TC(tp, fts_options_physical_nochdir); ATF_TP_ADD_TC(tp, fts_options_physical_comfollow); ATF_TP_ADD_TC(tp, fts_options_physical_comfollowdir); ATF_TP_ADD_TC(tp, fts_options_physical_nostat); ATF_TP_ADD_TC(tp, fts_options_physical_nostat_type); ATF_TP_ADD_TC(tp, fts_options_physical_seedot); return (atf_no_error()); } diff --git a/lib/libc/tests/gen/fts_test.h b/lib/libc/tests/gen/fts_test.h new file mode 100644 index 000000000000..b3f15050f265 --- /dev/null +++ b/lib/libc/tests/gen/fts_test.h @@ -0,0 +1,81 @@ +/*- + * Copyright (c) 2025 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef FTS_TEST_H_INCLUDED +#define FTS_TEST_H_INCLUDED + +struct fts_expect { + int fts_info; + const char *fts_name; + const char *fts_accpath; +}; + +struct fts_testcase { + char **paths; + int fts_options; + struct fts_expect *fts_expect; +}; + +/* shorter name for dead links */ +#define FTS_DL FTS_SLNONE + +/* are we being debugged? */ +static bool fts_test_debug; + +/* + * Set debug flag if appropriate. + */ +static void +fts_check_debug(void) +{ + fts_test_debug = !getenv("__RUNNING_INSIDE_ATF_RUN") && + isatty(STDERR_FILENO); +} + +/* + * Lexical order for reproducability. + */ +static int +fts_lexical_compar(const FTSENT * const *a, const FTSENT * const *b) +{ + return (strcmp((*a)->fts_name, (*b)->fts_name)); +} + +/* + * Run FTS with the specified paths and options and verify that it + * produces the expected result in the correct order. + */ +static void +fts_test(const struct atf_tc *tc, const struct fts_testcase *fts_tc) +{ + FTS *fts; + FTSENT *ftse; + const struct fts_expect *expect = fts_tc->fts_expect; + long level = 0; + + fts = fts_open(fts_tc->paths, fts_tc->fts_options, fts_lexical_compar); + ATF_REQUIRE_MSG(fts != NULL, "fts_open(): %m"); + while ((ftse = fts_read(fts)) != NULL && expect->fts_name != NULL) { + if (expect->fts_info == FTS_DP || expect->fts_info == FTS_DNR) + level--; + if (fts_test_debug) { + fprintf(stderr, "%2ld %2d %s\n", level, + ftse->fts_info, ftse->fts_name); + } + ATF_CHECK_STREQ(expect->fts_name, ftse->fts_name); + ATF_CHECK_STREQ(expect->fts_accpath, ftse->fts_accpath); + ATF_CHECK_INTEQ(expect->fts_info, ftse->fts_info); + ATF_CHECK_INTEQ(level, ftse->fts_level); + if (expect->fts_info == FTS_D) + level++; + expect++; + } + ATF_CHECK_EQ(NULL, ftse); + ATF_CHECK_EQ(NULL, expect->fts_name); + ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m"); +} + +#endif /* FTS_TEST_H_INCLUDED */