diff options
| -rwxr-xr-x | bin/photoalbum | 205 | ||||
| -rwxr-xr-x | src/photoalbum.sh | 205 | ||||
| -rwxr-xr-x | tests/cli.sh | 156 | ||||
| -rwxr-xr-x | tests/helpers.sh | 59 |
4 files changed, 589 insertions, 36 deletions
diff --git a/bin/photoalbum b/bin/photoalbum index 03d017b..a3ff361 100755 --- a/bin/photoalbum +++ b/bin/photoalbum @@ -113,7 +113,8 @@ tarball() { find "$DIST_DIR" -maxdepth 1 -type f -name '*.tar' -delete base=$(basename "$INCOMING_DIR") - echo "Creating tarball $DIST_DIR/$tarball_name from $INCOMING_DIR" + echo "Creating tarball $(_display_path "$DIST_DIR/$tarball_name")" \ + "from $INCOMING_DIR" ( cd "$(dirname "$INCOMING_DIR")" tar "$tar_opts" -f "$DIST_DIR/$tarball_name" "$base" @@ -145,6 +146,17 @@ _css_string_escape() { printf '%s\n' "$text" } +_display_path() { + local -r path="$1"; shift + local -r final_dist="${PHOTOALBUM_FINAL_DIST_DIR:-}" + + if [[ -n "$final_dist" && "$path" == "$DIST_DIR"* ]]; then + printf '%s%s\n' "$final_dist" "${path#"$DIST_DIR"}" + else + printf '%s\n' "$path" + fi +} + template() { local -r template_name="$1"; shift local -r html="$1"; shift @@ -168,7 +180,7 @@ template() { local thumbs_dir_html local title_html - echo "Generating $dist_html/$html" + echo "Generating $(_display_path "$dist_html")/$html" mkdir -p "$dist_html" animation_class_html=$(_html_escape "${animation_class:-}") @@ -224,7 +236,7 @@ cleanphotos() { continue fi - echo "Cleaning up $photo" + echo "Cleaning up $(_display_path "$photo")" for sub in thumbs blurs photos; do if [ -f "$DIST_DIR/$sub/$basename" ]; then rm -v "$DIST_DIR/$sub/$basename" @@ -244,11 +256,11 @@ scalephotos() { mkdir -p "$dirname" if [ -f "$destphoto" ]; then - echo "Already exists: $destphoto" + echo "Already exists: $(_display_path "$destphoto")" continue fi - echo "Processing $photo to $destphoto" + echo "Processing $photo to $(_display_path "$destphoto")" if [ -n "${HEIGHT:-}" ]; then # Scale down size. imagemagick \ @@ -385,12 +397,12 @@ albumhtml() { if [[ -f "$DIST_DIR/$thumbs_dir/$photo" \ && -f "$DIST_DIR/$blurs_dir/$photo" ]]; then - echo "Already exists: $DIST_DIR/$thumbs_dir/$photo and" \ - "$DIST_DIR/$blurs_dir/$photo" + echo "Already exists: $(_display_path "$DIST_DIR/$thumbs_dir/$photo") and" \ + "$(_display_path "$DIST_DIR/$blurs_dir/$photo")" else dirname="$DIST_DIR/$thumbs_dir" mkdir -p "$dirname" - echo "Creating thumb $DIST_DIR/$thumbs_dir/$photo" + echo "Creating thumb $(_display_path "$DIST_DIR/$thumbs_dir/$photo")" # Double the height, as CSS scales images based on boxing too. height=$(( THUMBHEIGHT * 2 )) imagemagick \ @@ -400,7 +412,7 @@ albumhtml() { dirname="$DIST_DIR/$blurs_dir" mkdir -p "$dirname" - echo "Creating blur $DIST_DIR/$blurs_dir/$photo" + echo "Creating blur $(_display_path "$DIST_DIR/$blurs_dir/$photo")" imagemagick \ "$DIST_DIR/$thumbs_dir/$photo" \ -flip \ @@ -465,7 +477,8 @@ randomphoto() { ) if (( ${#photos[@]} == 0 )); then - echo "ERROR: No photos found in $DIST_DIR/$photos_dir" >&2 + echo "ERROR: No photos found in" \ + "$(_display_path "$DIST_DIR/$photos_dir")" >&2 return 1 fi @@ -503,6 +516,168 @@ generate() { fi } +existing_parent_dir() { + local -r path="$1"; shift + local existing_parent + + existing_parent=$(dirname "$path") + + while [ ! -e "$existing_parent" ]; do + existing_parent=$(dirname "$existing_parent") + done + + printf '%s\n' "$existing_parent" +} + +generation_staging_dir() { + local -r final_dist="$1"; shift + local final_base + local staging_parent + + final_base=$(basename "$final_dist") + staging_parent=$(existing_parent_dir "$final_dist") + + mktemp -d "$staging_parent/.photoalbum.$final_base.staging.XXXXXX" +} + +prepare_generation_staging_dir() { + local -r final_dist="$1"; shift + local -r staging_dir="$1"; shift + local cache_dir + + for cache_dir in photos thumbs blurs; do + if [ -d "$final_dist/$cache_dir" ]; then + if ! mkdir -p "$staging_dir/$cache_dir"; then + return 1 + fi + if ! cp -a "$final_dist/$cache_dir/." "$staging_dir/$cache_dir/"; then + return 1 + fi + fi + done +} + +cleanup_generation_staging_dir() { + if [ -n "${PHOTOALBUM_ACTIVE_STAGING_DIR:-}" ]; then + rm -rf "$PHOTOALBUM_ACTIVE_STAGING_DIR" + PHOTOALBUM_ACTIVE_STAGING_DIR='' + fi +} + +clear_generation_staging_traps() { + trap - EXIT INT TERM +} + +ignore_generation_staging_interrupts() { + trap '' INT TERM +} + +replace_dist_with_staging() { + local -r staging_dir="$1"; shift + local -r final_dist="$1"; shift + local backup_dist='' + local backup_parent='' + local final_base + local final_parent + local staging_parent + local -i status=0 + + final_base=$(basename "$final_dist") + final_parent=$(dirname "$final_dist") + staging_parent=$(existing_parent_dir "$final_dist") + + if ! mkdir -p "$final_parent"; then + return 1 + fi + + if [ -e "$final_dist" ]; then + if ! backup_parent=$( + mktemp -d "$staging_parent/.photoalbum.$final_base.backup.XXXXXX" + ); then + return 1 + fi + backup_dist="$backup_parent/dist" + + if ! mv "$final_dist" "$backup_dist"; then + rm -rf "$backup_parent" + return 1 + fi + fi + + if mv "$staging_dir" "$final_dist"; then + if [ -n "$backup_parent" ]; then + rm -rf "$backup_parent" + fi + return 0 + else + status=$? + fi + + if [ -n "$backup_dist" ] && [ -e "$backup_dist" ]; then + if [ -e "$final_dist" ] && ! rm -rf "$final_dist"; then + echo "ERROR: Failed to restore $final_dist from $backup_dist" >&2 + return "$status" + fi + if ! mv "$backup_dist" "$final_dist"; then + echo "ERROR: Failed to restore $final_dist from $backup_dist" >&2 + return "$status" + fi + rm -rf "$backup_parent" + fi + + return "$status" +} + +generate_staged() { + local -r final_dist="$DIST_DIR" + local staging_dir + local -i status=0 + + staging_dir=$(generation_staging_dir "$final_dist") + PHOTOALBUM_ACTIVE_STAGING_DIR="$staging_dir" + trap cleanup_generation_staging_dir EXIT + trap 'cleanup_generation_staging_dir; exit 130' INT + trap 'cleanup_generation_staging_dir; exit 143' TERM + + if ! prepare_generation_staging_dir "$final_dist" "$staging_dir"; then + cleanup_generation_staging_dir + clear_generation_staging_traps + return 1 + fi + + set +e + ( + set -e + PHOTOALBUM_FINAL_DIST_DIR="$final_dist" + export PHOTOALBUM_FINAL_DIST_DIR + DIST_DIR="$staging_dir" + generate + ) + status=$? + set -e + + if (( status != 0 )); then + cleanup_generation_staging_dir + clear_generation_staging_traps + return "$status" + fi + + ignore_generation_staging_interrupts + set +e + replace_dist_with_staging "$staging_dir" "$final_dist" + status=$? + set -e + + if (( status != 0 )); then + cleanup_generation_staging_dir + clear_generation_staging_traps + return "$status" + fi + + PHOTOALBUM_ACTIVE_STAGING_DIR='' + clear_generation_staging_traps +} + resolve_config_file() { local -r config_file="${1:-}" @@ -619,7 +794,6 @@ validate_yes_no_config_var() { validate_dist_dir() { local existing_parent - local parent_dir if [ -e "$DIST_DIR" ]; then if [ ! -d "$DIST_DIR" ]; then @@ -632,12 +806,7 @@ validate_dist_dir() { return fi - parent_dir=$(dirname "$DIST_DIR") - existing_parent="$parent_dir" - - while [ ! -e "$existing_parent" ]; do - existing_parent=$(dirname "$existing_parent") - done + existing_parent=$(existing_parent_dir "$DIST_DIR") if [ ! -d "$existing_parent" ]; then config_error "DIST_DIR parent $existing_parent must be a directory" @@ -854,7 +1023,7 @@ main() { ;; --generate) validate_generation_config - generate + generate_staged ;; esac ;; diff --git a/src/photoalbum.sh b/src/photoalbum.sh index 9cb17a8..af520f5 100755 --- a/src/photoalbum.sh +++ b/src/photoalbum.sh @@ -113,7 +113,8 @@ tarball() { find "$DIST_DIR" -maxdepth 1 -type f -name '*.tar' -delete base=$(basename "$INCOMING_DIR") - echo "Creating tarball $DIST_DIR/$tarball_name from $INCOMING_DIR" + echo "Creating tarball $(_display_path "$DIST_DIR/$tarball_name")" \ + "from $INCOMING_DIR" ( cd "$(dirname "$INCOMING_DIR")" tar "$tar_opts" -f "$DIST_DIR/$tarball_name" "$base" @@ -145,6 +146,17 @@ _css_string_escape() { printf '%s\n' "$text" } +_display_path() { + local -r path="$1"; shift + local -r final_dist="${PHOTOALBUM_FINAL_DIST_DIR:-}" + + if [[ -n "$final_dist" && "$path" == "$DIST_DIR"* ]]; then + printf '%s%s\n' "$final_dist" "${path#"$DIST_DIR"}" + else + printf '%s\n' "$path" + fi +} + template() { local -r template_name="$1"; shift local -r html="$1"; shift @@ -168,7 +180,7 @@ template() { local thumbs_dir_html local title_html - echo "Generating $dist_html/$html" + echo "Generating $(_display_path "$dist_html")/$html" mkdir -p "$dist_html" animation_class_html=$(_html_escape "${animation_class:-}") @@ -224,7 +236,7 @@ cleanphotos() { continue fi - echo "Cleaning up $photo" + echo "Cleaning up $(_display_path "$photo")" for sub in thumbs blurs photos; do if [ -f "$DIST_DIR/$sub/$basename" ]; then rm -v "$DIST_DIR/$sub/$basename" @@ -244,11 +256,11 @@ scalephotos() { mkdir -p "$dirname" if [ -f "$destphoto" ]; then - echo "Already exists: $destphoto" + echo "Already exists: $(_display_path "$destphoto")" continue fi - echo "Processing $photo to $destphoto" + echo "Processing $photo to $(_display_path "$destphoto")" if [ -n "${HEIGHT:-}" ]; then # Scale down size. imagemagick \ @@ -385,12 +397,12 @@ albumhtml() { if [[ -f "$DIST_DIR/$thumbs_dir/$photo" \ && -f "$DIST_DIR/$blurs_dir/$photo" ]]; then - echo "Already exists: $DIST_DIR/$thumbs_dir/$photo and" \ - "$DIST_DIR/$blurs_dir/$photo" + echo "Already exists: $(_display_path "$DIST_DIR/$thumbs_dir/$photo") and" \ + "$(_display_path "$DIST_DIR/$blurs_dir/$photo")" else dirname="$DIST_DIR/$thumbs_dir" mkdir -p "$dirname" - echo "Creating thumb $DIST_DIR/$thumbs_dir/$photo" + echo "Creating thumb $(_display_path "$DIST_DIR/$thumbs_dir/$photo")" # Double the height, as CSS scales images based on boxing too. height=$(( THUMBHEIGHT * 2 )) imagemagick \ @@ -400,7 +412,7 @@ albumhtml() { dirname="$DIST_DIR/$blurs_dir" mkdir -p "$dirname" - echo "Creating blur $DIST_DIR/$blurs_dir/$photo" + echo "Creating blur $(_display_path "$DIST_DIR/$blurs_dir/$photo")" imagemagick \ "$DIST_DIR/$thumbs_dir/$photo" \ -flip \ @@ -465,7 +477,8 @@ randomphoto() { ) if (( ${#photos[@]} == 0 )); then - echo "ERROR: No photos found in $DIST_DIR/$photos_dir" >&2 + echo "ERROR: No photos found in" \ + "$(_display_path "$DIST_DIR/$photos_dir")" >&2 return 1 fi @@ -503,6 +516,168 @@ generate() { fi } +existing_parent_dir() { + local -r path="$1"; shift + local existing_parent + + existing_parent=$(dirname "$path") + + while [ ! -e "$existing_parent" ]; do + existing_parent=$(dirname "$existing_parent") + done + + printf '%s\n' "$existing_parent" +} + +generation_staging_dir() { + local -r final_dist="$1"; shift + local final_base + local staging_parent + + final_base=$(basename "$final_dist") + staging_parent=$(existing_parent_dir "$final_dist") + + mktemp -d "$staging_parent/.photoalbum.$final_base.staging.XXXXXX" +} + +prepare_generation_staging_dir() { + local -r final_dist="$1"; shift + local -r staging_dir="$1"; shift + local cache_dir + + for cache_dir in photos thumbs blurs; do + if [ -d "$final_dist/$cache_dir" ]; then + if ! mkdir -p "$staging_dir/$cache_dir"; then + return 1 + fi + if ! cp -a "$final_dist/$cache_dir/." "$staging_dir/$cache_dir/"; then + return 1 + fi + fi + done +} + +cleanup_generation_staging_dir() { + if [ -n "${PHOTOALBUM_ACTIVE_STAGING_DIR:-}" ]; then + rm -rf "$PHOTOALBUM_ACTIVE_STAGING_DIR" + PHOTOALBUM_ACTIVE_STAGING_DIR='' + fi +} + +clear_generation_staging_traps() { + trap - EXIT INT TERM +} + +ignore_generation_staging_interrupts() { + trap '' INT TERM +} + +replace_dist_with_staging() { + local -r staging_dir="$1"; shift + local -r final_dist="$1"; shift + local backup_dist='' + local backup_parent='' + local final_base + local final_parent + local staging_parent + local -i status=0 + + final_base=$(basename "$final_dist") + final_parent=$(dirname "$final_dist") + staging_parent=$(existing_parent_dir "$final_dist") + + if ! mkdir -p "$final_parent"; then + return 1 + fi + + if [ -e "$final_dist" ]; then + if ! backup_parent=$( + mktemp -d "$staging_parent/.photoalbum.$final_base.backup.XXXXXX" + ); then + return 1 + fi + backup_dist="$backup_parent/dist" + + if ! mv "$final_dist" "$backup_dist"; then + rm -rf "$backup_parent" + return 1 + fi + fi + + if mv "$staging_dir" "$final_dist"; then + if [ -n "$backup_parent" ]; then + rm -rf "$backup_parent" + fi + return 0 + else + status=$? + fi + + if [ -n "$backup_dist" ] && [ -e "$backup_dist" ]; then + if [ -e "$final_dist" ] && ! rm -rf "$final_dist"; then + echo "ERROR: Failed to restore $final_dist from $backup_dist" >&2 + return "$status" + fi + if ! mv "$backup_dist" "$final_dist"; then + echo "ERROR: Failed to restore $final_dist from $backup_dist" >&2 + return "$status" + fi + rm -rf "$backup_parent" + fi + + return "$status" +} + +generate_staged() { + local -r final_dist="$DIST_DIR" + local staging_dir + local -i status=0 + + staging_dir=$(generation_staging_dir "$final_dist") + PHOTOALBUM_ACTIVE_STAGING_DIR="$staging_dir" + trap cleanup_generation_staging_dir EXIT + trap 'cleanup_generation_staging_dir; exit 130' INT + trap 'cleanup_generation_staging_dir; exit 143' TERM + + if ! prepare_generation_staging_dir "$final_dist" "$staging_dir"; then + cleanup_generation_staging_dir + clear_generation_staging_traps + return 1 + fi + + set +e + ( + set -e + PHOTOALBUM_FINAL_DIST_DIR="$final_dist" + export PHOTOALBUM_FINAL_DIST_DIR + DIST_DIR="$staging_dir" + generate + ) + status=$? + set -e + + if (( status != 0 )); then + cleanup_generation_staging_dir + clear_generation_staging_traps + return "$status" + fi + + ignore_generation_staging_interrupts + set +e + replace_dist_with_staging "$staging_dir" "$final_dist" + status=$? + set -e + + if (( status != 0 )); then + cleanup_generation_staging_dir + clear_generation_staging_traps + return "$status" + fi + + PHOTOALBUM_ACTIVE_STAGING_DIR='' + clear_generation_staging_traps +} + resolve_config_file() { local -r config_file="${1:-}" @@ -619,7 +794,6 @@ validate_yes_no_config_var() { validate_dist_dir() { local existing_parent - local parent_dir if [ -e "$DIST_DIR" ]; then if [ ! -d "$DIST_DIR" ]; then @@ -632,12 +806,7 @@ validate_dist_dir() { return fi - parent_dir=$(dirname "$DIST_DIR") - existing_parent="$parent_dir" - - while [ ! -e "$existing_parent" ]; do - existing_parent=$(dirname "$existing_parent") - done + existing_parent=$(existing_parent_dir "$DIST_DIR") if [ ! -d "$existing_parent" ]; then config_error "DIST_DIR parent $existing_parent must be a directory" @@ -854,7 +1023,7 @@ main() { ;; --generate) validate_generation_config - generate + generate_staged ;; esac ;; diff --git a/tests/cli.sh b/tests/cli.sh index c998075..91de42a 100755 --- a/tests/cli.sh +++ b/tests/cli.sh @@ -87,15 +87,20 @@ test_init_existing_config_fails_without_overwrite() { } test_clean() { + local staging_dir + test::setup printf 'DIST_DIR=%q/dist\n' "$TEST_TMPDIR" \ > "$TEST_TMPDIR/photoalbum.conf" mkdir -p "$TEST_TMPDIR/dist" + staging_dir="$TEST_TMPDIR/.photoalbum.dist.staging.manual" + mkdir -p "$staging_dir" ( cd "$TEST_TMPDIR" "$TEST_PHOTOALBUM" --clean test::assert_path_absent "$TEST_TMPDIR/dist" + test::assert_dir_exists "$staging_dir" ) test::teardown } @@ -727,6 +732,145 @@ test_integration_generates_album_outputs_and_cleans() { test::teardown } +test_generate_replaces_dist_after_success() { + local config_file + local fake_bin + + test::setup + fake_bin="$TEST_TMPDIR/bin" + config_file="$TEST_TMPDIR/photoalbum.conf" + + test::install_fake_imagemagick "$fake_bin" + PATH="$fake_bin:$PATH" \ + test::generate_fixture_images "$TEST_TMPDIR/incoming" + test::write_album_config \ + "$config_file" "$TEST_TMPDIR/incoming" "$TEST_TMPDIR/dist" \ + 'Replace album' 40 + mkdir -p "$TEST_TMPDIR/dist/html" "$TEST_TMPDIR/dist/photos" + printf 'stale\n' > "$TEST_TMPDIR/dist/stale-root-file" + printf 'stale\n' > "$TEST_TMPDIR/dist/html/stale.html" + printf 'stale\n' > "$TEST_TMPDIR/dist/photos/stale.jpg" + + ( + cd "$TEST_TMPDIR" + PATH="$fake_bin:$PATH" "$TEST_PHOTOALBUM" --generate + ) + + test::assert_file_exists "$TEST_TMPDIR/dist/photos/01-landscape.jpg" + test::assert_file_exists "$TEST_TMPDIR/dist/html/page-1.html" + test::assert_path_absent "$TEST_TMPDIR/dist/stale-root-file" + test::assert_path_absent "$TEST_TMPDIR/dist/html/stale.html" + test::assert_path_absent "$TEST_TMPDIR/dist/photos/stale.jpg" + test::assert_no_staging_dirs "$TEST_TMPDIR" + test::teardown +} + +test_generate_imagemagick_failure_preserves_dist() { + local config_file + local fake_bin + local output + + test::setup + fake_bin="$TEST_TMPDIR/bin" + config_file="$TEST_TMPDIR/photoalbum.conf" + + test::install_failing_imagemagick "$fake_bin" + mkdir -p "$TEST_TMPDIR/incoming" "$TEST_TMPDIR/dist" + printf 'fake image\n' > "$TEST_TMPDIR/incoming/01.jpg" + printf 'old dist\n' > "$TEST_TMPDIR/dist/index.html" + printf 'keep me\n' > "$TEST_TMPDIR/dist/sentinel" + test::write_album_config \ + "$config_file" "$TEST_TMPDIR/incoming" "$TEST_TMPDIR/dist" \ + 'Failing ImageMagick album' 40 + + output=$( + cd "$TEST_TMPDIR" + PATH="$fake_bin:$PATH" \ + test::capture_failure_output "$TEST_PHOTOALBUM" --generate + ) + + test::assert_contains 'simulated ImageMagick failure' "$output" + test "$(cat "$TEST_TMPDIR/dist/index.html")" = 'old dist' + test "$(cat "$TEST_TMPDIR/dist/sentinel")" = 'keep me' + test::assert_path_absent "$TEST_TMPDIR/dist/photos/01.jpg" + test::assert_no_staging_dirs "$TEST_TMPDIR" + test::teardown +} + +test_generate_template_failure_preserves_dist() { + local config_file + local fake_bin + local output + local template_dir + + test::setup + fake_bin="$TEST_TMPDIR/bin" + config_file="$TEST_TMPDIR/photoalbum.conf" + template_dir="$TEST_TMPDIR/templates" + + test::install_fake_imagemagick "$fake_bin" + PATH="$fake_bin:$PATH" \ + test::generate_fixture_images "$TEST_TMPDIR/incoming" + cp -R "$TEST_REPO_ROOT/share/templates/default" "$template_dir" + printf 'return 42\n' > "$template_dir/preview.tmpl" + mkdir -p "$TEST_TMPDIR/dist" + printf 'old index\n' > "$TEST_TMPDIR/dist/index.html" + test::write_album_config \ + "$config_file" "$TEST_TMPDIR/incoming" "$TEST_TMPDIR/dist" \ + 'Failing template album' 40 + printf 'TEMPLATE_DIR=%q\n' "$template_dir" >> "$config_file" + + output=$( + cd "$TEST_TMPDIR" + PATH="$fake_bin:$PATH" \ + test::capture_failure_output "$TEST_PHOTOALBUM" --generate + ) + + test::assert_contains 'Generating' "$output" + test "$(cat "$TEST_TMPDIR/dist/index.html")" = 'old index' + test::assert_path_absent "$TEST_TMPDIR/dist/photos/01-landscape.jpg" + test::assert_no_staging_dirs "$TEST_TMPDIR" + test::teardown +} + +test_generate_swap_failure_restores_dist() { + local config_file + local fake_bin + local mv_count_file + local output + + test::setup + fake_bin="$TEST_TMPDIR/bin" + config_file="$TEST_TMPDIR/photoalbum.conf" + mv_count_file="$TEST_TMPDIR/mv-count" + + test::install_fake_imagemagick "$fake_bin" + test::install_mv_spy "$fake_bin" + PATH="$fake_bin:$PATH" \ + test::generate_fixture_images "$TEST_TMPDIR/incoming" + mkdir -p "$TEST_TMPDIR/dist" + printf 'old index\n' > "$TEST_TMPDIR/dist/index.html" + printf 'old sentinel\n' > "$TEST_TMPDIR/dist/sentinel" + test::write_album_config \ + "$config_file" "$TEST_TMPDIR/incoming" "$TEST_TMPDIR/dist" \ + 'Swap failure album' 40 + + output=$( + cd "$TEST_TMPDIR" + PATH="$fake_bin:$PATH" \ + TEST_MV_COUNT_FILE="$mv_count_file" \ + TEST_FAIL_MV_ON=2 \ + test::capture_failure_output "$TEST_PHOTOALBUM" --generate + ) + + test::assert_contains 'simulated mv failure' "$output" + test "$(cat "$TEST_TMPDIR/dist/index.html")" = 'old index' + test "$(cat "$TEST_TMPDIR/dist/sentinel")" = 'old sentinel' + test::assert_path_absent "$TEST_TMPDIR/dist/photos/01-landscape.jpg" + test::assert_no_staging_dirs "$TEST_TMPDIR" + test::teardown +} + test_generate_missing_imagemagick_fails() { local config_file local output @@ -1019,6 +1163,18 @@ main() { '--generate creates output structure and --clean removes it' \ test_integration_generates_album_outputs_and_cleans test::run_case \ + '--generate replaces final dist after success' \ + test_generate_replaces_dist_after_success + test::run_case \ + '--generate ImageMagick failure preserves final dist' \ + test_generate_imagemagick_failure_preserves_dist + test::run_case \ + '--generate template failure preserves final dist' \ + test_generate_template_failure_preserves_dist + test::run_case \ + '--generate swap failure restores final dist' \ + test_generate_swap_failure_restores_dist + test::run_case \ '--generate fails when ImageMagick is missing' \ test_generate_missing_imagemagick_fails test::run_case \ diff --git a/tests/helpers.sh b/tests/helpers.sh index 8590b36..f0ef852 100755 --- a/tests/helpers.sh +++ b/tests/helpers.sh @@ -164,6 +164,19 @@ test::assert_find_count() { fi } +test::assert_no_staging_dirs() { + local -r dir="$1"; shift + local found + + found=$(find "$dir" -type d -name '.photoalbum.*' -print -quit) + + if [ -n "$found" ]; then + echo "FAIL: expected no staging directories under $dir" >&2 + echo "found $found" >&2 + exit 1 + fi +} + test::run_photoalbum() { "$TEST_PHOTOALBUM" "$@" 2>&1 } @@ -191,6 +204,52 @@ MAGICK cp "$bin_dir/magick" "$bin_dir/convert" } +test::install_failing_imagemagick() { + local -r bin_dir="$1"; shift + + mkdir -p "$bin_dir" + + cat > "$bin_dir/magick" <<'MAGICK' +#!/usr/bin/env bash +set -euo pipefail + +echo 'simulated ImageMagick failure' >&2 +exit 42 +MAGICK + chmod 0755 "$bin_dir/magick" + cp "$bin_dir/magick" "$bin_dir/convert" +} + +test::install_mv_spy() { + local -r bin_dir="$1"; shift + local real_mv + + mkdir -p "$bin_dir" + real_mv=$(command -v mv) + + cat > "$bin_dir/mv" <<MV +#!/usr/bin/env bash +set -euo pipefail + +count=0 +if [ -n "\${TEST_MV_COUNT_FILE:-}" ] && [ -f "\$TEST_MV_COUNT_FILE" ]; then + count=\$(<"\$TEST_MV_COUNT_FILE") +fi +count=\$(( count + 1 )) +if [ -n "\${TEST_MV_COUNT_FILE:-}" ]; then + printf '%s\n' "\$count" > "\$TEST_MV_COUNT_FILE" +fi + +if [[ -n "\${TEST_FAIL_MV_ON:-}" && "\$count" = "\$TEST_FAIL_MV_ON" ]]; then + echo 'simulated mv failure' >&2 + exit 42 +fi + +"$real_mv" "\$@" +MV + chmod 0755 "$bin_dir/mv" +} + test::install_sort_spy() { local -r bin_dir="$1"; shift local real_sort |
