diff options
Diffstat (limited to 'tests/cli.sh')
| -rwxr-xr-x | tests/cli.sh | 98 |
1 files changed, 78 insertions, 20 deletions
diff --git a/tests/cli.sh b/tests/cli.sh index 0618163..a7b82b4 100755 --- a/tests/cli.sh +++ b/tests/cli.sh @@ -7279,13 +7279,14 @@ test_stats_collect_reads_cached_identify_output() { test::teardown } -# task 8v0: chronological_photo_files (album-photo-select.source.sh) orders -# photos by EXIF date taken, ascending, falling back to source mtime (and then -# filename) for photos with no usable EXIF date. Exercises the function -# directly (not a full --generate) for precise control over dates/mtimes, the -# same pattern test_stats_collect_reads_cached_identify_output above uses for -# the EXIF cache layer. -test_chronological_photo_files_sorts_by_exif_date_with_mtime_fallback() { +# task 8v0 (and its bugfix once irregular.ninja/so.war.das surfaced a real +# camera clock-reset roll): chronological_photo_files +# (album-photo-select.source.sh) orders photos by EXIF date taken, ascending, +# falling back to filename order for photos with no usable EXIF date. +# Exercises the function directly (not a full --generate) for precise control +# over dates/mtimes, the same pattern test_stats_collect_reads_cached_identify_output +# above uses for the EXIF cache layer. +test_chronological_photo_files_sorts_by_exif_date_with_filename_fallback() { local incoming_dir local dist_dir local cache_dir @@ -7306,12 +7307,14 @@ test_chronological_photo_files_sorts_by_exif_date_with_mtime_fallback() { done # a.jpg/d.jpg have no DateTimeOriginal/Digitized/DateTime tag at all (only - # an unrelated Make tag), so they must fall back to mtime. d.jpg's mtime is - # deliberately EARLIER than a.jpg's, and both are deliberately out of - # filename order, to prove the fallback is a real mtime sort rather than an - # accidental filename sort. - touch -d '2020-01-01 00:00:00' "$incoming_dir/d.jpg" - touch -d '2020-01-02 00:00:00' "$incoming_dir/a.jpg" + # an unrelated Make tag), so they must fall back to filename order. Their + # mtimes are deliberately set OUT of filename order (d.jpg newer than + # a.jpg) to prove the fallback is a real filename sort rather than an + # accidental mtime sort (mtime is not trustworthy: bulk-copying/rsyncing an + # incoming directory commonly rewrites every file's mtime to the transfer + # time regardless of capture order). + touch -d '2020-01-02 00:00:00' "$incoming_dir/d.jpg" + touch -d '2020-01-01 00:00:00' "$incoming_dir/a.jpg" # b.jpg/c.jpg have a real EXIF date, deliberately reversed relative to their # filenames (c.jpg is EARLIER than b.jpg) so a filename-sort bug would not @@ -7338,11 +7341,63 @@ test_chronological_photo_files_sorts_by_exif_date_with_mtime_fallback() { mapfile -t ordered < <(chronological_photo_files photos) - # Real EXIF dates (c.jpg 2021, b.jpg 2023) sort before the mtime fallback - # group (d.jpg 2020-01-01, a.jpg 2020-01-02): a genuine timestamp is never - # displaced by an approximate fallback, even though the fallback group's - # own dates are chronologically earlier. - test "${ordered[*]}" = 'c.jpg b.jpg d.jpg a.jpg' + # Real EXIF dates (c.jpg 2021, b.jpg 2023) sort before the filename + # fallback group (a.jpg, d.jpg): a genuine timestamp is never displaced by + # an approximate fallback. Within the fallback group, a.jpg sorts before + # d.jpg by filename despite its mtime being earlier -- proving mtime plays + # no part in the fallback order. + test "${ordered[*]}" = 'c.jpg b.jpg a.jpg d.jpg' + + test::teardown +} + +# Regression test for the real-world bug found via irregular.ninja/so.war.das: +# a Fujifilm X100V whose battery died reset its clock to 2000-01-01, and every +# EXIF timestamp on that roll (DateTimeOriginal/Digitized/DateTime, all +# identical) was stamped with that bogus date instead of omitting it. Trusting +# it at face value put an entire clock-reset roll at the very FRONT of an +# album ahead of hundreds of correctly (2020s-)dated photos -- the opposite of +# what CHRONOLOGICAL_ORDER promises. old.jpg's implausible year-2000 EXIF date +# must therefore be treated the same as a missing tag (filename fallback, group +# 1), not as a real group-0 timestamp. +test_chronological_photo_files_treats_implausible_exif_year_as_camera_clock_reset() { + local incoming_dir + local dist_dir + local cache_dir + local name + local -a ordered=() + + test::setup + test::source_shuriken_lib + + incoming_dir="$TEST_TMPDIR/incoming" + dist_dir="$TEST_TMPDIR/dist" + cache_dir="$TEST_TMPDIR/cache/exif" + mkdir -p "$incoming_dir" "$dist_dir/photos" "$cache_dir" + + for name in old.jpg real.jpg; do + printf 'fake\n' > "$incoming_dir/$name" + printf 'fake\n' > "$dist_dir/photos/$name" + done + + { + photo_cache_signature 'old.jpg' "$incoming_dir/old.jpg" + printf ' exif:DateTimeOriginal: 2000:01:01 05:50:52\n' + } > "$cache_dir/old.jpg.txt" + { + photo_cache_signature 'real.jpg' "$incoming_dir/real.jpg" + printf ' exif:DateTimeOriginal: 2026:06:07 13:19:23\n' + } > "$cache_dir/real.jpg.txt" + + export INCOMING_DIR="$incoming_dir" + export DIST_DIR="$dist_dir" + + mapfile -t ordered < <(chronological_photo_files photos) + + # real.jpg (a plausible 2026 date, group 0) sorts before old.jpg (an + # implausible 2000 clock-reset date, demoted to the group-1 filename + # fallback), even though 2000 is numerically earlier than 2026. + test "${ordered[*]}" = 'real.jpg old.jpg' test::teardown } @@ -7970,8 +8025,11 @@ main() { 'stats collect reads cached identify output' \ test_stats_collect_reads_cached_identify_output test::run_case \ - 'chronological_photo_files sorts by EXIF date with mtime fallback' \ - test_chronological_photo_files_sorts_by_exif_date_with_mtime_fallback + 'chronological_photo_files sorts by EXIF date with filename fallback' \ + test_chronological_photo_files_sorts_by_exif_date_with_filename_fallback + test::run_case \ + 'chronological_photo_files treats implausible EXIF year as camera clock reset' \ + test_chronological_photo_files_treats_implausible_exif_year_as_camera_clock_reset test::run_case \ 'album/stats decoupling boundary (pn0)' \ test_album_stats_decoupling_boundary |
