From 2d31bd091e745d9b4b8c63a9528af12103063148 Mon Sep 17 00:00:00 2001 From: JustMe Date: Fri, 9 Oct 2026 14:30:38 +0800 Subject: [PATCH] ci: gate on the fast tests (about 20s); slow end-to-end tests run in parallel with the image build [CI] --- .woodpecker/e2e.yml | 31 +++++++++++++++++++ .woodpecker/verify.yml | 4 +-- AGENTS.md | 2 +- .../AgentEndToEndTests.cs | 2 ++ .../tests/MoShiro.Backend.Tests/HostTests.cs | 1 + .../tests/MoShiro.Backend.Tests/Ipv6Tests.cs | 2 ++ .../OnDemandTestsTests.cs | 1 + .../OperationsAndSecurityTests.cs | 2 ++ .../ProbeEndToEndTests.cs | 2 ++ ci/verify-backend.sh | 12 ++++++- docs/ops/deployment.md | 12 +++++-- 11 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 .woodpecker/e2e.yml diff --git a/.woodpecker/e2e.yml b/.woodpecker/e2e.yml new file mode 100644 index 0000000..ec9246f --- /dev/null +++ b/.woodpecker/e2e.yml @@ -0,0 +1,31 @@ +labels: + ci: domain-linux-native + platform: linux/amd64 + +clone: + - name: git + image: plugin-git + settings: + lfs: false + +when: + - event: pull_request + - event: push + branch: main + +# 等 verify 编译完、快的测试过了再跑(共用它的增量编译缓存,不重新编译); +# 不挡出镜像:dev-image 只等 verify 和 web-build,这个和出镜像并行,失败照样红 +depends_on: + - verify + +steps: + - name: backend-e2e-tests + image: bash + # [SKIP TESTS] 和 verify 一样,跳过后端测试 + when: + - evaluate: 'not (CI_COMMIT_MESSAGE contains "[SKIP TESTS]")' + environment: + NUGET_PACKAGES: /home/justme/.nuget/packages + commands: + # 标了 Category=Slow 的端到端测试,见 ci/verify-backend.sh + - bash ci/verify-backend.sh slow diff --git a/.woodpecker/verify.yml b/.woodpecker/verify.yml index 60963a8..2bc5a17 100644 --- a/.woodpecker/verify.yml +++ b/.woodpecker/verify.yml @@ -23,5 +23,5 @@ steps: environment: NUGET_PACKAGES: /home/justme/.nuget/packages commands: - # 增量编译 + 测试(把后端源码同步到固定目录,只重编译变了的项目),见 ci/verify-backend.sh - - bash ci/verify-backend.sh + # 增量编译 + 快的那批测试(约 20 秒);慢的端到端测试在 e2e workflow 里和出镜像并行跑。见 ci/verify-backend.sh + - bash ci/verify-backend.sh fast diff --git a/AGENTS.md b/AGENTS.md index 6a7949a..99bfbae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,7 +7,7 @@ MoShiro is a self-hosted network path quality monitor. Backend coordinates Agent ## Branch and workspace safety - `main` is the only working branch (the old `dev` branch is retired and no pipeline uses it). Commit and push to `main` directly; use a pull request when a review is wanted. -- A push to `main` publishes development images only if its commit message contains `[CI]` (for a merged PR, put it in the PR or merge commit title). Without `[CI]` nothing is built; use `[CI SKIP]` to skip the pipeline entirely. Backend tests and the web build run on every `main` push and must pass first; `[SKIP TESTS]` skips only the Backend tests. +- A push to `main` publishes development images only if its commit message contains `[CI]` (for a merged PR, put it in the PR or merge commit title). Without `[CI]` nothing is built; use `[CI SKIP]` to skip the pipeline entirely. The fast Backend tests (`verify`) and the web build run on every `main` push and must pass first; the slow end-to-end tests (`[Trait("Category", "Slow")]`, workflow `e2e`) run alongside the image build and do not block it; `[SKIP TESTS]` skips only the Backend tests. - Versions are filled in by CI (`ci/version.sh`): a `vX.Y.Z` tag builds exactly `X.Y.Z`; a `[CI]` build on `main` is `vX.Y.(Z+1)-dev.-` (`N` = commits since the latest tag). The `` in `backend/Directory.Build.props` and the `version` in the `web` `package.json` files are placeholders (`0.0.0-dev`); never bump them by hand. - Before editing, inspect `git status`. Preserve pre-existing user changes: do not reset, clean, stash, or include unrelated files in a commit. - Do not commit credentials, API keys, registry passwords, or generated runtime data. CI credentials belong in Woodpecker secrets (`zot_secret`, `gitea_token`, `release_signing_key` for signing release manifests). CI registers the signed manifests at `/api/release/offer`, which needs no token because the panel verifies the signature. The release signing private key never goes into the repository or the panel; only the public key in `packaging/update/` does. diff --git a/backend/tests/MoShiro.Backend.Tests/AgentEndToEndTests.cs b/backend/tests/MoShiro.Backend.Tests/AgentEndToEndTests.cs index de2f7f5..f4ad4ff 100644 --- a/backend/tests/MoShiro.Backend.Tests/AgentEndToEndTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/AgentEndToEndTests.cs @@ -257,7 +257,9 @@ public sealed class AgentEndToEndTests } } + // 慢的端到端测试(真实时间的阶梯、要等测量块出来):CI 里单独一批跑(Category=Slow),不拦着出镜像,见 ci/verify-backend.sh [Fact] + [Trait("Category", "Slow")] public async Task Hy2Ladder_EndToEnd_AgentSendsTheTrain_ProbeReportsReceipts_AndTheTestSucceeds() { await using AgentHarness agent = await AgentHarness.StartAsync(_backend); diff --git a/backend/tests/MoShiro.Backend.Tests/HostTests.cs b/backend/tests/MoShiro.Backend.Tests/HostTests.cs index c82b6c7..fe4b2db 100644 --- a/backend/tests/MoShiro.Backend.Tests/HostTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/HostTests.cs @@ -221,6 +221,7 @@ public sealed class HostStatusTests } [Fact] + [Trait("Category", "Slow")] public async Task AProbeThatTurnsReportingOff_SendsNothing() { await _backend.SiteMustBePublicAsync(); diff --git a/backend/tests/MoShiro.Backend.Tests/Ipv6Tests.cs b/backend/tests/MoShiro.Backend.Tests/Ipv6Tests.cs index 9961347..0b5ed82 100644 --- a/backend/tests/MoShiro.Backend.Tests/Ipv6Tests.cs +++ b/backend/tests/MoShiro.Backend.Tests/Ipv6Tests.cs @@ -9,7 +9,9 @@ using MoShiro.TestSupport; namespace MoShiro.Backend.Tests; /// 真实双栈回环:同一身份的两种会话、原始块、查询与测试结果必须各自独立。 +// 慢的端到端测试(真实时间的阶梯、要等测量块出来):CI 里单独一批跑(Category=Slow),不拦着出镜像,见 ci/verify-backend.sh [Collection("probe-e2e")] +[Trait("Category", "Slow")] public sealed class Ipv6Tests { private readonly BackendFixture _backend; diff --git a/backend/tests/MoShiro.Backend.Tests/OnDemandTestsTests.cs b/backend/tests/MoShiro.Backend.Tests/OnDemandTestsTests.cs index fc9fde4..f852ca6 100644 --- a/backend/tests/MoShiro.Backend.Tests/OnDemandTestsTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/OnDemandTestsTests.cs @@ -67,6 +67,7 @@ public sealed class OnDemandTestsTests } [Fact] + [Trait("Category", "Slow")] public async Task Hy2Ladder_RunsEndToEnd_AndProducesAResultWithScores() { await using AgentSimulator agent = await AgentWithProbeAsync(); diff --git a/backend/tests/MoShiro.Backend.Tests/OperationsAndSecurityTests.cs b/backend/tests/MoShiro.Backend.Tests/OperationsAndSecurityTests.cs index fb1e8ed..986db40 100644 --- a/backend/tests/MoShiro.Backend.Tests/OperationsAndSecurityTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/OperationsAndSecurityTests.cs @@ -179,7 +179,9 @@ public sealed class OperationsAndSecurityTests Assert.Equal("fleet_protocol_violation", (await agent.RequestRawAsync("fleet.exec")).Error!.Data!.Code); } + // 慢的端到端测试(真实时间的阶梯、要等测量块出来):CI 里单独一批跑(Category=Slow),不拦着出镜像,见 ci/verify-backend.sh [Fact] + [Trait("Category", "Slow")] public async Task ARepeatOffender_IsDisconnected_AndANodeCannotInitiateWhatOnlyTheBackendMay() { await using AgentSimulator agent = await _backend.ConnectAgentAsync(); diff --git a/backend/tests/MoShiro.Backend.Tests/ProbeEndToEndTests.cs b/backend/tests/MoShiro.Backend.Tests/ProbeEndToEndTests.cs index 03e7203..d8d1dce 100644 --- a/backend/tests/MoShiro.Backend.Tests/ProbeEndToEndTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/ProbeEndToEndTests.cs @@ -3,7 +3,9 @@ using System.Text.Json.Nodes; namespace MoShiro.Backend.Tests; /// 真实的后端 + 真实的 Agent + 真实的 Probe(都在进程内,回环网络)。 +// 慢的端到端测试(真实时间的阶梯、要等测量块出来):CI 里单独一批跑(Category=Slow),不拦着出镜像,见 ci/verify-backend.sh [Collection("probe-e2e")] +[Trait("Category", "Slow")] public sealed class ProbeEndToEndTests { private readonly BackendFixture _backend; diff --git a/ci/verify-backend.sh b/ci/verify-backend.sh index 46ca219..3361733 100644 --- a/ci/verify-backend.sh +++ b/ci/verify-backend.sh @@ -1,5 +1,8 @@ #!/usr/bin/env bash # 后端测试,带增量编译:把后端源码同步到一个固定目录,在那里 dotnet build / test。 +# 用法:verify-backend.sh [fast|slow] +# fast(默认):除了标了 Category=Slow 的以外的全部测试,约 20 秒,出镜像之前要过; +# slow:只跑标了 Category=Slow 的端到端测试(真实时间的阶梯、要等测量块出来,合起来一百来秒),和出镜像并行跑,失败照样红,但不拦着出镜像。 # # 为什么:每条流水线的工作区都是新克隆的(路径、文件时间都是新的),MSBuild 没法增量,每次从零编译整个解决方案要两分钟。 # 固定目录里 obj/bin 留着,同步时只更新内容真的变了的文件(按内容比较,没变的文件时间不动),MSBuild 就只重编译受影响的项目; @@ -9,6 +12,12 @@ set -euo pipefail root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +mode="${1:-fast}" +case "$mode" in + fast) filter=(--filter-not-trait "Category=Slow") ;; + slow) filter=(--filter-trait "Category=Slow") ;; + *) echo "usage: verify-backend.sh [fast|slow]" >&2; exit 2 ;; +esac cache="${MOSHIRO_CI_CACHE:-/home/justme/woodpecker/cache/backend-tests}" src="$cache/src" mkdir -p "$cache" @@ -58,7 +67,8 @@ fi # 测试在缓存目录里跑完整的解决方案(不再重复编译) log="$cache/last-test.log" status=0 -(cd "$src/backend" && dotnet test --solution MoShiro.slnx -c Release --no-build) 2>&1 | tee "$log" || status=${PIPESTATUS[0]} +# 别的程序集里没有匹配的测试会因为「零个测试」返回 8,忽略它 +(cd "$src/backend" && dotnet test --solution MoShiro.slnx -c Release --no-build "${filter[@]}" --ignore-exit-code 8) 2>&1 | tee "$log" || status=${PIPESTATUS[0]} [[ "$status" == 0 ]] && exit 0 # 个别端到端测试会偶发失败(比如 Hy2 阶梯测试里探针掉线):失败的测试单独重跑(最多两次),重跑通过就当通过(日志里留着第一次的失败); diff --git a/docs/ops/deployment.md b/docs/ops/deployment.md index f9bb130..47727aa 100644 --- a/docs/ops/deployment.md +++ b/docs/ops/deployment.md @@ -76,7 +76,15 @@ Woodpecker 在 `vMAJOR.MINOR.PATCH` tag 上,由 Windows runner 使用 AotAnywh ### Pull request 与 main 验证 -Pull request 和 `main` 的 push 会运行 Backend tests;通过后,独立的 `web-build` workflow 才会构建前端并检查 CSS。提交信息后缀 `[SKIP TESTS]` 只跳过 Backend tests,前端构建仍会运行。Gitea PR 事件的 `CI_COMMIT_MESSAGE` 是 PR 标题,因此在 PR 中跳过测试时,把后缀写入 PR 标题;普通分支 push 则写在 commit message 中。项目只用 `main` 一个工作分支,旧的 `dev` 分支不再被任何流水线使用。 +Pull request 和 `main` 的 push 会跑三个 workflow: + +- `verify`:后端的快测试(除了标了 `Category=Slow` 的全部,约 20 秒); +- `web-build`:前端构建和 CSS 检查,和 `verify` 并行(约 15 秒); +- `e2e`:标了 `Category=Slow` 的端到端测试(真实时间的 Hy2 阶梯、IPv6 和真探针的几条,一百来秒),等 `verify` 编译完之后跑,和出镜像并行,失败照样红,但不拦着出镜像。 + +后端测试用增量编译(`ci/verify-backend.sh`):源码按内容同步到 CI 主机上一个固定目录,保留 `obj/bin`,只重编译变了的项目;同一时间只有一条流水线用这份缓存。失败的测试会单独重跑(最多两次),重跑通过算通过。给新的慢测试打标记:`[Trait("Category", "Slow")]`。 + +提交信息后缀 `[SKIP TESTS]` 跳过 `verify` 和 `e2e` 里的后端测试,前端构建仍会运行。Gitea PR 事件的 `CI_COMMIT_MESSAGE` 是 PR 标题,因此在 PR 中跳过测试时,把后缀写入 PR 标题;普通分支 push 则写在 commit message 中。项目只用 `main` 一个工作分支,旧的 `dev` 分支不再被任何流水线使用。 ### 版本号 @@ -91,7 +99,7 @@ Pull request 和 `main` 的 push 会运行 Backend tests;通过后,独立的 ### main 开发镜像 -`main` 上只有提交信息包含 `[CI]` 的 push 才构建镜像,并且要等上面的 Backend tests 和 `web-build` 都通过(提交信息写 `[SKIP TESTS]` 可以跳过测试)。合并 PR 时,请把 `[CI]` 放入 PR 标题或合并提交标题。Backend、Agent、Probe 的 Linux/amd64 镜像会并行构建,并推送到同一个 Zot Registry 下独立的 `moshiro-dev/` 仓库: +`main` 上只有提交信息包含 `[CI]` 的 push 才构建镜像,并且要等上面的 `verify`(快测试)和 `web-build` 都通过(提交信息写 `[SKIP TESTS]` 可以跳过测试);慢的端到端测试(`e2e`)和出镜像并行,不拦着出镜像。合并 PR 时,请把 `[CI]` 放入 PR 标题或合并提交标题。Backend、Agent、Probe 的 Linux/amd64 镜像会并行构建,并推送到同一个 Zot Registry 下独立的 `moshiro-dev/` 仓库: | 镜像 | 滚动标签 | 固定版本标签 | | --- | --- | --- |