diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index 0d39284..4ee4eee 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -44,20 +44,51 @@ jobs: - name: Run ShellCheck shell: bash run: | - # All tracked Bash scripts outside tests are production by default. - # Both groups lint in parallel, one file per process, and both are - # started before the first wait so a failure in one cannot hide the - # other. pipefail stays on, so a failed file enumeration fails its - # group even though xargs then sees empty input; any failure becomes - # a single exit 1. + # All tracked Bash scripts outside tests are production by default and + # hold the warning threshold. One file per process; pipefail stays + # on, so a failed file enumeration fails this step even though xargs + # then sees empty input. set -o pipefail status=0 - git ls-files -z -- "*.sh" ":!:tests/**" | xargs -0 -r -n1 -P4 shellcheck -x --severity=warning -- & - production_pid=$! - git ls-files -z -- "tests/*.sh" | xargs -0 -r -n1 -P4 shellcheck -x --severity=error -- & - test_pid=$! - wait "$production_pid" || status=1 - wait "$test_pid" || status=1 + git ls-files -z -- "*.sh" ":!:tests/**" | xargs -0 -r -n1 -P4 shellcheck -x --severity=warning -- || status=1 + exit "$status" + + # The tests group lives in its own job so a production warning failure cannot + # mask a test-group defect; the required check aggregates both jobs. + lint-tests: + name: ShellCheck (tests) + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 1 + persist-credentials: false + + - name: Install pinned ShellCheck + env: + SHELLCHECK_VERSION: v0.10.0 + run: | + curl --fail --silent --show-error --location \ + "https://github.com/koalaman/shellcheck/releases/download/${SHELLCHECK_VERSION}/shellcheck-${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" \ + --output "$RUNNER_TEMP/shellcheck.tar.xz" + tar -xJf "$RUNNER_TEMP/shellcheck.tar.xz" -C "$RUNNER_TEMP" + echo "$RUNNER_TEMP/shellcheck-${SHELLCHECK_VERSION}" >> "$GITHUB_PATH" + + - name: Verify test dependencies + run: | + command -v shellcheck + shellcheck --version + + - name: Run ShellCheck tests + shell: bash + run: | + # Only tests/*.sh, at the error threshold. One file per process; + # pipefail stays on, so a failed file enumeration fails this step even + # though xargs then sees empty input. + set -o pipefail + status=0 + git ls-files -z -- "tests/*.sh" | xargs -0 -r -n1 -P4 shellcheck -x --severity=error -- || status=1 exit "$status" ubuntu-tests: @@ -68,23 +99,24 @@ jobs: fail-fast: false matrix: include: - # motd is cut into eleven segments spread over three jobs of four, - # four and three; push-worker is cut into seven, two jobs of four and + # motd is cut into thirteen segments spread over three jobs of five, + # four and four; push-worker is cut into seven, two jobs of four and # three; cloudflare is cut into twelve, two jobs of five and seven - # balanced by measured cost, keeping the plan at nineteen jobs under - # the twenty-job concurrency instead of queueing a third job. + # balanced by measured cost. With lint split into a production and a + # tests job the plan is exactly twenty jobs, the twenty-job + # concurrency, so one more job would queue instead of running. - shard: motd-a suite: motd - segments: 11 - segment_set: "0 1 2 3" + segments: 13 + segment_set: "0 1 2 3 4" - shard: motd-b suite: motd - segments: 11 - segment_set: "4 5 6 7" + segments: 13 + segment_set: "5 6 7 8" - shard: motd-c suite: motd - segments: 11 - segment_set: "8 9 10" + segments: 13 + segment_set: "9 10 11 12" - shard: push-worker-a suite: push-worker segments: 7 @@ -146,23 +178,24 @@ jobs: fail-fast: false matrix: include: - # motd is cut into eleven segments spread over three jobs of four, - # four and three; push-worker is cut into seven, two jobs of four and + # motd is cut into thirteen segments spread over three jobs of five, + # four and four; push-worker is cut into seven, two jobs of four and # three; cloudflare is cut into twelve, two jobs of five and seven - # balanced by measured cost, keeping the plan at nineteen jobs under - # the twenty-job concurrency instead of queueing a third job. + # balanced by measured cost. With lint split into a production and a + # tests job the plan is exactly twenty jobs, the twenty-job + # concurrency, so one more job would queue instead of running. - shard: motd-a suite: motd - segments: 11 - segment_set: "0 1 2 3" + segments: 13 + segment_set: "0 1 2 3 4" - shard: motd-b suite: motd - segments: 11 - segment_set: "4 5 6 7" + segments: 13 + segment_set: "5 6 7 8" - shard: motd-c suite: motd - segments: 11 - segment_set: "8 9 10" + segments: 13 + segment_set: "9 10 11 12" - shard: push-worker-a suite: push-worker segments: 7 @@ -223,15 +256,19 @@ jobs: ubuntu: name: Ubuntu 24.04 lint and tests if: ${{ always() }} - needs: [lint, ubuntu-tests] + needs: [lint, lint-tests, ubuntu-tests] runs-on: ubuntu-24.04 timeout-minutes: 2 steps: - - name: Require lint and every Ubuntu shard + - name: Require both lint jobs and every Ubuntu shard env: LINT_RESULT: ${{ needs.lint.result }} + LINT_TESTS_RESULT: ${{ needs.lint-tests.result }} TEST_RESULT: ${{ needs.ubuntu-tests.result }} - run: test "$LINT_RESULT" = success && test "$TEST_RESULT" = success + run: >- + test "$LINT_RESULT" = success && + test "$LINT_TESTS_RESULT" = success && + test "$TEST_RESULT" = success debian-13: name: Debian 13 tests diff --git a/README.md b/README.md index d96da57..8465230 100644 --- a/README.md +++ b/README.md @@ -23,15 +23,15 @@ 生产宿主机(包括 Netcup)仅执行差异检查及改动脚本的 `bash -n`,不运行全量 ShellCheck 或完整测试集; 分析和测试交由 CI:PR 按完整差异及依赖映射选择套件,README/许可证修改无需业务测试; 公共设施或未知依赖改动回退全量,main 推送仍执行全量。两个必需 Job 始终运行,输出选择和跳过记录。 -每个 OS 的测试作业按九个 shard 并行:`motd-a`/`motd-b`/`motd-c`(`test-motd.sh`,11 段按 4/4/3 分配)、 +每个 OS 的测试作业按九个 shard 并行:`motd-a`/`motd-b`/`motd-c`(`test-motd.sh`,13 段按 5/4/4 分配)、 `cloudflare-a`/`cloudflare-b`(`test-cloudflare-tunnel.sh`,12 段按 5/7 分配)、`push-worker-a`/`push-worker-b` (`test-push-worker-registration.sh`,7 段按 4/3 分配)、`push`(仅 `test-push.sh`)、`other`(其余全部套件); 本机全量用 `tests/run.sh all`。 -重套件(motd、Cloudflare Tunnel、push worker 注册)按语义分段,import 守卫 `seg_is`:`TEST_SEGMENTS` 个进程各跑互不重叠的一段并同时执行(本机默认 1,即按原顺序串行)。 -motd 切成十一段、Cloudflare Tunnel 切成十二段、push worker 注册切成七段:每个作业用 `TEST_SEGMENT_SET` 只跑自己的段,同一套件的所有作业并集恰好覆盖全部段且互不重叠;任一作业的段数、下标或重复项不合法时,在任何套件启动前以状态 2 退出。 +重套件(motd、Cloudflare Tunnel、push worker 注册)按语义分段,import 守卫 `seg_is`:`TEST_SEGMENTS` 是套件切成的段数;`TEST_SEGMENT_SET` 选本作业跑哪几段,CI 每个作业只跑自己的段;本机未设置时按原顺序串行。 +motd 切成十三段、Cloudflare Tunnel 切成十二段、push worker 注册切成七段:每个作业用 `TEST_SEGMENT_SET` 只跑自己的段,同一套件的所有作业并集恰好覆盖全部段且互不重叠;任一作业的段数、下标或重复项不合法时,在任何套件启动前以状态 2 退出。 `TEST_SEGMENT_SET` 只接受互不重复、且在 `TEST_SEGMENTS` 范围内的段下标,非法值时在任何套件启动前以状态 2 退出;未设置则照旧跑全部段。 -Ubuntu 使用固定 ShellCheck v0.10.0:所有受 Git 跟踪、位于 tests/ 之外的 .sh 文件执行 warning 门槛,tests/ 与 tests/lib/ 内的 .sh 文件执行 error 门槛;新目录中的生产 .sh 文件自动纳入,不维护生产文件白名单。Zsh 配置 p10k-config.zsh 不交给 Bash ShellCheck。两组 lint 都会执行并累计失败,Debian 不重复 lint。 -例外仅允许局部明确的 SC 规则编号和邻近理由;禁止文件级或全局 warning 抑制。测试 warning 不阻断不等于被 disable,禁止用抑制隐藏行为缺陷。CI 工作流改动继续触发全量测试回退;warning 门槛失败时后续 Ubuntu 行为套件不会执行,不能以 Debian 通过代替双环境验收。 +Ubuntu 使用固定 ShellCheck v0.10.0:所有受 Git 跟踪、位于 tests/ 之外的 .sh 文件执行 warning 门槛,tests/ 与 tests/lib/ 内的 .sh 文件执行 error 门槛;新目录中的生产 .sh 文件自动纳入,不维护生产文件白名单。Zsh 配置 p10k-config.zsh 不交给 Bash ShellCheck。两组 lint 各在一个 Ubuntu 作业里跑(生产组 `ShellCheck`、测试组 `ShellCheck (tests)`),聚合检查 `Ubuntu 24.04 lint and tests` 要求两者都通过;Debian 不重复 lint。 +例外仅允许局部明确的 SC 规则编号和邻近理由;禁止文件级或全局 warning 抑制。测试 warning 不阻断不等于被 disable,禁止用抑制隐藏行为缺陷。CI 工作流改动继续触发全量测试回退;lint 已是独立作业,warning 门槛失败不会阻止 Ubuntu 行为套件执行,但会让 `Ubuntu 24.04 lint and tests` 聚合检查失败;不能以 Debian 通过代替双环境验收。 语法检查仍覆盖全部 .sh 脚本;文档修改无需本地全套测试,但仍须通过必需 CI。 本机重测试必须使用原生资源限制及权限隔离;无法隔离或超限时停止,不自动提高额度或转宿主机执行。 diff --git a/tests/run.sh b/tests/run.sh index a15213c..a87bd4e 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -71,14 +71,14 @@ done # defines. TEST_SEGMENTS sets how many processes share the work; with # TEST_SEGMENTS=1 every suite runs in one process, all segments in order. declare -rA SEGMENTED_SUITES=( - [test-motd.sh]=11 + [test-motd.sh]=13 [test-cloudflare-tunnel.sh]=12 [test-push-worker-registration.sh]=7 ) segments=${TEST_SEGMENTS:-1} # Bounded lexical form first, so no accepted value can overflow shell arithmetic # or smuggle in a sign, a prefix or a multi-digit index past the range checks. -[[ "$segments" =~ ^([1-9]|1[0-2])$ ]] || { printf 'Invalid TEST_SEGMENTS: %s\n' "$segments" >&2; exit 2; } +[[ "$segments" =~ ^([1-9]|1[0-3])$ ]] || { printf 'Invalid TEST_SEGMENTS: %s\n' "$segments" >&2; exit 2; } # TEST_SEGMENT_SET narrows one job to a subset of the segments ("0 1 2 3"), so a # single suite can be spread over several jobs. Unset or blank keeps every @@ -94,7 +94,7 @@ if [[ -n "${TEST_SEGMENT_SET:-}" && -n "${TEST_SEGMENT_SET//[[:space:]]/}" ]]; t segment_value=${segment_value//$'\n'/ } read -r -a segment_entries <<< "$segment_value" for segment_index in "${segment_entries[@]}"; do - [[ "$segment_index" =~ ^([0-9]|1[01])$ ]] \ + [[ "$segment_index" =~ ^([0-9]|1[0-2])$ ]] \ || { printf 'Invalid TEST_SEGMENT_SET entry: %s\n' "$segment_index" >&2; exit 2; } (( segment_index < segments )) \ || { printf 'Segment %s out of range for TEST_SEGMENTS=%s\n' "$segment_index" "$segments" >&2; exit 2; } diff --git a/tests/test-lint-coverage.sh b/tests/test-lint-coverage.sh index e210652..189edf7 100644 --- a/tests/test-lint-coverage.sh +++ b/tests/test-lint-coverage.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash set -euo pipefail -# Execute only the workflow lint block against inert files and a ShellCheck spy. +# Execute each workflow lint step against inert files and a ShellCheck spy. The +# two groups live in separate jobs, so every check runs one step on its own: its +# exit status, its linted file set, and its enumeration failure. The required +# Ubuntu check must still gate both jobs. ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) unset GH_TOKEN GITHUB_TOKEN SSH_PRIVATE_KEY_B64 export GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null @@ -8,19 +11,39 @@ unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_CONFIG_COUNT root=$(mktemp -d) cleanup() { rm -rf -- "$root"; } trap cleanup EXIT -# Fail closed if the named step or its literal block changes shape. -awk ' - /^ - name: Run ShellCheck$/ { found++; step=1; next } - step && /^ run: \|$/ { block=1; next } - block && /^ / { print substr($0, 11); lines++; next } - block { step=0; block=0 } - END { if (found != 1 || lines == 0) exit 1 } -' "$ROOT_DIR/.github/workflows/shell-tests.yml" > "$root/lint.sh" -# Both groups must keep the per-file, four-way parallel invocation. -if [[ "$(grep -c -- '-n1 -P4' "$root/lint.sh")" != 2 ]]; then - printf 'the lint block no longer starts both groups with -n1 -P4\n' >&2 - exit 1 -fi +workflow="$ROOT_DIR/.github/workflows/shell-tests.yml" +fail() { printf 'FAIL: %s\n' "$*" >&2; exit 1; } +# Fail closed if the named step is missing, repeated, or its literal block +# changed shape. +extract_step() { + local name=$1 out=$2 + rm -f -- "$out" + if ! awk -v name="$name" -v out="$out" ' + $0 == " - name: " name { found++; step = 1; next } + step && /^ run: \|$/ { block = 1; next } + block && /^ / { print substr($0, 11) > out; lines++; next } + block { step = 0; block = 0 } + END { if (found != 1 || lines == 0) exit 1 } + ' "$workflow"; then + fail "workflow step '$name' is missing, repeated, or has no literal run block" + fi +} +extract_step 'Run ShellCheck' "$root/production.sh" +extract_step 'Run ShellCheck tests' "$root/tests.sh" +# Each step owns exactly one group: one per-file four-way pipeline, at that +# group's severity, with no trace of the other group's file selection. +check_step() { + local label=$1 file=$2 severity=$3 foreign=$4 + [[ "$(grep -c -- '-n1 -P4' "$file" || true)" == 1 ]] \ + || fail "$label does not start exactly one -n1 -P4 pipeline" + [[ "$(grep -c -- "--severity=$severity" "$file" || true)" == 1 ]] \ + || fail "$label does not use exactly one --severity=$severity" + if grep -Fq -- "$foreign" "$file"; then + fail "$label still selects the other group's files ($foreign)" + fi +} +check_step 'the production lint step' "$root/production.sh" warning 'tests/*.sh' +check_step 'the tests lint step' "$root/tests.sh" error ':!:tests/**' mkdir -p "$root/bin" "$root/repo" cat > "$root/bin/shellcheck" <<'STUB' #!/usr/bin/env bash @@ -44,8 +67,8 @@ set -euo pipefail # Only a file enumeration may be made to fail; everything else is real Git. for arg in "\$@"; do case "\${ENUM_FAIL:-none} \$arg" in - "production :!:tests/**"|"both :!:tests/**") exit 1 ;; - "tests tests/*.sh"|"both tests/*.sh") exit 1 ;; + "production :!:tests/**") exit 1 ;; + "tests tests/*.sh") exit 1 ;; esac done exec "$real_git" "\$@" @@ -65,55 +88,91 @@ printf "%s\n" \ printf "%s\n" \ $'--severity=error\ttests/direct.sh' \ $'--severity=error\ttests/nested/deep.sh' > "$root/tests.expected" -cat "$root/production.expected" "$root/tests.expected" > "$root/all.expected" -# Multiset comparison: every tracked script of both groups exactly once, with -# its own group's severity. Sorting keeps the parallel order out of the verdict, +# Multiset comparison: every tracked script of the group exactly once, with the +# group's own severity. Sorting keeps the parallel order out of the verdict, # while a duplicated or a missing file still shows up as a difference. check_linted() { local label=$1 expected=$2 LC_ALL=C sort "$root/capture" > "$root/capture.sorted" LC_ALL=C sort "$expected" > "$root/expected.sorted" if ! diff -u "$root/expected.sorted" "$root/capture.sorted"; then - printf 'FAIL: %s: linted file set is not the tracked split\n' "$label" >&2 - exit 1 + fail "$label: linted file set is not the tracked group" fi } -run_lint() { +run_step() { local status=0 - bash --noprofile --norc -e -o pipefail "$root/lint.sh" || status=$? + bash --noprofile --norc -e -o pipefail "$1" || status=$? printf '%s' "$status" } -# Both groups must always be linted in full, and a failure in either one must -# fail the step: the four result combinations cover that. +# A step must follow its own group alone: the other group's status is held at 0 +# and must not leak into this step's exit code. export ENUM_FAIL=none -for PRODUCTION_STATUS in 0 1; do - for TEST_STATUS in 0 1; do - export PRODUCTION_STATUS TEST_STATUS +for group in production tests; do + for group_status in 0 1; do + if [[ "$group" == production ]]; then + export PRODUCTION_STATUS=$group_status TEST_STATUS=0 + step=$root/production.sh expected=$root/production.expected + else + export PRODUCTION_STATUS=0 TEST_STATUS=$group_status + step=$root/tests.sh expected=$root/tests.expected + fi : > "$CAPTURE" - status=$(run_lint) - expected=0 - if (( PRODUCTION_STATUS != 0 || TEST_STATUS != 0 )); then expected=1; fi - [[ $status == "$expected" ]] \ - || { printf 'FAIL: lint exited %s, expected %s\n' "$status" "$expected" >&2; exit 1; } - check_linted "production=$PRODUCTION_STATUS tests=$TEST_STATUS" "$root/all.expected" - printf "PASS: workflow lint production=%s tests=%s exit=%s\n" \ - "$PRODUCTION_STATUS" "$TEST_STATUS" "$status" + status=$(run_step "$step") + [[ "$status" == "$group_status" ]] \ + || fail "$group step exited $status with its group at $group_status" + check_linted "$group=$group_status" "$expected" + printf 'PASS: %s lint step exit=%s follows its own group\n' "$group" "$status" done done -# A failed enumeration must fail the step without hiding the other group: the -# background pipeline keeps pipefail, so git failing is not swallowed by the -# empty xargs run behind it. -for ENUM_FAIL in production tests both; do - export ENUM_FAIL PRODUCTION_STATUS=0 TEST_STATUS=0 +# A failed enumeration must fail that step and lint nothing: the pipeline keeps +# pipefail, so git failing is not swallowed by the empty xargs run behind it. +for group in production tests; do + export ENUM_FAIL=$group PRODUCTION_STATUS=0 TEST_STATUS=0 + if [[ "$group" == production ]]; then step=$root/production.sh; else step=$root/tests.sh; fi : > "$CAPTURE" - status=$(run_lint) - [[ $status == 1 ]] \ - || { printf 'FAIL: %s enumeration failure exited %s\n' "$ENUM_FAIL" "$status" >&2; exit 1; } - case "$ENUM_FAIL" in - production) check_linted "$ENUM_FAIL enumeration failure" "$root/tests.expected" ;; - tests) check_linted "$ENUM_FAIL enumeration failure" "$root/production.expected" ;; - both) [[ ! -s "$CAPTURE" ]] \ - || { printf 'FAIL: both enumerations failed but files were linted\n' >&2; exit 1; } ;; - esac - printf 'PASS: %s enumeration failure exits 1\n' "$ENUM_FAIL" + status=$(run_step "$step") + [[ "$status" == 1 ]] || fail "$group enumeration failure exited $status" + [[ ! -s "$CAPTURE" ]] || fail "$group enumeration failure still linted files" + printf 'PASS: %s enumeration failure exits 1 without linting\n' "$group" +done +# The required check name must stay, and it must depend on both lint jobs and +# every Ubuntu shard, asserting each dependency's result; a job left out of +# `needs` would let its failure merge as green. +awk ' + /^ [a-z][a-z0-9-]*:$/ { job = $1; sub(/:$/, "", job) } + job == "ubuntu" { print } +' "$workflow" > "$root/aggregate.yml" +[[ -s "$root/aggregate.yml" ]] || fail 'the workflow has no ubuntu aggregation job' +grep -qx ' name: Ubuntu 24.04 lint and tests' "$root/aggregate.yml" \ + || fail 'the ubuntu aggregation job no longer carries the required check name' +grep -qx ' name: Debian 13 tests' "$workflow" \ + || fail 'the workflow no longer carries the Debian required check name' +needs=$(awk '/^ needs: / { sub(/^ needs: \[/, ""); sub(/\].*$/, ""); print; exit }' "$root/aggregate.yml") +[[ -n "$needs" ]] || fail 'the ubuntu aggregation job has no inline needs list' +entries=$(printf '%s\n' "$needs" | tr ',' '\n' | sed 's/[[:space:]]//g' | grep -c . || true) +for required in lint lint-tests ubuntu-tests; do + printf '%s\n' "$needs" | tr ',' '\n' | sed 's/[[:space:]]//g' | grep -qx -- "$required" \ + || fail "the ubuntu aggregation job does not need $required" done +checks=$(grep -c -- '= success' "$root/aggregate.yml" || true) +[[ "$checks" == "$entries" ]] \ + || fail "the ubuntu job needs $entries jobs but asserts $checks results" +# Counting `= success` lines cannot tell which result is bound to which variable: +# pointing LINT_TESTS_RESULT at needs.lint.result, or relaxing one `&&` to `||`, +# leaves the count intact while the gate goes green on a failed job. Require the +# exact literal lines instead, so every variable must read its own needs entry +# and all three assertions must be chained with `&&`. +while IFS= read -r expected; do + grep -Fxq -- "$expected" "$root/aggregate.yml" \ + || fail "the ubuntu aggregation job no longer has the line: $expected" +done <<'CONTRACT' + if: ${{ always() }} + LINT_RESULT: ${{ needs.lint.result }} + LINT_TESTS_RESULT: ${{ needs.lint-tests.result }} + TEST_RESULT: ${{ needs.ubuntu-tests.result }} + run: >- + test "$LINT_RESULT" = success && + test "$LINT_TESTS_RESULT" = success && + test "$TEST_RESULT" = success +CONTRACT +printf 'PASS: ubuntu aggregation needs and asserts %s, each bound to its own result\n' "$needs" diff --git a/tests/test-motd.sh b/tests/test-motd.sh index 1001493..3e161c1 100644 --- a/tests/test-motd.sh +++ b/tests/test-motd.sh @@ -689,7 +689,9 @@ for pending_phase in pending-create pending-write pending-chown pending-chmod pe motd_run_locked_operation install ) done +fi +if seg_is 3; then for pending_kind in broken-symlink symlink directory fifo mode owner gid; do ( root="$TEST_DIR/pending-kind-$pending_kind"; create_fixture "$root"; export_paths "$root" @@ -803,7 +805,7 @@ for native_id in uname motd_news; do done fi -if seg_is 3; then +if seg_is 4; then for kill_phase in transaction-snapshot-building transaction-final-moved target-committed-motd target-committed-issue_net target-committed-motd_news committed-cleanup committed-old-generation-cleaned committed-pending-removed; do ( root="$TEST_DIR/sigkill-$kill_phase"; create_fixture "$root"; export_paths "$root" @@ -826,7 +828,9 @@ for kill_phase in transaction-snapshot-building transaction-final-moved target-c motd_take_lock; motd_release_lock ) done +fi +if seg_is 5; then ( root="$TEST_DIR/generation-bounded"; create_fixture "$root"; export_paths "$root" load_motd @@ -903,7 +907,7 @@ done ) fi -if seg_is 4; then +if seg_is 6; then for hook_case in content-motd mode-issue_net inode-motd_news type-issue_net; do ( root="$TEST_DIR/hook-target-$hook_case"; create_fixture "$root"; export_paths "$root" @@ -998,7 +1002,7 @@ for pending_hook_case in id extra-line inode; do done fi -if seg_is 5; then +if seg_is 7; then for building_phase in transaction-snapshot-building capture-motd capture-issue_net capture-motd_news snapshot-commit transaction-final-move; do ( root="$TEST_DIR/building-fail-$building_phase"; create_fixture "$root"; export_paths "$root" @@ -1032,7 +1036,7 @@ for building_phase in transaction-snapshot-building capture-motd capture-issue_n done fi -if seg_is 6; then +if seg_is 8; then for previous_impl in tool module; do ( root="$TEST_DIR/previous-only-$previous_impl"; create_fixture "$root"; export_paths "$root" @@ -1128,7 +1132,7 @@ done ) fi -if seg_is 7; then +if seg_is 9; then for missing_command in bash chown flock ln od readlink sha256sum touch wc; do ( root="$TEST_DIR/module-missing-$missing_command"; create_fixture "$root"; export_paths "$root" @@ -1203,7 +1207,7 @@ for group_impl in tool module; do done fi -if seg_is 8; then +if seg_is 10; then for illegal_legacy_name in motd.previous-unknown issue.previous-unknown motd.invalid-backup evil.initial-backup; do ( root="$TEST_DIR/legacy-name-${illegal_legacy_name//[^a-zA-Z0-9]/-}"; create_fixture "$root"; export_paths "$root"; load_motd; motd_run_locked_operation install @@ -1265,11 +1269,11 @@ done ) fi -if seg_is 9; then +if seg_is 11; then run_rollback_damage_cases tool fi -if seg_is 10; then +if seg_is 12; then run_rollback_damage_cases module for rollback_impl in tool module; do diff --git a/tests/test-shards.sh b/tests/test-shards.sh index d18a403..0ac2bb4 100644 --- a/tests/test-shards.sh +++ b/tests/test-shards.sh @@ -163,7 +163,7 @@ check_matrix() { # "x" (or "10x") fails here instead of being filtered out. read -r -a set_tokens <<< "$segment_set_value" for token in "${set_tokens[@]}"; do - [[ "$token" =~ ^([0-9]|1[01])$ ]] \ + [[ "$token" =~ ^([0-9]|1[0-2])$ ]] \ || fail "$job $suite has an invalid segment_set entry: '$token'" (( token < suite_segments )) \ || fail "$job $suite segment_set entry $token is outside 0..$(( suite_segments - 1 ))" @@ -276,15 +276,17 @@ check_subset() { fail "subset '$set_value' for $suite ran: $actual" } } -check_subset motd 11 '0 1 2 3' 'test-motd.sh-0 +check_subset motd 13 '0 1 2 3 4' 'test-motd.sh-0 test-motd.sh-1 test-motd.sh-2 -test-motd.sh-3' +test-motd.sh-3 +test-motd.sh-4' # Double-digit segments must run as themselves, not as "1" plus a stray "0". -check_subset motd 11 '8 9 10' 'test-motd.sh-8 -test-motd.sh-9 -test-motd.sh-10' -check_subset motd 11 '' 'test-motd.sh-0 +check_subset motd 13 '9 10 11 12' 'test-motd.sh-9 +test-motd.sh-10 +test-motd.sh-11 +test-motd.sh-12' +check_subset motd 13 '' 'test-motd.sh-0 test-motd.sh-1 test-motd.sh-2 test-motd.sh-3 @@ -294,7 +296,9 @@ test-motd.sh-6 test-motd.sh-7 test-motd.sh-8 test-motd.sh-9 -test-motd.sh-10' +test-motd.sh-10 +test-motd.sh-11 +test-motd.sh-12' check_subset motd 1 '' 'test-motd.sh' check_subset cloudflare 12 '2 3 5 6 11' 'test-cloudflare-tunnel.sh-2 test-cloudflare-tunnel.sh-3 @@ -350,6 +354,7 @@ check_reject() { check_reject 'motd cut into two' motd 2 '' check_reject 'motd cut into four' motd 4 '' check_reject 'motd cut into eight' motd 8 '' +check_reject 'motd cut into eleven' motd 11 '' check_reject 'motd cut into twelve' motd 12 '' check_reject 'cloudflare cut into four' cloudflare 4 '' check_reject 'cloudflare cut into eight' cloudflare 8 '' @@ -357,7 +362,7 @@ check_reject 'cloudflare cut into two' cloudflare 2 '' check_reject 'push-worker cut into eight' push-worker 8 '' check_reject 'push-worker cut into four' push-worker 4 '' check_reject 'push-worker cut into two' push-worker 2 '' -check_reject 'motd segment past its source' motd 11 '11' +check_reject 'motd segment past its source' motd 13 '13' check_reject 'subset for an unsegmented suite' push 2 '0' check_reject 'subset for the other bucket' other 2 '0' check_reject 'subset without segmented mode' motd 1 '0' @@ -379,10 +384,10 @@ check_bad_set() { } : > "$TEMP_DIR/1" for bad in '1 01' '9223372036854775808' '18446744073709551616' '[1]' '1,2' '-1' '1 1' '+1' '0x1' 'x'; do - check_bad_set motd 11 "$bad" + check_bad_set motd 13 "$bad" done -for bad in '10x' '11' '010' '1 10 10' '0 11'; do - check_bad_set motd 11 "$bad" +for bad in '10x' '13' '010' '1 12 12' '0 13'; do + check_bad_set motd 13 "$bad" done # The seven-segment suite validates its own tokens the same way, so a set that # only matches a stale segment count cannot pass for a good one.