Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 22 May 2026 17:43:32 +0000
From:      Alan Somers <asomers@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Cc:        Jitendra Bhati <bhatijitendra2022@gmail.com>
Subject:   git: e624417db8a1 - main - lib/libc/tests/gen: add fts_children() tests
Message-ID:  <6a1095c4.38631.14d7c934@gitrepo.freebsd.org>

index | next in thread | raw e-mail

The branch main has been updated by asomers:

URL: https://cgit.FreeBSD.org/src/commit/?id=e624417db8a136849caa31fc34266645ed6c3429

commit e624417db8a136849caa31fc34266645ed6c3429
Author:     Jitendra Bhati <bhatijitendra2022@gmail.com>
AuthorDate: 2026-05-21 01:44:04 +0000
Commit:     Alan Somers <asomers@FreeBSD.org>
CommitDate: 2026-05-22 17:40:06 +0000

    lib/libc/tests/gen: add fts_children() tests
    
    Add ATF test cases covering fts_children() behaviour:
    
    - before fts_read returns root entry list
    - empty directory returns NULL with errno 0
    - non-empty directory returns all children in order
    - called twice returns equivalent results
    - FTS_NAMEONLY fills only fts_name, fts_info is FTS_NSOK
    - non-directory node returns NULL with errno 0
    - invalid options returns NULL with EINVAL
    
    Sponsored by:   Google LLC (GSoC 2026)
    Reviewed by:    asomers
    MFC after:      1 week
    Pull Request:   https://github.com/freebsd/freebsd-src/pull/2218
---
 lib/libc/tests/gen/Makefile            |   1 +
 lib/libc/tests/gen/fts_children_test.c | 323 +++++++++++++++++++++++++++++++++
 2 files changed, 324 insertions(+)

diff --git a/lib/libc/tests/gen/Makefile b/lib/libc/tests/gen/Makefile
index 9c1c1cb75bcf..9341fe8c9074 100644
--- a/lib/libc/tests/gen/Makefile
+++ b/lib/libc/tests/gen/Makefile
@@ -10,6 +10,7 @@ ATF_TESTS_C+=		fpclassify2_test
 .if ${COMPILER_FEATURES:Mblocks}
 ATF_TESTS_C+=		fts_blocks_test
 .endif
+ATF_TESTS_C+=		fts_children_test
 ATF_TESTS_C+=		fts_misc_test
 ATF_TESTS_C+=		fts_open_test
 ATF_TESTS_C+=		fts_options_test
