From 1e587d50b53ce9d8007b38d8dc62e31ec0f15824 Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Thu, 1 Oct 2026 21:49:38 +0530 Subject: [PATCH 1/2] ci: isolate release artifact validation from dirty worktrees --- tests/bash-42-release-smoke.sh | 12 +++++++++--- tests/release-artifact.bats | 25 ++++++++++++++++++++----- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/tests/bash-42-release-smoke.sh b/tests/bash-42-release-smoke.sh index 8433eac..1cfea38 100755 --- a/tests/bash-42-release-smoke.sh +++ b/tests/bash-42-release-smoke.sh @@ -46,7 +46,7 @@ git() { main() { local expected_major="${1-}" expected_minor="${2-}" expected_patch="${3-}" local script_dir repo_root release_script release_driver release_artifact - local capture_path output_path artifact_output source_commit artifact_fixture + local capture_path output_path artifact_output source_commit artifact_fixture source_repo if (($# != 0 && $# != 3)); then release_smoke_fail "usage: $0 [expected-major expected-minor expected-patch]" @@ -117,11 +117,17 @@ main() { artifact_output="$artifact_fixture" else artifact_output="$release_smoke_dir/artifact" - source_commit="$(command git -C "$repo_root" rev-parse --verify 'HEAD^{commit}' 2> /dev/null)" || { + source_repo="$release_smoke_dir/source-repo" + command git clone --local "$repo_root" "$source_repo" > /dev/null 2>&1 || { + release_smoke_fail "unable to create a clean source clone for artifact verification." + return 1 + } + source_commit="$(command git -C "$source_repo" rev-parse --verify 'HEAD^{commit}' 2> /dev/null)" || { release_smoke_fail "unable to resolve the source commit for artifact verification." return 1 } - if ! "$release_artifact" build --version 2.0.0 --commit "$source_commit" \ + if ! BASE_BASH_RELEASE_SOURCE_ROOT="$source_repo" \ + "$release_artifact" build --version 2.0.0 --commit "$source_commit" \ --output "$artifact_output" > "$output_path" 2>&1; then release_smoke_fail "canonical artifact build failed on Bash $BASH_VERSION." return 1 diff --git a/tests/release-artifact.bats b/tests/release-artifact.bats index 68275b2..4e0bc94 100644 --- a/tests/release-artifact.bats +++ b/tests/release-artifact.bats @@ -5,7 +5,10 @@ load ../lib/bash/tests/test_helper.sh setup() { setup_test_tmpdir RELEASE_ARTIFACT="$BASE_REPO_ROOT/scripts/release-artifact" - RELEASE_COMMIT="$(git -C "$BASE_REPO_ROOT" rev-parse HEAD)" + RELEASE_SOURCE_ROOT="$TEST_TMPDIR/source-repo" + git clone --local "$BASE_REPO_ROOT" "$RELEASE_SOURCE_ROOT" > /dev/null + RELEASE_COMMIT="$(git -C "$RELEASE_SOURCE_ROOT" rev-parse HEAD)" + export BASE_BASH_RELEASE_SOURCE_ROOT="$RELEASE_SOURCE_ROOT" } release_test_hash_file() { @@ -181,6 +184,19 @@ EOF [[ "$output" == *"Option '--commit' may be provided only once."* ]] } +@test "release artifact build rejects a dirty source root" { + local dirty_source="$TEST_TMPDIR/dirty-source" output="$TEST_TMPDIR/dirty-artifact" + + git clone --local "$BASE_REPO_ROOT" "$dirty_source" > /dev/null + printf 'dirty\n' >> "$dirty_source/VERSION" + + bats_run env BASE_BASH_RELEASE_SOURCE_ROOT="$dirty_source" \ + "$RELEASE_ARTIFACT" build --version 2.2.0-rc.1 --commit "$RELEASE_COMMIT" \ + --output "$output" + [ "$status" -eq 1 ] + [[ "$output" == *"The source checkout must be clean."* ]] +} + @test "release artifact build and verify support post-GA patch and minor versions" { local version artifact @@ -310,10 +326,9 @@ EOF @test "remote release verification rejects missing assets and cleans partial retries" { local artifact="$TEST_TMPDIR/artifact" verified="$TEST_TMPDIR/verified" source_repo - # Build from a clean local clone because the test worktree contains the - # uncommitted remote-verifier changes themselves. - source_repo="$TEST_TMPDIR/source-repo" - git clone --local "$BASE_REPO_ROOT" "$source_repo" > /dev/null + # Build from the per-test clean local clone because the test worktree may + # contain uncommitted release-artifact changes itself. + source_repo="$RELEASE_SOURCE_ROOT" export REMOTE_VERSION=2.0.0-rc.1 REMOTE_COMMIT="$RELEASE_COMMIT" REMOTE_SOURCE="$artifact" "$source_repo/scripts/release-artifact" build --version "$REMOTE_VERSION" \ --commit "$REMOTE_COMMIT" --output "$artifact" > /dev/null From c462f11f86a43b170aba7c59a91a9a0f83aef445 Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Thu, 1 Oct 2026 21:51:49 +0530 Subject: [PATCH 2/2] ci: run aggregate BATS validation without terminal stdin --- tests/validate-driver.bats | 37 +++++++++++++++++++++++++++++++++++++ tests/validate.sh | 6 +++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/tests/validate-driver.bats b/tests/validate-driver.bats index 3b43c4e..fc2dfe3 100644 --- a/tests/validate-driver.bats +++ b/tests/validate-driver.bats @@ -29,3 +29,40 @@ SCRIPT [[ "$output" == *"Validation stage failed: ShellCheck error profile (exit 42)."* ]] [ ! -e "$sentinel" ] } + +@test "validate detaches BATS from an interactive stdin" { + local shim_dir="$TEST_TMPDIR/shim" + local sentinel="$TEST_TMPDIR/bats-stdin" + local pty_runner="$TEST_TMPDIR/run-in-pty.py" + + mkdir -p "$shim_dir" + cat > "$shim_dir/shellcheck" <<'SCRIPT' +#!/usr/bin/env bash +exit 0 +SCRIPT + cat > "$shim_dir/bats" <<'SCRIPT' +#!/usr/bin/env bash +if [[ -t 0 ]]; then + printf 'tty\n' > "${VALIDATE_BATS_STDIN:?}" +else + printf 'non-tty\n' > "${VALIDATE_BATS_STDIN:?}" +fi +exit 42 +SCRIPT + cat > "$pty_runner" <<'PYTHON' +#!/usr/bin/env python3 +import os +import pty +import sys + +wait_status = pty.spawn(sys.argv[1:]) +sys.exit(os.waitstatus_to_exitcode(wait_status)) +PYTHON + chmod +x "$shim_dir/shellcheck" "$shim_dir/bats" "$pty_runner" + + run env PATH="$shim_dir:$PATH" VALIDATE_BATS_STDIN="$sentinel" \ + "$pty_runner" "$BASE_REPO_ROOT/tests/validate.sh" + + [ "$status" -eq 42 ] + [ "$(cat "$sentinel")" = "non-tty" ] +} diff --git a/tests/validate.sh b/tests/validate.sh index 3051264..f20900d 100755 --- a/tests/validate.sh +++ b/tests/validate.sh @@ -486,7 +486,11 @@ while IFS= read -r file; do [[ -n "$file" ]] && bats_files+=("$file") done <<< "$manifest_test_paths" -run_stage "BATS test suites" bats \ +run_bats_noninteractive() { + bats "$@" < /dev/null +} + +run_stage "BATS test suites" run_bats_noninteractive \ "${bats_files[@]}" || exit $? run_stage "Project intake REST contract" python3 tests/project-intake-test.py || exit $?