Splux Linux

source-based Linux using SPS

SPS · log · files · refs · tags · releases · people · languages · GitHub · file

tests/test_output.sh


Shell

13191 bytes at 2909919 · GitHub

#!/bin/sh
# Artifact handoff, live build output, and fetch command construction.

set -eu

case $0 in */*) test_dir=${0%/*} ;; *) test_dir=. ;; esac
project_dir=$(CDPATH= cd "$test_dir/.." 2>/dev/null && pwd) || exit 1
mkpkg=$project_dir/bin/mkpkg

fail()
{
	printf 'test_output: %s\n' "$*" >&2
	exit 1
}

contains()
{
	case $1 in
		*"$2"*) return 0 ;;
		*) fail "expected output to contain '$2'" ;;
	esac
}

omits()
{
	case $1 in
		*"$2"*) fail "output unexpectedly contained '$2'" ;;
	esac
}

sha256_file()
{
	if command -v sha256sum >/dev/null 2>&1; then
		sha256sum "$1" | awk '{ print $1 }'
	elif command -v shasum >/dev/null 2>&1; then
		shasum -a 256 "$1" | awk '{ print $1 }'
	elif command -v openssl >/dev/null 2>&1; then
		openssl dgst -sha256 "$1" | awk '{ print $NF }'
	else
		fail "no SHA-256 utility available for test fixture"
	fi
}

tmp=$(mktemp -d "${TMPDIR:-/tmp}/sps-test-output.XXXXXX") || fail mktemp
trap 'rm -rf "$tmp"' 0 HUP INT TERM

export SPS_ROOT=$tmp/root
export SPS_DB=$tmp/db
export SPS_CACHE=$tmp/cache
export SPS_BUILD=$tmp/build
export SPS_CONFIG=/dev/null
export SPS_REPOS_CONFIG=/dev/null
export SPS_LIBDIR=$project_dir/lib
export SPS_COMPRESSION=none
PATH=$project_dir/bin:$PATH
export PATH
mkdir -p "$tmp/root" "$tmp/out" "$tmp/repo"

run_env()
{
	SPS_ROOT=$tmp/root SPS_DB=$tmp/db SPS_CACHE=$tmp/cache \
	SPS_BUILD=$tmp/build SPS_CONFIG=/dev/null SPS_REPOS_CONFIG=/dev/null \
	SPS_LIBDIR=$project_dir/lib SPS_COMPRESSION=none \
		"$@"
}

# --- mkpkg --artifact-file on success ---
mkdir -p "$tmp/ok"
cat >"$tmp/ok/recipe" <<'EOF'
name        stream-ok
version     1.0
release     1
arch        any
description Artifact handoff success
build       printf '%s\n' BUILD-LINE-ONE
build       printf '%s\n' BUILD-LINE-TWO
install     mkdir -p "$PKG/usr/share"
install     printf '%s\n' ok >"$PKG/usr/share/stream-ok"
EOF

printf 'stale-success\n' >"$tmp/out/result"
run_env "$mkpkg" --artifact-file "$tmp/out/result" --no-download \
	--output "$tmp/out" "$tmp/ok/recipe" \
	>"$tmp/ok.stdout" 2>"$tmp/ok.stderr" || fail 'successful mkpkg failed'
expected=$tmp/out/stream-ok-1.0-1-any.pkg.tar
[ -f "$expected" ] || fail 'package archive was not created'
[ "$(sed -n '1p' "$tmp/out/result")" = "$expected" ] ||
	fail "artifact file did not contain the package path"
[ "$(wc -l <"$tmp/out/result" | awk '{ print $1 }')" = 1 ] ||
	fail 'artifact file was not a single pathname'
omits "$(cat "$tmp/ok.stdout")" "$expected"
contains "$(cat "$tmp/ok.stdout")" BUILD-LINE-ONE
contains "$(cat "$tmp/ok.stdout")" BUILD-LINE-TWO
contains "$(cat "$tmp/ok.stderr")" 'mkpkg: stream-ok 1.0-1'
contains "$(cat "$tmp/ok.stderr")" 'mkpkg: running build phase'
contains "$(cat "$tmp/ok.stderr")" 'mkpkg: built stream-ok-1.0-1-any.pkg.tar'
omits "$(cat "$tmp/ok.stdout")" 'mkpkg:'

# Direct human mode must not require another frontend, and --print-artifact
# keeps the old stdout pathname contract when a script asks for it.
run_env "$mkpkg" --print-artifact --no-download --output "$tmp/out-print" \
	"$tmp/ok/recipe" >"$tmp/print.stdout" 2>"$tmp/print.stderr" ||
	fail '--print-artifact build failed'
print_path=$(awk 'NF { path = $0 } END { print path }' "$tmp/print.stdout")
[ "$print_path" = "$tmp/out-print/stream-ok-1.0-1-any.pkg.tar" ] ||
	fail '--print-artifact did not print the archive path'

# --- failed build does not commit a result ---
mkdir -p "$tmp/fail"
cat >"$tmp/fail/recipe" <<'EOF'
name        stream-fail
version     1.0
release     1
arch        any
description Artifact handoff failure
build       printf '%s\n' BUILD-FAIL-LINE
build       printf '%s\n' "runtime.c:381:17: error: 'struct context' has no member named 'state'" >&2
build       exit 1
install     mkdir -p "$PKG/usr/share"
install     printf '%s\n' fail >"$PKG/usr/share/stream-fail"
EOF

printf 'stale-failure\n' >"$tmp/out/fail-result"
set +e
run_env "$mkpkg" --artifact-file "$tmp/out/fail-result" --no-download \
	--output "$tmp/out" "$tmp/fail/recipe" \
	>"$tmp/fail.stdout" 2>"$tmp/fail.stderr"
fail_status=$?
set -e
[ "$fail_status" -eq 10 ] || fail "failed build returned $fail_status"
contains "$(cat "$tmp/fail.stdout")" BUILD-FAIL-LINE
contains "$(cat "$tmp/fail.stderr")" "has no member named 'state'"
contains "$(cat "$tmp/fail.stderr")" 'mkpkg: build phase failed'
[ ! -e "$tmp/out/fail-result" ] || [ ! -s "$tmp/out/fail-result" ] ||
	fail 'failed build left a successful artifact result'

# --- fetch command construction; no public network ---
mkdir -p "$tmp/fakebin" "$tmp/fetch"
payload=$tmp/payload.dat
printf 'downloaded-bytes\n' >"$payload"
payload_hash=$(sha256_file "$payload")
cat >"$tmp/fetch/recipe" <<EOF
name        fetchpkg
version     1.0
release     1
arch        any
description Download command construction
source      https://example.invalid/payload.dat
hash        sha256:$payload_hash
install     mkdir -p "\$PKG/usr/share"
install     cp "\$SRC/payload.dat" "\$PKG/usr/share/payload.dat"
EOF

cat >"$tmp/fakebin/curl" <<'EOF'
#!/bin/sh
printf '%s\n' "$*" >> "$CURL_LOG"
out=
prev=
for arg
do
	if [ "$prev" = -o ]; then
		out=$arg
	fi
	prev=$arg
done
[ -n "$out" ] || exit 2
cp "$FAKE_PAYLOAD" "$out"
EOF
chmod +x "$tmp/fakebin/curl"

CURL_LOG=$tmp/curl.log
FAKE_PAYLOAD=$payload
export CURL_LOG FAKE_PAYLOAD
: >"$CURL_LOG"
PATH="$tmp/fakebin:$PATH" run_env "$mkpkg" --artifact-file "$tmp/out/fetch-result" \
	--output "$tmp/out" "$tmp/fetch/recipe" \
	>"$tmp/fetch.stdout" 2>"$tmp/fetch.stderr" || fail 'fake-curl fetch build failed'
contains "$(cat "$tmp/fetch.stderr")" 'mkpkg: fetching payload.dat'
contains "$(cat "$tmp/fetch.stderr")" 'mkpkg: checksum ok'
contains "$(cat "$CURL_LOG")" '-fL'
contains "$(cat "$CURL_LOG")" '--progress-bar'
omits "$(cat "$CURL_LOG")" ' -sS'
[ "$(sed -n '1p' "$tmp/out/fetch-result")" = \
	"$tmp/out/fetchpkg-1.0-1-any.pkg.tar" ] ||
	fail 'fetched package did not publish its artifact path'

# Cached source must not invoke the downloader again.
: >"$CURL_LOG"
PATH="$tmp/fakebin:$PATH" run_env "$mkpkg" --artifact-file "$tmp/out/fetch-result2" \
	--output "$tmp/out2" "$tmp/fetch/recipe" \
	>"$tmp/fetch2.stdout" 2>"$tmp/fetch2.stderr" || fail 'cached source rebuild failed'
[ ! -s "$CURL_LOG" ] || fail 'cached source rebuild invoked curl'
contains "$(cat "$tmp/fetch2.stderr")" 'mkpkg: source cache hit payload.dat'

# Download failure must stay visible and must not publish an artifact.
cat >"$tmp/fakebin/curl" <<'EOF'
#!/bin/sh
printf '%s\n' "$*" >> "$CURL_LOG"
printf '%s\n' 'curl: (22) The requested URL returned error: 404' >&2
exit 22
EOF
chmod +x "$tmp/fakebin/curl"
printf 'stale-download\n' >"$tmp/out/dlfail-result"
rm -rf "$tmp/cache/sources"
mkdir -p "$tmp/cache/sources"
set +e
PATH="$tmp/fakebin:$PATH" run_env "$mkpkg" --artifact-file "$tmp/out/dlfail-result" \
	--output "$tmp/out-dlfail" "$tmp/fetch/recipe" \
	>"$tmp/dlfail.stdout" 2>"$tmp/dlfail.stderr"
dl_status=$?
set -e
[ "$dl_status" -eq 9 ] || fail "download failure returned $dl_status"
contains "$(cat "$tmp/dlfail.stderr")" 'download failed'
[ ! -e "$tmp/out/dlfail-result" ] || [ ! -s "$tmp/out/dlfail-result" ] ||
	fail 'download failure left a successful artifact result'

# --- sget live output, cache, quiet, verbose, dependencies ---
mkdir -p "$tmp/repo/libbar" "$tmp/repo/foo" "$tmp/repo/broken" "$tmp/repo/quietpkg"
cat >"$tmp/repo/libbar/recipe" <<'EOF'
name        libbar
version     2.1
release     1
arch        any
description Dependency with live build output
build       printf '%s\n' BUILD-LINE-ONE
build       printf '%s\n' BUILD-LINE-TWO
install     mkdir -p "$PKG/usr/share/sps-test"
install     printf '%s\n' libbar >"$PKG/usr/share/sps-test/libbar"
EOF
cat >"$tmp/repo/foo/recipe" <<'EOF'
name        foo
version     4.7
release     2
arch        any
description Root package with a dependency
depend      libbar
build       printf '%s\n' FOO-BUILD-LINE
install     mkdir -p "$PKG/usr/share/sps-test"
install     printf '%s\n' foo >"$PKG/usr/share/sps-test/foo"
EOF
cat >"$tmp/repo/quietpkg/recipe" <<'EOF'
name        quietpkg
version     1.0
release     1
arch        any
description Quiet-mode build output
build       printf '%s\n' QUIET-SHOULD-HIDE
install     mkdir -p "$PKG/usr/share/sps-test"
install     printf '%s\n' quiet >"$PKG/usr/share/sps-test/quietpkg"
EOF
cat >"$tmp/repo/broken/recipe" <<'EOF'
name        broken
version     1.0
release     1
arch        any
description Build failure through sget
build       printf '%s\n' BROKEN-BUILD-LINE
build       printf '%s\n' "runtime.c:381:17: error: 'struct context' has no member named 'state'" >&2
build       exit 1
install     mkdir -p "$PKG/usr/share/sps-test"
install     printf '%s\n' broken >"$PKG/usr/share/sps-test/broken"
EOF
printf 'repo test %s 10\n' "$tmp/repo" >"$tmp/repos.conf"
: >"$tmp/sps.conf"
export SPS_CONFIG=$tmp/sps.conf
export SPS_REPOS_CONFIG=$tmp/repos.conf
rm -rf "$tmp/db" "$tmp/cache/packages" "$tmp/root/usr"
mkdir -p "$tmp/db/installed" "$tmp/cache/packages" "$tmp/root"
: >"$tmp/db/owners"
: >"$tmp/db/world"
: >"$tmp/db/history"

src update >/dev/null
sget install foo >"$tmp/sget.stdout" 2>"$tmp/sget.stderr" ||
	fail 'sget install foo failed'
sget_all=$(cat "$tmp/sget.stdout"; cat "$tmp/sget.stderr")
contains "$sget_all" 'install libbar 2.1-1'
contains "$sget_all" 'install foo 4.7-2'
contains "$sget_all" BUILD-LINE-ONE
contains "$sget_all" BUILD-LINE-TWO
contains "$sget_all" FOO-BUILD-LINE
contains "$sget_all" 'mkpkg: built libbar-2.1-1-any.pkg.tar'
contains "$sget_all" 'pkin: installed libbar 2.1-1'
contains "$sget_all" 'pkin: installed foo 4.7-2'
contains "$sget_all" 'pkin: unpacking'
contains "$sget_all" 'pkin: /usr/share/sps-test/foo'
[ "$(cat "$tmp/root/usr/share/sps-test/foo")" = foo ] || fail 'foo payload missing'

# Cached binary package: no build phases, no fake progress.
sget install --reinstall foo >"$tmp/cache.stdout" 2>"$tmp/cache.stderr" ||
	fail 'cached reinstall failed'
cache_all=$(cat "$tmp/cache.stdout"; cat "$tmp/cache.stderr")
contains "$cache_all" 'sget: using cached foo-4.7-2-any.pkg.tar'
omits "$cache_all" FOO-BUILD-LINE
omits "$cache_all" 'running build phase'

# Failed sget build keeps the compiler diagnostic and a nonzero status.
set +e
sget install broken >"$tmp/broken.stdout" 2>"$tmp/broken.stderr"
broken_status=$?
set -e
[ "$broken_status" -eq 10 ] || fail "sget failed build returned $broken_status"
broken_all=$(cat "$tmp/broken.stdout"; cat "$tmp/broken.stderr")
contains "$broken_all" BROKEN-BUILD-LINE
contains "$broken_all" "has no member named 'state'"
contains "$broken_all" 'mkpkg: build phase failed'
contains "$broken_all" 'sget: failed to build broken 1.0-1'

# Quiet mode hides routine build output; failure still dumps the log.
sget -q install quietpkg >"$tmp/quiet.stdout" 2>"$tmp/quiet.stderr" ||
	fail 'quiet install failed'
quiet_all=$(cat "$tmp/quiet.stdout"; cat "$tmp/quiet.stderr")
contains "$quiet_all" 'install quietpkg 1.0-1'
contains "$quiet_all" 'pkin: installed quietpkg 1.0-1'
omits "$quiet_all" QUIET-SHOULD-HIDE
omits "$quiet_all" 'running build phase'
omits "$quiet_all" 'pkin: /usr/share/sps-test/quietpkg'

set +e
sget -q install --reinstall --nodeps broken >"$tmp/qfail.stdout" 2>"$tmp/qfail.stderr"
qfail_status=$?
set -e
[ "$qfail_status" -eq 10 ] || fail "quiet failed build returned $qfail_status"
qfail_all=$(cat "$tmp/qfail.stdout"; cat "$tmp/qfail.stderr")
contains "$qfail_all" BROKEN-BUILD-LINE
contains "$qfail_all" 'sget: failed to build broken 1.0-1'

# Verbose mode reports cache and recipe decisions without wrapping compiler lines.
sget -v install --reinstall foo >"$tmp/verbose.stdout" 2>"$tmp/verbose.stderr" ||
	fail 'verbose reinstall failed'
contains "$(cat "$tmp/verbose.stderr")" 'sget: selected '
contains "$(cat "$tmp/verbose.stderr")" 'sget: binary cache hit'

# Interrupted mkpkg must not leave a successful artifact indication.
mkdir -p "$tmp/slow"
cat >"$tmp/slow/recipe" <<'EOF'
name        slowpkg
version     1.0
release     1
arch        any
description Interrupt during build
build       printf '%s\n' started >"$SPS_ROOT/build-started"
build       sleep 5
install     mkdir -p "$PKG/usr/share"
install     printf '%s\n' slow >"$PKG/usr/share/slowpkg"
EOF
printf 'stale-int\n' >"$tmp/out/int-result"
# Background jobs ignore SIGINT in some shells, so this uses TERM. mkpkg's
# TERM and INT traps both exit through the same cleanup path.
SPS_ROOT=$tmp/root SPS_DB=$tmp/db SPS_CACHE=$tmp/cache \
SPS_BUILD=$tmp/build SPS_CONFIG=/dev/null SPS_REPOS_CONFIG=/dev/null \
SPS_LIBDIR=$project_dir/lib SPS_COMPRESSION=none \
	"$mkpkg" --artifact-file "$tmp/out/int-result" --no-download \
	--output "$tmp/out-int" "$tmp/slow/recipe" \
	>"$tmp/int.stdout" 2>"$tmp/int.stderr" &
int_pid=$!
i=0
while [ "$i" -lt 20 ]; do
	if [ -f "$tmp/root/build-started" ]; then
		break
	fi
	sleep 1
	i=$((i + 1))
done
[ -f "$tmp/root/build-started" ] ||
	fail 'slow build did not start before interrupt'
kill -TERM "$int_pid" 2>/dev/null || :
set +e
wait "$int_pid"
int_status=$?
set -e
[ "$int_status" -ne 0 ] || fail 'interrupted mkpkg returned success'
[ ! -e "$tmp/out/int-result" ] || [ ! -s "$tmp/out/int-result" ] ||
	fail 'interrupted mkpkg left a successful artifact result'

printf '%s\n' 'test_output: ok'