diff options
| author | Paul Buetow <paul@buetow.org> | 2026-06-14 15:43:12 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-06-14 15:43:12 +0300 |
| commit | fe4e83cd5159809a70f24301fcabc08779b51706 (patch) | |
| tree | 29b71066a22db0630ed37ced8e8710e8175b83f7 /tests | |
| parent | 3eeef1e0f68d750e7817748180a2007095e99ef3 (diff) | |
6m0 clean up BASH_ENV context tempfile via traps on interrupt
source_template_file created its context tempfile with mktemp but only ran
rm -f on the success and explicit-failure paths. When a render was interrupted
by a signal (terminate_active_generation SIGTERMs the backgrounded render
subtree on Ctrl-C/abort) the tempfile leaked.
Register cleanup in exactly one place, inline in source_template_file's body:
- A RETURN trap covers normal and error returns and clears all of these traps
(including itself) so it cannot linger and fire on an enclosing function's
return against the out-of-scope context_file local (set -u). The trap must be
set in the function body, not a helper: without functrace a RETURN trap is not
function-scoped and would fire when a helper returns, deleting the file before
the render runs.
- INT/TERM/HUP handlers remove the file, clear the traps and re-raise so the
process still exits with the signal's default disposition. PIPE is not
trapped (the internal context-build pipeline emits legitimate SIGPIPE);
SIGKILL is untrappable, leaving only the OS-reaped KILL-escalation case.
The redundant rm -f calls are removed. Adds
test_template_interrupt_removes_context_file, which runs an in-flight render and
SIGTERMs it, asserting the context file is gone.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'tests')
| -rwxr-xr-x | tests/cli.sh | 95 |
1 files changed, 95 insertions, 0 deletions
diff --git a/tests/cli.sh b/tests/cli.sh index b8b56f6..9bd399b 100755 --- a/tests/cli.sh +++ b/tests/cli.sh @@ -4090,6 +4090,98 @@ BASH test::teardown } +test_template_interrupt_removes_context_file() { + local context_file + local fake_bin + local started_file + local template_dir + local child_pid + local -i status=0 + + test::setup + fake_bin="$TEST_TMPDIR/bin" + template_dir="$TEST_TMPDIR/templates" + context_file="$TEST_TMPDIR/template-context" + started_file="$TEST_TMPDIR/template-started" + mkdir -p "$fake_bin" "$template_dir" "$TEST_TMPDIR/dist" + { + printf '#!/usr/bin/env bash\n' + # shellcheck disable=SC2016 + printf 'printf %%s\\\\n \"$SHURIKEN_FAKE_CONTEXT_FILE\"\n' + } > "$fake_bin/mktemp" + chmod 0755 "$fake_bin/mktemp" + # The rendered template signals it started, then blocks so the render is + # genuinely in flight (context already built, env -i bash running) when we + # deliver the interrupt. This exercises the signal traps, not the RETURN + # trap: a plain RETURN trap does not fire when a signal kills the shell. + { + printf 'printf rendered > %q\n' "$started_file" + printf 'sleep 30\n' + } > "$template_dir/preview.tmpl" + + # Run a full template render in a child shell we can signal mid-render. Going + # through the template entry point populates a valid render context so the + # context build succeeds and the render actually reaches the blocking sleep. + bash -euo pipefail -s \ + "$TEST_SHURIKEN" \ + "$fake_bin" \ + "$template_dir" \ + "$TEST_TMPDIR/dist" \ + "$context_file" \ + <<'BASH' & +shuriken="$1"; shift +fake_bin="$1"; shift +template_dir="$1"; shift +dist_dir="$1"; shift +context_file="$1"; shift + +# shellcheck source=/dev/null +source <(sed '$d' "$shuriken") + +PATH="$fake_bin:$PATH" +SHURIKEN_FAKE_CONTEXT_FILE="$context_file" +export SHURIKEN_FAKE_CONTEXT_FILE +DIST_DIR="$dist_dir" +TEMPLATE_DIR="$template_dir" +TITLE='Template interrupt cleanup' +HEIGHT='' +THUMBHEIGHT=30 +MAXPREVIEWS=40 +ORIGINAL_BASEPATH='' +TARBALL_INCLUDE=no +SHURIKEN_OUTPUT_MODE=quiet +apply_config_defaults + +template preview out.html \ + animation_class '' \ + backhref '#' \ + html_dir . \ + page_num 1 \ + photo photo.jpg \ + preview_num 1 \ + thumbs_dir thumbs +BASH + child_pid=$! + + # Wait for the render to begin, then interrupt it with SIGTERM. + while [ ! -f "$started_file" ] && kill -0 "$child_pid" 2>/dev/null; do + sleep 0.05 + done + kill -TERM "$child_pid" 2>/dev/null || true + set +e + wait "$child_pid" + status=$? + set -e + + if (( status == 0 )); then + printf 'FAIL: expected interrupted render to exit non-zero\n' >&2 + exit 1 + fi + + test::assert_path_absent "$context_file" + test::teardown +} + test_generate_swap_failure_restores_dist() { local config_file local fake_bin @@ -5034,6 +5126,9 @@ main() { 'template setup failure fails status-tested render' \ test_template_setup_failure_fails_status_tested_render test::run_case \ + 'template interrupt removes context file' \ + test_template_interrupt_removes_context_file + test::run_case \ '--generate swap failure restores final dist' \ test_generate_swap_failure_restores_dist test::run_case \ |
