lib/libc/tests/gen: add fts_set() tests
Add ATF test cases for fts_set(): fts_set: - invalid instruction returns non-zero with EINVAL - FTS_AGAIN revisits the current node - FTS_AGAIN consecutive visits node three times - FTS_FOLLOW on symlink to file yields FTS_F - FTS_FOLLOW on symlink to directory causes descent - FTS_FOLLOW on dead symlink yields FTS_SLNONE - FTS_SKIP prevents descent into directory - fts_set_clientptr/fts_get_clientptr round-trip - fts_get_stream returns parent FTS* from FTSENT* Sponsored by: Google LLC (GSoC 2026) Reviewed by: asomers MFC after: 2 weeks Pull Request: https://github.com/freebsd/freebsd-src/pull/2242
This commit is contained in:
committed by
Alan Somers
parent
5d36fcc53d
commit
940142d610
@@ -14,6 +14,7 @@ ATF_TESTS_C+= fts_children_test
|
|||||||
ATF_TESTS_C+= fts_misc_test
|
ATF_TESTS_C+= fts_misc_test
|
||||||
ATF_TESTS_C+= fts_open_test
|
ATF_TESTS_C+= fts_open_test
|
||||||
ATF_TESTS_C+= fts_options_test
|
ATF_TESTS_C+= fts_options_test
|
||||||
|
ATF_TESTS_C+= fts_set_test
|
||||||
ATF_TESTS_C+= ftw_test
|
ATF_TESTS_C+= ftw_test
|
||||||
ATF_TESTS_C+= getentropy_test
|
ATF_TESTS_C+= getentropy_test
|
||||||
ATF_TESTS_C+= getmntinfo_test
|
ATF_TESTS_C+= getmntinfo_test
|
||||||
|
|||||||
@@ -0,0 +1,361 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Jitendra Bhati
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tests for fts_set(), fts_set_clientptr(), fts_get_clientptr(),
|
||||||
|
* and fts_get_stream().
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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_set with invalid options must return non-zero with EINVAL.
|
||||||
|
* Note: fts_set returns 1 (not -1) on error.
|
||||||
|
*/
|
||||||
|
ATF_TC(invalid_options);
|
||||||
|
ATF_TC_HEAD(invalid_options, tc)
|
||||||
|
{
|
||||||
|
atf_tc_set_md_var(tc, "descr",
|
||||||
|
"fts_set with invalid options returns non-zero with EINVAL");
|
||||||
|
}
|
||||||
|
ATF_TC_BODY(invalid_options, tc)
|
||||||
|
{
|
||||||
|
char *paths[] = { ".", NULL };
|
||||||
|
FTS *fts;
|
||||||
|
FTSENT *ent;
|
||||||
|
|
||||||
|
ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);
|
||||||
|
|
||||||
|
ent = fts_read(fts);
|
||||||
|
ATF_REQUIRE(ent != NULL);
|
||||||
|
ATF_REQUIRE_ERRNO(EINVAL, fts_set(fts, ent, 99) != 0);
|
||||||
|
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FTS_AGAIN causes the current node to be re-stat()ed and returned
|
||||||
|
* again on the next fts_read() call.
|
||||||
|
*/
|
||||||
|
ATF_TC(again);
|
||||||
|
ATF_TC_HEAD(again, tc)
|
||||||
|
{
|
||||||
|
atf_tc_set_md_var(tc, "descr",
|
||||||
|
"FTS_AGAIN causes the current node to be returned once more");
|
||||||
|
}
|
||||||
|
ATF_TC_BODY(again, tc)
|
||||||
|
{
|
||||||
|
char *paths[] = { "dir", NULL };
|
||||||
|
FTS *fts;
|
||||||
|
FTSENT *ent;
|
||||||
|
int revisit_count;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
revisit_count = 0;
|
||||||
|
while ((ent = fts_read(fts)) != NULL) {
|
||||||
|
if (ent->fts_info == FTS_F && revisit_count == 0) {
|
||||||
|
ATF_REQUIRE_EQ_MSG(0,
|
||||||
|
fts_set(fts, ent, FTS_AGAIN),
|
||||||
|
"fts_set(FTS_AGAIN): %m");
|
||||||
|
revisit_count++;
|
||||||
|
} else if (ent->fts_info == FTS_F && revisit_count >= 1) {
|
||||||
|
revisit_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ATF_CHECK_EQ_MSG(0, errno, "traversal ended with errno %d", errno);
|
||||||
|
ATF_CHECK_EQ_MSG(2, revisit_count,
|
||||||
|
"expected file visited twice via FTS_AGAIN, saw %d",
|
||||||
|
revisit_count);
|
||||||
|
|
||||||
|
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FTS_AGAIN set twice in a row causes the node to be visited three
|
||||||
|
* times total. Each fts_read() clears fts_options, so the caller must
|
||||||
|
* set FTS_AGAIN again explicitly each time.
|
||||||
|
*/
|
||||||
|
ATF_TC(again_consecutive);
|
||||||
|
ATF_TC_HEAD(again_consecutive, tc)
|
||||||
|
{
|
||||||
|
atf_tc_set_md_var(tc, "descr",
|
||||||
|
"FTS_AGAIN set twice in a row visits the node three times");
|
||||||
|
}
|
||||||
|
ATF_TC_BODY(again_consecutive, tc)
|
||||||
|
{
|
||||||
|
char *paths[] = { "file", NULL };
|
||||||
|
FTS *fts;
|
||||||
|
FTSENT *ent;
|
||||||
|
int visit_count;
|
||||||
|
|
||||||
|
ATF_REQUIRE_EQ(0, close(creat("file", 0644)));
|
||||||
|
|
||||||
|
ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);
|
||||||
|
|
||||||
|
visit_count = 0;
|
||||||
|
while ((ent = fts_read(fts)) != NULL) {
|
||||||
|
if (ent->fts_info == FTS_F) {
|
||||||
|
visit_count++;
|
||||||
|
if (visit_count < 3)
|
||||||
|
ATF_REQUIRE_EQ(0,
|
||||||
|
fts_set(fts, ent, FTS_AGAIN));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ATF_CHECK_EQ_MSG(3, visit_count,
|
||||||
|
"expected 3 visits with consecutive FTS_AGAIN, got %d",
|
||||||
|
visit_count);
|
||||||
|
|
||||||
|
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FTS_FOLLOW on an FTS_SL entry pointing to a regular file yields FTS_F.
|
||||||
|
*/
|
||||||
|
ATF_TC(follow_symlink_to_file);
|
||||||
|
ATF_TC_HEAD(follow_symlink_to_file, tc)
|
||||||
|
{
|
||||||
|
atf_tc_set_md_var(tc, "descr",
|
||||||
|
"FTS_FOLLOW on FTS_SL to regular file yields FTS_F");
|
||||||
|
}
|
||||||
|
ATF_TC_BODY(follow_symlink_to_file, tc)
|
||||||
|
{
|
||||||
|
char *paths[] = { "dir", NULL };
|
||||||
|
FTS *fts;
|
||||||
|
FTSENT *ent;
|
||||||
|
int followed;
|
||||||
|
|
||||||
|
ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
|
||||||
|
ATF_REQUIRE_EQ(0, close(creat("dir/target", 0644)));
|
||||||
|
ATF_REQUIRE_EQ(0, symlink("target", "dir/link"));
|
||||||
|
|
||||||
|
ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL,
|
||||||
|
fts_lexical_compar)) != NULL);
|
||||||
|
|
||||||
|
followed = 0;
|
||||||
|
while ((ent = fts_read(fts)) != NULL) {
|
||||||
|
if (ent->fts_info == FTS_SL &&
|
||||||
|
strcmp(ent->fts_name, "link") == 0)
|
||||||
|
ATF_REQUIRE_EQ(0, fts_set(fts, ent, FTS_FOLLOW));
|
||||||
|
else if (ent->fts_info == FTS_F &&
|
||||||
|
strcmp(ent->fts_name, "link") == 0)
|
||||||
|
followed = 1;
|
||||||
|
}
|
||||||
|
ATF_CHECK_MSG(followed != 0,
|
||||||
|
"FTS_FOLLOW on symlink-to-file must yield FTS_F");
|
||||||
|
|
||||||
|
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FTS_FOLLOW on an FTS_SL entry pointing to a directory causes descent
|
||||||
|
* into the target directory.
|
||||||
|
*/
|
||||||
|
ATF_TC(follow_symlink_to_dir);
|
||||||
|
ATF_TC_HEAD(follow_symlink_to_dir, tc)
|
||||||
|
{
|
||||||
|
atf_tc_set_md_var(tc, "descr",
|
||||||
|
"FTS_FOLLOW on FTS_SL to directory causes descent");
|
||||||
|
}
|
||||||
|
ATF_TC_BODY(follow_symlink_to_dir, tc)
|
||||||
|
{
|
||||||
|
char *paths[] = { "dir", NULL };
|
||||||
|
FTS *fts;
|
||||||
|
FTSENT *ent;
|
||||||
|
int saw_inside;
|
||||||
|
|
||||||
|
ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
|
||||||
|
ATF_REQUIRE_EQ(0, mkdir("dir/real", 0755));
|
||||||
|
ATF_REQUIRE_EQ(0, close(creat("dir/real/inside", 0644)));
|
||||||
|
ATF_REQUIRE_EQ(0, symlink("real", "dir/link"));
|
||||||
|
|
||||||
|
ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL,
|
||||||
|
fts_lexical_compar)) != NULL);
|
||||||
|
|
||||||
|
saw_inside = 0;
|
||||||
|
while ((ent = fts_read(fts)) != NULL) {
|
||||||
|
if (ent->fts_info == FTS_SL &&
|
||||||
|
strcmp(ent->fts_name, "link") == 0)
|
||||||
|
ATF_REQUIRE_EQ(0, fts_set(fts, ent, FTS_FOLLOW));
|
||||||
|
if (ent->fts_info == FTS_F &&
|
||||||
|
strcmp(ent->fts_name, "inside") == 0 &&
|
||||||
|
strcmp(ent->fts_path, "dir/link/inside") == 0)
|
||||||
|
saw_inside = 1;
|
||||||
|
}
|
||||||
|
ATF_CHECK_MSG(saw_inside != 0,
|
||||||
|
"FTS_FOLLOW on symlink-to-dir should descend and visit 'inside'");
|
||||||
|
|
||||||
|
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FTS_FOLLOW on a dangling symlink (FTS_SLNONE) yields FTS_SLNONE again.
|
||||||
|
* FTS_SLNONE requires FTS_LOGICAL — under FTS_PHYSICAL a dangling
|
||||||
|
* symlink is reported as FTS_SL.
|
||||||
|
*/
|
||||||
|
ATF_TC(follow_dead_symlink);
|
||||||
|
ATF_TC_HEAD(follow_dead_symlink, tc)
|
||||||
|
{
|
||||||
|
atf_tc_set_md_var(tc, "descr",
|
||||||
|
"FTS_FOLLOW on dead symlink yields FTS_SLNONE");
|
||||||
|
}
|
||||||
|
ATF_TC_BODY(follow_dead_symlink, tc)
|
||||||
|
{
|
||||||
|
char *paths[] = { "dead", NULL };
|
||||||
|
FTS *fts;
|
||||||
|
FTSENT *ent;
|
||||||
|
|
||||||
|
ATF_REQUIRE_EQ(0, symlink("no-such-target", "dead"));
|
||||||
|
|
||||||
|
ATF_REQUIRE((fts = fts_open(paths, FTS_LOGICAL, NULL)) != NULL);
|
||||||
|
|
||||||
|
ent = fts_read(fts);
|
||||||
|
ATF_REQUIRE(ent != NULL);
|
||||||
|
ATF_REQUIRE_EQ_MSG(FTS_SLNONE, ent->fts_info,
|
||||||
|
"expected FTS_SLNONE for dead symlink, got %d", ent->fts_info);
|
||||||
|
|
||||||
|
ATF_REQUIRE_EQ(0, fts_set(fts, ent, FTS_FOLLOW));
|
||||||
|
ent = fts_read(fts);
|
||||||
|
ATF_REQUIRE(ent != NULL);
|
||||||
|
ATF_CHECK_EQ_MSG(FTS_SLNONE, ent->fts_info,
|
||||||
|
"FTS_FOLLOW on dead symlink should still be FTS_SLNONE, got %d",
|
||||||
|
ent->fts_info);
|
||||||
|
|
||||||
|
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FTS_SKIP on an FTS_D node prevents descent into that directory.
|
||||||
|
* The next fts_read() converts the node to FTS_DP without visiting
|
||||||
|
* any children.
|
||||||
|
*/
|
||||||
|
ATF_TC(skip);
|
||||||
|
ATF_TC_HEAD(skip, tc)
|
||||||
|
{
|
||||||
|
atf_tc_set_md_var(tc, "descr",
|
||||||
|
"FTS_SKIP prevents descent into a directory");
|
||||||
|
}
|
||||||
|
ATF_TC_BODY(skip, tc)
|
||||||
|
{
|
||||||
|
char *paths[] = { "dir", NULL };
|
||||||
|
FTS *fts;
|
||||||
|
FTSENT *ent;
|
||||||
|
int saw_inside;
|
||||||
|
|
||||||
|
ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
|
||||||
|
ATF_REQUIRE_EQ(0, mkdir("dir/skip_me", 0755));
|
||||||
|
ATF_REQUIRE_EQ(0, close(creat("dir/skip_me/inside", 0644)));
|
||||||
|
ATF_REQUIRE_EQ(0, close(creat("dir/sibling", 0644)));
|
||||||
|
|
||||||
|
ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL,
|
||||||
|
fts_lexical_compar)) != NULL);
|
||||||
|
|
||||||
|
saw_inside = 0;
|
||||||
|
while ((ent = fts_read(fts)) != NULL) {
|
||||||
|
if (ent->fts_info == FTS_D &&
|
||||||
|
strcmp(ent->fts_name, "skip_me") == 0)
|
||||||
|
ATF_REQUIRE_EQ(0, fts_set(fts, ent, FTS_SKIP));
|
||||||
|
if (strcmp(ent->fts_name, "inside") == 0)
|
||||||
|
saw_inside = 1;
|
||||||
|
}
|
||||||
|
ATF_CHECK_MSG(saw_inside == 0,
|
||||||
|
"FTS_SKIP: 'inside' must not have been visited");
|
||||||
|
|
||||||
|
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* fts_set_clientptr() and fts_get_clientptr() store and retrieve an
|
||||||
|
* arbitrary pointer on the FTS stream.
|
||||||
|
*/
|
||||||
|
ATF_TC(clientptr_roundtrip);
|
||||||
|
ATF_TC_HEAD(clientptr_roundtrip, tc)
|
||||||
|
{
|
||||||
|
atf_tc_set_md_var(tc, "descr",
|
||||||
|
"fts_set_clientptr / fts_get_clientptr round-trip");
|
||||||
|
}
|
||||||
|
ATF_TC_BODY(clientptr_roundtrip, tc)
|
||||||
|
{
|
||||||
|
char *paths[] = { ".", NULL };
|
||||||
|
FTS *fts;
|
||||||
|
int value = 42;
|
||||||
|
|
||||||
|
ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);
|
||||||
|
|
||||||
|
ATF_CHECK_EQ(NULL, fts_get_clientptr(fts));
|
||||||
|
|
||||||
|
fts_set_clientptr(fts, &value);
|
||||||
|
ATF_CHECK_EQ_MSG(&value, fts_get_clientptr(fts),
|
||||||
|
"fts_get_clientptr did not return the stored pointer");
|
||||||
|
|
||||||
|
fts_set_clientptr(fts, NULL);
|
||||||
|
ATF_CHECK_EQ(NULL, fts_get_clientptr(fts));
|
||||||
|
|
||||||
|
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* fts_get_stream() returns the parent FTS* from any FTSENT* returned
|
||||||
|
* by fts_read().
|
||||||
|
*/
|
||||||
|
ATF_TC(get_stream_backpointer);
|
||||||
|
ATF_TC_HEAD(get_stream_backpointer, tc)
|
||||||
|
{
|
||||||
|
atf_tc_set_md_var(tc, "descr",
|
||||||
|
"fts_get_stream returns the parent FTS* from an FTSENT*");
|
||||||
|
}
|
||||||
|
ATF_TC_BODY(get_stream_backpointer, 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, NULL)) != NULL);
|
||||||
|
|
||||||
|
while ((ent = fts_read(fts)) != NULL) {
|
||||||
|
ATF_CHECK_EQ_MSG(fts, fts_get_stream(ent),
|
||||||
|
"fts_get_stream(ent) must return the parent FTS*, "
|
||||||
|
"entry: %s info: %d",
|
||||||
|
ent->fts_name, ent->fts_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
|
||||||
|
}
|
||||||
|
|
||||||
|
ATF_TP_ADD_TCS(tp)
|
||||||
|
{
|
||||||
|
fts_check_debug();
|
||||||
|
ATF_TP_ADD_TC(tp, invalid_options);
|
||||||
|
ATF_TP_ADD_TC(tp, again);
|
||||||
|
ATF_TP_ADD_TC(tp, again_consecutive);
|
||||||
|
ATF_TP_ADD_TC(tp, follow_symlink_to_file);
|
||||||
|
ATF_TP_ADD_TC(tp, follow_symlink_to_dir);
|
||||||
|
ATF_TP_ADD_TC(tp, follow_dead_symlink);
|
||||||
|
ATF_TP_ADD_TC(tp, skip);
|
||||||
|
ATF_TP_ADD_TC(tp, clientptr_roundtrip);
|
||||||
|
ATF_TP_ADD_TC(tp, get_stream_backpointer);
|
||||||
|
|
||||||
|
return (atf_no_error());
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user