diff options
| author | Paul Buetow <paul@buetow.org> | 2026-06-12 15:28:39 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-06-12 15:28:39 +0300 |
| commit | d5a58a1115bb3faa45caefeb053a5808406afa35 (patch) | |
| tree | 013229daf22e5c062fdf5e860c457eb9d0c976bc | |
| parent | 81a3be6379198c697de2f8639dcaa1cdc9d66a53 (diff) | |
Harden status-tested validation paths for bm0
| -rwxr-xr-x | bin/shuriken | 117 | ||||
| -rw-r--r-- | src/lib/action.source.sh | 94 | ||||
| -rw-r--r-- | src/lib/template.source.sh | 23 | ||||
| -rwxr-xr-x | tests/cli.sh | 164 |
4 files changed, 354 insertions, 44 deletions
diff --git a/bin/shuriken b/bin/shuriken index d3db424..3c815ed 100755 --- a/bin/shuriken +++ b/bin/shuriken @@ -741,7 +741,6 @@ source_template_file() { local -r output_path="$1"; shift local -r render_vars_name="$1"; shift local context_file - local restore_errexit=no local -i status=0 context_file=$(mktemp) @@ -750,18 +749,16 @@ source_template_file() { return "$status" fi - if [[ "$-" == *e* ]]; then - restore_errexit=yes - set +e - fi - ( - set -e - serialize_template_render_context "$render_vars_name" - printf 'unset BASH_ENV\n' - ) > "$context_file" - status=$? - if [ "$restore_errexit" = yes ]; then - set -e + if { + declare -p TEMPLATE_RENDER_FIELD_SPECS + declare -p "$render_vars_name" + declare -f + printf 'serialize_template_render_context %q\n' "$render_vars_name" + printf "printf 'unset BASH_ENV\\\\n'\n" + } | bash -euo pipefail > "$context_file"; then + status=0 + else + status=$? fi if (( status != 0 )); then rm -f "$context_file" @@ -3168,12 +3165,87 @@ run_simple_action() { esac } +run_action_body_context() { + local name + local -a variable_names=( + VERSION + DEFAULTRC + PACKAGED_TEMPLATE_DIR + PACKAGED_ASSET_DIR + DEFAULT_TEMPLATE_DIR + DEFAULT_ASSET_DIR + SHURIKEN_SOURCE_DIR + SHURIKEN_OUTPUT_MODE + SHURIKEN_ACTIVE_GENERATION_PID + SHURIKEN_FORCE_GENERATE + SHURIKEN_CURRENT_DATE_TEXT + SHURIKEN_CONFIG_SOURCE + SHURIKEN_FINAL_DIST_DIR + INCOMING_DIR + DIST_DIR + TEMPLATE_DIR + TITLE + HEIGHT + THUMBHEIGHT + MAXPREVIEWS + IMAGE_JOBS + IMAGEMAGICK_TIMEOUT + TAR_TIMEOUT + ORIGINAL_BASEPATH + RANDOM_SEED + SHUFFLE + SPLASH_PAGE + TARBALL_INCLUDE + TARBALL_SUFFIX + TAR_OPTS + SYNC_DELETE + SYNC_DESTINATIONS + TEMPLATE_RENDER_FIELD_SPECS + ) + + for name in "${variable_names[@]}"; do + declare -p "$name" 2>/dev/null || true + done + declare -f +} + run_action_body() { - if [[ "$-" == *e* ]]; then - "$@" + local -r action_name="$1"; shift + local -i status=0 + + if { + run_action_body_context + printf '%q "$@"\n' "$action_name" + } | bash -euo pipefail -s -- "$@"; then + status=0 + else + status=$? + fi + + return "$status" +} + +run_action_body_direct() { + "$@" +} + +run_configured_action_body() { + local -r action_name="$1"; shift + local -r runner="${SHURIKEN_ACTION_BODY_RUNNER:-run_action_body}" + local -i status=0 + + if [ "$runner" = run_action_body_direct ]; then + "$runner" "$action_name" "$@" + return + fi + + if "$runner" "$action_name" "$@"; then + status=0 else - ( set -e; "$@" ) + status=$? fi + + return "$status" } load_configured_action() { @@ -3268,7 +3340,7 @@ run_configured_action() { return "$status" fi - run_action_body generate_staged + run_configured_action_body generate_staged status=$? if (( status != 0 )); then return "$status" @@ -3281,7 +3353,7 @@ run_configured_action() { return "$status" fi - run_action_body refresh_splash + run_configured_action_body refresh_splash status=$? if (( status != 0 )); then return "$status" @@ -3294,7 +3366,7 @@ run_configured_action() { return "$status" fi - run_action_body sync_dist + run_configured_action_body sync_dist status=$? if (( status != 0 )); then return "$status" @@ -3307,7 +3379,7 @@ run_configured_action() { return "$status" fi - run_action_body dry_run + run_configured_action_body dry_run status=$? if (( status != 0 )); then return "$status" @@ -3320,7 +3392,7 @@ run_configured_action() { return "$status" fi - run_action_body print_config + run_configured_action_body print_config status=$? if (( status != 0 )); then return "$status" @@ -3341,7 +3413,8 @@ run_action() { fi ;; --clean|--generate|--refresh-splash|--sync|--dry-run|--print-config) - run_configured_action + SHURIKEN_ACTION_BODY_RUNNER=run_action_body_direct \ + run_configured_action status=$? if (( status != 0 )); then return "$status" diff --git a/src/lib/action.source.sh b/src/lib/action.source.sh index f3e92be..77b7f8c 100644 --- a/src/lib/action.source.sh +++ b/src/lib/action.source.sh @@ -23,12 +23,87 @@ run_simple_action() { esac } +run_action_body_context() { + local name + local -a variable_names=( + VERSION + DEFAULTRC + PACKAGED_TEMPLATE_DIR + PACKAGED_ASSET_DIR + DEFAULT_TEMPLATE_DIR + DEFAULT_ASSET_DIR + SHURIKEN_SOURCE_DIR + SHURIKEN_OUTPUT_MODE + SHURIKEN_ACTIVE_GENERATION_PID + SHURIKEN_FORCE_GENERATE + SHURIKEN_CURRENT_DATE_TEXT + SHURIKEN_CONFIG_SOURCE + SHURIKEN_FINAL_DIST_DIR + INCOMING_DIR + DIST_DIR + TEMPLATE_DIR + TITLE + HEIGHT + THUMBHEIGHT + MAXPREVIEWS + IMAGE_JOBS + IMAGEMAGICK_TIMEOUT + TAR_TIMEOUT + ORIGINAL_BASEPATH + RANDOM_SEED + SHUFFLE + SPLASH_PAGE + TARBALL_INCLUDE + TARBALL_SUFFIX + TAR_OPTS + SYNC_DELETE + SYNC_DESTINATIONS + TEMPLATE_RENDER_FIELD_SPECS + ) + + for name in "${variable_names[@]}"; do + declare -p "$name" 2>/dev/null || true + done + declare -f +} + run_action_body() { - if [[ "$-" == *e* ]]; then - "$@" + local -r action_name="$1"; shift + local -i status=0 + + if { + run_action_body_context + printf '%q "$@"\n' "$action_name" + } | bash -euo pipefail -s -- "$@"; then + status=0 + else + status=$? + fi + + return "$status" +} + +run_action_body_direct() { + "$@" +} + +run_configured_action_body() { + local -r action_name="$1"; shift + local -r runner="${SHURIKEN_ACTION_BODY_RUNNER:-run_action_body}" + local -i status=0 + + if [ "$runner" = run_action_body_direct ]; then + "$runner" "$action_name" "$@" + return + fi + + if "$runner" "$action_name" "$@"; then + status=0 else - ( set -e; "$@" ) + status=$? fi + + return "$status" } load_configured_action() { @@ -123,7 +198,7 @@ run_configured_action() { return "$status" fi - run_action_body generate_staged + run_configured_action_body generate_staged status=$? if (( status != 0 )); then return "$status" @@ -136,7 +211,7 @@ run_configured_action() { return "$status" fi - run_action_body refresh_splash + run_configured_action_body refresh_splash status=$? if (( status != 0 )); then return "$status" @@ -149,7 +224,7 @@ run_configured_action() { return "$status" fi - run_action_body sync_dist + run_configured_action_body sync_dist status=$? if (( status != 0 )); then return "$status" @@ -162,7 +237,7 @@ run_configured_action() { return "$status" fi - run_action_body dry_run + run_configured_action_body dry_run status=$? if (( status != 0 )); then return "$status" @@ -175,7 +250,7 @@ run_configured_action() { return "$status" fi - run_action_body print_config + run_configured_action_body print_config status=$? if (( status != 0 )); then return "$status" @@ -196,7 +271,8 @@ run_action() { fi ;; --clean|--generate|--refresh-splash|--sync|--dry-run|--print-config) - run_configured_action + SHURIKEN_ACTION_BODY_RUNNER=run_action_body_direct \ + run_configured_action status=$? if (( status != 0 )); then return "$status" diff --git a/src/lib/template.source.sh b/src/lib/template.source.sh index 8ab87b2..c7d9ded 100644 --- a/src/lib/template.source.sh +++ b/src/lib/template.source.sh @@ -296,7 +296,6 @@ source_template_file() { local -r output_path="$1"; shift local -r render_vars_name="$1"; shift local context_file - local restore_errexit=no local -i status=0 context_file=$(mktemp) @@ -305,18 +304,16 @@ source_template_file() { return "$status" fi - if [[ "$-" == *e* ]]; then - restore_errexit=yes - set +e - fi - ( - set -e - serialize_template_render_context "$render_vars_name" - printf 'unset BASH_ENV\n' - ) > "$context_file" - status=$? - if [ "$restore_errexit" = yes ]; then - set -e + if { + declare -p TEMPLATE_RENDER_FIELD_SPECS + declare -p "$render_vars_name" + declare -f + printf 'serialize_template_render_context %q\n' "$render_vars_name" + printf "printf 'unset BASH_ENV\\\\n'\n" + } | bash -euo pipefail > "$context_file"; then + status=0 + else + status=$? fi if (( status != 0 )); then rm -f "$context_file" diff --git a/tests/cli.sh b/tests/cli.sh index c747594..42522a2 100755 --- a/tests/cli.sh +++ b/tests/cli.sh @@ -2130,6 +2130,71 @@ BASH test::teardown } +test_generate_action_failure_fails_status_tested_dispatcher() { + local config_file + local continued_file + local output + local success_file + local -i status=0 + + test::setup + config_file="$TEST_TMPDIR/shuriken.conf" + continued_file="$TEST_TMPDIR/dry-run-continued" + success_file="$TEST_TMPDIR/dispatcher-success" + mkdir -p "$TEST_TMPDIR/incoming" + test::write_preflight_config \ + "$config_file" "$TEST_TMPDIR/incoming" "$TEST_TMPDIR/dist" \ + "$TEST_REPO_ROOT/share/templates/default" + + set +e + output=$( + bash -euo pipefail -s \ + "$TEST_SHURIKEN" \ + "$config_file" \ + "$continued_file" \ + "$success_file" \ + 2>&1 \ + <<'BASH' +shuriken="$1"; shift +config_file="$1"; shift +continued_file="$1"; shift +success_file="$1"; shift +export continued_file success_file + +# shellcheck source=/dev/null +source <(sed '$d' "$shuriken") + +dry_run() { + false + printf 'dry_run continued\n' > "$continued_file" +} + +SHURIKEN_CLI_ACTION=--dry-run +SHURIKEN_CLI_CONFIG_FILE="$config_file" +SHURIKEN_CLI_HAS_CONFIG_OVERRIDES=no +SHURIKEN_CLI_OVERRIDES=() +SHURIKEN_CLI_SYNC_DESTINATIONS=() +SHURIKEN_FORCE_GENERATE=no + +if run_configured_action; then + printf 'dispatcher reported success\n' > "$success_file" +fi +BASH + ) + status=$? + set -e + + if (( status != 0 )); then + printf 'FAIL: expected child shell to finish status-tested dispatch\n' >&2 + printf '%s\n' "$output" >&2 + exit 1 + fi + + test::assert_path_absent "$continued_file" + test::assert_path_absent "$success_file" + test::teardown +} + test_generate_real_failure_returns_with_errexit_disabled() { local config_file local fake_bin @@ -3656,6 +3721,99 @@ BASH test::teardown } +test_template_setup_failure_fails_status_tested_render() { + local context_file + local fake_bin + local output + local ran_file + local success_file + local template_dir + local -i status=0 + + test::setup + fake_bin="$TEST_TMPDIR/bin" + template_dir="$TEST_TMPDIR/templates" + context_file="$TEST_TMPDIR/template-context" + ran_file="$TEST_TMPDIR/template-ran" + success_file="$TEST_TMPDIR/template-success" + 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" + { + printf 'printf rendered >> %q\n' "$ran_file" + printf 'printf rendered\n' + } > "$template_dir/preview.tmpl" + + set +e + output=$( + bash -euo pipefail -s \ + "$TEST_SHURIKEN" \ + "$fake_bin" \ + "$template_dir" \ + "$TEST_TMPDIR/dist" \ + "$context_file" \ + "$success_file" \ + 2>&1 \ + <<'BASH' +shuriken="$1"; shift +fake_bin="$1"; shift +template_dir="$1"; shift +dist_dir="$1"; shift +context_file="$1"; shift +success_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 status test' +HEIGHT='' +THUMBHEIGHT=30 +MAXPREVIEWS=40 +ORIGINAL_BASEPATH='' +TARBALL_INCLUDE=no +SHURIKEN_OUTPUT_MODE=quiet + +serialize_template_render_context() { + false + printf 'partial_context=yes\n' +} + +if template preview out.html \ + animation_class '' \ + backhref '#' \ + html_dir . \ + page_num 1 \ + photo photo.jpg \ + preview_num 1 \ + thumbs_dir thumbs; then + printf 'template reported success\n' > "$success_file" +fi +BASH + ) + status=$? + set -e + + if (( status != 0 )); then + printf 'FAIL: expected child shell to finish status-tested render\n' >&2 + printf '%s\n' "$output" >&2 + exit 1 + fi + + test::assert_path_absent "$context_file" + test::assert_path_absent "$ran_file" + test::assert_path_absent "$success_file" + test::teardown +} + test_generate_swap_failure_restores_dist() { local config_file local fake_bin @@ -4476,6 +4634,9 @@ main() { '--generate action runs with errexit active' \ test_generate_action_runs_with_errexit_active test::run_case \ + '--generate action failure fails status-tested dispatcher' \ + test_generate_action_failure_fails_status_tested_dispatcher + test::run_case \ '--generate real failure returns with errexit disabled' \ test_generate_real_failure_returns_with_errexit_disabled test::run_case \ @@ -4575,6 +4736,9 @@ main() { 'template setup failure removes context file with errexit' \ test_template_setup_failure_removes_context_file_with_errexit test::run_case \ + 'template setup failure fails status-tested render' \ + test_template_setup_failure_fails_status_tested_render + test::run_case \ '--generate swap failure restores final dist' \ test_generate_swap_failure_restores_dist test::run_case \ |
