#!/usr/bin/env bash # # End-to-end smoke test for goscp against the bundled miniature sshd. # # ./smoketest/run-goscp.sh # # The test server runs commands through /bin/sh, so the real scp binary on this # machine plays the remote side of the protocol. Everything stays local; the # uploaded trees are checked directly on disk. set -uo pipefail here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" root="$(dirname "$here")" work="$root/.smoketest-goscp" export GOCACHE="${GOCACHE:-$root/.gocache}" USER_NAME=tester PASS_WORD=s3cr3t HOST=127.0.0.1 pass=0 fail=0 ok() { pass=$((pass + 1)); printf ' ok %s\n' "$1"; } bad() { fail=$((fail + 1)); printf ' FAIL %s\n' "$1"; [ -n "${2:-}" ] && printf ' %s\n' "$2"; } command -v scp >/dev/null 2>&1 || { echo "scp binary not found; cannot run this test" >&2; exit 1; } rm -rf "$work" mkdir -p "$work/bin" "$work/src/sub" "$work/server-cwd" printf 'building\n' go build -o "$work/bin/goscp" ./cmd/goscp || exit 1 go build -o "$work/bin/sshd" ./smoketest/sshd || exit 1 printf 'hello\n' >"$work/src/a.txt" : >"$work/src/empty.bin" printf 'companion data\n' >"$work/src/notes.md" printf 'space content\n' >"$work/src/sub/name with spaces.txt" head -c 1048576 /dev/urandom >"$work/src/sub/big.bin" printf '#!/bin/sh\necho hi\n' >"$work/src/run.sh" BIG_SUM="$(sha256sum "$work/src/sub/big.bin" | cut -d' ' -f1)" sshd_pids=() cleanup() { local p for p in ${sshd_pids[@]+"${sshd_pids[@]}"}; do kill "$p" 2>/dev/null; done wait 2>/dev/null } trap cleanup EXIT # The server runs with cwd $work/server-cwd so that "default destination" # (the remote home, ".") ( cd "$work/server-cwd" && exec "$work/bin/sshd" -addr 127.0.0.1:0 -user "$USER_NAME" -pass "$PASS_WORD" \ -hostkey "$work/hostkey" ) >"$work/sshd.out" 2>"$work/sshd.err" & sshd_pids+=($!) for i in $(seq 1 100); do grep -q '^HOSTKEY_SHA256=' "$work/sshd.out" 2>/dev/null && break sleep 0.05 done adr="$(sed -n 's/^ADDR=//p' "$work/sshd.out" | head -1)" PORT="${adr##*:}" if [ -z "$PORT" ]; then echo "sshd did not start: $(cat "$work/sshd.err")" >&2 exit 1 fi TARGET="$USER_NAME@$HOST" G=( "$TARGET" -p "$PASS_WORD" -port "$PORT" ) gscp() { timeout 60 "$work/bin/goscp" "$@" /dev/null 2>"$work/err.txt" rc=$? if [ "$rc" -eq 0 ] && [ "$(cat "$work/server-cwd/a.txt" 2>/dev/null)" = "hello" ]; then ok "file lands in the remote home" else bad "file lands in the remote home" "exit=$rc stderr=$(head -1 "$work/err.txt")" fi gscp "${G[@]}" -files "$work/src/empty.bin" -dest "$work/dest1" >/dev/null 2>&1 if [ -f "$work/dest1/empty.bin" ] && [ ! -s "$work/dest1/empty.bin" ]; then ok "zero byte file" else bad "zero byte file" "missing or non-empty" fi printf '\n== directory tree ==\n' gscp "${G[@]}" -files "$work/src" -dest "$work/dest2" >/dev/null 2>"$work/err.txt" rc=$? if [ "$rc" -eq 0 ] && [ -f "$work/dest2/src/sub/big.bin" ] && [ -f "$work/dest2/src/sub/name with spaces.txt" ]; then ok "nested tree with spaces in names" else bad "nested tree with spaces in names" "exit=$rc stderr=$(head -1 "$work/err.txt")" fi got="$(sha256sum "$work/dest2/src/sub/big.bin" 2>/dev/null | cut -d' ' -f1)" [ "$got" = "$BIG_SUM" ] && ok "1 MiB binary is byte identical" || bad "1 MiB binary is byte identical" "sha256 mismatch" if [ -d "$work/dest2/src/sub" ] && [ "$(stat -c %a "$work/dest2/src/sub")" = "755" ]; then ok "directories are 0755" else bad "directories are 0755" "mode=$(stat -c %a "$work/dest2/src/sub" 2>/dev/null)" fi mode="$(stat -c %a "$work/dest2/src/a.txt" 2>/dev/null)" [ "$mode" = "644" ] && ok "plain files are 0644 by default" || bad "plain files are 0644 by default" "mode=$mode" printf '\n== file modes ==\n' chmod 755 "$work/src/run.sh" gscp "${G[@]}" -files "$work/src/run.sh" -dest "$work/dest3" >/dev/null 2>&1 mode="$(stat -c %a "$work/dest3/run.sh" 2>/dev/null)" [ "$mode" = "755" ] && ok "local exec bit is preserved" || bad "local exec bit is preserved" "mode=$mode" printf '\n== multiple files and extra positionals ==\n' gscp "${G[@]}" -files "$work/src/a.txt" -files "$work/src/notes.md" -dest "$work/dest4" >/dev/null 2>&1 if [ -f "$work/dest4/a.txt" ] && [ -f "$work/dest4/notes.md" ]; then ok "two -files flags" else bad "two -files flags" "missing output" fi # one -files followed by several bare paths: everything after the target is # treated as a path, so both spellings below are equivalent gscp "${G[@]}" -files "$work/src/a.txt" "$work/src/notes.md" "$work/src/empty.bin" -dest "$work/dest4b" >/dev/null 2>&1 if [ -f "$work/dest4b/a.txt" ] && [ -f "$work/dest4b/notes.md" ] && [ -f "$work/dest4b/empty.bin" ]; then ok "-files with several bare paths" else bad "-files with several bare paths" "missing output" fi gscp "${G[@]}" -dest "$work/dest5" "$work/src/a.txt" "$work/src/notes.md" >/dev/null 2>&1 if [ -f "$work/dest5/a.txt" ] && [ -f "$work/dest5/notes.md" ]; then ok "trailing positional paths" else bad "trailing positional paths" "missing output" fi printf '\n== destination handling ==\n' gscp "${G[@]}" -files "$work/src/a.txt" -dest "$work/deep/nested/dir" >/dev/null 2>&1 [ -f "$work/deep/nested/dir/a.txt" ] && ok "missing destination directories are created" || bad "missing destination directories are created" "not created" gscp "${G[@]}" -files "$work/src/a.txt" -dest "$work/rel" >/dev/null 2>&1 [ -f "$work/rel/a.txt" ] && ok "absolute -dest" || bad "absolute -dest" "missing" printf '\n== output and exit codes ==\n' out="$(gscp "${G[@]}" -files "$work/src/a.txt" -dest "$work/dest6" -v 2>&1 >/dev/null)" if printf '%s' "$out" | grep -q 'a.txt'; then ok "-v lists the uploaded entry" else bad "-v lists the uploaded entry" "stderr: $(printf '%s' "$out" | head -2 | tr '\n' '|')" fi out="$(gscp "${G[@]}" -files "$work/src/a.txt" -dest "$work/dest7" -q 2>&1 >/dev/null)" [ -z "$out" ] && ok "-q keeps stderr empty" || bad "-q keeps stderr empty" "stderr: $out" gscp "${G[@]}" -files "$work/does-not-exist" -dest "$work/dest8" >/dev/null 2>"$work/err.txt" rc=$? [ "$rc" -eq 1 ] && grep -q 'does-not-exist' "$work/err.txt" && ok "missing local path -> exit 1" \ || bad "missing local path -> exit 1" "exit=$rc" gscp "${G[@]}" -dest "$work/dest9" >/dev/null 2>&1 [ $? -eq 2 ] && ok "no -files -> exit 2" || bad "no -files -> exit 2" gscp "$TARGET" -p wrong -port "$PORT" -files "$work/src/a.txt" >/dev/null 2>&1 [ $? -eq 255 ] && ok "wrong password -> exit 255" || bad "wrong password -> exit 255" gscp "$TARGET" -port "$PORT" -files "$work/src/a.txt" >/dev/null 2>&1 [ $? -eq 2 ] && ok "no credentials -> exit 2" || bad "no credentials -> exit 2" printf '\n== password from environment ==\n' GOSCP_PASSWORD="$PASS_WORD" timeout 60 "$work/bin/goscp" "$TARGET" -port "$PORT" -files "$work/src/a.txt" -dest "$work/dest10" /dev/null 2>&1 [ -f "$work/dest10/a.txt" ] && ok "GOSCP_PASSWORD works" || bad "GOSCP_PASSWORD works" "missing output" printf '\n%d passed, %d failed\n' "$pass" "$fail" [ "$fail" -eq 0 ]