diff mbox series

[pseudo,v4] ftw, nftw, ftw64, nftw64: add tests

Message ID 20250504125951.3639531-1-skandigraun@gmail.com
State New
Headers show
Series [pseudo,v4] ftw, nftw, ftw64, nftw64: add tests | expand

Commit Message

Gyorgy Sarvari May 4, 2025, 12:59 p.m. UTC
Add tests for nftw, ftw, nftw64 and ftw64 calls.

The tests try to exercise the main functionality of the call(s): walk a filetree
with different configurations, where applicable: ftw/ftw64 don't take much config,
they just walk a tree.

nftw/nftw64 take behavior modifying flags, which are verified if they still work:
FTW_SKIP_SIBLINGS, FTW_SKIP_SUBTREE, FTW_CHDIR, FTW_DEPTH flags are verified.

The main idea is that each test is executed with glibc and with pseudo shim also.
During execution, different important details are saved into a textfile (encountered filepath,
gid/uid ownership details, and current working directory for FTW_CHDIR flag)

The output of glibc is considered to be correct, and the pseudo output is compared to that.

Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com>
---
v1: https://lists.yoctoproject.org/g/yocto-patches/message/1208 - only nftw call is tested, includes
    main wrapper also.

v2: https://lists.yoctoproject.org/g/yocto-patches/message/1299 - Added tests for ftw, ftw64 and nftw64 also

v3: https://lore.kernel.org/yocto-patches/1746302395-8723-4-git-send-email-mark.hatle@kernel.crashing.org/T/#u
    Reposted by Mark Hatle, no changes compared to v3

v4: Refactored tests, in an attempt to make them less reliant on the order of encountering the
    file entries. The main logic has also changed. In previous versions the expected output was
    hardcoded, whether it was executed with or without pseudo. In this version the output of non-pseudo
    version is considered to be an oracle, and the output of pseudo version is compared to this.



 Makefile.in           |   4 +-
 test/ftw-test-impl.c  |  93 +++++++++++++++++
 test/nftw-test-impl.c | 163 +++++++++++++++++++++++++++++
 test/test-ftw.c       |   4 +
 test/test-ftw64.c     |   4 +
 test/test-nftw.c      |   4 +
 test/test-nftw.sh     | 232 ++++++++++++++++++++++++++++++++++++++++++
 test/test-nftw64.c    |   4 +
 8 files changed, 506 insertions(+), 2 deletions(-)
 create mode 100644 test/ftw-test-impl.c
 create mode 100644 test/nftw-test-impl.c
 create mode 100644 test/test-ftw.c
 create mode 100644 test/test-ftw64.c
 create mode 100644 test/test-nftw.c
 create mode 100755 test/test-nftw.sh
 create mode 100644 test/test-nftw64.c
diff mbox series

Patch

diff --git a/Makefile.in b/Makefile.in
index 48fdbd2..27da831 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -55,7 +55,7 @@  GUTS=$(filter-out "$(GLOB_PATTERN)",$(wildcard $(GLOB_PATTERN)))
 
 SOURCES=$(wildcard *.c)
 OBJS=$(subst .c,.o,$(SOURCES))
