Skip to content
Merged
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
107 changes: 72 additions & 35 deletions .github/workflows/shell-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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。
本机重测试必须使用原生资源限制及权限隔离;无法隔离或超限时停止,不自动提高额度或转宿主机执行。

Expand Down
6 changes: 3 additions & 3 deletions tests/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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; }
Expand Down
Loading