nigig-org/tools/test-rust-clean.sh
andodeki d6b35d7989
Some checks failed
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
Payment domain, storage, platform and UI / isolated-payment-tests (push) Has been cancelled
Payment domain, storage, platform and UI / payment-ui-tests (push) Has been cancelled
test(spreadsheet-ui): add headless unit target
2026-07-31 19:57:04 +00:00

290 lines
12 KiB
Bash
Executable file

#!/usr/bin/env bash
# Isolated, self-cleaning Rust test runner for Nigig's pure domain crate.
#
# This deliberately tests a copied crate outside the workspace: the top-level
# workspace currently has sibling path dependencies (Makepad/Robius) that are
# not included in a standalone checkout. It installs Rust, Cargo caches, the
# crate copy and all build artifacts in one temporary directory, then deletes
# that directory on success, failure, Ctrl-C or termination.
#
# Usage:
# ./tools/test-rust-clean.sh
# RUST_TOOLCHAIN=1.85.0 ./tools/test-rust-clean.sh
# TEST_TARGET=platform ./tools/test-rust-clean.sh
# TEST_TARGET=pdf ./tools/test-rust-clean.sh
# TEST_TARGET=pdf-ui ./tools/test-rust-clean.sh # needs GUI system libs
# TEST_TARGET=valhalla ./tools/test-rust-clean.sh
# KEEP_TEST_ENV=1 ./tools/test-rust-clean.sh # debugging only; prints path
#
# It does not modify source files, Cargo.lock, ~/.cargo, ~/.rustup or the
# workspace target directory.
set -Eeuo pipefail
IFS=$'\n\t'
REPO_ROOT="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
DOMAIN_DIR="$REPO_ROOT/crates/nigig-pay-domain"
STORAGE_DIR="$REPO_ROOT/crates/nigig-pay-storage"
PLATFORM_DIR="$REPO_ROOT/crates/nigig-pay-platform"
# The platform crate's Android adapter is an optional feature, so its USSD
# path is exercised separately from the default (portable) test run.
USSD_DIR="$REPO_ROOT/crates/robius-ussd"
VALHALLA_DIR="$REPO_ROOT/crates/apps/valhalla"
VALHALLA_CRATES=(valhalla-core valhalla-cost valhalla-path valhalla-search valhalla-match valhalla-narrative valhalla-elevation valhalla-service valhalla-makepad valhalla-builder valhalla-diff)
TEST_TARGET="${TEST_TARGET:-domain}"
SPREADSHEET_DIR="$REPO_ROOT/crates/apps/spreadsheet/spreadsheet-engine"
SPREADSHEET_UI_DIR="$REPO_ROOT/crates/apps/spreadsheet/spreadsheet-ui"
PDF_DIR="$REPO_ROOT/crates/apps/pdf"
# The PDF crates form a layered stack (cos <- document <- graphics) wired by
# relative path dependencies, so all three are copied together and each is
# tested in dependency order. pdf-makepad is excluded on purpose: it needs
# makepad_draw, which is not available in a standalone checkout.
PDF_CRATES=(pdf-cos pdf-document pdf-graphics)
# pdf-makepad additionally needs Makepad and system GUI libraries, so it is
# a separate target rather than part of the default pdf run.
PDF_UI_CRATES=(pdf-cos pdf-document pdf-graphics pdf-makepad)
# Read the toolchain from rust-toolchain.toml so the runner can never drift
# from the repository's declared version. Override only deliberately.
declared_toolchain() {
sed -n 's/^[[:space:]]*channel[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \
"$REPO_ROOT/rust-toolchain.toml" | head -n 1
}
TOOLCHAIN="${RUST_TOOLCHAIN:-$(declared_toolchain)}"
if [[ -z "$TOOLCHAIN" ]]; then
echo "error: could not read channel from rust-toolchain.toml" >&2
exit 2
fi
KEEP_TEST_ENV="${KEEP_TEST_ENV:-0}"
CHECK_FORMAT="${CHECK_FORMAT:-1}"
if [[ ! -f "$DOMAIN_DIR/Cargo.toml" ]]; then
echo "error: expected pure domain crate at $DOMAIN_DIR" >&2
exit 2
fi
if [[ "$TEST_TARGET" != "domain" && "$TEST_TARGET" != "storage" \
&& "$TEST_TARGET" != "platform" \
&& "$TEST_TARGET" != "spreadsheet" && "$TEST_TARGET" != "spreadsheet-ui" \
&& "$TEST_TARGET" != "spreadsheet-ui-check" \
&& "$TEST_TARGET" != "spreadsheet-ui-unit" \
&& "$TEST_TARGET" != "pdf" && "$TEST_TARGET" != "pdf-ui" \
&& "$TEST_TARGET" != "valhalla" ]]; then
echo "error: TEST_TARGET must be domain, storage, platform, spreadsheet," >&2
echo " spreadsheet-ui," >&2
echo " pdf, pdf-ui, or valhalla" >&2
exit 2
fi
if [[ "$TEST_TARGET" == "pdf" ]]; then
for crate in "${PDF_CRATES[@]}"; do
if [[ ! -f "$PDF_DIR/$crate/Cargo.toml" ]]; then
echo "error: expected PDF crate at $PDF_DIR/$crate" >&2
exit 2
fi
done
fi
if [[ "$TEST_TARGET" == "valhalla" ]]; then
for crate in "${VALHALLA_CRATES[@]}"; do
if [[ ! -f "$VALHALLA_DIR/$crate/Cargo.toml" ]]; then
echo "error: expected Valhalla crate at $VALHALLA_DIR/$crate" >&2
exit 2
fi
done
fi
if [[ "$TEST_TARGET" == "spreadsheet" && ! -f "$SPREADSHEET_DIR/Cargo.toml" ]]; then
echo "error: expected spreadsheet engine crate at $SPREADSHEET_DIR" >&2
exit 2
fi
if [[ ("$TEST_TARGET" == "spreadsheet-ui" || "$TEST_TARGET" == "spreadsheet-ui-check" || "$TEST_TARGET" == "spreadsheet-ui-unit") && ! -f "$SPREADSHEET_UI_DIR/Cargo.toml" ]]; then
echo "error: expected spreadsheet UI crate at $SPREADSHEET_UI_DIR" >&2
exit 2
fi
if [[ "$TEST_TARGET" == "storage" && ! -f "$STORAGE_DIR/Cargo.toml" ]]; then
echo "error: expected storage crate at $STORAGE_DIR" >&2
exit 2
fi
if [[ "$TEST_TARGET" == "platform" && ! -f "$PLATFORM_DIR/Cargo.toml" ]]; then
echo "error: expected platform crate at $PLATFORM_DIR" >&2
exit 2
fi
mkdir -p "$HOME/tmp"
WORK_DIR="$(mktemp -d "${TMPDIR:-$HOME/tmp}/nigig-rust-test.XXXXXXXX")"
cleanup() {
local status=$?
if [[ "$KEEP_TEST_ENV" == "1" ]]; then
echo "test environment retained for debugging: $WORK_DIR" >&2
else
rm -rf -- "$WORK_DIR"
echo "cleaned isolated Rust test environment" >&2
fi
exit "$status"
}
trap cleanup EXIT HUP INT TERM
export RUSTUP_HOME="$WORK_DIR/rustup"
export CARGO_HOME="$WORK_DIR/cargo"
export CARGO_TARGET_DIR="$WORK_DIR/target"
export PATH="$CARGO_HOME/bin:$PATH"
export CARGO_NET_GIT_FETCH_WITH_CLI=true
export CARGO_INCREMENTAL=0
export RUSTFLAGS="-C strip=debuginfo"
RUSTUP_INIT="$WORK_DIR/rustup-init"
echo "creating isolated Rust toolchain ($TOOLCHAIN) in $WORK_DIR" >&2
curl --fail --location --proto '=https' --tlsv1.2 \
https://sh.rustup.rs -o "$RUSTUP_INIT"
chmod 700 "$RUSTUP_INIT"
"$RUSTUP_INIT" -y --profile minimal --default-toolchain "$TOOLCHAIN" \
--component rustfmt --component clippy --no-modify-path
# Avoid workspace resolution: copy only the selected pure crate(s) and test
# their manifest. Storage needs the sibling domain crate for its relative path.
TEST_CRATE="$WORK_DIR/nigig-pay-domain"
cp -a "$DOMAIN_DIR" "$TEST_CRATE"
if [[ "$TEST_TARGET" == "storage" ]]; then
TEST_CRATE="$WORK_DIR/nigig-pay-storage"
cp -a "$STORAGE_DIR" "$TEST_CRATE"
elif [[ "$TEST_TARGET" == "platform" ]]; then
# robius-ussd is an *optional* dependency, but Cargo still resolves its
# path at manifest-read time, so it has to travel with the crate.
TEST_CRATE="$WORK_DIR/nigig-pay-platform"
cp -a "$PLATFORM_DIR" "$TEST_CRATE"
cp -a "$USSD_DIR" "$WORK_DIR/robius-ussd"
elif [[ "$TEST_TARGET" == "spreadsheet" ]]; then
TEST_CRATE="$WORK_DIR/spreadsheet-engine"
cp -a "$SPREADSHEET_DIR" "$TEST_CRATE"
elif [[ "$TEST_TARGET" == "spreadsheet-ui" || "$TEST_TARGET" == "spreadsheet-ui-check" || "$TEST_TARGET" == "spreadsheet-ui-unit" ]]; then
# Preserve the UI crate's sibling engine path dependency.
mkdir -p "$WORK_DIR/spreadsheet"
cp -a "$SPREADSHEET_DIR" "$WORK_DIR/spreadsheet/spreadsheet-engine"
cp -a "$SPREADSHEET_UI_DIR" "$WORK_DIR/spreadsheet/spreadsheet-ui"
TEST_CRATE="$WORK_DIR/spreadsheet/spreadsheet-ui"
elif [[ "$TEST_TARGET" == "pdf" ]]; then
for crate in "${PDF_CRATES[@]}"; do
cp -a "$PDF_DIR/$crate" "$WORK_DIR/$crate"
done
# The regression corpus sits beside the crates and is referenced by a
# relative path, so it has to come along or the corpus tests cannot
# find their fixtures.
mkdir -p "$WORK_DIR/tests"
cp -a "$PDF_DIR/tests/corpus" "$WORK_DIR/tests/corpus"
elif [[ "$TEST_TARGET" == "pdf-ui" ]]; then
for crate in "${PDF_UI_CRATES[@]}"; do
cp -a "$PDF_DIR/$crate" "$WORK_DIR/$crate"
done
mkdir -p "$WORK_DIR/tests"
cp -a "$PDF_DIR/tests/corpus" "$WORK_DIR/tests/corpus"
elif [[ "$TEST_TARGET" == "valhalla" ]]; then
for crate in "${VALHALLA_CRATES[@]}"; do
cp -a "$VALHALLA_DIR/$crate" "$WORK_DIR/$crate"
done
TEST_CRATE="$WORK_DIR/valhalla-makepad"
fi
cargo --version
rustc --version
if [[ "$TEST_TARGET" == "spreadsheet-ui" || "$TEST_TARGET" == "spreadsheet-ui-check" || "$TEST_TARGET" == "spreadsheet-ui-unit" ]]; then
# Makepad's Linux backend uses native Wayland/X11 libraries. Keep this
# preflight separate from Rust/Cargo caches; the runner only cleans its
# isolated temporary environment and never removes host packages.
"$REPO_ROOT/tools/makepad-native-libs.sh" --check
fi
# Run tests
if [[ "$TEST_TARGET" == "domain" || "$TEST_TARGET" == "storage" \
|| "$TEST_TARGET" == "platform" ]]; then
if [[ ! -f "$TEST_CRATE/Cargo.lock" ]]; then
echo "error: $TEST_TARGET crate has no checked-in Cargo.lock" >&2
exit 2
fi
cargo test --manifest-path "$TEST_CRATE/Cargo.toml" --all-targets --locked
elif [[ "$TEST_TARGET" == "pdf" ]]; then
for crate in "${PDF_CRATES[@]}"; do
echo "--- testing $crate" >&2
cargo test --manifest-path "$WORK_DIR/$crate/Cargo.toml" --all-targets
done
elif [[ "$TEST_TARGET" == "pdf-ui" ]]; then
for crate in "${PDF_UI_CRATES[@]}"; do
echo "--- testing $crate" >&2
cargo test --manifest-path "$WORK_DIR/$crate/Cargo.toml" --all-targets
done
elif [[ "$TEST_TARGET" == "spreadsheet-ui-check" ]]; then
cargo check --manifest-path "$TEST_CRATE/Cargo.toml" --lib --bins
elif [[ "$TEST_TARGET" == "spreadsheet-ui-unit" ]]; then
cargo test --manifest-path "$TEST_CRATE/Cargo.toml" --lib
elif [[ "$TEST_TARGET" == "valhalla" ]]; then
for crate in "${VALHALLA_CRATES[@]}"; do
echo "--- testing $crate" >&2
cargo test --manifest-path "$WORK_DIR/$crate/Cargo.toml" --all-targets
done
else
cargo test --manifest-path "$TEST_CRATE/Cargo.toml" --all-targets
fi
# Formatting is a source-quality check, not a formatter: never rewrite files.
if [[ "$CHECK_FORMAT" == "1" && "$TEST_TARGET" == "pdf-ui" ]]; then
for crate in "${PDF_UI_CRATES[@]}"; do
cargo fmt --manifest-path "$WORK_DIR/$crate/Cargo.toml" -- --check
done
elif [[ "$CHECK_FORMAT" == "1" && "$TEST_TARGET" == "pdf" ]]; then
for crate in "${PDF_CRATES[@]}"; do
cargo fmt --manifest-path "$WORK_DIR/$crate/Cargo.toml" -- --check
done
elif [[ "$CHECK_FORMAT" == "1" && "$TEST_TARGET" == "valhalla" ]]; then
for crate in "${VALHALLA_CRATES[@]}"; do
cargo fmt --manifest-path "$WORK_DIR/$crate/Cargo.toml" -- --check
done
elif [[ "$CHECK_FORMAT" == "1" ]]; then
cargo fmt --manifest-path "$TEST_CRATE/Cargo.toml" -- --check
else
echo "format check skipped (CHECK_FORMAT=$CHECK_FORMAT)" >&2
fi
if [[ "$TEST_TARGET" == "domain" || "$TEST_TARGET" == "storage" \
|| "$TEST_TARGET" == "platform" ]]; then
cargo clippy --manifest-path "$TEST_CRATE/Cargo.toml" --all-targets --locked -- -D warnings
fi
# The PDF stack parses untrusted input, so a warning there is a real defect
# class (silent truncation, unwrap on malformed bytes). Gate it on warnings.
if [[ "$TEST_TARGET" == "pdf" || "$TEST_TARGET" == "pdf-ui" ]]; then
if [[ "$TEST_TARGET" == "pdf-ui" ]]; then
clippy_crates=("${PDF_UI_CRATES[@]}")
else
clippy_crates=("${PDF_CRATES[@]}")
fi
for crate in "${clippy_crates[@]}"; do
cargo clippy --manifest-path "$WORK_DIR/$crate/Cargo.toml" \
--all-targets -- -D warnings
done
fi
if [[ "$TEST_TARGET" == "platform" ]]; then
# The Android adapter compiles for the host even though it only *runs* on
# a device, so a mismapped provider error is caught here rather than on a
# phone.
cargo test --manifest-path "$TEST_CRATE/Cargo.toml" --locked --features ussd
cargo clippy --manifest-path "$TEST_CRATE/Cargo.toml" --all-targets --locked \
--features ussd -- -D warnings
# Review item 5.4: MockGateway fabricates confirmations. A release build
# that enables it must fail to compile. This asserts the guard is real
# rather than trusting the comment above it.
if cargo build --manifest-path "$TEST_CRATE/Cargo.toml" --locked \
--release --features mock >/dev/null 2>&1; then
echo "error: the mock gateway compiled into a release build" >&2
exit 1
fi
echo "mock gateway is correctly refused in release builds" >&2
fi
if [[ "$TEST_TARGET" == "storage" ]]; then
cargo test --manifest-path "$TEST_CRATE/Cargo.toml" --locked --features sqlcipher
cargo clippy --manifest-path "$TEST_CRATE/Cargo.toml" --all-targets --locked \
--features sqlcipher -- -D warnings
fi
if [[ "$TEST_TARGET" == "domain" ]]; then
cargo bench --manifest-path "$TEST_CRATE/Cargo.toml" --locked
fi
echo "isolated $TEST_TARGET tests passed" >&2