Merge origin/master and preserve data-channel lifecycle guards

Keep data-channel close notifications while adopting terminal-error transport draining. Retain recipient-scoped event visibility alongside source-emission admission and shared filter evaluation. Reconcile overlapping NUnit 5 assertion fixes.
This commit is contained in:
Marc
2026-10-20 15:29:11 +02:00
463 changed files with 117554 additions and 5541 deletions
+19
View File
@@ -93,6 +93,11 @@ dotnet test UA.slnx
Conventions and requirements:
- **Frameworks.** Test projects use either **NUnit** (with `Assert.That` assertions and **Moq** for mocking) or **TUnit** (with its own assertions and mock helpers). Do not mix the two in one project, and do not use the classic NUnit asserts (`Assert.AreEqual`, …).
- **Out-of-tree sample-test output.** When `--artifacts-path` places the WoT
aggregation tests outside the checkout, set `OPCUA_TEST_REPOSITORY_ROOT` to
the checkout containing `UA.slnx`. The sample fixtures read their original
repository assets there; no asset or assembly copying is needed. An invalid
configured root fails explicitly instead of falling back to another checkout.
- **NUnit 5 async assertions.** Always await `Assert.ThrowsAsync`, `Assert.CatchAsync`, and `Assert.DoesNotThrowAsync`; these methods return tasks and no longer block until the assertion completes. The enclosing test or helper must return `Task`.
- **Ordered NUnit fixtures.** Use `DependsOnTest` rather than the obsolete `Order` attribute. Existing sequencing-only dependencies use `AllowFailure = true` so one failed test does not suppress later tests. Filtering a dependent test also runs its prerequisites; dependency chains must not use fixture-level `Parallelizable`.
- **Coverage.** Coverage is measured with **Coverlet** and must not regress; every non-application, non-test project should stay at or above **80 %**. Two gates enforce this in CI — see [Continuous integration](#continuous-integration).
@@ -483,6 +488,20 @@ GitHub refuses to start a run whose matrices expand past 256 jobs, and the limit
The executor fails a project when its build fails, when the TRX counters report any failure, when a test fixture's setup or teardown fails, when it produces **no** TRX at all, when nothing in it actually ran, or when it outlives its per-project ceiling. A "no tests ran" outcome is a failure, not a pass — and that includes a suite whose tests were *all* skipped, which records a non-zero total while verifying nothing.
The WoT aggregation sample tests keep their complete companion-model corpus in a
dedicated CI batch. That batch has a finite 240-minute combined build/test budget
and a 65-minute inactivity limit; ordinary projects retain their 45-minute combined
budget. The sample's full generation-replacement case includes two complete
publications and must not be truncated to fit the ordinary project budget.
The Server, WoT and Types integration projects also run separately, with a 90-minute
combined budget. Server and WoT keep the 10-minute inactivity guard; Types uses
30 minutes for complete instrumented companion-model conversions. Hosted cold builds
have consumed 20-28 minutes before their healthy integration suites start;
the larger finite budget prevents truncation without hiding an inactive test.
The .NET Framework WoT profile reserves 150 combined minutes because its complete
regeneration and server-integration run exceeded an hour after a 27-minute cold
build. Its per-test inactivity guard remains unchanged.
That per-project ceiling is a **single combined budget** covering the project's build *and* its test run, measured by one stopwatch. The matrix derives each job's `timeout-minutes` from it as `20 + projectCount × perProjectTimeout`, so spending it twice per project would let a batch outlive its job: GitHub would cancel the run, and a cancelled job produces neither the executor's per-project annotation nor its results. A batch whose budgets add up past the job ceiling is an error in `get-ci-matrix.ps1` rather than a clamped `timeout-minutes`, for the same reason. `CiMatrixScriptTests` pins both halves of that arithmetic.
The verdict comes from the emitted TRX rather than from the `dotnet test` exit code, and lives in [`.github/scripts/get-test-verdict.ps1`](../.github/scripts/get-test-verdict.ps1) so it can be tested on its own — see [`CiTestVerdictTests`](../tests/Opc.Ua.Tools.Tests/CiTestVerdictTests.cs). A failed fixture setup or teardown, such as the assembly-level certificate leak check in a `[OneTimeTearDown]`, is not a test result, so no counter records it: the NUnit adapter writes it to the TRX as a run message (`TearDown failed for test fixture …`), and the verdict rejects it whatever the exit code says. A non-zero exit is tolerated when, and only when, the results record at least one **passing** test, no failure, error, timeout, abort or `passedButRunAborted`, and no failed fixture setup or teardown. That combination means the host died during process **exit**, after the last test and every teardown had already run; failing it would report a false red. It is not a macOS quirk — Windows hosts do it too (run 35714133848, `test-windows-net48 (5/30)`: `Opc.Ua.Client.Tests` reported 256 passed, 0 failed, host exit 1). A host that dies mid-run leaves a non-zero counter and is still rejected, and a run in which every test was skipped is rejected whatever the exit code says. Every tolerated run raises a warning annotation and is labelled in the job summary, so a host that keeps dying stays visible.