nnn

mirror of https://github.com/jarun/nnn
Log | Files | Refs | README | LICENSE

test-76ee5803.sh (5271B)


      1 #!/bin/sh
      2 #
      3 # Test for commit 76ee5803: "Take care of other shell escape issues"
      4 #
      5 # This commit fixes quoting of temp file paths (%s -> '%s') in shell
      6 # commands used by batch_rename() and cpmv_rename().  The bug only
      7 # manifests when the temp-file directory contains spaces.  Since nnn
      8 # prefers /tmp (which never has spaces), we use Linux user+mount
      9 # namespaces (unshare -mr) to make /tmp inaccessible, forcing nnn's
     10 # set_tmp_path() to fall back to $TMPDIR — which we point at a path
     11 # that contains spaces.
     12 #
     13 # Requirements: Linux with user-namespace support, unshare(1)
     14 #
     15 # Strategy:
     16 #   1. Create $TMPDIR with spaces; make /tmp inaccessible via namespace
     17 #   2. Create test files and trigger batch rename (r -> c)
     18 #   3. A mock EDITOR renames "aaa.txt" -> "zzz.txt" in the temp file
     19 #   4. Verify the rename happened on the filesystem
     20 #
     21 # Expected: PASS with the fix, FAIL without (the unquoted paste/sed
     22 # commands choke on the spaced temp path).
     23 
     24 set -e
     25 
     26 SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
     27 NNN_TEST="$SCRIPT_DIR/nnn_runner.sh"
     28 
     29 # Locate nnn binary
     30 NNN_BIN="${1:-./nnn}"
     31 case "$NNN_BIN" in
     32     /*) ;;
     33     *)  NNN_BIN="$(cd "$(dirname "$NNN_BIN")" && pwd)/$(basename "$NNN_BIN")" ;;
     34 esac
     35 
     36 [ -x "$NNN_BIN" ] || { echo "error: nnn not found at $NNN_BIN" >&2; exit 1; }
     37 
     38 # ---------- check for namespace support ----------
     39 if ! unshare -mr true 2>/dev/null; then
     40     printf "SKIP: user/mount namespaces not available\n"
     41     exit 0
     42 fi
     43 
     44 # ---------- set up test environment ----------
     45 # All temp data lives under TESTROOT.  We place it under $HOME rather
     46 # than /tmp because /tmp will be masked inside the namespace.
     47 TESTROOT="$(mktemp -d "${HOME}/nnn-shell-esc.XXXXXX")"
     48 trap 'rm -rf "$TESTROOT"' EXIT
     49 
     50 # TMPDIR for nnn — path deliberately contains a space
     51 SPACE_TMP="$TESTROOT/tmp dir"
     52 mkdir -p "$SPACE_TMP"
     53 
     54 # NNN_SEL — also under a path with spaces
     55 SELDIR="$TESTROOT/path with spaces"
     56 mkdir -p "$SELDIR"
     57 
     58 # Clean XDG_CONFIG_HOME so the .nmv plugin is NOT found,
     59 # forcing nnn to use the built-in batch_rename()
     60 FAKECFG="$TESTROOT/config"
     61 mkdir -p "$FAKECFG/nnn/plugins"
     62 
     63 # Working directory with test files
     64 WORKDIR="$TESTROOT/workdir"
     65 mkdir -p "$WORKDIR"
     66 printf 'content_a\n' > "$WORKDIR/aaa.txt"
     67 printf 'content_b\n' > "$WORKDIR/bbb.txt"
     68 
     69 # Mock EDITOR: renames aaa.txt -> zzz.txt in the batch-rename temp file
     70 MOCK_EDITOR="$TESTROOT/mock-editor.sh"
     71 cat > "$MOCK_EDITOR" << 'EDEOF'
     72 #!/bin/sh
     73 sed -i 's/^aaa\.txt$/zzz.txt/' "$1"
     74 EDEOF
     75 chmod +x "$MOCK_EDITOR"
     76 
     77 # nnn-test input file
     78 INPUT_FILE="$TESTROOT/test-input.txt"
     79 cat > "$INPUT_FILE" << 'INPUTEOF'
     80 # Wait for nnn to render
     81 @sleep 300
     82 @assert aaa.txt
     83 @assert bbb.txt
     84 
     85 # Trigger batch rename: r key then c (current dir)
     86 r
     87 @sleep 300
     88 c
     89 @sleep 800
     90 
     91 # After rename, nnn should refresh
     92 @sleep 500
     93 @mark
     94 @snapshot after-rename
     95 
     96 # Quit
     97 q
     98 INPUTEOF
     99 
    100 # ---------- inner script that runs inside the namespace ----------
    101 # Two levels of namespace nesting:
    102 #   Outer (unshare -mr): gives root to mount a mode-000 tmpfs on /tmp
    103 #   Inner (unshare --user): drops back to nobody — /tmp is now blocked,
    104 #     but TESTROOT dirs (owned by the real user) map to nobody and remain
    105 #     writable. nnn's xdiraccess("/tmp") fails → falls back to $TMPDIR.
    106 INNER="$TESTROOT/inner.sh"
    107 cat > "$INNER" << INNEREOF
    108 #!/bin/sh
    109 set -e
    110 
    111 # Make /tmp inaccessible so nnn falls back to \$TMPDIR
    112 mount -t tmpfs -o size=1M,mode=000 tmpfs /tmp
    113 
    114 # Run the actual test in a nested user namespace (drops to nobody)
    115 exec unshare --user sh -c '
    116   export TMPDIR="$SPACE_TMP"
    117   export NNN_SEL="$SELDIR/.selection"
    118   export XDG_CONFIG_HOME="$FAKECFG"
    119   export EDITOR="$MOCK_EDITOR"
    120   export VISUAL="$MOCK_EDITOR"
    121 
    122   OUT_DIR="$TESTROOT/output"
    123   "$NNN_TEST" -v -e "$NNN_BIN" -d "$WORKDIR" -o "\$OUT_DIR" "$INPUT_FILE"
    124 '
    125 INNEREOF
    126 chmod +x "$INNER"
    127 
    128 # ---------- run test ----------
    129 printf "Testing commit 76ee5803: shell escape with spaced TMPDIR...\n"
    130 printf "  NNN binary: %s\n" "$NNN_BIN"
    131 printf "  TMPDIR:     %s\n" "$SPACE_TMP"
    132 printf "  WORKDIR:    %s\n" "$WORKDIR"
    133 
    134 unshare -mr "$INNER"
    135 TEST_EXIT=$?
    136 
    137 # ---------- verify filesystem ----------
    138 printf "\n--- Filesystem verification ---\n"
    139 PASS=0
    140 FAIL=0
    141 
    142 if [ -f "$WORKDIR/zzz.txt" ]; then
    143     printf "  PASS: zzz.txt exists (renamed from aaa.txt)\n"
    144     PASS=$((PASS + 1))
    145 else
    146     printf "  FAIL: zzz.txt not found (batch rename failed)\n"
    147     FAIL=$((FAIL + 1))
    148 fi
    149 
    150 if [ ! -f "$WORKDIR/aaa.txt" ]; then
    151     printf "  PASS: aaa.txt no longer exists\n"
    152     PASS=$((PASS + 1))
    153 else
    154     printf "  FAIL: aaa.txt still exists (rename didn't happen)\n"
    155     FAIL=$((FAIL + 1))
    156 fi
    157 
    158 if [ -f "$WORKDIR/zzz.txt" ]; then
    159     CONTENT="$(cat "$WORKDIR/zzz.txt")"
    160     if [ "$CONTENT" = "content_a" ]; then
    161         printf "  PASS: zzz.txt has correct content\n"
    162         PASS=$((PASS + 1))
    163     else
    164         printf "  FAIL: zzz.txt has wrong content: %s\n" "$CONTENT"
    165         FAIL=$((FAIL + 1))
    166     fi
    167 fi
    168 
    169 if [ -f "$WORKDIR/bbb.txt" ]; then
    170     printf "  PASS: bbb.txt unchanged\n"
    171     PASS=$((PASS + 1))
    172 else
    173     printf "  FAIL: bbb.txt missing\n"
    174     FAIL=$((FAIL + 1))
    175 fi
    176 
    177 printf "\n=== Shell escape test: %d passed, %d failed ===\n" "$PASS" "$FAIL"
    178 
    179 if [ "$FAIL" -gt 0 ] || [ "$TEST_EXIT" -ne 0 ]; then
    180     printf "Snapshots: %s/output\n" "$TESTROOT"
    181     ls -la "$WORKDIR"/
    182     exit 2
    183 fi
    184 
    185 exit 0