diff --git a/scripts/generate-checksums.sh b/scripts/generate-checksums.sh index 89d4bbb..8e89fde 100755 --- a/scripts/generate-checksums.sh +++ b/scripts/generate-checksums.sh @@ -85,7 +85,7 @@ mkdir -p "${OUT_DIR}" # Find all release files (.tar.gz and .deb) raw_files="$(cd "${DIST_DIR}" && find . -maxdepth 3 -type f \( -name '*.tar.gz' -o -name '*.deb' \) -print)" cleaned_files="${raw_files//.\//}" -sorted_files="$(echo "${cleaned_files}" | sort)" +sorted_files="$(echo "${cleaned_files}" | LC_ALL=C sort)" mapfile -t files <<< "${sorted_files}" if [[ ${#files[@]} -eq 0 ]]; then @@ -97,10 +97,13 @@ echo "Release assets:" printf '%s\n' "${files[@]}" # Compute checksums in GNU coreutils format (checksum first, compatible with sha256sum -c) -# Output uses basenames only for cleaner checksums.txt +# Output uses basenames only for cleaner checksums.txt. Sort by the +# filename field (-k2,2) under LC_ALL=C so output is byte-for-byte +# reproducible across environments; bare `sort` would order lines by +# the leading sha256 prefix, which isn't meaningful. (cd "${DIST_DIR}" && sha256sum "${files[@]}") | while read -r sum file; do echo "${sum} $(basename "${file}")" -done | sort > "${OUT_DIR}/checksums.txt" +done | LC_ALL=C sort -k2,2 > "${OUT_DIR}/checksums.txt" echo "" echo "${OUT_DIR}/checksums.txt:" diff --git a/tests/bats/scripts/generate-checksums.bats b/tests/bats/scripts/generate-checksums.bats index 722f280..541c837 100644 --- a/tests/bats/scripts/generate-checksums.bats +++ b/tests/bats/scripts/generate-checksums.bats @@ -54,12 +54,11 @@ _make_artifacts() { assert_success } -@test "checksums.txt records every artifact found in the dist dir" { - # Note: the script's final `| sort` sorts lines by the sha256 - # prefix (the first column on each line), not by filename, so we - # assert only the *set* of records — pinning the buggy hash-order - # would lock in a quirk that shouldn't be load-bearing. +@test "checksums.txt is sorted alphabetically by filename" { local dist="${FAKE_REPO}/dist" + # Stage in non-alphabetical order to prove the sort actually + # reorders. The script sorts under LC_ALL=C, so the assertion is + # locale-independent. _make_artifacts "${dist}" \ "ci-tools_1.0.0_osx-x64.tar.gz" \ "ci-tools_1.0.0_amd64.deb" \ @@ -67,10 +66,27 @@ _make_artifacts() { run "${SCRIPT}" "${dist}" assert_success run awk '{print $2}' "${OUT_DIR}/checksums.txt" - assert_line "ci-tools_1.0.0_osx-x64.tar.gz" - assert_line "ci-tools_1.0.0_amd64.deb" - assert_line "ci-tools_1.0.0_linux-x64.tar.gz" - assert_equal "${#lines[@]}" 3 + assert_output "ci-tools_1.0.0_amd64.deb +ci-tools_1.0.0_linux-x64.tar.gz +ci-tools_1.0.0_osx-x64.tar.gz" +} + +@test "checksums.txt sort is locale-independent (script pins LC_ALL=C)" { + # Filenames where C and en_US.UTF-8 collation differ: under C, + # uppercase letters sort before lowercase (byte order); under + # en_US.UTF-8, sort is case-insensitive alphabetical. The + # ci-tools image generates en_US.UTF-8 for exactly this kind of + # check (see images/ci-tools/Dockerfile). If a future refactor + # drops the in-script LC_ALL=C, this test fails — output would + # come back as A.deb, b.deb, C.deb instead of the C-order below. + local dist="${FAKE_REPO}/dist" + _make_artifacts "${dist}" "A.deb" "b.deb" "C.deb" + run env LC_ALL=en_US.UTF-8 "${SCRIPT}" "${dist}" + assert_success + run awk '{print $2}' "${OUT_DIR}/checksums.txt" + assert_output "A.deb +C.deb +b.deb" } @test "checksums.txt records basenames only, even for nested artifacts" {