Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ jobs:
image: alpine:latest

steps:
- name: Install bash, zsh and dash
run: apk add --no-cache bash zsh dash
- name: Install bash, zsh, dash and shellcheck
run: apk add --no-cache bash zsh dash shellcheck

- name: Checkout code
uses: actions/checkout@v7
Expand All @@ -137,7 +137,8 @@ jobs:
adduser -D tester
chown -R tester "$GITHUB_WORKSPACE"
su tester -s /bin/sh -c 'cd tests && umask 022 &&
TEST_PARALLEL_JOBS=4 TEST_SHELLS="bash,bash --posix,zsh,dash,busybox sh" \
TEST_MAX_SKIPPED=10 TEST_PARALLEL_JOBS=4 \
TEST_SHELLS="bash,bash --posix,zsh,dash,busybox sh" \
TEST_REQUIRE_SHELLS="zsh dash busybox" bash ./run_tests.sh'

# Runs consumer-style scripts against the last release of the previous major version and
Expand Down
13 changes: 13 additions & 0 deletions tests/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,19 @@ main() {
print_summary
local exit_status=$?

# Optional skip-count guard (TEST_MAX_SKIPPED): when set, a run that skips more
# tests than the threshold fails, so a future environment change cannot silently
# reduce coverage again (Issue #152).
if [[ -n "${TEST_MAX_SKIPPED:-}" ]]; then
if ! [[ "$TEST_MAX_SKIPPED" =~ ^[0-9]+$ ]]; then
echo -e "${COLOR_RED}Error: TEST_MAX_SKIPPED must be a non-negative integer (got '$TEST_MAX_SKIPPED')${COLOR_RESET}"
exit_status=1
elif [[ "$SKIPPED_TESTS" -gt "$TEST_MAX_SKIPPED" ]]; then
echo -e "${COLOR_RED}Error: $SKIPPED_TESTS tests skipped, exceeding TEST_MAX_SKIPPED=$TEST_MAX_SKIPPED${COLOR_RESET}"
exit_status=1
fi
fi

# Generate JUnit XML if requested
if [[ "$JUNIT_OUTPUT" == "true" ]]; then
generate_junit_xml "$OUTPUT_DIR/junit.xml"
Expand Down
30 changes: 30 additions & 0 deletions tests/test_helpers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,36 @@ skip_test() {
teardown_test 0
}

# Return 0 when the system runs musl, where C.UTF-8 works without any installed locale
# files (musl's default since 1.1.11, 2015). musl ships no `locale` utility, so
# `locale -a` lists nothing even though C.UTF-8 (and every other locale name) is
# available. Everything else, including glibc, returns 1 and keeps the `locale -a`
# based detection.
musl_c_utf8() {
command -v ldd >/dev/null 2>&1 || return 1
local ldd_banner
# musl's ldd prints the version banner (and exits nonzero) on stderr; glibc's prints on
# stdout. musl's ldd also exits 1, and run_tests.sh runs with pipefail, so the banner is
# captured first and the probe's status comes from grep alone.
ldd_banner="$(ldd --version 2>&1 || true)"
printf '%s\n' "$ldd_banner" | grep -qi musl
}

# Print an installed UTF-8 locale name, or nothing. On musl there is no `locale`
# utility and no locale files, but C.UTF-8 still works (musl_c_utf8 above).
utf8_locale_name() {
local candidate
for candidate in C.UTF-8 C.utf8 en_US.UTF-8 en_US.utf8; do
if locale -a 2>/dev/null | grep -qx "$candidate"; then
printf '%s\n' "$candidate"
return 0
fi
done
if musl_c_utf8; then
printf '%s\n' "C.UTF-8"
fi
}

# Assertion functions
# shellcheck disable=SC2016

Expand Down
17 changes: 5 additions & 12 deletions tests/test_posix_compat.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,6 @@ run_snippet_in_shell() {
return "$status"
}

# Print an installed UTF-8 locale name, or nothing
utf8_locale_name() {
local candidate
for candidate in C.UTF-8 C.utf8 en_US.UTF-8 en_US.utf8; do
if locale -a 2>/dev/null | grep -qx "$candidate"; then
printf '%s\n' "$candidate"
return 0
fi
done
}

test_posix_caller_detection() {
local shell_command="$1"
start_test "[$shell_command] script name is the calling script when sourced with ."
Expand Down Expand Up @@ -840,7 +829,11 @@ test_posix_levels_in_turkish_locale() {
require_shell "${shell_command%% *}" || return 0

if ! locale -a 2>/dev/null | grep -qiE '^tr_TR\.utf-?8$'; then
skip_test "tr_TR.UTF-8 locale not installed"
if musl_c_utf8; then
skip_test "musl has no locale-specific case mapping: the LC_ALL=C guard cannot be tested"
else
skip_test "tr_TR.UTF-8 locale not installed"
fi
return 0
fi

Expand Down
7 changes: 7 additions & 0 deletions tests/test_shell_compat.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ compat_locale_available() {
if [[ "$normalized_wanted" == "c" || "$normalized_wanted" == "posix" ]]; then
return 0
fi
# The C.UTF-8 case goes through the shared helper (test_helpers.sh), which reports
# it on musl even though `locale -a` prints nothing there. Other names stay
# unavailable: musl only speaks UTF-8.
if [[ "$normalized_wanted" == "c.utf8" ]] \
&& [[ "$(utf8_locale_name)" == "C.UTF-8" ]]; then
return 0
fi
locale -a 2>/dev/null | tr '[:upper:]' '[:lower:]' | tr -d '-' \
| grep -qx "$normalized_wanted"
}
Expand Down
11 changes: 3 additions & 8 deletions tests/test_validator_equivalence.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,11 @@ EQUIVALENCE_GOLDEN_DIR="$EQUIVALENCE_DIR/golden"
EQUIVALENCE_DRIVER="$EQUIVALENCE_DIR/validator_driver.sh"

# Print the name of an installed UTF-8 locale, or nothing.
# The UTF-8 golden file records glibc's character classes, so it is only checked on Linux.
# The UTF-8 golden file records glibc's character classes, so it is only checked on
# Linux; the shared utf8_locale_name (test_helpers.sh) adds the musl C.UTF-8 fallback.
equivalence_utf8_locale() {
[[ "$(uname -s)" == "Linux" ]] || return 0
local candidate
for candidate in C.UTF-8 C.utf8 en_US.UTF-8 en_US.utf8; do
if locale -a 2>/dev/null | grep -qx "$candidate"; then
printf '%s\n' "$candidate"
return 0
fi
done
utf8_locale_name
}

# Print the check names used in a golden file, space separated
Expand Down