golink: 一条命令在远端执行,退出码原样透传,从不提示、不分配终端。 goscp: 用 SCP 协议上传,-files 一个参数同时接受文件和目录。 - 纯 Go,唯一依赖 golang.org/x/crypto,可离线交叉编译到六个桌面平台 - internal/sshx 统一建连、认证(password + keyboard-interactive)与主机密钥策略 - smoketest/ 自带迷你 sshd,57 项端到端测试(golink 38 + goscp 19)
179 lines
6.9 KiB
Bash
Executable File
179 lines
6.9 KiB
Bash
Executable File
#!/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; }
|
|
|
|
printf '\n== single file, default destination ==\n'
|
|
gscp "${G[@]}" -files "$work/src/a.txt" >/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 >/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 ]
|