summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-06-03 20:15:04 +0300
committerPaul Buetow <paul@buetow.org>2026-06-03 20:15:04 +0300
commit2a22fa084a89494016bf92b6674eb5bd3ceefcfb (patch)
treefba6114e2d7cb48eb2c8d74c8afa96c4a9296f6b
parenta9e9d2db616c4cb1a1fd192e38d53fba69b0102d (diff)
Add deterministic generation mode for task 3j0
-rw-r--r--README.md19
-rwxr-xr-xbin/photoalbum122
-rw-r--r--src/photoalbum.default.conf3
-rwxr-xr-xsrc/photoalbum.sh122
-rwxr-xr-xtests/cli.sh59
5 files changed, 295 insertions, 30 deletions
diff --git a/README.md b/README.md
index 11acd58..3b0dc09 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,7 @@ missing, run `photoalbum --init` first.
The config file is a Bash file with assignments such as `INCOMING_DIR`,
`DIST_DIR`, `TEMPLATE_DIR`, `TITLE`, `HEIGHT`, `THUMBHEIGHT`, `MAXPREVIEWS`,
-`SHUFFLE`, and `TARBALL_INCLUDE`.
+`RANDOM_SEED`, `SHUFFLE`, and `TARBALL_INCLUDE`.
Before generating, `photoalbum` validates the loaded config and command-line
overrides. It checks required values, positive integer settings, `yes`/`no`
@@ -66,11 +66,11 @@ tarball filename uses `<timestamp>` as a placeholder so the output is stable.
`--print-config` writes stable shell-style assignments to stdout in this order:
`CONFIG_SOURCE`, `INCOMING_DIR`, `DIST_DIR`, `TEMPLATE_DIR`, `TITLE`, `HEIGHT`,
-`THUMBHEIGHT`, `MAXPREVIEWS`, `SHUFFLE`, `TARBALL_INCLUDE`, `TARBALL_SUFFIX`,
-`TAR_OPTS`, and `ORIGINAL_BASEPATH`. Scalar values use Bash `%q` quoting and
-`TAR_OPTS` is normalized to a Bash array assignment, so the output can be parsed
-by shell tooling. `--quiet` does not suppress this output, and `--verbose` does
-not add human-readable diagnostics to it.
+`THUMBHEIGHT`, `MAXPREVIEWS`, `RANDOM_SEED`, `SHUFFLE`, `TARBALL_INCLUDE`,
+`TARBALL_SUFFIX`, `TAR_OPTS`, and `ORIGINAL_BASEPATH`. Scalar values use Bash
+`%q` quoting and `TAR_OPTS` is normalized to a Bash array assignment, so the
+output can be parsed by shell tooling. `--quiet` does not suppress this output,
+and `--verbose` does not add human-readable diagnostics to it.
Successful generation writes `photoalbum.json` into the output directory. This
metadata records the generator version and timestamp, config source, template
@@ -88,11 +88,18 @@ The following long options override config values:
| `--height VALUE` | `HEIGHT` |
| `--thumbheight VALUE` | `THUMBHEIGHT` |
| `--maxpreviews N` | `MAXPREVIEWS` |
+| `--random-seed VALUE` | `RANDOM_SEED` |
| `--shuffle` | `SHUFFLE=yes` |
| `--no-shuffle` | `SHUFFLE=no` |
| `--tarball` | `TARBALL_INCLUDE=yes` |
| `--no-tarball` | `TARBALL_INCLUDE=no` |
+By default, background photos, animation classes, generated timestamps, and
+`--shuffle` preview order remain non-deterministic. Set `RANDOM_SEED` in the
+config, or pass `--random-seed VALUE`, to make those choices repeatable for
+stable tests or reproducible album builds. Use the same seed and inputs to
+produce the same HTML.
+
`--dry-run` and `--print-config` accept the same override options as
`--generate`. `--clean` accepts the same override options, but only `--dist`
changes what it removes.
diff --git a/bin/photoalbum b/bin/photoalbum
index cc367c1..482ef20 100755
--- a/bin/photoalbum
+++ b/bin/photoalbum
@@ -30,6 +30,7 @@ usage() {
--height VALUE
--thumbheight VALUE
--maxpreviews N
+ --random-seed VALUE
--shuffle
--no-shuffle
--tarball
@@ -323,6 +324,30 @@ _display_path() {
fi
}
+current_date_text() {
+ if random_seed_is_set; then
+ printf 'Thu Jan 1 00:00:00 UTC 1970\n'
+ else
+ command date
+ fi
+}
+
+current_timestamp_slug() {
+ if random_seed_is_set; then
+ printf '1970-01-01-000000\n'
+ else
+ command date +'%Y-%m-%d-%H%M%S'
+ fi
+}
+
+current_timestamp_iso() {
+ if random_seed_is_set; then
+ printf '1970-01-01T00:00:00Z\n'
+ else
+ command date -u +'%Y-%m-%dT%H:%M:%SZ'
+ fi
+}
+
template() {
local -r template_name="$1"; shift
local -r html="$1"; shift
@@ -387,7 +412,20 @@ template() {
thumbs_dir_html \
title_html
- source "$TEMPLATE_DIR/$template_name.tmpl" >> "$dist_html/$html"
+ if random_seed_is_set; then
+ # shellcheck disable=SC2329
+ date() {
+ if (( $# == 0 )); then
+ current_date_text
+ else
+ command date "$@"
+ fi
+ }
+ source "$TEMPLATE_DIR/$template_name.tmpl" >> "$dist_html/$html"
+ unset -f date
+ else
+ source "$TEMPLATE_DIR/$template_name.tmpl" >> "$dist_html/$html"
+ fi
}
cleanphotos() {
@@ -488,8 +526,36 @@ scalephotos() {
done < <(incoming_image_files)
}
+random_seed_is_set() {
+ [ -n "${RANDOM_SEED:-}" ]
+}
+
+deterministic_index() {
+ local -r namespace="$1"; shift
+ local -r count="$1"; shift
+ local checksum
+
+ checksum=$(printf '%s' "${RANDOM_SEED}:$namespace" | cksum)
+ checksum=${checksum%% *}
+
+ printf '%s\n' $(( checksum % count ))
+}
+
+random_index() {
+ local -r namespace="$1"; shift
+ local -r count="$1"; shift
+
+ if random_seed_is_set; then
+ deterministic_index "$namespace" "$count"
+ else
+ printf '%s\n' $(( RANDOM % count ))
+ fi
+}
+
random_animation_css_class() {
local -r speed="$1"; shift
+ local -r context="${1:-$speed}"
+ local -i index
local -a classes=(
"animate-opacity-$speed"
"animate-top-$speed"
@@ -508,12 +574,28 @@ random_animation_css_class() {
"animate-glitch-step-$speed"
)
- printf '%s\n' "${classes[@]}" | sort -R | sed -n '1p'
+ index=$(random_index "animation:$speed:$context" "${#classes[@]}")
+ printf '%s\n' "${classes[index]}"
+}
+
+deterministic_shuffle() {
+ local checksum
+ local line
+
+ while IFS= read -r line; do
+ checksum=$(printf '%s' "${RANDOM_SEED}:shuffle:$line" | cksum)
+ checksum=${checksum%% *}
+ printf '%010u\t%s\n' "$checksum" "$line"
+ done | sort -n -k1,1 -k2,2 | cut -f2-
}
maybe_shuffle() {
if [ "${SHUFFLE:-no}" = yes ]; then
- sort -R
+ if random_seed_is_set; then
+ deterministic_shuffle
+ else
+ sort -R
+ fi
else
sort
fi
@@ -559,7 +641,7 @@ albumhtml() {
name="page-$num"
# Random background image for preview page.
- background_image=$(randomphoto "$photos_dir")
+ background_image=$(randomphoto "$photos_dir" "$name")
show_header_bar='yes'
export background_image show_header_bar
template 'header' "$name.html"
@@ -579,7 +661,7 @@ albumhtml() {
prev="$name"
name="$next"
- background_image=$(randomphoto "$photos_dir")
+ background_image=$(randomphoto "$photos_dir" "$name")
show_header_bar='no'
export background_image prev show_header_bar
template header "$name.html"
@@ -587,7 +669,7 @@ albumhtml() {
fi
# Preview page.
- animation_class=$(random_animation_css_class slow)
+ animation_class=$(random_animation_css_class slow "$photo")
export animation_class
template preview "$name.html"
@@ -597,7 +679,7 @@ albumhtml() {
export background_image show_header_bar
template header "$num-$i.html"
- animation_class=$(random_animation_css_class fast)
+ animation_class=$(random_animation_css_class fast "$photo")
export animation_class
template view "$num-$i.html"
template footer "$num-$i.html"
@@ -674,6 +756,8 @@ albumhtml() {
randomphoto() {
local -r photos_dir="$1"; shift
+ local -r context="${1:-$photos_dir}"
+ local -i index
local photo
local -a photos=()
@@ -690,7 +774,8 @@ randomphoto() {
return 1
fi
- printf '%s\n' "${photos[RANDOM % ${#photos[@]}]}"
+ index=$(random_index "photo:$photos_dir:$context" "${#photos[@]}")
+ printf '%s\n' "${photos[index]}"
}
count_files() {
@@ -722,11 +807,11 @@ tarball_name_plan() {
generated_tarball_name() {
local base
- local now
+ local timestamp
base=$(basename "$INCOMING_DIR")
- now=$(date +'%Y-%m-%d-%H%M%S')
- printf '%s-%s%s\n' "$base" "$now" "${TARBALL_SUFFIX:-.tar}"
+ timestamp=$(current_timestamp_slug)
+ printf '%s-%s%s\n' "$base" "$timestamp" "${TARBALL_SUFFIX:-.tar}"
}
count_tree_files() {
@@ -743,7 +828,7 @@ count_tree_files() {
write_generation_metadata() {
local -r tarball_file="$1"; shift
- local -r generated_at=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
+ local -r generated_at=$(current_timestamp_iso)
local -r config_source="${PHOTOALBUM_CONFIG_SOURCE:-}"
local -r template_name=$(basename "$TEMPLATE_DIR")
local -r source_image_count=$(count_incoming_images)
@@ -781,6 +866,7 @@ write_generation_metadata() {
printf ' "height": %s,\n' "$(_json_string "${HEIGHT:-}")"
printf ' "thumbheight": %s,\n' "$(_json_string "${THUMBHEIGHT:-}")"
printf ' "maxpreviews": %s,\n' "$(_json_string "${MAXPREVIEWS:-}")"
+ printf ' "random_seed": %s,\n' "$(_json_string "${RANDOM_SEED:-}")"
printf ' "shuffle": %s,\n' "$(_json_bool "${SHUFFLE:-no}")"
printf ' "original_basepath": %s\n' \
"$(_json_string "${ORIGINAL_BASEPATH:-}")"
@@ -848,6 +934,7 @@ dry_run() {
printf 'Height: %s\n' "${HEIGHT:-}"
printf 'Thumb height: %s\n' "$THUMBHEIGHT"
printf 'Max previews per page: %s\n' "$MAXPREVIEWS"
+ printf 'Random seed: %s\n' "${RANDOM_SEED:-}"
printf 'Shuffle: %s\n' "${SHUFFLE:-no}"
printf 'Image count: %s\n' "$image_count"
printf 'Tarball setting: %s\n' "${TARBALL_INCLUDE:-no}"
@@ -914,6 +1001,7 @@ print_config() {
print_shell_assignment HEIGHT "${HEIGHT:-}"
print_shell_assignment THUMBHEIGHT "$THUMBHEIGHT"
print_shell_assignment MAXPREVIEWS "$MAXPREVIEWS"
+ print_shell_assignment RANDOM_SEED "${RANDOM_SEED:-}"
print_shell_assignment SHUFFLE "${SHUFFLE:-no}"
print_shell_assignment TARBALL_INCLUDE "${TARBALL_INCLUDE:-no}"
print_shell_assignment TARBALL_SUFFIX "${TARBALL_SUFFIX:-.tar}"
@@ -1106,6 +1194,7 @@ missing_config() {
apply_config_defaults() {
HEIGHT="${HEIGHT:-}"
ORIGINAL_BASEPATH="${ORIGINAL_BASEPATH:-}"
+ RANDOM_SEED="${RANDOM_SEED:-}"
SHUFFLE="${SHUFFLE:-no}"
TARBALL_INCLUDE="${TARBALL_INCLUDE:-no}"
TARBALL_SUFFIX="${TARBALL_SUFFIX:-.tar}"
@@ -1148,6 +1237,9 @@ apply_cli_overrides() {
if [ -n "$cli_maxpreviews" ]; then
MAXPREVIEWS="$cli_maxpreviews"
fi
+ if [ -n "$cli_random_seed" ]; then
+ RANDOM_SEED="$cli_random_seed"
+ fi
if [ -n "$cli_shuffle" ]; then
SHUFFLE="$cli_shuffle"
fi
@@ -1329,6 +1421,7 @@ main() {
local cli_height=''
local cli_incoming_dir=''
local cli_maxpreviews=''
+ local cli_random_seed=''
local cli_shuffle=''
local cli_tarball_include=''
local cli_template_dir=''
@@ -1392,6 +1485,11 @@ main() {
has_config_overrides='yes'
shift
;;
+ --random-seed)
+ cli_random_seed=$(option_value "$option" "$@")
+ has_config_overrides='yes'
+ shift
+ ;;
--shuffle)
cli_shuffle='yes'
has_config_overrides='yes'
diff --git a/src/photoalbum.default.conf b/src/photoalbum.default.conf
index 4c581de..a4ffb1b 100644
--- a/src/photoalbum.default.conf
+++ b/src/photoalbum.default.conf
@@ -9,6 +9,9 @@ HEIGHT=1200
MAXPREVIEWS=40
# Randomly shuffle all previews.
# SHUFFLE=yes
+# Set to any non-empty value for repeatable background, animation, timestamp,
+# and shuffled preview choices.
+# RANDOM_SEED=album-build-1
# Diverse directories, need to be full paths, not relative!
INCOMING_DIR=$(pwd)/incoming
diff --git a/src/photoalbum.sh b/src/photoalbum.sh
index 59ef981..4a47031 100755
--- a/src/photoalbum.sh
+++ b/src/photoalbum.sh
@@ -30,6 +30,7 @@ usage() {
--height VALUE
--thumbheight VALUE
--maxpreviews N
+ --random-seed VALUE
--shuffle
--no-shuffle
--tarball
@@ -323,6 +324,30 @@ _display_path() {
fi
}
+current_date_text() {
+ if random_seed_is_set; then
+ printf 'Thu Jan 1 00:00:00 UTC 1970\n'
+ else
+ command date
+ fi
+}
+
+current_timestamp_slug() {
+ if random_seed_is_set; then
+ printf '1970-01-01-000000\n'
+ else
+ command date +'%Y-%m-%d-%H%M%S'
+ fi
+}
+
+current_timestamp_iso() {
+ if random_seed_is_set; then
+ printf '1970-01-01T00:00:00Z\n'
+ else
+ command date -u +'%Y-%m-%dT%H:%M:%SZ'
+ fi
+}
+
template() {
local -r template_name="$1"; shift
local -r html="$1"; shift
@@ -387,7 +412,20 @@ template() {
thumbs_dir_html \
title_html
- source "$TEMPLATE_DIR/$template_name.tmpl" >> "$dist_html/$html"
+ if random_seed_is_set; then
+ # shellcheck disable=SC2329
+ date() {
+ if (( $# == 0 )); then
+ current_date_text
+ else
+ command date "$@"
+ fi
+ }
+ source "$TEMPLATE_DIR/$template_name.tmpl" >> "$dist_html/$html"
+ unset -f date
+ else
+ source "$TEMPLATE_DIR/$template_name.tmpl" >> "$dist_html/$html"
+ fi
}
cleanphotos() {
@@ -488,8 +526,36 @@ scalephotos() {
done < <(incoming_image_files)
}
+random_seed_is_set() {
+ [ -n "${RANDOM_SEED:-}" ]
+}
+
+deterministic_index() {
+ local -r namespace="$1"; shift
+ local -r count="$1"; shift
+ local checksum
+
+ checksum=$(printf '%s' "${RANDOM_SEED}:$namespace" | cksum)
+ checksum=${checksum%% *}
+
+ printf '%s\n' $(( checksum % count ))
+}
+
+random_index() {
+ local -r namespace="$1"; shift
+ local -r count="$1"; shift
+
+ if random_seed_is_set; then
+ deterministic_index "$namespace" "$count"
+ else
+ printf '%s\n' $(( RANDOM % count ))
+ fi
+}
+
random_animation_css_class() {
local -r speed="$1"; shift
+ local -r context="${1:-$speed}"
+ local -i index
local -a classes=(
"animate-opacity-$speed"
"animate-top-$speed"
@@ -508,12 +574,28 @@ random_animation_css_class() {
"animate-glitch-step-$speed"
)
- printf '%s\n' "${classes[@]}" | sort -R | sed -n '1p'
+ index=$(random_index "animation:$speed:$context" "${#classes[@]}")
+ printf '%s\n' "${classes[index]}"
+}
+
+deterministic_shuffle() {
+ local checksum
+ local line
+
+ while IFS= read -r line; do
+ checksum=$(printf '%s' "${RANDOM_SEED}:shuffle:$line" | cksum)
+ checksum=${checksum%% *}
+ printf '%010u\t%s\n' "$checksum" "$line"
+ done | sort -n -k1,1 -k2,2 | cut -f2-
}
maybe_shuffle() {
if [ "${SHUFFLE:-no}" = yes ]; then
- sort -R
+ if random_seed_is_set; then
+ deterministic_shuffle
+ else
+ sort -R
+ fi
else
sort
fi
@@ -559,7 +641,7 @@ albumhtml() {
name="page-$num"
# Random background image for preview page.
- background_image=$(randomphoto "$photos_dir")
+ background_image=$(randomphoto "$photos_dir" "$name")
show_header_bar='yes'
export background_image show_header_bar
template 'header' "$name.html"
@@ -579,7 +661,7 @@ albumhtml() {
prev="$name"
name="$next"
- background_image=$(randomphoto "$photos_dir")
+ background_image=$(randomphoto "$photos_dir" "$name")
show_header_bar='no'
export background_image prev show_header_bar
template header "$name.html"
@@ -587,7 +669,7 @@ albumhtml() {
fi
# Preview page.
- animation_class=$(random_animation_css_class slow)
+ animation_class=$(random_animation_css_class slow "$photo")
export animation_class
template preview "$name.html"
@@ -597,7 +679,7 @@ albumhtml() {
export background_image show_header_bar
template header "$num-$i.html"
- animation_class=$(random_animation_css_class fast)
+ animation_class=$(random_animation_css_class fast "$photo")
export animation_class
template view "$num-$i.html"
template footer "$num-$i.html"
@@ -674,6 +756,8 @@ albumhtml() {
randomphoto() {
local -r photos_dir="$1"; shift
+ local -r context="${1:-$photos_dir}"
+ local -i index
local photo
local -a photos=()
@@ -690,7 +774,8 @@ randomphoto() {
return 1
fi
- printf '%s\n' "${photos[RANDOM % ${#photos[@]}]}"
+ index=$(random_index "photo:$photos_dir:$context" "${#photos[@]}")
+ printf '%s\n' "${photos[index]}"
}
count_files() {
@@ -722,11 +807,11 @@ tarball_name_plan() {
generated_tarball_name() {
local base
- local now
+ local timestamp
base=$(basename "$INCOMING_DIR")
- now=$(date +'%Y-%m-%d-%H%M%S')
- printf '%s-%s%s\n' "$base" "$now" "${TARBALL_SUFFIX:-.tar}"
+ timestamp=$(current_timestamp_slug)
+ printf '%s-%s%s\n' "$base" "$timestamp" "${TARBALL_SUFFIX:-.tar}"
}
count_tree_files() {
@@ -743,7 +828,7 @@ count_tree_files() {
write_generation_metadata() {
local -r tarball_file="$1"; shift
- local -r generated_at=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
+ local -r generated_at=$(current_timestamp_iso)
local -r config_source="${PHOTOALBUM_CONFIG_SOURCE:-}"
local -r template_name=$(basename "$TEMPLATE_DIR")
local -r source_image_count=$(count_incoming_images)
@@ -781,6 +866,7 @@ write_generation_metadata() {
printf ' "height": %s,\n' "$(_json_string "${HEIGHT:-}")"
printf ' "thumbheight": %s,\n' "$(_json_string "${THUMBHEIGHT:-}")"
printf ' "maxpreviews": %s,\n' "$(_json_string "${MAXPREVIEWS:-}")"
+ printf ' "random_seed": %s,\n' "$(_json_string "${RANDOM_SEED:-}")"
printf ' "shuffle": %s,\n' "$(_json_bool "${SHUFFLE:-no}")"
printf ' "original_basepath": %s\n' \
"$(_json_string "${ORIGINAL_BASEPATH:-}")"
@@ -848,6 +934,7 @@ dry_run() {
printf 'Height: %s\n' "${HEIGHT:-}"
printf 'Thumb height: %s\n' "$THUMBHEIGHT"
printf 'Max previews per page: %s\n' "$MAXPREVIEWS"
+ printf 'Random seed: %s\n' "${RANDOM_SEED:-}"
printf 'Shuffle: %s\n' "${SHUFFLE:-no}"
printf 'Image count: %s\n' "$image_count"
printf 'Tarball setting: %s\n' "${TARBALL_INCLUDE:-no}"
@@ -914,6 +1001,7 @@ print_config() {
print_shell_assignment HEIGHT "${HEIGHT:-}"
print_shell_assignment THUMBHEIGHT "$THUMBHEIGHT"
print_shell_assignment MAXPREVIEWS "$MAXPREVIEWS"
+ print_shell_assignment RANDOM_SEED "${RANDOM_SEED:-}"
print_shell_assignment SHUFFLE "${SHUFFLE:-no}"
print_shell_assignment TARBALL_INCLUDE "${TARBALL_INCLUDE:-no}"
print_shell_assignment TARBALL_SUFFIX "${TARBALL_SUFFIX:-.tar}"
@@ -1106,6 +1194,7 @@ missing_config() {
apply_config_defaults() {
HEIGHT="${HEIGHT:-}"
ORIGINAL_BASEPATH="${ORIGINAL_BASEPATH:-}"
+ RANDOM_SEED="${RANDOM_SEED:-}"
SHUFFLE="${SHUFFLE:-no}"
TARBALL_INCLUDE="${TARBALL_INCLUDE:-no}"
TARBALL_SUFFIX="${TARBALL_SUFFIX:-.tar}"
@@ -1148,6 +1237,9 @@ apply_cli_overrides() {
if [ -n "$cli_maxpreviews" ]; then
MAXPREVIEWS="$cli_maxpreviews"
fi
+ if [ -n "$cli_random_seed" ]; then
+ RANDOM_SEED="$cli_random_seed"
+ fi
if [ -n "$cli_shuffle" ]; then
SHUFFLE="$cli_shuffle"
fi
@@ -1329,6 +1421,7 @@ main() {
local cli_height=''
local cli_incoming_dir=''
local cli_maxpreviews=''
+ local cli_random_seed=''
local cli_shuffle=''
local cli_tarball_include=''
local cli_template_dir=''
@@ -1392,6 +1485,11 @@ main() {
has_config_overrides='yes'
shift
;;
+ --random-seed)
+ cli_random_seed=$(option_value "$option" "$@")
+ has_config_overrides='yes'
+ shift
+ ;;
--shuffle)
cli_shuffle='yes'
has_config_overrides='yes'
diff --git a/tests/cli.sh b/tests/cli.sh
index 728ede4..1351af8 100755
--- a/tests/cli.sh
+++ b/tests/cli.sh
@@ -447,6 +447,54 @@ test_generate_no_shuffle_override_uses_sorted_order() {
test::teardown
}
+test_generate_random_seed_repeats_html_with_shuffle() {
+ local config_file
+ local fake_bin
+ local sort_log
+
+ test::setup
+ fake_bin="$TEST_TMPDIR/bin"
+ config_file="$TEST_TMPDIR/photoalbum.conf"
+ sort_log="$TEST_TMPDIR/sort.log"
+
+ test::install_fake_imagemagick "$fake_bin"
+ test::install_sort_spy "$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-one" \
+ 'Seeded shuffle album' 2
+
+ (
+ cd "$TEST_TMPDIR"
+ PATH="$fake_bin:$PATH" TEST_SORT_LOG="$sort_log" \
+ "$TEST_PHOTOALBUM" \
+ --generate \
+ --shuffle \
+ --random-seed stable-seed \
+ --dist "$TEST_TMPDIR/dist-one"
+ PATH="$fake_bin:$PATH" TEST_SORT_LOG="$sort_log" \
+ "$TEST_PHOTOALBUM" \
+ --generate \
+ --shuffle \
+ --random-seed stable-seed \
+ --dist "$TEST_TMPDIR/dist-two"
+ )
+
+ if ! diff -ru "$TEST_TMPDIR/dist-one/html" "$TEST_TMPDIR/dist-two/html"; then
+ echo 'FAIL: seeded generation should produce identical HTML' >&2
+ exit 1
+ fi
+ if ! cmp -s "$TEST_TMPDIR/dist-one/index.html" \
+ "$TEST_TMPDIR/dist-two/index.html"; then
+ echo 'FAIL: seeded generation should produce identical top-level HTML' >&2
+ exit 1
+ fi
+
+ test::assert_path_absent "$sort_log"
+ test::teardown
+}
+
test_generate_cli_tarball_overrides_config() {
local config_file
local fake_bin
@@ -774,6 +822,7 @@ TITLE=A\\ simple\\ Photoalbum
HEIGHT=1200
THUMBHEIGHT=300
MAXPREVIEWS=40
+RANDOM_SEED=''
SHUFFLE=no
TARBALL_INCLUDE=yes
TARBALL_SUFFIX=.tar
@@ -906,6 +955,7 @@ TITLE=Selected\\ config
HEIGHT=120
THUMBHEIGHT=30
MAXPREVIEWS=7
+RANDOM_SEED=''
SHUFFLE=yes
TARBALL_INCLUDE=no
TARBALL_SUFFIX=.tar
@@ -940,6 +990,7 @@ TITLE=Current\\ directory\\ config
HEIGHT=120
THUMBHEIGHT=30
MAXPREVIEWS=8
+RANDOM_SEED=''
SHUFFLE=no
TARBALL_INCLUDE=no
TARBALL_SUFFIX=.tar
@@ -984,6 +1035,7 @@ test_print_config_applies_cli_overrides_without_writes() {
--height 456 \
--thumbheight 45 \
--maxpreviews 9 \
+ --random-seed cli-seed \
--shuffle \
--tarball
)
@@ -996,6 +1048,7 @@ TITLE=CLI\\ title
HEIGHT=456
THUMBHEIGHT=45
MAXPREVIEWS=9
+RANDOM_SEED=cli-seed
SHUFFLE=yes
TARBALL_INCLUDE=yes
TARBALL_SUFFIX=.tar
@@ -1174,6 +1227,7 @@ test_dry_run_reports_cli_overrides_without_writes() {
--height 456 \
--thumbheight 45 \
--maxpreviews 2 \
+ --random-seed dry-seed \
--shuffle \
--tarball
)
@@ -1189,6 +1243,7 @@ test_dry_run_reports_cli_overrides_without_writes() {
test::assert_contains 'Height: 456' "$output"
test::assert_contains 'Thumb height: 45' "$output"
test::assert_contains 'Max previews per page: 2' "$output"
+ test::assert_contains 'Random seed: dry-seed' "$output"
test::assert_contains 'Shuffle: yes' "$output"
test::assert_contains 'Image count: 6' "$output"
test::assert_contains 'Tarball setting: yes' "$output"
@@ -2130,6 +2185,7 @@ test_missing_option_values_fail() {
--height
--thumbheight
--maxpreviews
+ --random-seed
)
for option in "${value_options[@]}"; do
@@ -2168,6 +2224,9 @@ main() {
'--generate --no-shuffle overrides config' \
test_generate_no_shuffle_override_uses_sorted_order
test::run_case \
+ '--generate --random-seed repeats HTML with shuffle' \
+ test_generate_random_seed_repeats_html_with_shuffle
+ test::run_case \
'--generate --tarball overrides config' \
test_generate_cli_tarball_overrides_config
test::run_case \