#!/bin/sh # POSIX smoke suite for the files agent tool. # req: files/001 files/002 files/003 files/004 files/005 files/006 files/007 files/008 files/009 files/010 files/011 files/012 files/013 files/014 set -u bin=${FILES_BIN:-./zig-out/bin/files} fail() { printf 'files-smoke: %s\n' "$*" >&2 exit 1 } assert_empty() { file=$1 label=$2 [ ! -s "$file" ] || fail "$label not empty: $(cat "$file")" } assert_contains() { file=$1 text=$2 label=$3 grep -F "$text" "$file" >/dev/null 2>&1 || fail "$label missing: $text" } assert_not_contains() { file=$1 text=$2 label=$3 if grep -F "$text" "$file" >/dev/null 2>&1; then fail "$label unexpectedly contains: $text" fi } assert_status() { got=$1 want=$2 label=$3 [ "$got" -eq "$want" ] || fail "$label exit $got, want $want" } assert_same_file() { want=$1 got=$2 label=$3 cmp -s "$want" "$got" || fail "$label output mismatch" } assert_unchanged_tree() { dir=$1 before=$2 label=$3 find "$dir" -print | sort > "$sorted" assert_same_file "$before" "$sorted" "$label" } assert_runtime_failure() { got=$1 label=$2 [ "$got" -ne 0 ] || fail "$label exit was zero" [ "$got" -ne 2 ] || fail "$label used usage exit 2 for runtime failure" } assert_help_contract() { file=$1 label=$2 assert_contains "$file" "usage: files [PATH...]" "$label" assert_contains "$file" "stdout:" "$label" assert_contains "$file" "One path per line" "$label" assert_contains "$file" "exit status:" "$label" assert_contains "$file" "paths only" "$label" assert_contains "$file" "No filtering, sorting, metadata" "$label" assert_contains "$file" "No deletion, exec, callbacks, network, hidden writes, config, or expression language" "$label" } write_tree_listing_expected() { prefix=$1 { printf '%s\n' "${prefix}--dash" printf '%s\n' "${prefix}a/name with space" printf '%s\n' "${prefix}a/one" if [ "$have_symlink" = yes ]; then printf '%s\n' "${prefix}linkdir" printf '%s\n' "${prefix}linkfile" fi printf '%s\n' "${prefix}two" } > "$expected" } [ -x "$bin" ] || fail "executable not found: $bin" case $bin in /*) abs_bin=$bin ;; *) abs_bin=$(pwd -P)/$bin ;; esac i=0 base=${TMPDIR:-/tmp}/files-smoke.$$ while :; do work=$base.$i if mkdir "$work" 2>/dev/null; then break fi i=$((i + 1)) [ "$i" -lt 50 ] || fail "could not create temp directory under ${TMPDIR:-/tmp}" done trap 'rm -rf "$work"' EXIT HUP INT TERM out=$work/out err=$work/err expected=$work/expected sorted=$work/sorted tree=$work/tree mkdir -p "$tree/a" "$tree/empty" || fail "mkdir tree" printf 'dash\n' > "$tree/--dash" || fail "write dash" printf 'one\n' > "$tree/a/one" || fail "write one" printf 'space\n' > "$tree/a/name with space" || fail "write space" printf 'two\n' > "$tree/two" || fail "write two" if ln -s a "$tree/linkdir" 2>/dev/null && ln -s two "$tree/linkfile" 2>/dev/null; then have_symlink=yes else have_symlink=no fi "$bin" --help > "$out" 2> "$err" assert_status $? 0 "--help" assert_empty "$err" "--help stderr" assert_help_contract "$out" "help" "$bin" -h > "$out" 2> "$err" assert_status $? 0 "-h" assert_empty "$err" "-h stderr" assert_help_contract "$out" "-h help" help_roots=$work/help-roots mkdir -p "$help_roots" || fail "mkdir help roots" printf 'help\n' > "$help_roots/--help" || fail "write --help root" printf 'short\n' > "$help_roots/-h" || fail "write -h root" printf 'extra\n' > "$help_roots/extra" || fail "write extra root" ( cd "$help_roots" || exit 1 "$abs_bin" --help extra > "$out" 2> "$err" ) assert_status $? 0 "--help as path root" assert_empty "$err" "--help as path root stderr" { printf '%s\n' "--help" printf '%s\n' "extra" } > "$expected" assert_same_file "$expected" "$out" "--help as path root" ( cd "$help_roots" || exit 1 "$abs_bin" -h extra > "$out" 2> "$err" ) assert_status $? 0 "-h as path root" assert_empty "$err" "-h as path root stderr" { printf '%s\n' "-h" printf '%s\n' "extra" } > "$expected" assert_same_file "$expected" "$out" "-h as path root" "$bin" "$tree" > "$out" 2> "$err" assert_status $? 0 "directory listing" assert_empty "$err" "directory listing stderr" sort "$out" > "$sorted" write_tree_listing_expected "$tree/" sort "$expected" > "$expected.sorted" mv "$expected.sorted" "$expected" assert_same_file "$expected" "$sorted" "directory listing" assert_not_contains "$out" "$tree/linkdir/one" "symlink traversal" ( cd "$tree" || exit 1 "$abs_bin" . > "$out" 2> "$err" ) assert_status $? 0 "default dot listing" assert_empty "$err" "default dot stderr" sort "$out" > "$sorted" write_tree_listing_expected "" sort "$expected" > "$expected.sorted" mv "$expected.sorted" "$expected" assert_same_file "$expected" "$sorted" "default dot" ( cd "$tree" || exit 1 "$abs_bin" > "$out" 2> "$err" ) assert_status $? 0 "no-arg default listing" assert_empty "$err" "no-arg default stderr" sort "$out" > "$sorted" write_tree_listing_expected "" sort "$expected" > "$expected.sorted" mv "$expected.sorted" "$expected" assert_same_file "$expected" "$sorted" "no-arg default" "$bin" "$tree/empty" > "$out" 2> "$err" assert_status $? 0 "empty directory" assert_empty "$out" "empty directory stdout" assert_empty "$err" "empty directory stderr" "$bin" "$tree/two" > "$out" 2> "$err" assert_status $? 0 "file input" assert_empty "$err" "file input stderr" printf '%s\n' "$tree/two" > "$expected" assert_same_file "$expected" "$out" "file input" single=$work/single mkdir -p "$single" || fail "mkdir single" printf 'single\n' > "$single/only" || fail "write single" "$bin" "$tree/two" "$single" "$tree/--dash" > "$out" 2> "$err" assert_status $? 0 "multiple roots" assert_empty "$err" "multiple roots stderr" { printf '%s\n' "$tree/two" printf '%s\n' "$single/only" printf '%s\n' "$tree/--dash" } > "$expected" assert_same_file "$expected" "$out" "multiple roots" "$bin" "$tree/two" "$tree/two" "$single" "$single" > "$out" 2> "$err" assert_status $? 0 "duplicate roots" assert_empty "$err" "duplicate roots stderr" { printf '%s\n' "$tree/two" printf '%s\n' "$tree/two" printf '%s\n' "$single/only" printf '%s\n' "$single/only" } > "$expected" assert_same_file "$expected" "$out" "duplicate roots" "$bin" "$single/" "$tree/two" > "$out" 2> "$err" assert_status $? 0 "trailing slash root" assert_empty "$err" "trailing slash root stderr" { printf '%s\n' "$single/only" printf '%s\n' "$tree/two" } > "$expected" assert_same_file "$expected" "$out" "trailing slash root" assert_not_contains "$out" "$single//only" "trailing slash double slash" if [ "$have_symlink" = yes ]; then "$bin" "$tree/linkfile" "$tree/linkdir" > "$out" 2> "$err" assert_status $? 0 "symlink roots" assert_empty "$err" "symlink roots stderr" { printf '%s\n' "$tree/linkfile" printf '%s\n' "$tree/linkdir" } > "$expected" assert_same_file "$expected" "$out" "symlink roots" assert_not_contains "$out" "$tree/linkdir/one" "symlink root traversal" "$bin" "$tree/linkdir" "$single" > "$out" 2> "$err" assert_status $? 0 "symlink then directory root" assert_empty "$err" "symlink then directory root stderr" { printf '%s\n' "$tree/linkdir" printf '%s\n' "$single/only" } > "$expected" assert_same_file "$expected" "$out" "symlink then directory root" assert_not_contains "$out" "$tree/linkdir/one" "symlink then directory traversal" fi later_missing=$work/later-missing if "$bin" "$tree/two" "$later_missing" > "$out" 2> "$err"; then fail "missing later root unexpectedly succeeded" else status=$? fi assert_runtime_failure "$status" "missing later root" printf '%s\n' "$tree/two" > "$expected" assert_same_file "$expected" "$out" "missing later root partial stdout" assert_contains "$err" "files: $later_missing:" "missing later root stderr" if "$bin" "" > "$out" 2> "$err"; then fail "empty path unexpectedly succeeded" else status=$? fi assert_runtime_failure "$status" "empty path" assert_empty "$out" "empty path stdout" assert_contains "$err" "files: empty PATH" "empty path stderr" missing=$work/missing if "$bin" "$missing" > "$out" 2> "$err"; then fail "missing path unexpectedly succeeded" else status=$? fi assert_runtime_failure "$status" "missing path" assert_empty "$out" "missing path stdout" assert_contains "$err" "files: $missing:" "missing path stderr" restricted=$work/restricted mkdir -p "$restricted/blocked" || fail "mkdir restricted" printf 'hidden\n' > "$restricted/blocked/file" || fail "write restricted file" chmod 000 "$restricted/blocked" || fail "chmod restricted" if "$bin" "$restricted" > "$out" 2> "$err"; then status=0 else status=$? fi chmod 700 "$restricted/blocked" || fail "restore restricted permissions" if [ "$status" -eq 0 ]; then if [ ! -r "$restricted/blocked" ] || [ ! -x "$restricted/blocked" ]; then fail "unreadable directory unexpectedly succeeded" fi else assert_runtime_failure "$status" "unreadable directory" assert_empty "$out" "unreadable directory stdout" assert_contains "$err" "files: $restricted:" "unreadable directory stderr" fi big=$work/big mkdir -p "$big" || fail "mkdir big" n=1 while [ "$n" -le 1000 ]; do d=$big/d$((n % 10)) mkdir -p "$d" || fail "mkdir big dir $n" : > "$d/$n" || fail "write big $n" n=$((n + 1)) done "$bin" "$big" > "$out" 2> "$err" assert_status $? 0 "generated tree listing" assert_empty "$err" "generated tree stderr" count=$(wc -l < "$out" | tr -d ' ') [ "$count" -eq 1000 ] || fail "generated tree count $count, want 1000" { "$bin" "$big" | head -n 1 > /dev/null; } 2> "$err" assert_empty "$err" "pipeline stderr" { "$bin" "$big" "$work/pipe-missing" | head -n 1 > /dev/null; } 2> "$err" assert_empty "$err" "multi-root pipeline stderr" no_mutation=$work/no-mutation mkdir -p "$no_mutation/home" "$no_mutation/cache" "$no_mutation/target/sub" || fail "mkdir no-mutation" printf 'kept\n' > "$no_mutation/target/sub/file" || fail "write no-mutation file" find "$no_mutation" -print | sort > "$expected" HOME=$no_mutation/home XDG_CACHE_HOME=$no_mutation/cache "$bin" "$no_mutation/target" > "$out" 2> "$err" assert_status $? 0 "no-mutation listing" assert_empty "$err" "no-mutation listing stderr" assert_unchanged_tree "$no_mutation" "$expected" "no-mutation listing" HOME=$no_mutation/home XDG_CACHE_HOME=$no_mutation/cache "$bin" --help > "$out" 2> "$err" assert_status $? 0 "no-mutation help" assert_empty "$err" "no-mutation help stderr" assert_unchanged_tree "$no_mutation" "$expected" "no-mutation help" printf 'files-smoke: ok\n' >&2