diff --git a/lib/libc/tests/gen/fts_children_test.c b/lib/libc/tests/gen/fts_children_test.c
new file mode 100644
index 000000000000..171b1227104c
--- /dev/null
+++ b/lib/libc/tests/gen/fts_children_test.c
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2026 Jitendra Bhati
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * Tests for fts_children().
+ */
+
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <fts.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+#include "fts_test.h"
+
+/*
+ * fts_children() before fts_read() returns the list of root entries.
+ */
+ATF_TC(before_read);
+ATF_TC_HEAD(before_read, tc)
+{
+	atf_tc_set_md_var(tc, "descr",
+	    "fts_children before fts_read returns root entry list");
+}
+ATF_TC_BODY(before_read, tc)
+{
+	char *paths[] = { "dir", NULL };
+	FTS *fts;
+	FTSENT *children, *p;
+	int count;
+
+	ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
+	ATF_REQUIRE_EQ(0, close(creat("dir/a", 0644)));
+
+	ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);
+
+	errno = 0;
+	children = fts_children(fts, 0);
+	ATF_REQUIRE_MSG(children != NULL,
+	    "fts_children before fts_read must return the root list");
+	ATF_CHECK_EQ(0, errno);
+
+	count = 0;
+	for (p = children; p != NULL; p = p->fts_link) {
+		ATF_CHECK_EQ_MSG(FTS_D, p->fts_info,
+		    "root entry should be FTS_D, got %d", p->fts_info);
+		count++;
+	}
+	ATF_CHECK_EQ_MSG(1, count,
+	    "expected 1 root entry, found %d", count);
+
+	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
+}
+
+/*
+ * fts_children() on an empty directory returns NULL with errno == 0.
+ * errno=0 distinguishes "empty" from an actual error.
+ */
+ATF_TC(empty_dir);
+ATF_TC_HEAD(empty_dir, tc)
+{
+	atf_tc_set_md_var(tc, "descr",
+	    "fts_children on empty directory returns NULL with errno 0");
+}
+ATF_TC_BODY(empty_dir, tc)
+{
+	char *paths[] = { "dir", NULL };
+	FTS *fts;
+	FTSENT *ent, *children;
+
+	ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
+
+	ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);
+
+	ent = fts_read(fts);
+	ATF_REQUIRE(ent != NULL);
+	ATF_REQUIRE_EQ_MSG(FTS_D, ent->fts_info,
+	    "expected FTS_D, got %d", ent->fts_info);
+
+	errno = 1;	/* sentinel — fts_children must clear this */
+	children = fts_children(fts, 0);
+	ATF_CHECK_MSG(children == NULL,
+	    "fts_children on empty dir must return NULL");
+	ATF_CHECK_EQ_MSG(0, errno,
+	    "fts_children on empty dir must set errno=0, got %d", errno);
+
+	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
+}
+
+/*
+ * fts_children() on a non-empty directory returns a linked list of all
+ * children in comparator order.
+ */
+ATF_TC(nonempty_dir);
+ATF_TC_HEAD(nonempty_dir, tc)
+{
+	atf_tc_set_md_var(tc, "descr",
+	    "fts_children on non-empty directory returns all children");
+}
+ATF_TC_BODY(nonempty_dir, tc)
+{
+	static const char *expected[] = { "a", "b", "c", NULL };
+	char *paths[] = { "dir", NULL };
+	FTS *fts;
+	FTSENT *ent, *children, *p;
+	int i;
+
+	ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
+	ATF_REQUIRE_EQ(0, close(creat("dir/a", 0644)));
+	ATF_REQUIRE_EQ(0, close(creat("dir/b", 0644)));
+	ATF_REQUIRE_EQ(0, close(creat("dir/c", 0644)));
+
+	ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL,
+	    fts_lexical_compar)) != NULL);
+
+	ent = fts_read(fts);
+	ATF_REQUIRE(ent != NULL);
+	ATF_REQUIRE_EQ(FTS_D, ent->fts_info);
+
+	children = fts_children(fts, 0);
+	ATF_REQUIRE_MSG(children != NULL, "fts_children(): %m");
+
+	i = 0;
+	for (p = children; p != NULL; p = p->fts_link, i++) {
+		ATF_REQUIRE_MSG(expected[i] != NULL,
+		    "more children returned than expected");
+		ATF_CHECK_STREQ(expected[i], p->fts_name);
+		ATF_CHECK_EQ(FTS_F, p->fts_info);
+	}
+	ATF_CHECK_MSG(expected[i] == NULL,
+	    "fewer children returned than expected");
+
+	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
+}
+
+/*
+ * fts_children() called twice on the same FTS_D node must return an
+ * equivalent list both times.
+ */
+ATF_TC(called_twice);
+ATF_TC_HEAD(called_twice, tc)
+{
+	atf_tc_set_md_var(tc, "descr",
+	    "fts_children called twice returns equivalent results");
+}
+ATF_TC_BODY(called_twice, tc)
+{
+	char *paths[] = { "dir", NULL };
+	FTS *fts;
+	FTSENT *ent, *first, *second, *p;
+	int count1, count2;
+
+	ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
+	ATF_REQUIRE_EQ(0, close(creat("dir/x", 0644)));
+	ATF_REQUIRE_EQ(0, close(creat("dir/y", 0644)));
+
+	ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL,
+	    fts_lexical_compar)) != NULL);
+
+	ent = fts_read(fts);
+	ATF_REQUIRE(ent != NULL);
+	ATF_REQUIRE_EQ(FTS_D, ent->fts_info);
+
+	first = fts_children(fts, 0);
+	ATF_REQUIRE_MSG(first != NULL, "first fts_children call: %m");
+
+	count1 = 0;
+	for (p = first; p != NULL; p = p->fts_link)
+		count1++;
+
+	/*
+	 * The second call frees the first list and rebuilds.  Do not
+	 * dereference 'first' after this point — it has been freed.
+	 */
+	second = fts_children(fts, 0);
+	ATF_REQUIRE_MSG(second != NULL, "second fts_children call: %m");
+
+	count2 = 0;
+	for (p = second; p != NULL; p = p->fts_link)
+		count2++;
+
+	ATF_CHECK_EQ_MSG(count1, count2,
+	    "first call returned %d children, second returned %d",
+	    count1, count2);
+	ATF_CHECK_EQ(2, count2);
+
+	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
+}
+
+/*
+ * fts_children(FTS_NAMEONLY): only fts_name and fts_namelen are filled.
+ * fts_info is FTS_NSOK for every entry.
+ */
+ATF_TC(nameonly);
+ATF_TC_HEAD(nameonly, tc)
+{
+	atf_tc_set_md_var(tc, "descr",
+	    "FTS_NAMEONLY fills only fts_name, fts_info is FTS_NSOK");
+}
+ATF_TC_BODY(nameonly, tc)
+{
+	char *paths[] = { "dir", NULL };
+	FTS *fts;
+	FTSENT *ent, *children, *p;
+	int count;
+
+	ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
+	ATF_REQUIRE_EQ(0, close(creat("dir/f1", 0644)));
+	ATF_REQUIRE_EQ(0, close(creat("dir/f2", 0644)));
+
+	ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL,
+	    fts_lexical_compar)) != NULL);
+
+	ent = fts_read(fts);
+	ATF_REQUIRE(ent != NULL);
+	ATF_REQUIRE_EQ(FTS_D, ent->fts_info);
+
+	children = fts_children(fts, FTS_NAMEONLY);
+	ATF_REQUIRE_MSG(children != NULL, "fts_children(FTS_NAMEONLY): %m");
+
+	count = 0;
+	for (p = children; p != NULL; p = p->fts_link) {
+		ATF_CHECK_MSG(p->fts_name[0] != '\0',
+		    "FTS_NAMEONLY: fts_name is empty");
+		ATF_CHECK_EQ(strlen(p->fts_name), p->fts_namelen);
+		ATF_CHECK_EQ_MSG(FTS_NSOK, p->fts_info,
+		    "FTS_NAMEONLY: expected FTS_NSOK, got %d", p->fts_info);
+		count++;
+	}
+	ATF_CHECK_EQ(2, count);
+
+	/* Normal traversal must still work after FTS_NAMEONLY. */
+	while (fts_read(fts) != NULL)
+		;
+	ATF_CHECK_EQ_MSG(0, errno,
+	    "traversal after FTS_NAMEONLY ended with errno %d", errno);
+
+	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
+}
+
+/*
+ * fts_children() on a non-directory node must return NULL with errno == 0.
+ */
+ATF_TC(nondirectory);
+ATF_TC_HEAD(nondirectory, tc)
+{
+	atf_tc_set_md_var(tc, "descr",
+	    "fts_children on a non-directory node returns NULL with errno 0");
+}
+ATF_TC_BODY(nondirectory, tc)
+{
+	char *paths[] = { "dir", NULL };
+	FTS *fts;
+	FTSENT *ent;
+
+	ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
+	ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644)));
+
+	ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL,
+	    fts_lexical_compar)) != NULL);
+
+	ent = fts_read(fts);	/* FTS_D dir */
+	ATF_REQUIRE(ent != NULL);
+	ATF_REQUIRE_EQ(FTS_D, ent->fts_info);
+
+	ent = fts_read(fts);	/* FTS_F file */
+	ATF_REQUIRE(ent != NULL);
+	ATF_REQUIRE_EQ(FTS_F, ent->fts_info);
+
+	errno = 1;
+	ATF_CHECK_MSG(fts_children(fts, 0) == NULL,
+	    "fts_children on FTS_F must return NULL");
+	ATF_CHECK_EQ_MSG(0, errno,
+	    "fts_children on FTS_F must set errno=0, got %d", errno);
+
+	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
+}
+
+/*
+ * fts_children() with an invalid options value must return NULL with
+ * errno == EINVAL.
+ */
+ATF_TC(invalid_options);
+ATF_TC_HEAD(invalid_options, tc)
+{
+	atf_tc_set_md_var(tc, "descr",
+	    "fts_children with invalid options returns NULL with EINVAL");
+}
+ATF_TC_BODY(invalid_options, tc)
+{
+	char *paths[] = { ".", NULL };
+	FTS *fts;
+
+	ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);
+
+	ATF_REQUIRE_ERRNO(EINVAL, fts_children(fts, 99) == NULL);
+
+	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+	fts_check_debug();
+	ATF_TP_ADD_TC(tp, before_read);
+	ATF_TP_ADD_TC(tp, empty_dir);
+	ATF_TP_ADD_TC(tp, nonempty_dir);
+	ATF_TP_ADD_TC(tp, called_twice);
+	ATF_TP_ADD_TC(tp, nameonly);
+	ATF_TP_ADD_TC(tp, nondirectory);
+	ATF_TP_ADD_TC(tp, invalid_options);
+
+	return (atf_no_error());
+}


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a1095c4.38631.14d7c934>