-TESTS=$(patsubst %.c,%,$(wildcard test/*.c))
+TESTS=$(patsubst %.c,%,$(wildcard test/test-*.c))
 
 SHOBJS=pseudo_tables.o pseudo_util.o
 DBOBJS=pseudo_db.o
@@ -78,7 +78,7 @@  all: $(LIBPSEUDO) $(PSEUDO) $(PSEUDODB) $(PSEUDOLOG) $(PSEUDO_PROFILE)
 test: all $(TESTS) | $(BIN) $(LIB)
 	./run_tests.sh -v
 
-test/%: test/%.c
+test/test-%: test/test-%.c
 	$(CC) $(CFLAGS) $(CFLAGS_PSEUDO) -o $@ $<
 
 install-lib: $(LIBPSEUDO)
diff --git a/test/ftw-test-impl.c b/test/ftw-test-impl.c
new file mode 100644
index 0000000..b117bd8
--- /dev/null
+++ b/test/ftw-test-impl.c
@@ -0,0 +1,93 @@ 
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <ftw.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+static int current_recursion_level = 0;
+static int max_recursion = 0;
+
+static int print_filename = 0;
+static int print_ownership = 0;
+
+
+static int callback(const char* fpath, const struct FTW_STAT_STRUCT *sb, int __attribute__ ((unused)) typeflag){
+    if (current_recursion_level < max_recursion) {
+        ++current_recursion_level;
+        if (FTW_NAME("./walking/a1", callback, 10) != 0) {
+            printf("Recursive call failed\n");
+            exit(1);
+        }
+    }
+
+    if (print_filename) {
+        char *absolute_path = realpath(fpath, NULL);
+        printf("%s\n", absolute_path);
+        free(absolute_path);
+    }
+
+    if (print_ownership) {
+        printf("%d - %d\n", sb->st_gid, sb->st_uid);
+    }
+
+    return 0;
+}
+
+static int run_test() {
+    int ret;
+    ret = FTW_NAME("./walking", callback, 10);
+    return ret;
+}
+
+/*
+ * Walk the given directory structure, and print the given details
+ * (ownership or absolute path) of the entries encountered.
+ */
+static int test_walking(){
+    return run_test();
+}
+
+/*
+ * This test is very similar to test_walking(), but the callback at the
+ * start also calls ftw(), "max_recursion" times.
+ * It is trying to test pseudo's implementation of handling multiple
+ * concurrent (n)ftw calls in the same thread.
+ */
+static int test_walking_recursion(){
+    max_recursion = 3;
+    return run_test();
+}
+
+/*
+ * Arguments:
+ * argv[1]: the test name
+ * argv[2]: "filename" or "ownership" - determine which detail to print
+ */
+int main(int argc, char* argv[])
+{
+    if (argc != 3) {
+        printf("Usage: %s TESTNAME DETAILS\n", argv[0]);
+        printf("TESTNAME is one of: no_recursion, recursion\n");
+        printf("DETAILS is one of: filename, ownership\n");
+        return 1;
+    }
+
+    if (strcmp(argv[2], "filename") == 0) {
+        print_filename = 1;
+    } else if (strcmp(argv[2], "ownership") == 0) {
+        print_ownership = 1;
+    } else {
+        printf("Unknown second parameter");
+        return 1;
+    }
+    
+    if (strcmp(argv[1], "no_recursion") == 0) {
+        return test_walking();
+    } else if (strcmp(argv[1], "recursion") == 0) {
+        return test_walking_recursion();
+    } else {
+        printf("Unknown test name: %s\n", argv[1]);
+        return 1;
+    }
+}
diff --git a/test/nftw-test-impl.c b/test/nftw-test-impl.c
new file mode 100644
index 0000000..714da76
--- /dev/null
+++ b/test/nftw-test-impl.c
@@ -0,0 +1,163 @@ 
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <ftw.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#define TEST_CHDIR 1
+#define TEST_NO_CHDIR 0
+
+static int print_filename = 0;
+static int print_ownership = 0;
+static int print_cwd = 0;
+
+static int skip_subtree = 0;
+static int skip_subtree_counter = 0;
+
+static int skip_siblings = 0;
+char* basedir;
+
+
+// When FTW_CHDIR is enabled, cwd changes each time, so realpath
+// might not be able to resolve the path
+//char* filepath = realpath(fpath, NULL);
+char* get_absolute_path(const char* fpath) {
+    char* filepath = realpath(fpath, NULL);
+
+    if (filepath == NULL) {
+        if (fpath[0] == '/') {
+            filepath = malloc(strlen(fpath) + 1);
+            strcpy(filepath, fpath);
+        } else {
+            filepath = malloc(strlen(fpath) + strlen(basedir));
+            memset(filepath, 0, strlen(fpath) + strlen(basedir));
+            strcat(filepath, basedir);
+            // skip the initial "."
+            strcat(filepath, fpath + 1);
+        }
+    }
+    return filepath;
+}
+
+static int callback(const char* fpath, const struct NFTW_STAT_STRUCT *sb, int typeflag, struct FTW *ftwbuf){
+    if (print_filename) {
+        char* filepath = get_absolute_path(fpath);
+        printf("%s\n", filepath);
+        free(filepath);
+    }
+
+
+    if (print_ownership) {
+        printf("%d - %d\n", sb->st_gid, sb->st_uid);
+    }
+
+    if (print_cwd) {
+        char* cwd = getcwd(NULL, 0);
+        printf("%s\n", cwd);
+        free(cwd);
+    }
+
+
+    // if subtrees are skipped, don't skip immediately at the root,
+    // otherwise it's not much of a test. Skip it after encountering 2
+    // subfolders (which is an arbitrary numbers, without much science,
+    // admittedly, but better than 0)
+    if (skip_subtree && typeflag == FTW_D) {
+        if (skip_subtree_counter >= 2)
+            return FTW_SKIP_SUBTREE;
+
+        skip_subtree_counter++;
+    }
+
+    if (skip_siblings && typeflag == FTW_F) {
+        return FTW_SKIP_SIBLINGS;
+    }
+
+    return 0;
+}
+
+static int run_test(int flags) {
+    return NFTW_NAME("./walking", callback, 10, flags);
+}
+
+static int test_skip_siblings_file_depth_walking(int change_dir){
+    int flags = FTW_ACTIONRETVAL | FTW_DEPTH;
+
+    // store base_dir, because the fpath returned by (n)ftw can be relative to this
+    // folder - that way a full absolute path can be constructed and compared,
+    // if needed.
+    if (change_dir){
+        basedir = getcwd(NULL, 0);
+        flags |= FTW_CHDIR;
+    }
+
+    skip_siblings = 1;
+    return run_test(flags);
+}
+
+/*
+ * Every time a folder entry is sent to the callback, respond with FTW_SKIP_SUBTREE.
+ * This should skip that particular folder completely, and continue processing
+ * with its siblings (or parent, if there are no siblings).
+ * Return value is expected to be 0, default walking order.
+ */
+static int test_skip_subtree_on_folder(){
+    int flags = FTW_ACTIONRETVAL;
+    skip_subtree = 1;
+    return run_test(flags);
+}
+
+/*
+ * Arguments:
+ * argv[1]: always the test name
+ * argv[2], argv[3]: in case the test name refers to a test without using
+ *                   pseudo (no_pseudo), then they should be the gid and uid
+ *                   of the current user. Otherwise these arguments are ignored.
+ *
+ * skip_subtree_pseudo/skip_subtree_no_pseudo: these tests are calling nftw()
+ * with the FTW_ACTIONRETVAL flag, which reacts based on the return value from the
+ * callback. These tests check the call's reaction to FTW_SKIP_SUBTREE call,
+ * upon which nftw() should stop processing the current folder, and continue
+ * with the next sibling of the folder.
+ *
+ * skip_siblings_pseudo/skip_siblings_no_pseudo: very similar to skip_subtree
+ * tests, but it verified FTW_SKIP_SIBLINGS response, which should stop processing
+ * the current folder, and continue in its parent.
+ *
+ * skip_siblings_chdir_pseudo/skip_siblings_chdir_no_pseudoL same as skip_siblings
+ * tests, but also pass the FTW_CHDIR flag and verify that the working directory
+ * is changed as expected between callback calls.
+ */
+int main(int argc, char* argv[])
+{
+    if (argc != 3) {
+        printf("Usage: %s TESTNAME DETAILS\n", argv[0]);
+        printf("TESTNAME is one of: skip_subtree, skip_siblings, skip_siblings_chdir\n");
+        printf("DETAILS is one of: filename, ownership, cwd\n");
+        return 1;
+    }
+
+    if (strcmp(argv[2], "filename") == 0) {
+        print_filename = 1;
+    } else if (strcmp(argv[2], "ownership") == 0) {
+        print_ownership = 1;
+    } else if (strcmp(argv[2], "cwd") == 0) {
+        print_cwd = 1;
+    } else {
+        printf("Unknown second parameter\n");
+        return 1;
+    }
+
+
+    if (strcmp(argv[1], "skip_subtree") == 0) {
+        return test_skip_subtree_on_folder();
+    } else if (strcmp(argv[1], "skip_siblings") == 0) {
+        return test_skip_siblings_file_depth_walking(TEST_NO_CHDIR);
+    } else if (strcmp(argv[1], "skip_siblings_chdir") == 0) {
+        return test_skip_siblings_file_depth_walking(TEST_CHDIR);
+    } else {
+        printf("Unknown test name\n");
+        return 1;
+    }
+}
diff --git a/test/test-ftw.c b/test/test-ftw.c
new file mode 100644
index 0000000..5c47dd9
--- /dev/null
+++ b/test/test-ftw.c
@@ -0,0 +1,4 @@ 
+#define FTW_NAME ftw
+#define FTW_STAT_STRUCT stat
+
+#include "ftw-test-impl.c"
diff --git a/test/test-ftw64.c b/test/test-ftw64.c
new file mode 100644
index 0000000..0b8f906
--- /dev/null
+++ b/test/test-ftw64.c
@@ -0,0 +1,4 @@ 
+#define FTW_NAME ftw64
+#define FTW_STAT_STRUCT stat64
+
+#include "ftw-test-impl.c"
diff --git a/test/test-nftw.c b/test/test-nftw.c
new file mode 100644
index 0000000..ecadc1e
--- /dev/null
+++ b/test/test-nftw.c
@@ -0,0 +1,4 @@ 
+#define NFTW_NAME nftw
+#define NFTW_STAT_STRUCT stat
+
+#include "nftw-test-impl.c"
diff --git a/test/test-nftw.sh b/test/test-nftw.sh
new file mode 100755
index 0000000..fb4d78a
--- /dev/null
+++ b/test/test-nftw.sh
@@ -0,0 +1,232 @@ 
+#!/bin/bash
+#
+# Test nftw call and its behavior modifying flags
+#
+# The ./test/test-(n)ftw binaries print out different details of the filentries
+# they encounter during walking the test directory. These details are saved, both using
+# glibc (pseudo disabled), and with using the pseudo shim. glibc output is used
+# as the oracle, and it is expected that the two outputs are always the same - except
+# for ownership details. When it comes to file ownership, pseudo shim is expected to
+# return root owner, while glibc should return the current user.
+#
+# The verify_results() function compares the saved details, with- and without- pseudo:
+# filename, ownership and current_working_directory. If they don't match, the test fails.
+#
+# Below there are a number of tests executed, separated visually, they save different details
+# of the same testcases with and without pseudo, before calling the verification function.
+#
+# SPDX-License-Identifier: LGPL-2.1-only
+#
+
+trap "rm -rf ./walking ./with_pseudo* ./without_pseudo*" 0
+
+ret=0
+
+verify_results(){
+  current_user_uid=`env -i id -u`
+  current_user_gid=`env -i id -g`
+
+  # the list of files should match with and without pseudo
+  diff -Naur ./with_pseudo_filename ./without_pseudo_filename > /dev/null
+  file_list_result=$?
+
+  # in case there is a cwd file (recording the current working dir for each entry),
+  # than they should match also
+  cwd_list_result=0
+  if [ -f ./with_pseudo_cwd ]; then
+    diff -Naur ./with_pseudo_cwd ./without_pseudo_cwd > /dev/null
+    cwd_list_result=$?
+  fi
+
+  # ownership file's content is "$GID - $UID", per line
+  # when using pseudo, both GID and UID should be 0
+  incorrect_pseudo_ownership_count=`grep -vc "0 - 0" with_pseudo_ownership || true`
+
+  # when not using pseudo, the GID and UID should match the
+  # current user's IDs
+  incorrect_no_pseudo_ownership_count=`grep -vc "$current_user_gid - $current_user_uid" without_pseudo_ownership || true`
+
+  if [ $file_list_result -ne 0 ]; then
+    echo test-nftw: $1: Failed, invalid file list
+    ret=1
+  elif [ $incorrect_pseudo_ownership_count -ne 0 ]; then
+    echo test-nftw: $1: Failed, incorrect pseudo ownership details
+    ret=1
+  elif [ $incorrect_no_pseudo_ownership_count -ne 0 ]; then
+    echo test-nftw: $1: Failed, incorrect no pseudo ownership details
+    ret=1
+  elif [ $cwd_list_result -ne 0 ]; then
+    echo test-nftw: $1: Failed, incorrect current working directory
+    ret = 1
+  else
+    echo test-nftw: $1: Passed
+  fi  
+}
+
+
+
+mkdir -p walking/a1/b1/c1
+touch walking/a1/b1/c1/file
+mkdir walking/a1/b2
+mkdir walking/a1/b3
+touch walking/a1/b1/c1/file2
+touch walking/a1/b1/c1/file3
+touch walking/a1/b2/file4
+touch walking/a1/b2/file5
+
+
+
+
+# Test for ftw(), walk tree without recursion
+unset PSEUDO_DISABLED
+./test/test-ftw no_recursion filename > with_pseudo_filename
+./test/test-ftw no_recursion ownership > with_pseudo_ownership
+
+export PSEUDO_DISABLED=1
+./test/test-ftw no_recursion filename > without_pseudo_filename
+./test/test-ftw no_recursion ownership > without_pseudo_ownership
+
+verify_results "ftw no recursion"
+
+
+
+
+
+
+
+# Test for ftw64(), walk tree without recursion
+unset PSEUDO_DISABLED
+./test/test-ftw64 no_recursion filename > with_pseudo_filename
+./test/test-ftw64 no_recursion ownership > with_pseudo_ownership
+
+export PSEUDO_DISABLED=1
+./test/test-ftw64 no_recursion filename > without_pseudo_filename
+./test/test-ftw64 no_recursion ownership > without_pseudo_ownership
+
+verify_results "ftw64 no recursion"
+
+
+
+
+
+
+
+# Test for ftw(), walk tree while calling ftw() again recursively, from
+# within an ftw() callback itself
+unset PSEUDO_DISABLED
+./test/test-ftw recursion filename > with_pseudo_filename
+./test/test-ftw recursion ownership > with_pseudo_ownership
+
+export PSEUDO_DISABLED=1
+./test/test-ftw recursion filename > without_pseudo_filename
+./test/test-ftw recursion ownership > without_pseudo_ownership
+
+verify_results "ftw recursion"
+
+
+
+
+
+
+# Test for ftw64(), walk tree while calling ftw64() again recursively, from
+# within an ftw64() callback itself
+unset PSEUDO_DISABLED
+./test/test-ftw64 recursion filename > with_pseudo_filename
+./test/test-ftw64 recursion ownership > with_pseudo_ownership
+
+export PSEUDO_DISABLED=1
+./test/test-ftw64 recursion filename > without_pseudo_filename
+./test/test-ftw64 recursion ownership > without_pseudo_ownership
+
+verify_results "ftw64 recursion"
+
+
+
+
+
+# Test for nftw(), walk tree, but return SKIP_SUBTREE response each
+# time a directory entry is received by the callback.
+unset PSEUDO_DISABLED
+./test/test-nftw skip_subtree filename > with_pseudo_filename
+./test/test-nftw skip_subtree ownership > with_pseudo_ownership
+
+export PSEUDO_DISABLED=1
+./test/test-nftw skip_subtree filename > without_pseudo_filename
+./test/test-nftw skip_subtree ownership > without_pseudo_ownership
+
+verify_results "nftw skip_subtree"
+
+
+
+
+
+# Test for nftw64(), walk tree, but return SKIP_SUBTREE response each
+# time a directory entry is received by the callback.
+unset PSEUDO_DISABLED
+./test/test-nftw64 skip_subtree filename > with_pseudo_filename
+./test/test-nftw64 skip_subtree ownership > with_pseudo_ownership
+
+export PSEUDO_DISABLED=1
+./test/test-nftw64 skip_subtree filename > without_pseudo_filename
+./test/test-nftw64 skip_subtree ownership > without_pseudo_ownership
+
+verify_results "nftw64 skip_subtree"
+
+
+
+# Test for nftw(), walk tree, but return SKIP_SIBLINGS response each
+# time a file entry is received by the callback
+unset PSEUDO_DISABLED
+./test/test-nftw skip_siblings filename > with_pseudo_filename
+./test/test-nftw skip_siblings ownership > with_pseudo_ownership
+
+#export PSEUDO_DISABLED=1
+./test/test-nftw skip_siblings filename > without_pseudo_filename
+./test/test-nftw skip_siblings ownership > without_pseudo_ownership
+
+verify_results "nftw skip_siblings"
+
+
+
+
+
+
+# Test for nftw64(), walk tree, but return SKIP_SIBLINGS response each
+# time a file entry is received by the callback
+unset PSEUDO_DISABLED
+./test/test-nftw64 skip_siblings filename > with_pseudo_filename
+./test/test-nftw64 skip_siblings ownership > with_pseudo_ownership
+
+export PSEUDO_DISABLED=1
+./test/test-nftw64 skip_siblings filename > without_pseudo_filename
+./test/test-nftw64 skip_siblings ownership > without_pseudo_ownership
+
+verify_results "nftw64 skip_siblings"
+
+
+
+
+
+
+
+# Test for nftw(), walk tree, but return SKIP_SIBLINGS response each
+# time a file entry is received by the callback, and also set the
+# FTW_CHDIR flag on the call to switch to the corresponding folder
+
+unset PSEUDO_DISABLED
+./test/test-nftw skip_siblings_chdir filename > with_pseudo_filename
+./test/test-nftw skip_siblings_chdir ownership > with_pseudo_ownership
+./test/test-nftw skip_siblings_chdir cwd > with_pseudo_cwd
+
+
+
+export PSEUDO_DISABLED=1
+./test/test-nftw skip_siblings_chdir filename > without_pseudo_filename
+./test/test-nftw skip_siblings_chdir ownership > without_pseudo_ownership
+./test/test-nftw skip_siblings_chdir cwd > without_pseudo_cwd
+
+verify_results "nftw skip_siblings_chdir"
+
+
+
+exit $ret
diff --git a/test/test-nftw64.c b/test/test-nftw64.c
new file mode 100644
index 0000000..20f25af
--- /dev/null
+++ b/test/test-nftw64.c
@@ -0,0 +1,4 @@ 
+#define NFTW_NAME nftw64
+#define NFTW_STAT_STRUCT stat64
+
+#include "nftw-test-impl.c"