fix #485: isolate console capture and align Gitea portable test lanes
This commit is contained in:
parent
3ebb120dd2
commit
0275b4ca5a
7 changed files with 144 additions and 11 deletions
|
|
@ -5,10 +5,10 @@
|
|||
# self-hosted runners carry the pinned SDK band from global.json already.
|
||||
# actions/checkout IS mirrored, so it is used normally.
|
||||
#
|
||||
# The suite runs through tools/run-release-gate.ps1 rather than a bare
|
||||
# `dotnet test`: that script owns the xUnit trait-lane filter which excludes
|
||||
# the InstalledDat / Live / Manual / OS-specific lanes. A bare `dotnet test`
|
||||
# fails ~36 tests by design because those lanes assert their own preconditions.
|
||||
# The streaming suite below mirrors tools/run-release-gate.ps1's portable
|
||||
# filter, including the capability-owned Vulkan lane. The ordinary runners
|
||||
# do not promise a Vulkan device. VulkanLaneOwnershipContractTests guards
|
||||
# filter parity; a bare dotnet test also selects environment-dependent lanes.
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
|
|
@ -52,7 +52,7 @@ jobs:
|
|||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$filter = 'Lane!=InstalledDat&Lane!=PreparedPackage&Lane!=Live&Lane!=Manual&Lane!=Timing&Lane!=Windows&Lane!=Linux&Lane!=SystemFont&Purpose!=Diagnostic&Status!=KnownFailure'
|
||||
$filter = 'Lane!=InstalledDat&Lane!=PreparedPackage&Lane!=Live&Lane!=Manual&Lane!=Timing&Lane!=Windows&Lane!=Linux&Lane!=Vulkan&Lane!=SystemFont&Purpose!=Diagnostic&Status!=KnownFailure'
|
||||
$failed = @()
|
||||
foreach ($proj in Get-ChildItem tests -Directory | Sort-Object Name) {
|
||||
$csproj = Join-Path $proj.FullName "$($proj.Name).csproj"
|
||||
|
|
@ -84,7 +84,7 @@ jobs:
|
|||
echo '::group::AcDream.Core.Net.Tests (single-threaded)'
|
||||
dotnet test tests/AcDream.Core.Net.Tests/AcDream.Core.Net.Tests.csproj \
|
||||
-c Release --nologo \
|
||||
--filter 'Lane!=InstalledDat&Lane!=PreparedPackage&Lane!=Live&Lane!=Manual&Lane!=Timing&Lane!=Windows&Lane!=SystemFont&Purpose!=Diagnostic&Status!=KnownFailure' \
|
||||
--filter 'Lane!=InstalledDat&Lane!=PreparedPackage&Lane!=Live&Lane!=Manual&Lane!=Timing&Lane!=Windows&Lane!=Vulkan&Lane!=SystemFont&Purpose!=Diagnostic&Status!=KnownFailure' \
|
||||
-- xUnit.MaxParallelThreads=1
|
||||
echo '::endgroup::'
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ jobs:
|
|||
tests/AcDream.UI.Abstractions.Tests ; do
|
||||
echo "::group::$p"
|
||||
dotnet test "$p" -c Release --nologo \
|
||||
--filter 'Lane!=InstalledDat&Lane!=PreparedPackage&Lane!=Live&Lane!=Manual&Lane!=Timing&Lane!=Windows&Lane!=SystemFont&Purpose!=Diagnostic&Status!=KnownFailure'
|
||||
--filter 'Lane!=InstalledDat&Lane!=PreparedPackage&Lane!=Live&Lane!=Manual&Lane!=Timing&Lane!=Windows&Lane!=Vulkan&Lane!=SystemFont&Purpose!=Diagnostic&Status!=KnownFailure'
|
||||
echo "::endgroup::"
|
||||
done
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,20 @@ confirmed closed by the owner, 11 need a focused live gate, and 43 are safe to
|
|||
remain closed. See
|
||||
[`docs/research/2026-08-28-owner-closed-issue-validity-audit.md`](research/2026-08-28-owner-closed-issue-validity-audit.md).
|
||||
|
||||
## #485 — Gitea portable CI races console capture and selects Vulkan-only tests
|
||||
|
||||
**Status:** DONE — correction implemented and locally verified, 2026-09-06;
|
||||
new remote run to be verified after push. Run209 on3ebb120dd built cleanly,
|
||||
but Windows App tests failed: one StringBuilder capture race, four
|
||||
ErrorIncompatibleDriver failures. Linux passed; release was skipped.
|
||||
The camera diagnostics collection now excludes other parallel collections;
|
||||
Gitea filters match the canonical portable filter (including Lane!=Vulkan).
|
||||
Two regression guards added. Release test assembly build0W/0E and corrected
|
||||
App lane7,156/0/0 PASS; no production code changed. The dedicated Vulkan lane
|
||||
remains available and strict, not a runtime skip.
|
||||
Evidence and separate alternate-output build limitation:
|
||||
`research/2026-09-01-overhaul/2026-09-06-ci-209-correction.md`.
|
||||
|
||||
## #484 — Distant mountain silhouettes flicker and change with viewpoint
|
||||
|
||||
**Status:** OPEN — OWNER-DEFERRED, 2026-09-06, after report on #483 candidate.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,14 @@ test can fail on them and a run costs ~7 minutes plus a 121 MB release. A red ga
|
|||
cannot publish: `release` uses `needs:`, not a `workflow_run` trigger, whose
|
||||
Forgejo support is unreliable.
|
||||
|
||||
The streaming Windows filter must exactly match the default in
|
||||
`tools/run-release-gate.ps1`; Linux differs only by enabling `Lane=Linux`.
|
||||
`VulkanLaneOwnershipContractTests` enforces both copies. These ordinary Gitea
|
||||
runners do not promise Vulkan capability, so `Lane=Vulkan` is excluded here,
|
||||
not silently skipped inside a test. The explicit hardware lane remains in
|
||||
the GitHub `linux-vulkan` workflow and can be run locally on a capable device
|
||||
(see `release-gate.md`); a green Gitea run alone does not certify GPU pixels.
|
||||
|
||||
## Why Gitea and not GitHub
|
||||
|
||||
GitHub Actions is **billing-blocked** on this account ("recent account payments
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
# Post-closeout Gitea CI correction — #485, 2026-09-06
|
||||
|
||||
## Evidence and cause
|
||||
|
||||
[Run209](https://git.snakedesert.se/erik/acdream/actions/runs/209) tested
|
||||
`3ebb120dd271c873476cd071b2a33f09d17ce710`. Linux passed. Windows built
|
||||
successfully (0 warnings/errors), then App.Tests failed five cases:
|
||||
|
||||
- `Collect_WeatherTurnFiredMatchesThePrintedOcLineExactly(true)`:
|
||||
`StringBuilder.ToString` threw ArgumentOutOfRangeException (chunkLength).
|
||||
The camera diagnostics collection serialized its members but still ran
|
||||
beside other collections. Other tests could write to the process-wide
|
||||
captured Console.Out while the test read its underlying StringWriter.
|
||||
They could also observe the temporary global walk-transcript flag.
|
||||
- The four `Lane=Vulkan` pixel cases (ordinary shared-index, both
|
||||
world-shader light strides, atmospheric receivers) failed at
|
||||
`vkCreateInstance: ErrorIncompatibleDriver`. They were already correctly
|
||||
tagged, but Gitea's copied portable filter lacked the Vulkan exclusion
|
||||
present in `tools/run-release-gate.ps1`.
|
||||
|
||||
Windows App result: 7,153 passed / 5 failed / 0 skipped. No other Windows
|
||||
test assembly failed. Release was skipped because it depends on both gates.
|
||||
These are test isolation and CI lane-routing defects, not a new distance
|
||||
algorithm failure.
|
||||
|
||||
## Bounded correction
|
||||
|
||||
Make the existing camera diagnostics collection exclusive with
|
||||
`DisableParallelization=true`. Other ordinary collections remain parallel.
|
||||
No timer, retry, assertion weakening or production change. A deterministic
|
||||
metadata regression protects exclusivity.
|
||||
|
||||
Align the Windows Gitea filter with the canonical portable filter; both Linux
|
||||
filters retain only their intentional difference (enable Lane=Linux).
|
||||
A contract regression compares all three filters to that canonical source.
|
||||
The Vulkan tests are unchanged, still fail on missing capability when selected,
|
||||
and remain explicitly owned by the separate hardware lane. Gitea's ordinary
|
||||
runners do not certify GPU pixels; this distinction is documented.
|
||||
|
||||
## Verification before push
|
||||
|
||||
- Changed App test assembly Release build, reusing unchanged production
|
||||
references (`BuildProjectReferences=false`): **PASS, 0 warnings/errors**.
|
||||
- Full App suite under the exact corrected Gitea Windows filter:
|
||||
**7,156 passed / 0 failed / 0 skipped**. The total is run209's7,158 minus
|
||||
four hardware-only cases plus two new regression tests, not lost coverage.
|
||||
TRX: `artifacts/ci-209-fix-tests/ci-209-app-corrected.trx`.
|
||||
- Lead reviewed the console/global-isolation fix; independent review of
|
||||
CI filters, ownership contract and documentation: **PASS**.
|
||||
- Running client binary SHA256 remained
|
||||
`869BA14CB2C610E006C80BDFFCBD75629339A3486B8B98D7708B23050700FF34`.
|
||||
Neither ACDream nor retail was closed or controlled.
|
||||
|
||||
An attempted full solution build with alternate
|
||||
`--artifacts-path artifacts/ci-209-fix-build` failed in existing packaging
|
||||
path assumptions: smoke-plugin GetTargetPath resolves against App's absolute
|
||||
output path (MSB3030), and nested Bake publication misses its win-x64 assets
|
||||
target (NETSDK1047). That attempt is **FAIL**, not the passing test-only build
|
||||
above. No packaging workaround was added to this CI correction. The ordinary
|
||||
CI build already passed on the original commit; the subsequent remote run
|
||||
must verify the correction in CI's actual clean-build layout.
|
||||
|
||||
This note records pre-push evidence. Remote success is reported only after
|
||||
observing the new commit's Gitea results. Campaign closeout and deferred #484
|
||||
are unchanged; no retail-behavior deviation is introduced.
|
||||
|
|
@ -36,7 +36,9 @@ namespace AcDream.App.Tests.Rendering;
|
|||
/// <c>Console.SetOut</c> by the transcript tests to capture output.
|
||||
/// Interleaved redirection can restore a DISPOSED <c>StringWriter</c> as the
|
||||
/// process-wide <c>Console.Out</c>, which then throws in unrelated
|
||||
/// tests.</description></item>
|
||||
/// tests. Even classes that only write to the console can race a capture's
|
||||
/// <c>StringWriter.ToString</c> snapshot; Console's synchronized writer
|
||||
/// does not synchronize reads of the underlying StringBuilder.</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para>
|
||||
|
|
@ -54,13 +56,17 @@ namespace AcDream.App.Tests.Rendering;
|
|||
/// per-test values (several are <c>[Theory]</c> cases that set different knobs
|
||||
/// per case), so a fixture cannot own the save/restore without rewriting every
|
||||
/// member's internals. The existing per-test <c>finally</c> blocks already
|
||||
/// return the defaults; this attribute supplies the mutual exclusion they were
|
||||
/// missing. Follows the <c>WorldEnvironmentControllerCollection</c> precedent.
|
||||
/// return the defaults. <c>DisableParallelization</c> additionally excludes
|
||||
/// OTHER collections for this collection's duration: member-only serialization
|
||||
/// cannot protect process-wide console captures or prevent unrelated walk tests
|
||||
/// from observing the temporary transcript flag. Ordinary collections can still
|
||||
/// run in parallel with each other. Follows the
|
||||
/// <c>ThreadSchedulingCollection</c> exclusivity precedent.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>Evidence + history: <c>docs/ISSUES.md</c> #251.</para>
|
||||
/// </summary>
|
||||
[CollectionDefinition(Name)]
|
||||
[CollectionDefinition(Name, DisableParallelization = true)]
|
||||
public sealed class CameraDiagnosticsCollection
|
||||
{
|
||||
public const string Name = "Camera diagnostics globals";
|
||||
|
|
|
|||
|
|
@ -56,6 +56,32 @@ public sealed class VulkanLaneOwnershipContractTests
|
|||
Assert.Contains("--filter \"Lane=Vulkan\"", hardwareStep, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GiteaPortableFilters_MatchCanonicalReleaseGateWithOnlyLinuxLaneEnabledOnLinux()
|
||||
{
|
||||
string root = RepositoryRoot();
|
||||
string releaseGate = File.ReadAllText(Path.Combine(root, "tools", "run-release-gate.ps1"));
|
||||
Match canonical = Regex.Match(
|
||||
releaseGate,
|
||||
"\\[string\\]\\$TestFilter\\s*=\\s*'(?<filter>[^']+)'",
|
||||
RegexOptions.CultureInvariant);
|
||||
Assert.True(canonical.Success, "Could not locate the canonical portable filter.");
|
||||
string expected = canonical.Groups["filter"].Value;
|
||||
|
||||
string workflow = File.ReadAllText(Path.Combine(root, ".gitea", "workflows", "ci.yml"));
|
||||
Match windows = Regex.Match(
|
||||
workflow, "\\$filter\\s*=\\s*'(?<filter>[^']+)'", RegexOptions.CultureInvariant);
|
||||
Assert.True(windows.Success, "Could not locate Gitea's Windows test filter.");
|
||||
Assert.Equal(expected, windows.Groups["filter"].Value);
|
||||
|
||||
MatchCollection linux = Regex.Matches(
|
||||
workflow, "--filter\\s+'(?<filter>[^']+)'", RegexOptions.CultureInvariant);
|
||||
Assert.Equal(2, linux.Count);
|
||||
string expectedLinux = string.Join('&', expected.Split('&').Where(term => term != "Lane!=Linux"));
|
||||
foreach (Match filter in linux)
|
||||
Assert.Equal(expectedLinux, filter.Groups["filter"].Value);
|
||||
}
|
||||
|
||||
private static string Step(string workflow, int start)
|
||||
{
|
||||
int next = workflow.IndexOf(" - name:", start + 1, StringComparison.Ordinal);
|
||||
|
|
|
|||
|
|
@ -29,11 +29,25 @@ namespace AcDream.App.Tests.Rendering.Walk;
|
|||
/// comment, issue #251): interleaved
|
||||
/// <c>Console.SetOut</c> redirection across parallel test classes can
|
||||
/// restore a disposed <see cref="StringWriter"/> process-wide.
|
||||
/// The collection also excludes other parallel collections so their writes
|
||||
/// cannot race snapshots of the capture or contaminate the transcript.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Collection(CameraDiagnosticsCollection.Name)]
|
||||
public sealed partial class WalkFrameDriverTests
|
||||
{
|
||||
[Fact]
|
||||
public void TranscriptCaptureCollection_ExcludesOtherParallelCollections()
|
||||
{
|
||||
// A shared collection alone only serializes its members. CI run 209
|
||||
// raced unrelated console output against capture.ToString(), and other
|
||||
// walk tests can also emit transcript lines while our global flag is on.
|
||||
var definition = Assert.Single(typeof(CameraDiagnosticsCollection)
|
||||
.GetCustomAttributes(typeof(CollectionDefinitionAttribute), inherit: false)
|
||||
.Cast<CollectionDefinitionAttribute>());
|
||||
Assert.True(definition.DisableParallelization);
|
||||
}
|
||||
|
||||
private static (WalkCell Cell1, WalkCell Cell2) BuildTranscriptFixtureCells(TestContext ctx)
|
||||
{
|
||||
var cell1 = new WalkCell
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue