From nobody Fri May 22 17:43:32 2026 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4gMXgn0Fz3z6fJjt for ; Fri, 22 May 2026 17:43:33 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R13" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4gMXgm6V5jz48tN for ; Fri, 22 May 2026 17:43:32 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1779471813; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=n3I47a/BjUgldU9TLLq/JEcp9/Fw+N1u4e+iA6D2oxw=; b=mHfbf8gGV9mEchTK1aTlUxG8KAZsNr5zSFiwjcP4abTGw9g7gl8ktcebth6vdJLig/HAYw FQvgY1Fcw3xMjmh8No8sEKqA/MmIvBWKYo4ujxqCH4iSjOmrdEmCpxhmtYJoW+CeRjIHO3 oQGZ0d8IxuToIop6tXT7GIWGoErFxZB/SKDAc9RO/0KOwPUhWUXm+JeK9OHPglWrxEqM1E 9rrxwyh5uI/J7WJwX/YrFwmqM8XoqjwbO7Kqmi3JnEi8yY0DPciqu2xlZw63tupOI5I4HZ ZJQ12lb9dlKKrJWzHagPWQ+C1YrKhxJOxKp2Q9nmQhrVlz4+BJhNXxlF3RK8Xg== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1779471813; a=rsa-sha256; cv=none; b=MlaErGAjdyqKSghJ7+kguAf5TsyT7TgTldSeyhG+5dYy0l3h2I9xB6tDcsaLfoZmCESaHn dcaxHrvU1rjODoNSWWOJP6jyZMZj7t2+ExqrwtVoeuqFwmmwlz6PwN6dfWAnSXVg3aj44n rk2D2O4nsLywEzz6iiVjou0XoNuGuZzPqTUVDrTA9XJlXy8tKJpk5nWD2HJAeNjAuyYXf7 XctNjmBmJ7czl5Q12Z7tat3jUukHMDIOTGLbYZ1LZjDaLNsFpq0KrcTfg1ii9x8UdpHz0f jC2eOeCPEoPx4zBPb+wPE24FN3Yo8DKJ0vhHFW4/IA0ePjLJgEmbWUUj2P+a3Q== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1779471813; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=n3I47a/BjUgldU9TLLq/JEcp9/Fw+N1u4e+iA6D2oxw=; b=Z04lW1YUY0VgVpSajx8xOk9gt9UAKYjRUGHO4rrMtiqLG8o3w+3e03P69hsyzM+Ae8j0Ok Y4v6Oq1NNNbDeLHAMblkCGnJGjruBxA7KlvdZ+8sF+9NCfda6LMtl44cRIrrmOFcC6ezuB 8V/T/LxKZs7Ropi9gTsIbWvBsM9JodO2D065FOctljQodjHscyiMAo0RciIu6qEOvanZLk yjm3s+WhajT4x8igcQBZ4q3dt4ZHWIooj9ot6hcEWOIJNsDOdUGdclMLz5vugJ96RO+d68 F+l9f6ikfPocCIA3goqk+JLMbGCXXSYtpaY2P3oCNLjR8HHSh1sRe8GGcOl4rA== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4gMXgm63Z9zX21 for ; Fri, 22 May 2026 17:43:32 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 38631 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Fri, 22 May 2026 17:43:32 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Cc: Jitendra Bhati From: Alan Somers Subject: git: e624417db8a1 - main - lib/libc/tests/gen: add fts_children() tests List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: asomers X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: e624417db8a136849caa31fc34266645ed6c3429 Auto-Submitted: auto-generated Date: Fri, 22 May 2026 17:43:32 +0000 Message-Id: <6a1095c4.38631.14d7c934@gitrepo.freebsd.org> The branch main has been updated by asomers: URL: https://cgit.FreeBSD.org/src/commit/?id=e624417db8a136849caa31fc34266645ed6c3429 commit e624417db8a136849caa31fc34266645ed6c3429 Author: Jitendra Bhati AuthorDate: 2026-05-21 01:44:04 +0000 Commit: Alan Somers 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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()); +}