From 238046d959602608004b4a1c24e21190c7a9fa9c Mon Sep 17 00:00:00 2001 From: JustMe Date: Sat, 10 Oct 2026 04:54:18 +0800 Subject: [PATCH] test(backend): parallelize isolated fixtures [CI] [SKIP TESTS] --- .../MoShiro.Backend.Tests/AddressTests.cs | 3 +- .../AgentEndToEndTests.cs | 3 +- .../MoShiro.Backend.Tests/ApiBasicsTests.cs | 9 +--- .../tests/MoShiro.Backend.Tests/AuditTests.cs | 3 +- .../MoShiro.Backend.Tests/CapabilityTests.cs | 3 +- .../EnrollmentAndTaskSetTests.cs | 3 +- .../tests/MoShiro.Backend.Tests/HostTests.cs | 3 +- .../IngestAndQueryTests.cs | 3 +- .../MoShiro.Backend.Tests/InstallTests.cs | 3 +- .../tests/MoShiro.Backend.Tests/Ipv6Tests.cs | 3 +- .../tests/MoShiro.Backend.Tests/LinkTests.cs | 3 +- .../tests/MoShiro.Backend.Tests/LiveTests.cs | 3 +- .../MoShiro.Backend.Tests/NodeInfoTests.cs | 3 +- .../OnDemandTestsTests.cs | 3 +- .../OpenApiConformanceTests.cs | 3 +- .../OperationsAndSecurityTests.cs | 3 +- .../tests/MoShiro.Backend.Tests/OwnerTests.cs | 3 +- .../MoShiro.Backend.Tests/PasskeyTests.cs | 3 +- .../ProbeEndToEndTests.cs | 3 +- .../ProtocolDeclarationTests.cs | 3 +- .../MoShiro.Backend.Tests/ReleaseKeyTests.cs | 3 +- .../ReleaseOfferTests.cs | 3 +- .../MoShiro.Backend.Tests/ScheduleTests.cs | 3 +- .../MoShiro.Backend.Tests/StaticSiteTests.cs | 3 +- .../TestAssemblyConfiguration.cs | 1 + .../MoShiro.Backend.Tests/TestReleaseKey.cs | 21 +++++++-- .../tests/MoShiro.Backend.Tests/TrustTests.cs | 3 +- .../MoShiro.Backend.Tests/UpdateTests.cs | 3 +- ci/verify-backend.sh | 44 ++++++++++++++++--- 29 files changed, 84 insertions(+), 66 deletions(-) create mode 100644 backend/tests/MoShiro.Backend.Tests/TestAssemblyConfiguration.cs diff --git a/backend/tests/MoShiro.Backend.Tests/AddressTests.cs b/backend/tests/MoShiro.Backend.Tests/AddressTests.cs index b08b4e8..b7c6730 100644 --- a/backend/tests/MoShiro.Backend.Tests/AddressTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/AddressTests.cs @@ -8,8 +8,7 @@ using MoShiro.TestSupport; namespace MoShiro.Backend.Tests; /// public_host 和 ipv6_host 自动识别:Agent 上报公网地址,手填的优先,可以撤销手填。 -[Collection("probe-e2e")] -public sealed class AddressTests +public sealed class AddressTests : IClassFixture { private const string V4 = "8.8.4.4"; private const string V6 = "2606:4700:4700::1111"; diff --git a/backend/tests/MoShiro.Backend.Tests/AgentEndToEndTests.cs b/backend/tests/MoShiro.Backend.Tests/AgentEndToEndTests.cs index b55c441..09746c8 100644 --- a/backend/tests/MoShiro.Backend.Tests/AgentEndToEndTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/AgentEndToEndTests.cs @@ -6,8 +6,7 @@ using MoShiro.TestSupport; namespace MoShiro.Backend.Tests; /// 真实的后端 + 真实的 Agent(进程内)+ 假 Probe:覆盖 Agent 与 Backend 的链路、Probe 与 Agent 的点对点会话和数据面。 -[Collection("backend")] -public sealed class AgentEndToEndTests +public sealed class AgentEndToEndTests : IClassFixture { private readonly BackendFixture _backend; diff --git a/backend/tests/MoShiro.Backend.Tests/ApiBasicsTests.cs b/backend/tests/MoShiro.Backend.Tests/ApiBasicsTests.cs index b7a7625..20b4f0a 100644 --- a/backend/tests/MoShiro.Backend.Tests/ApiBasicsTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/ApiBasicsTests.cs @@ -4,8 +4,7 @@ using System.Text.Json.Nodes; namespace MoShiro.Backend.Tests; -[Collection("backend")] -public sealed class ApiBasicsTests +public sealed class ApiBasicsTests : IClassFixture { private readonly BackendFixture _backend; @@ -226,9 +225,3 @@ public sealed class ApiBasicsTests Assert.Contains("moshiro_analysis_queue_length", text); } } - -[CollectionDefinition("backend")] -public sealed class BackendCollection : ICollectionFixture; - -[CollectionDefinition("probe-e2e")] -public sealed class ProbeEndToEndCollection : ICollectionFixture; diff --git a/backend/tests/MoShiro.Backend.Tests/AuditTests.cs b/backend/tests/MoShiro.Backend.Tests/AuditTests.cs index 9d1f5ec..2554294 100644 --- a/backend/tests/MoShiro.Backend.Tests/AuditTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/AuditTests.cs @@ -4,8 +4,7 @@ using System.Text.Json.Nodes; namespace MoShiro.Backend.Tests; /// 审计日志:写操作、登录成败都会记一行;谁做的(用户名或 key:令牌名)、结果、细节。 -[Collection("probe-e2e")] -public sealed class AuditTests +public sealed class AuditTests : IClassFixture { private readonly BackendFixture _backend; diff --git a/backend/tests/MoShiro.Backend.Tests/CapabilityTests.cs b/backend/tests/MoShiro.Backend.Tests/CapabilityTests.cs index e2a7393..e24a04e 100644 --- a/backend/tests/MoShiro.Backend.Tests/CapabilityTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/CapabilityTests.cs @@ -9,8 +9,7 @@ using MoShiro.Backend.Application; namespace MoShiro.Backend.Tests; /// 能力模型(docs/security/access.md 第 3 节):每个管理端点声明能力;密钥只能拿到自己拥有能力的子集;写蕴含读。 -[Collection("probe-e2e")] -public sealed class CapabilityTests +public sealed class CapabilityTests : IClassFixture { private readonly BackendFixture _backend; diff --git a/backend/tests/MoShiro.Backend.Tests/EnrollmentAndTaskSetTests.cs b/backend/tests/MoShiro.Backend.Tests/EnrollmentAndTaskSetTests.cs index f6134b6..37b9210 100644 --- a/backend/tests/MoShiro.Backend.Tests/EnrollmentAndTaskSetTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/EnrollmentAndTaskSetTests.cs @@ -5,8 +5,7 @@ using MoShiro.TestSupport; namespace MoShiro.Backend.Tests; -[Collection("backend")] -public sealed class EnrollmentAndTaskSetTests +public sealed class EnrollmentAndTaskSetTests : IClassFixture { private readonly BackendFixture _backend; diff --git a/backend/tests/MoShiro.Backend.Tests/HostTests.cs b/backend/tests/MoShiro.Backend.Tests/HostTests.cs index fe4b2db..70036aa 100644 --- a/backend/tests/MoShiro.Backend.Tests/HostTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/HostTests.cs @@ -115,8 +115,7 @@ public sealed class HostSamplerTests } } -[Collection("backend")] -public sealed class HostStatusTests +public sealed class HostStatusTests : IClassFixture { private readonly BackendFixture _backend; diff --git a/backend/tests/MoShiro.Backend.Tests/IngestAndQueryTests.cs b/backend/tests/MoShiro.Backend.Tests/IngestAndQueryTests.cs index 3a4b065..b4453d4 100644 --- a/backend/tests/MoShiro.Backend.Tests/IngestAndQueryTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/IngestAndQueryTests.cs @@ -7,8 +7,7 @@ using MoShiro.TestSupport; namespace MoShiro.Backend.Tests; -[Collection("backend")] -public sealed class IngestAndQueryTests +public sealed class IngestAndQueryTests : IClassFixture { private const long Minute = 60_000; diff --git a/backend/tests/MoShiro.Backend.Tests/InstallTests.cs b/backend/tests/MoShiro.Backend.Tests/InstallTests.cs index 71e85b4..767bacb 100644 --- a/backend/tests/MoShiro.Backend.Tests/InstallTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/InstallTests.cs @@ -7,8 +7,7 @@ namespace MoShiro.Backend.Tests; /// 一行安装:Agent 和 Probe 共用同一份静态脚本(所有人拿到的字节都一样,所以哈希固定、可以先核对再执行), /// 面板地址、角色、一次性代码都走命令里的环境变量;脚本再向面板取 CI 签名的发布清单来验证镜像。 /// -[Collection("probe-e2e")] -public sealed class InstallTests +public sealed class InstallTests : IClassFixture { private readonly BackendFixture _backend; diff --git a/backend/tests/MoShiro.Backend.Tests/Ipv6Tests.cs b/backend/tests/MoShiro.Backend.Tests/Ipv6Tests.cs index 0b5ed82..adb00bb 100644 --- a/backend/tests/MoShiro.Backend.Tests/Ipv6Tests.cs +++ b/backend/tests/MoShiro.Backend.Tests/Ipv6Tests.cs @@ -10,9 +10,8 @@ namespace MoShiro.Backend.Tests; /// 真实双栈回环:同一身份的两种会话、原始块、查询与测试结果必须各自独立。 // 慢的端到端测试(真实时间的阶梯、要等测量块出来):CI 里单独一批跑(Category=Slow),不拦着出镜像,见 ci/verify-backend.sh -[Collection("probe-e2e")] [Trait("Category", "Slow")] -public sealed class Ipv6Tests +public sealed class Ipv6Tests : IClassFixture { private readonly BackendFixture _backend; diff --git a/backend/tests/MoShiro.Backend.Tests/LinkTests.cs b/backend/tests/MoShiro.Backend.Tests/LinkTests.cs index fdd0450..e6eff94 100644 --- a/backend/tests/MoShiro.Backend.Tests/LinkTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/LinkTests.cs @@ -4,8 +4,7 @@ using MoShiro.TestSupport; namespace MoShiro.Backend.Tests; -[Collection("probe-e2e")] -public sealed class LinkTests +public sealed class LinkTests : IClassFixture { private readonly BackendFixture _backend; diff --git a/backend/tests/MoShiro.Backend.Tests/LiveTests.cs b/backend/tests/MoShiro.Backend.Tests/LiveTests.cs index c875df6..251ed80 100644 --- a/backend/tests/MoShiro.Backend.Tests/LiveTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/LiveTests.cs @@ -6,8 +6,7 @@ using MoShiro.TestSupport; namespace MoShiro.Backend.Tests; -[Collection("backend")] -public sealed class LiveTests +public sealed class LiveTests : IClassFixture { private readonly BackendFixture _backend; diff --git a/backend/tests/MoShiro.Backend.Tests/NodeInfoTests.cs b/backend/tests/MoShiro.Backend.Tests/NodeInfoTests.cs index 0bb40dc..5ee8fbb 100644 --- a/backend/tests/MoShiro.Backend.Tests/NodeInfoTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/NodeInfoTests.cs @@ -174,8 +174,7 @@ public sealed partial class NodeInfoTests /// 访客能拿到的东西里不能有 IP 等敏感信息:公开接口的整个响应里不出现 IP 形状的字符串,也没有 ip、city、asn、经纬度这些字段。 /// Agent 和 Probe 这里是真的连上了(Probe 的来源地址是 127.0.0.1),所以如果哪里把地址带出来了,这里一定能发现。 /// -[Collection("probe-e2e")] -public sealed partial class NodePrivacyTests +public sealed partial class NodePrivacyTests : IClassFixture { private static readonly string[] s_forbiddenKeys = ["ip", "city", "asn", "latitude", "longitude", "cidr", "remote", "public_host", "geo_host", "host_name", "hostname"]; diff --git a/backend/tests/MoShiro.Backend.Tests/OnDemandTestsTests.cs b/backend/tests/MoShiro.Backend.Tests/OnDemandTestsTests.cs index f852ca6..e29ecd8 100644 --- a/backend/tests/MoShiro.Backend.Tests/OnDemandTestsTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/OnDemandTestsTests.cs @@ -6,8 +6,7 @@ using MoShiro.TestSupport; namespace MoShiro.Backend.Tests; -[Collection("backend")] -public sealed class OnDemandTestsTests +public sealed class OnDemandTestsTests : IClassFixture { private readonly BackendFixture _backend; diff --git a/backend/tests/MoShiro.Backend.Tests/OpenApiConformanceTests.cs b/backend/tests/MoShiro.Backend.Tests/OpenApiConformanceTests.cs index de5f88a..a0e645c 100644 --- a/backend/tests/MoShiro.Backend.Tests/OpenApiConformanceTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/OpenApiConformanceTests.cs @@ -7,8 +7,7 @@ namespace MoShiro.Backend.Tests; /// 对照 docs/api/api.md 第 7 节 检查运行时生成的 OpenAPI 文档:operationId、路径风格、错误响应格式、字段命名。 /// 新增端点违反规范时,这里会直接失败(等价于文档里的 eng/check-openapi)。 /// -[Collection("backend")] -public sealed partial class OpenApiConformanceTests +public sealed partial class OpenApiConformanceTests : IClassFixture { private readonly BackendFixture _backend; diff --git a/backend/tests/MoShiro.Backend.Tests/OperationsAndSecurityTests.cs b/backend/tests/MoShiro.Backend.Tests/OperationsAndSecurityTests.cs index 986db40..863dc76 100644 --- a/backend/tests/MoShiro.Backend.Tests/OperationsAndSecurityTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/OperationsAndSecurityTests.cs @@ -12,8 +12,7 @@ using MoShiro.TestSupport; namespace MoShiro.Backend.Tests; -[Collection("backend")] -public sealed class OperationsAndSecurityTests +public sealed class OperationsAndSecurityTests : IClassFixture { private const long Minute = 60_000; diff --git a/backend/tests/MoShiro.Backend.Tests/OwnerTests.cs b/backend/tests/MoShiro.Backend.Tests/OwnerTests.cs index 393b8c5..495c5bf 100644 --- a/backend/tests/MoShiro.Backend.Tests/OwnerTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/OwnerTests.cs @@ -38,8 +38,7 @@ public sealed class OwnerPolicyTests } } -[Collection("probe-e2e")] -public sealed class OwnerEndToEndTests : IDisposable +public sealed class OwnerEndToEndTests : IDisposable, IClassFixture { private readonly BackendFixture _backend; private readonly BackendOptions _options; diff --git a/backend/tests/MoShiro.Backend.Tests/PasskeyTests.cs b/backend/tests/MoShiro.Backend.Tests/PasskeyTests.cs index d4d56ad..027bb85 100644 --- a/backend/tests/MoShiro.Backend.Tests/PasskeyTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/PasskeyTests.cs @@ -86,8 +86,7 @@ public sealed class SoftwarePasskey : IDisposable } /// passkey 登录:注册、登录、拒绝各种错误的断言、只能管理自己的 passkey。设计见 docs/security/access.md 第 4 节。 -[Collection("probe-e2e")] -public sealed class PasskeyTests +public sealed class PasskeyTests : IClassFixture { private readonly BackendFixture _backend; diff --git a/backend/tests/MoShiro.Backend.Tests/ProbeEndToEndTests.cs b/backend/tests/MoShiro.Backend.Tests/ProbeEndToEndTests.cs index d8d1dce..b43a62d 100644 --- a/backend/tests/MoShiro.Backend.Tests/ProbeEndToEndTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/ProbeEndToEndTests.cs @@ -4,9 +4,8 @@ namespace MoShiro.Backend.Tests; /// 真实的后端 + 真实的 Agent + 真实的 Probe(都在进程内,回环网络)。 // 慢的端到端测试(真实时间的阶梯、要等测量块出来):CI 里单独一批跑(Category=Slow),不拦着出镜像,见 ci/verify-backend.sh -[Collection("probe-e2e")] [Trait("Category", "Slow")] -public sealed class ProbeEndToEndTests +public sealed class ProbeEndToEndTests : IClassFixture { private readonly BackendFixture _backend; diff --git a/backend/tests/MoShiro.Backend.Tests/ProtocolDeclarationTests.cs b/backend/tests/MoShiro.Backend.Tests/ProtocolDeclarationTests.cs index 90a8a9d..0ec3615 100644 --- a/backend/tests/MoShiro.Backend.Tests/ProtocolDeclarationTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/ProtocolDeclarationTests.cs @@ -12,8 +12,7 @@ namespace MoShiro.Backend.Tests; /// 二进制通道都有对应的收件箱,文档里写了每一个方法。新增方法绕不过声明,声明了没人处理也会被发现。 /// 注册处理函数时路由器本身还会核对方向、类型和角色,不符就在启动时失败。 /// -[Collection("probe-e2e")] -public sealed class ProtocolDeclarationTests +public sealed class ProtocolDeclarationTests : IClassFixture { private static readonly MethodTable s_table = MoShiroMethodTable.Table; diff --git a/backend/tests/MoShiro.Backend.Tests/ReleaseKeyTests.cs b/backend/tests/MoShiro.Backend.Tests/ReleaseKeyTests.cs index 725c50e..1ee335c 100644 --- a/backend/tests/MoShiro.Backend.Tests/ReleaseKeyTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/ReleaseKeyTests.cs @@ -11,8 +11,7 @@ namespace MoShiro.Backend.Tests; /// 可信资产来源:官方发布密钥始终信任;部署的人可以另外导入自己的发布公钥(默认没有),用自己的私钥给自己构建的清单签名。 /// 面板只收公钥,拒绝私钥;删除之后只有它签过的清单不再被接受,已登记的也丢掉。 /// -[Collection("probe-e2e")] -public sealed class ReleaseKeyTests +public sealed class ReleaseKeyTests : IClassFixture { private static long s_seq = TimeProvider.System.GetUtcNow().ToUnixTimeSeconds() * 1000 + 500_000; diff --git a/backend/tests/MoShiro.Backend.Tests/ReleaseOfferTests.cs b/backend/tests/MoShiro.Backend.Tests/ReleaseOfferTests.cs index 9f20e68..ff50210 100644 --- a/backend/tests/MoShiro.Backend.Tests/ReleaseOfferTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/ReleaseOfferTests.cs @@ -11,8 +11,7 @@ namespace MoShiro.Backend.Tests; /// CI 登记清单不需要令牌,但清单必须带 CI 发布密钥的有效签名:令牌存在面板的数据库里,清库、重装或还没有 passkey 的时候就拿不到, /// 而清单本来就是签名的,面板直接验签更简单,也没有长期令牌可偷。 /// -[Collection("probe-e2e")] -public sealed class ReleaseOfferTests +public sealed class ReleaseOfferTests : IClassFixture { // openssl 生成的测试密钥对和签名(`openssl dgst -sha256 -sign`,DER):证明验签接受 CI 脚本实际产出的格式 private const string TestKey = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQwlE2KjYWszeaTsng6CfPxhOqeIV\n+7dm940f7XfHJr9A6/OTiZ2YnhFWWipZ4Z4poPtb6hMW04tIARO8+Km34w==\n-----END PUBLIC KEY-----"; diff --git a/backend/tests/MoShiro.Backend.Tests/ScheduleTests.cs b/backend/tests/MoShiro.Backend.Tests/ScheduleTests.cs index fe315bb..c64713c 100644 --- a/backend/tests/MoShiro.Backend.Tests/ScheduleTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/ScheduleTests.cs @@ -7,8 +7,7 @@ using MoShiro.TestSupport; namespace MoShiro.Backend.Tests; /// 定时重启:默认开、北京时间 04:30;节点连上时补发,管理端改了设置马上群发,关掉就发空。 -[Collection("probe-e2e")] -public sealed class ScheduleTests +public sealed class ScheduleTests : IClassFixture { private readonly BackendFixture _backend; diff --git a/backend/tests/MoShiro.Backend.Tests/StaticSiteTests.cs b/backend/tests/MoShiro.Backend.Tests/StaticSiteTests.cs index a16e76d..52c7154 100644 --- a/backend/tests/MoShiro.Backend.Tests/StaticSiteTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/StaticSiteTests.cs @@ -132,8 +132,7 @@ public sealed class StaticSiteTests } /// HTTP 层:后端自己的路径不会被前端的回退吞掉;响应头对。 -[Collection("probe-e2e")] -public sealed class StaticSiteHttpTests +public sealed class StaticSiteHttpTests : IClassFixture { private readonly BackendFixture _backend; diff --git a/backend/tests/MoShiro.Backend.Tests/TestAssemblyConfiguration.cs b/backend/tests/MoShiro.Backend.Tests/TestAssemblyConfiguration.cs new file mode 100644 index 0000000..79b0a1a --- /dev/null +++ b/backend/tests/MoShiro.Backend.Tests/TestAssemblyConfiguration.cs @@ -0,0 +1 @@ +[assembly: Xunit.v3.Parallelization(MaxThreads = 16, Mode = Xunit.Sdk.ParallelMode.Collections)] diff --git a/backend/tests/MoShiro.Backend.Tests/TestReleaseKey.cs b/backend/tests/MoShiro.Backend.Tests/TestReleaseKey.cs index c932b96..a4d127d 100644 --- a/backend/tests/MoShiro.Backend.Tests/TestReleaseKey.cs +++ b/backend/tests/MoShiro.Backend.Tests/TestReleaseKey.cs @@ -11,11 +11,26 @@ namespace MoShiro.Backend.Tests; public sealed class TestReleaseKey : IReleaseKey { private static readonly ECDsa s_key = ECDsa.Create(ECCurve.NamedCurves.nistP256); + private static readonly object s_gate = new(); public static TestReleaseKey Instance { get; } = new(); - public string Pem => s_key.ExportSubjectPublicKeyInfoPem(); + public string Pem + { + get + { + lock (s_gate) + { + return s_key.ExportSubjectPublicKeyInfoPem(); + } + } + } - public static string Sign(string manifest) => - Convert.ToBase64String(s_key.SignData(Encoding.UTF8.GetBytes(manifest), HashAlgorithmName.SHA256, DSASignatureFormat.Rfc3279DerSequence)); + public static string Sign(string manifest) + { + lock (s_gate) + { + return Convert.ToBase64String(s_key.SignData(Encoding.UTF8.GetBytes(manifest), HashAlgorithmName.SHA256, DSASignatureFormat.Rfc3279DerSequence)); + } + } } diff --git a/backend/tests/MoShiro.Backend.Tests/TrustTests.cs b/backend/tests/MoShiro.Backend.Tests/TrustTests.cs index baa2cd3..be968b9 100644 --- a/backend/tests/MoShiro.Backend.Tests/TrustTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/TrustTests.cs @@ -17,8 +17,7 @@ namespace MoShiro.Backend.Tests; /// 信任根:操作员(passkey)签操作员集合;安装命令里带着创世指纹和属主(都是公开值),节点注册时验证面板的集合并固定在本地。 /// 设计见 docs/security/access.md 第 8 节。 /// -[Collection("probe-e2e")] -public sealed class TrustTests +public sealed class TrustTests : IClassFixture { private readonly BackendFixture _backend; diff --git a/backend/tests/MoShiro.Backend.Tests/UpdateTests.cs b/backend/tests/MoShiro.Backend.Tests/UpdateTests.cs index 33a6b21..712def4 100644 --- a/backend/tests/MoShiro.Backend.Tests/UpdateTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/UpdateTests.cs @@ -5,8 +5,7 @@ using MoShiro.TestSupport; namespace MoShiro.Backend.Tests; -[Collection("probe-e2e")] -public sealed class UpdateTests +public sealed class UpdateTests : IClassFixture { private const string Digest = "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; diff --git a/ci/verify-backend.sh b/ci/verify-backend.sh index ea765a8..95c8589 100644 --- a/ci/verify-backend.sh +++ b/ci/verify-backend.sh @@ -14,8 +14,8 @@ 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") ;; + fast) filter=(--filter-not-trait "Category=Slow"); test_timeout=300 ;; + slow) filter=(--filter-trait "Category=Slow"); test_timeout=900 ;; *) echo "usage: verify-backend.sh [fast|slow]" >&2; exit 2 ;; esac cache="${MOSHIRO_CI_CACHE:-/home/justme/woodpecker/cache/backend-tests}" @@ -70,9 +70,43 @@ quic_runtime="$cache/quic-runtime" bash "$root/ci/fetch-quic-test-runtime.sh" "$quic_runtime" export LD_LIBRARY_PATH="$quic_runtime/runtime${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" status=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]} +# Each test module can spawn its own testhost. Run the whole command in a new +# session and time out its process group, so orphaned testhosts cannot keep the +# Woodpecker step and the shared cache lock alive. +if ! command -v setsid >/dev/null 2>&1; then + echo "setsid is required to enforce the backend test timeout" >&2 + exit 2 +fi + +run_with_timeout() { + local seconds="$1" marker="$cache/test-timeout.marker" test_pid watcher_pid result=0 + shift + rm -f "$marker" + ( + cd "$src/backend" + setsid "$@" & + test_pid=$! + setsid bash -c 'sleep "$1"; : > "$2"; kill -TERM -- "-$3" 2>/dev/null || true' _ "$seconds" "$marker" "$test_pid" >/dev/null 2>&1 & + watcher_pid=$! + wait "$test_pid" || result=$? + if [[ -f "$marker" ]]; then + kill -KILL -- "-$test_pid" 2>/dev/null || true + result=124 + fi + kill -TERM -- "-$watcher_pid" 2>/dev/null || true + wait "$watcher_pid" 2>/dev/null || true + rm -f "$marker" + return "$result" + ) +} + +# Ignore exit 8 from other assemblies with no matching tests; both suites have a hard wall-clock cap. +run_with_timeout "$test_timeout" dotnet test --solution MoShiro.slnx -c Release --no-build --max-parallel-test-modules 16 "${filter[@]}" --ignore-exit-code 8 2>&1 | tee "$log" || status=${PIPESTATUS[0]} [[ "$status" == 0 ]] && exit 0 +if [[ "$status" == 124 || "$status" == 137 ]]; then + echo "backend tests exceeded the ${test_timeout}s timeout" >&2 + exit "$status" +fi # 个别端到端测试会偶发失败(比如 Hy2 阶梯测试里探针掉线):失败的测试单独重跑(最多两次),重跑通过就当通过(日志里留着第一次的失败); # 失败太多说明是真的坏了,不重跑 @@ -87,7 +121,7 @@ for name in "${failed[@]}"; do for attempt in 1 2; do echo "retrying $name (attempt $attempt of 2)" # 过滤要带通配符;别的程序集里没有匹配的测试,会因为「零个测试」返回 8,忽略它 - if (cd "$src/backend" && dotnet test --solution MoShiro.slnx -c Release --no-build --filter-method "*$name*" --ignore-exit-code 8); then + if run_with_timeout 90 dotnet test --solution MoShiro.slnx -c Release --no-build --max-parallel-test-modules 16 --filter-method "*$name*" --ignore-exit-code 8; then passed=true break fi