diff options
| author | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:28:16 +0300 |
|---|---|---|
| committer | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:28:16 +0300 |
| commit | 155dc8e70a7bf008c3cb1a2b7d968373da7aeed8 (patch) | |
| tree | 30b09216ab9e0a38a559e24f1de3f0ed0db609f0 /corebinutils/pwd/tests | |
| parent | 681e61b5ab23760db91aea6b30cd38ef3526e308 (diff) | |
| parent | e19ed3fe0f247b74692c23b07d02ee3779ab5fc9 (diff) | |
| download | Project-Tick-155dc8e70a7bf008c3cb1a2b7d968373da7aeed8.tar.gz Project-Tick-155dc8e70a7bf008c3cb1a2b7d968373da7aeed8.zip | |
Add 'corebinutils/pwd/' from commit 'e19ed3fe0f247b74692c23b07d02ee3779ab5fc9'
git-subtree-dir: corebinutils/pwd
git-subtree-mainline: 681e61b5ab23760db91aea6b30cd38ef3526e308
git-subtree-split: e19ed3fe0f247b74692c23b07d02ee3779ab5fc9
Diffstat (limited to 'corebinutils/pwd/tests')
| -rw-r--r-- | corebinutils/pwd/tests/deep_helper.c | 105 | ||||
| -rw-r--r-- | corebinutils/pwd/tests/test.sh | 466 |
2 files changed, 571 insertions, 0 deletions
diff --git a/corebinutils/pwd/tests/deep_helper.c b/corebinutils/pwd/tests/deep_helper.c new file mode 100644 index 0000000000..d413cf4750 --- /dev/null +++ b/corebinutils/pwd/tests/deep_helper.c @@ -0,0 +1,105 @@ +/* + * deep_helper.c – test helper for pwd's deep-path test + * + * Usage: deep_helper <depth> <segment> <pwd_binary> + * + * Creates `depth` nested directories each named `segment`, navigates + * into the deepest one using chdir(2) (not the shell's cd builtin), + * then exec(2)s `pwd_binary` as a child process. The output of + * pwd_binary goes to stdout. + * + * Exit codes: + * 0 – pwd_binary exited 0 + * 1 – setup failure (mkdir/chdir/exec) + * N – whatever pwd_binary exited with + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#define _GNU_SOURCE 1 + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +int +main(int argc, char *argv[]) +{ + long depth; + char *end; + const char *segment; + const char *pwd_bin; + int i, status; + pid_t child; + + if (argc != 4) { + fprintf(stderr, "usage: deep_helper <depth> <segment> <pwd_binary>\n"); + return 1; + } + + errno = 0; + depth = strtol(argv[1], &end, 10); + if (end == argv[1] || *end != '\0' || errno != 0 || depth < 0) { + fprintf(stderr, "deep_helper: invalid depth: %s\n", argv[1]); + return 1; + } + segment = argv[2]; + pwd_bin = argv[3]; + + for (i = 0; i < (int)depth; i++) { + if (mkdir(segment, 0755) != 0 && errno != EEXIST) { + fprintf(stderr, "deep_helper: mkdir(%s) at depth %d: %s\n", + segment, i, strerror(errno)); + return 1; + } + if (chdir(segment) != 0) { + /* + * chdir(2) with a single short component should not fail + * ENAMETOOLONG on Linux even when the cumulative path + * exceeds PATH_MAX. If it does, report it clearly. + */ + fprintf(stderr, "deep_helper: chdir(%s) at depth %d: %s\n", + segment, i, strerror(errno)); + return 1; + } + } + + /* Unset PWD so pwd(1) falls back to getcwd(NULL, 0). */ + if (unsetenv("PWD") != 0) { + fprintf(stderr, "deep_helper: unsetenv(PWD): %s\n", + strerror(errno)); + return 1; + } + + /* Fork so we can capture the exit status of pwd_bin. */ + child = fork(); + if (child == -1) { + fprintf(stderr, "deep_helper: fork: %s\n", strerror(errno)); + return 1; + } + if (child == 0) { + /* Child: exec pwd_bin with no arguments (default -L) */ + char *args[] = { (char *)pwd_bin, "-P", NULL }; + execv(pwd_bin, args); + fprintf(stderr, "deep_helper: execv(%s): %s\n", + pwd_bin, strerror(errno)); + _exit(1); + } + + /* Parent: wait for child. */ + if (waitpid(child, &status, 0) == -1) { + fprintf(stderr, "deep_helper: waitpid: %s\n", strerror(errno)); + return 1; + } + if (WIFEXITED(status)) + return WEXITSTATUS(status); + + fprintf(stderr, "deep_helper: pwd_bin killed by signal %d\n", + WTERMSIG(status)); + return 1; +} diff --git a/corebinutils/pwd/tests/test.sh b/corebinutils/pwd/tests/test.sh new file mode 100644 index 0000000000..28114c1339 --- /dev/null +++ b/corebinutils/pwd/tests/test.sh @@ -0,0 +1,466 @@ +#!/bin/sh +# +# tests/test.sh — pwd(1) Linux port test suite +# +# Environment: +# PWD_BIN path to the pwd binary under test +# LC_ALL must be C (set below) +# +# Contract per test: +# Every test asserts exit status + stdout + stderr. +# "It ran" alone is never sufficient. +# +# Robustness: +# - Works inside containers and PID namespaces +# - No assumption about the runner's $PWD value +# - TZ and LC_ALL forced to avoid locale/timezone noise +# - TMPDIR-based isolation; cleaned up on exit + +set -eu + +export LC_ALL=C +export TZ=UTC + +ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd -P) +PWD_BIN="${PWD_BIN:-$ROOT/out/pwd}" + +TMPDIR="${TMPDIR:-/tmp}" +WORKDIR=$(mktemp -d "$TMPDIR/pwd-test.XXXXXX") +STDOUT_FILE="$WORKDIR/stdout" +STDERR_FILE="$WORKDIR/stderr" +trap 'rm -rf "$WORKDIR"' EXIT INT TERM + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +fail() { + printf 'FAIL: %s\n' "$1" >&2 + exit 1 +} + +pass() { + printf 'PASS: %s\n' "$1" +} + +# run_capture cmd [args...] +# Captures stdout → STDOUT_FILE, stderr → STDERR_FILE, status → LAST_STATUS. +# Never aborts due to nonzero exit before capture. +run_capture() { + if "$@" >"$STDOUT_FILE" 2>"$STDERR_FILE"; then + LAST_STATUS=0 + else + LAST_STATUS=$? + fi + LAST_STDOUT=$(cat "$STDOUT_FILE") + LAST_STDERR=$(cat "$STDERR_FILE") +} + +assert_status() { + name=$1 + expected=$2 + actual=$3 + if [ "$expected" -ne "$actual" ]; then + printf 'FAIL: %s\n' "$name" >&2 + printf ' expected status: %d\n' "$expected" >&2 + printf ' actual status: %d\n' "$actual" >&2 + exit 1 + fi +} + +assert_eq() { + name=$1 + expected=$2 + actual=$3 + if [ "$expected" != "$actual" ]; then + printf 'FAIL: %s\n' "$name" >&2 + printf ' expected: %s\n' "$expected" >&2 + printf ' actual: %s\n' "$actual" >&2 + exit 1 + fi +} + +assert_empty() { + name=$1 + text=$2 + if [ -n "$text" ]; then + printf 'FAIL: %s (expected empty, got: %s)\n' "$name" "$text" >&2 + exit 1 + fi +} + +assert_contains() { + name=$1 + text=$2 + pattern=$3 + case $text in + *"$pattern"*) ;; + *) fail "$name: expected '$pattern' in '$text'" ;; + esac +} + +# --------------------------------------------------------------------------- +# Prerequisite +# --------------------------------------------------------------------------- + +[ -x "$PWD_BIN" ] || fail "missing or non-executable binary: $PWD_BIN" + +# --------------------------------------------------------------------------- +# 1. Basic invocation — no flags +# --------------------------------------------------------------------------- + +( + cd "$WORKDIR" + run_capture "$PWD_BIN" + assert_status "basic no-flag status" 0 "$LAST_STATUS" + assert_eq "basic no-flag stdout" "$WORKDIR" "$LAST_STDOUT" + assert_empty "basic no-flag stderr" "$LAST_STDERR" +) +pass "basic no-flag" + +# --------------------------------------------------------------------------- +# 2. Explicit -L (logical, $PWD matches physical — simplest case) +# --------------------------------------------------------------------------- + +( + cd "$WORKDIR" + run_capture "$PWD_BIN" -L + assert_status "explicit -L status" 0 "$LAST_STATUS" + assert_eq "explicit -L stdout" "$WORKDIR" "$LAST_STDOUT" + assert_empty "explicit -L stderr" "$LAST_STDERR" +) +pass "explicit -L" + +# --------------------------------------------------------------------------- +# 3. Explicit -P (physical, no symlinks → same as logical in WORKDIR) +# --------------------------------------------------------------------------- + +( + cd "$WORKDIR" + run_capture "$PWD_BIN" -P + assert_status "explicit -P status" 0 "$LAST_STATUS" + assert_eq "explicit -P stdout" "$WORKDIR" "$LAST_STDOUT" + assert_empty "explicit -P stderr" "$LAST_STDERR" +) +pass "explicit -P" + +# --------------------------------------------------------------------------- +# 4. Symlink: -L shows logical path, -P shows physical path +# --------------------------------------------------------------------------- + +PHYBASE="$WORKDIR/phy" +LOGBASE="$WORKDIR/log" +PHYDIR="$PHYBASE/sub" +mkdir -p "$PHYDIR" +ln -s "$PHYBASE" "$LOGBASE" + +( + cd "$LOGBASE/sub" + export PWD="$LOGBASE/sub" + run_capture "$PWD_BIN" -L + assert_status "symlink -L status" 0 "$LAST_STATUS" + assert_eq "symlink -L stdout" "$LOGBASE/sub" "$LAST_STDOUT" + assert_empty "symlink -L stderr" "$LAST_STDERR" +) +pass "symlink -L" + +( + cd "$LOGBASE/sub" + export PWD="$LOGBASE/sub" + run_capture "$PWD_BIN" -P + assert_status "symlink -P status" 0 "$LAST_STATUS" + assert_eq "symlink -P stdout" "$PHYDIR" "$LAST_STDOUT" + assert_empty "symlink -P stderr" "$LAST_STDERR" +) +pass "symlink -P" + +# --------------------------------------------------------------------------- +# 5. Last flag wins: -L -P should behave as -P +# --------------------------------------------------------------------------- + +( + cd "$LOGBASE/sub" + export PWD="$LOGBASE/sub" + run_capture "$PWD_BIN" -L -P + assert_status "last-flag -L -P status" 0 "$LAST_STATUS" + assert_eq "last-flag -L -P stdout" "$PHYDIR" "$LAST_STDOUT" + assert_empty "last-flag -L -P stderr" "$LAST_STDERR" +) +pass "last-flag -L -P is physical" + +( + cd "$LOGBASE/sub" + export PWD="$LOGBASE/sub" + run_capture "$PWD_BIN" -P -L + assert_status "last-flag -P -L status" 0 "$LAST_STATUS" + assert_eq "last-flag -P -L stdout" "$LOGBASE/sub" "$LAST_STDOUT" + assert_empty "last-flag -P -L stderr" "$LAST_STDERR" +) +pass "last-flag -P -L is logical" + +# --------------------------------------------------------------------------- +# 6. Relative $PWD → logical fallback to physical +# --------------------------------------------------------------------------- + +( + cd "$LOGBASE/sub" + export PWD="log/sub" # relative — must be rejected + run_capture "$PWD_BIN" -L + assert_status "relative PWD -L status" 0 "$LAST_STATUS" + # Must fall back to physical + assert_eq "relative PWD -L stdout" "$PHYDIR" "$LAST_STDOUT" + assert_empty "relative PWD -L stderr" "$LAST_STDERR" +) +pass "relative PWD fallback to physical" + +# --------------------------------------------------------------------------- +# 7. $PWD contains /./ → must reject it (POSIX 4.13) +# --------------------------------------------------------------------------- + +( + cd "$PHYDIR" + export PWD="$PHYBASE/./sub" + run_capture "$PWD_BIN" -L + assert_status "dot-component PWD -L status" 0 "$LAST_STATUS" + assert_eq "dot-component PWD -L stdout" "$PHYDIR" "$LAST_STDOUT" + assert_empty "dot-component PWD -L stderr" "$LAST_STDERR" +) +pass "PWD with /./ rejected, fallback to physical" + +# --------------------------------------------------------------------------- +# 8. $PWD contains /../ → must reject it +# --------------------------------------------------------------------------- + +( + cd "$PHYDIR" + export PWD="$PHYBASE/../phy/sub" + run_capture "$PWD_BIN" -L + assert_status "dotdot-component PWD -L status" 0 "$LAST_STATUS" + assert_eq "dotdot-component PWD -L stdout" "$PHYDIR" "$LAST_STDOUT" + assert_empty "dotdot-component PWD -L stderr" "$LAST_STDERR" +) +pass "PWD with /../ rejected, fallback to physical" + +# --------------------------------------------------------------------------- +# 9. $PWD points to wrong inode → fallback to physical +# --------------------------------------------------------------------------- + +( + cd "$PHYDIR" + export PWD="$PHYBASE" # $PWD is a parent dir, not the same inode + run_capture "$PWD_BIN" -L + assert_status "wrong-inode PWD -L status" 0 "$LAST_STATUS" + assert_eq "wrong-inode PWD -L stdout" "$PHYDIR" "$LAST_STDOUT" + assert_empty "wrong-inode PWD -L stderr" "$LAST_STDERR" +) +pass "PWD inode mismatch fallback to physical" + +# --------------------------------------------------------------------------- +# 10. $PWD does not exist → fallback to physical +# --------------------------------------------------------------------------- + +( + cd "$PHYDIR" + export PWD="$WORKDIR/does-not-exist" + run_capture "$PWD_BIN" -L + assert_status "nonexistent PWD -L status" 0 "$LAST_STATUS" + assert_eq "nonexistent PWD -L stdout" "$PHYDIR" "$LAST_STDOUT" + assert_empty "nonexistent PWD -L stderr" "$LAST_STDERR" +) +pass "nonexistent PWD fallback to physical" + +# --------------------------------------------------------------------------- +# 11. Unset $PWD → logical falls back to physical +# --------------------------------------------------------------------------- + +( + cd "$PHYDIR" + unset PWD + run_capture "$PWD_BIN" -L + assert_status "unset PWD -L status" 0 "$LAST_STATUS" + assert_eq "unset PWD -L stdout" "$PHYDIR" "$LAST_STDOUT" + assert_empty "unset PWD -L stderr" "$LAST_STDERR" +) +pass "unset PWD fallback to physical" + +# --------------------------------------------------------------------------- +# 12. Bad flag → usage written to stderr, exit 1 +# --------------------------------------------------------------------------- + +( + cd "$WORKDIR" + run_capture "$PWD_BIN" -x + assert_status "bad-flag status" 1 "$LAST_STATUS" + assert_empty "bad-flag stdout" "$LAST_STDOUT" + assert_contains "bad-flag stderr has usage" "$LAST_STDERR" "usage:" + assert_contains "bad-flag stderr has -L" "$LAST_STDERR" "-L" + assert_contains "bad-flag stderr has -P" "$LAST_STDERR" "-P" +) +pass "bad flag exits 1 with usage on stderr" + +# --------------------------------------------------------------------------- +# 13. Extra operand → usage on stderr, exit 1 +# --------------------------------------------------------------------------- + +( + cd "$WORKDIR" + run_capture "$PWD_BIN" extra-arg + assert_status "extra-arg status" 1 "$LAST_STATUS" + assert_empty "extra-arg stdout" "$LAST_STDOUT" + assert_contains "extra-arg stderr has usage" "$LAST_STDERR" "usage:" +) +pass "extra operand exits 1 with usage" + +# --------------------------------------------------------------------------- +# 14. stdout failure → exit non-zero, error on stderr +# --------------------------------------------------------------------------- +# Pipe into a program that exits immediately, causing SIGPIPE / EPIPE. +# We explicitly ignore PIPE so the subshell stays alive long enough to +# capture the exit status from $pwd. +# +# This test is done in a subshell; the outer set -e is irrelevant inside it. + +( + RESULT_FILE="$WORKDIR/pipe-result" + ERR_FILE="$WORKDIR/pipe-err" + # We run a pipeline in a subshell that traps PIPE so pwd can finish writing. + # - The left side: sleep 0 && "$PWD_BIN" (so shell sets up pipe before exec) + # - The right side: true immediately exits → EPIPE on next write + sh -c ' + trap "" PIPE + sleep 0 + "$1" 2>"$2" + echo $? >"$3" + ' -- "$PWD_BIN" "$ERR_FILE" "$RESULT_FILE" | true || true + + # Give the subshell a moment to finish writing result file + # (In practice this is instantaneous on any real system) + i=0 + while [ $i -lt 20 ] && [ ! -s "$RESULT_FILE" ]; do + sleep 0.1 + i=$((i + 1)) + done + + if [ ! -s "$RESULT_FILE" ]; then + # PIPE delivery timing is platform-dependent; skip rather than + # produce a false negative in constrained environments. + printf 'SKIP: stdout-failure test (PIPE timing uncertainty)\n' >&2 + else + result=$(cat "$RESULT_FILE") + err=$(cat "$ERR_FILE" 2>/dev/null || true) + if [ "$result" -eq 0 ]; then + # Some musl/kernel combinations silently succeed when output + # is consumed before close. Accept skip here as well. + printf 'SKIP: stdout-failure test (PIPE not delivered)\n' >&2 + else + assert_contains "stdout-failure stderr" "$err" "stdout" + pass "stdout failure exits non-zero with stderr message" + fi + fi +) + +# --------------------------------------------------------------------------- +# 15. Deep path — no static PATH_MAX limit +# --------------------------------------------------------------------------- +# +# Verify that pwd -P handles deep directory trees correctly via the dynamic +# getcwd(NULL, 0) interface rather than a fixed-size stack buffer. +# +# We use a C helper (deep_helper) that calls chdir(2) directly, bypassing +# the bash/dash shell builtin cd which enforces PATH_MAX even for relative +# single-component changes. Only the C layer's getcwd(3) call inside pwd +# is asked to assemble the full path. +# +# The helper creates 90 levels × 50-char segment = 4500+ bytes of path +# (plus the WORKDIR prefix), then exec(2)s "$PWD_BIN" -P from inside. + +DEEP_HELPER_BIN="${DEEP_HELPER_BIN:-}" +SEGMENT="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # 50 chars + +if [ -z "$DEEP_HELPER_BIN" ] || [ ! -x "$DEEP_HELPER_BIN" ]; then + printf 'SKIP: deep-path test (DEEP_HELPER_BIN not set or not executable)\n' >&2 +else + DEEPBASE="$WORKDIR/deep" + mkdir -p "$DEEPBASE" + # run_capture is called inside a subshell (cd changes dir), so + # LAST_* variables won't propagate. Instead we rely on $STDOUT_FILE + # and $STDERR_FILE (persistent files in $WORKDIR) and the subshell + # exit status to carry the result back. + set +e + (cd "$DEEPBASE" && "$DEEP_HELPER_BIN" 70 "$SEGMENT" "$PWD_BIN" \ + >"$STDOUT_FILE" 2>"$STDERR_FILE") + deep_exit=$? + set -e + deep_stdout=$(cat "$STDOUT_FILE") + deep_stderr=$(cat "$STDERR_FILE") + + if [ "$deep_exit" -ne 0 ]; then + fail "deep-path: helper/pwd exited $deep_exit; stderr: $deep_stderr" + fi + + case "$deep_stdout" in + /*) + ;; + *) + fail "deep-path: output is not absolute: $deep_stdout" + ;; + esac + + deeplength=$(printf '%s' "$deep_stdout" | wc -c) + if [ "$deeplength" -lt 3500 ]; then + fail "deep-path: path not long enough to test getcwd(NULL,0) ($deeplength < 3500)" + fi + + assert_empty "deep-path stderr" "$deep_stderr" + pass "deep path (${deeplength} bytes) with -P via getcwd(NULL,0)" +fi + +# --------------------------------------------------------------------------- +# 16. Path output ends with newline (no trailing garbage) +# --------------------------------------------------------------------------- + +( + cd "$WORKDIR" + run_capture "$PWD_BIN" + # Ensure the captured stdout is exactly WORKDIR (shell strips trailing newlines + # from $(...), but our assert_eq checks LAST_STDOUT which is set via $(...), + # so both sides will have trailing newlines stripped identically). + assert_eq "newline-terminated stdout" "$WORKDIR" "$LAST_STDOUT" + assert_empty "newline-terminated stderr" "$LAST_STDERR" +) +pass "output is newline-terminated" + +# --------------------------------------------------------------------------- +# 17. Symlink traversal depth: $PWD via multiple layers +# --------------------------------------------------------------------------- + +LAYERED1="$WORKDIR/layer1" +LAYERED2="$WORKDIR/layer2" +LAYERED_PHY="$WORKDIR/layerphy/deep" +mkdir -p "$LAYERED_PHY" +ln -s "$LAYERED_PHY" "$LAYERED1" +ln -s "$LAYERED1" "$LAYERED2" # layer2 → layer1 → layerphy/deep + +( + cd "$LAYERED2" + export PWD="$LAYERED2" + run_capture "$PWD_BIN" -L + assert_status "multilayer -L status" 0 "$LAST_STATUS" + assert_eq "multilayer -L stdout" "$LAYERED2" "$LAST_STDOUT" + assert_empty "multilayer -L stderr" "$LAST_STDERR" +) +pass "multilayer symlink -L" + +( + cd "$LAYERED2" + export PWD="$LAYERED2" + run_capture "$PWD_BIN" -P + assert_status "multilayer -P status" 0 "$LAST_STATUS" + assert_eq "multilayer -P stdout" "$LAYERED_PHY" "$LAST_STDOUT" + assert_empty "multilayer -P stderr" "$LAST_STDERR" +) +pass "multilayer symlink -P" + +# --------------------------------------------------------------------------- +printf '\nALL TESTS PASSED.\n' +exit 0 |
