Skip to content

release-rehearsal.test.sh reads its pass condition out of a fixed /tmp path, so a run that could not write reports as a refusal #431

Description

@vladimirrott

tests/release/release-rehearsal.test.sh proves that the rehearsal script
refuses a publishing mode. It writes the refusal to a fixed path under /tmp
and then greps that path for the pass condition:

$ git rev-parse --short=8 HEAD
61b3a878
$ sed -n '38,42p' tests/release/release-rehearsal.test.sh
if "$rehearsal" --publish >/tmp/sysknife-rehearsal-publish.out 2>&1; then
    printf 'FAIL: rehearsal accepted a publishing mode\n' >&2
    exit 1
fi
grep -Fq 'never publishes' /tmp/sysknife-rehearsal-publish.out

When the redirect fails, bash never runs the command, and the failure is
indistinguishable from the refusal this test is written to observe. The if
takes the false branch, which is the passing branch, and line 42 then reads a
file this run did not write.

Why it matters

Measured, replaying the shape at :38-42 against a file the process cannot
write:

$ f=$(mktemp /tmp/sk-demo-XXXX.out); printf 'rehearsal never publishes\n' > "$f"; chmod 444 "$f"; if echo 'this run really ran' > "$f" 2>&1; then echo "if-branch: TRUE (test would FAIL loudly)"; else echo "if-branch: FALSE (test treats this as the expected refusal)"; fi; grep -Fq 'never publishes' "$f" && echo "line 42 assertion: SATISFIED"; cat "$f"; rm -f "$f"
/bin/bash: line 1: /tmp/sk-demo-wtD2.out: Permission denied
if-branch: FALSE (test treats this as the expected refusal)
line 42 assertion: SATISFIED
rehearsal never publishes

scripts/release_rehearsal.sh was never invoked, and the contract test reported
that it correctly refuses to publish. That is a guard answering a question it
could not ask, which is the shape this repo files issues about in its own CI.

Two ways to arrive there, neither exotic. Another account on a shared runner or
a shared developer box owns /tmp/sysknife-rehearsal-publish.out, because the
name is fixed and predictable. Or /tmp is full or read-only, in which case
every gate that writes there fails in a way that reports as a pass here and as
an error elsewhere.

The file is also left behind. The test has no trap:

$ grep -c trap tests/release/release-rehearsal.test.sh
0

This is the only line in tests/release/ that reads a fixed /tmp path back as
a pass condition:

$ grep -rn 'grep .*/tmp/' tests/release/
tests/release/release-rehearsal.test.sh:42:grep -Fq 'never publishes' /tmp/sysknife-rehearsal-publish.out

Six of the twenty-two scripts in that directory already use mktemp, so the
pattern to copy is in the same directory.

Scope

  • Give the test a private temporary directory (mktemp -d) and a
    trap 'rm -rf "$tmp"' EXIT, and put the publish output inside it.
  • Separate "the command ran and refused" from "the command never ran". Capture
    the status explicitly rather than reading it off an if whose false branch
    also covers a redirect failure. Assert that the output file is non-empty
    before grepping it.
  • Leave the assertion itself alone. never publishes is the right string and
    the refusal is the right behaviour; only the plumbing that observes it moves.

Do not fix this by adding || exit 1 to the redirect. That closes the one arrival
path you thought of and leaves the shape intact for the next one.

Tests first

Write the negative case before the fix, because the point of the change is that
a specific wrong state now fails.

  1. Pin the fixed path: make the destination unwritable for the running user and
    run the test as it stands. It passes. Paste that.
  2. Apply the fix. The same setup now fails, and the message names the write
    rather than the missing string.
  3. Then prove the positive case still holds: an ordinary run passes, and the
    temporary directory is gone afterwards.

Step 1 is the one that matters. A version of this change that only adds mktemp
and a trap is tidier and proves nothing, because the run that could not write
still reads as a refusal.

Difficulty

easy. One file, about six lines, and the hard part is the test in step 1
rather than the fix. No Rust, no VM, no daemon, no credentials.

Getting started

CONTRIBUTING.md
has the build and test commands. No CLA and no copyright waiver. The project is MIT.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingeasyDifficulty: self-contained, no deep context neededgood first issueGood for newcomershelp wantedExtra attention is needed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions