PausedSelector_SeededDroppedServerReady_RecoversOnIdleSweep kept failing on the Linux runner even after its wall-clock patience was widened to 60 s — the assembly ran 1 m 39 s and recovery still never completed, so this is real starvation of the session's background threads, not a tight timeout. Measured on the runner itself: default parallelism FAILED in 40 s MaxParallelThreads=1 PASSED in 10 s Serial is both correct and four times faster there, because the contention was also costing wall-clock. Scoped to this one project on Linux: Windows has 18 cores, passes with default parallelism in ~7 s, and serializing this assembly for everyone previously REGRESSED it (1000 passed in 7 s -> 999/1000 in 17 s). Replaces the earlier '-- xUnit.MaxParallelThreads=2' that was applied to every Linux project: too weak to help and too broad to be safe.
182 lines
8.2 KiB
YAML
182 lines
8.2 KiB
YAML
# Gitea Actions CI gate for the self-hosted runners.
|
|
#
|
|
# Deliberately does NOT use actions/setup-dotnet: data.forgejo.org (the mirror
|
|
# Gitea resolves actions from) does not host that action at all, and the
|
|
# 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.
|
|
name: CI
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
windows-gate:
|
|
runs-on: windows-latest
|
|
timeout-minutes: 45
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- name: Verify the pinned SDK band resolves
|
|
shell: pwsh
|
|
run: |
|
|
dotnet --version
|
|
dotnet --list-sdks
|
|
|
|
# NOT tools/run-release-gate.ps1 here. That script redirects every child
|
|
# process to its own log file, so the step emits nothing for minutes at a
|
|
# time; Forgejo treats a task that stops reporting as a zombie and fails
|
|
# it while the work is still running (observed: job marked failed with 20
|
|
# dotnet processes still alive and a complete 8.7 MB TRX on disk). Running
|
|
# the projects directly keeps output streaming. The script stays the
|
|
# canonical LOCAL gate; the trait filter below is copied from its default.
|
|
- name: Build
|
|
shell: pwsh
|
|
run: dotnet build AcDream.slnx -c Release --nologo
|
|
|
|
- name: Test (lane-filtered, streaming)
|
|
shell: pwsh
|
|
run: |
|
|
$ErrorActionPreference = 'Stop'
|
|
$filter = 'Lane!=InstalledDat&Lane!=PreparedPackage&Lane!=Live&Lane!=Manual&Lane!=Windows&Lane!=Linux&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"
|
|
if (-not (Test-Path $csproj)) { continue }
|
|
Write-Host "::group::$($proj.Name)"
|
|
dotnet test $csproj -c Release --no-build --nologo --filter $filter
|
|
if ($LASTEXITCODE -ne 0) { $failed += $proj.Name }
|
|
Write-Host "::endgroup::"
|
|
}
|
|
if ($failed.Count) { throw "Failed test projects: $($failed -join ', ')" }
|
|
|
|
linux-portable:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 45
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- name: Portable closure (Linux lanes run here, not on Windows)
|
|
run: |
|
|
set -e
|
|
dotnet --version
|
|
for p in \
|
|
tests/AcDream.Platform.Tests \
|
|
tests/AcDream.Core.Tests \
|
|
tests/AcDream.Core.Net.Tests \
|
|
tests/AcDream.Content.Tests \
|
|
tests/AcDream.Runtime.Tests \
|
|
tests/AcDream.Headless.Tests \
|
|
tests/AcDream.Launcher.Core.Tests \
|
|
tests/AcDream.UI.Abstractions.Tests ; do
|
|
echo "::group::$p"
|
|
# Core.Net drives real network sessions on background threads. On
|
|
# this small container, full-assembly parallelism starves them:
|
|
# measured on the runner itself, the assembly FAILS in 40 s with
|
|
# default parallelism and PASSES in 10 s with one thread — serial is
|
|
# both correct AND faster here. Windows (18 cores) is unaffected and
|
|
# keeps default parallelism; serializing it there previously caused
|
|
# a regression, so this stays scoped to Linux.
|
|
extra=()
|
|
case "$p" in
|
|
*Core.Net.Tests) extra=(-- xUnit.MaxParallelThreads=1) ;;
|
|
esac
|
|
dotnet test "$p" -c Release --nologo \
|
|
--filter 'Lane!=InstalledDat&Lane!=PreparedPackage&Lane!=Live&Lane!=Manual&Lane!=Windows&Lane!=SystemFont&Purpose!=Diagnostic&Status!=KnownFailure' \
|
|
"${extra[@]}"
|
|
echo "::endgroup::"
|
|
done
|
|
|
|
release:
|
|
# Same workflow rather than a workflow_run trigger: workflow_run is a
|
|
# GitHub feature whose Forgejo support is unreliable, while `needs` is
|
|
# guaranteed. A red gate therefore cannot publish.
|
|
needs: [windows-gate, linux-portable]
|
|
runs-on: windows-latest
|
|
timeout-minutes: 60
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- name: Compute release version
|
|
id: ver
|
|
shell: pwsh
|
|
run: |
|
|
$v = '0.1.0-build.{0}' -f ([DateTime]::UtcNow.ToString('yyyyMMddHHmm'))
|
|
"version=$v" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
|
|
Write-Host "release version: $v"
|
|
|
|
- name: Build payloads with release-attachment URLs
|
|
shell: pwsh
|
|
env:
|
|
TAG: ${{ steps.ver.outputs.version }}
|
|
run: |
|
|
./tools/publish-bin.ps1 -Version $env:TAG -BaseUrl "${{ github.server_url }}/${{ github.repository }}/releases/download/$env:TAG"
|
|
|
|
- name: Create the release and upload payloads
|
|
shell: pwsh
|
|
env:
|
|
TAG: ${{ steps.ver.outputs.version }}
|
|
TOKEN: ${{ secrets.GITEA_TOKEN }}
|
|
run: |
|
|
$ErrorActionPreference = 'Stop'
|
|
$api = "${{ github.server_url }}/api/v1/repos/${{ github.repository }}"
|
|
$headers = @{ Authorization = "token $env:TOKEN" }
|
|
$body = @{
|
|
tag_name = $env:TAG
|
|
name = "acdream alpha $env:TAG"
|
|
body = "Automated alpha build from ${{ github.sha }}."
|
|
draft = $false
|
|
prerelease = $true
|
|
target_commitish = 'main'
|
|
} | ConvertTo-Json
|
|
$release = Invoke-RestMethod -Method Post -Uri "$api/releases" -Headers $headers -ContentType 'application/json' -Body $body
|
|
Write-Host "created release id=$($release.id)"
|
|
foreach ($f in Get-ChildItem bin -File) {
|
|
Write-Host ("uploading {0} ({1:N1} MB)" -f $f.Name, ($f.Length/1MB))
|
|
Invoke-RestMethod -Method Post -Headers $headers -Uri "$api/releases/$($release.id)/assets?name=$($f.Name)" -Form @{ attachment = Get-Item $f.FullName } | Out-Null
|
|
}
|
|
|
|
- name: Republish the `latest` pointer release
|
|
shell: pwsh
|
|
env:
|
|
TAG: ${{ steps.ver.outputs.version }}
|
|
TOKEN: ${{ secrets.GITEA_TOKEN }}
|
|
run: |
|
|
$ErrorActionPreference = 'Stop'
|
|
$api = "${{ github.server_url }}/api/v1/repos/${{ github.repository }}"
|
|
$headers = @{ Authorization = "token $env:TOKEN" }
|
|
|
|
# Forgejo has no /releases/latest/download/ route, so the launcher
|
|
# needs a pointer at a URL that never changes. A one-asset release on
|
|
# the fixed `latest` tag is that pointer. Keeping it in a release
|
|
# rather than in git means no payload branch, no bot commits on main,
|
|
# and no push that would retrigger this workflow.
|
|
$existing = Invoke-RestMethod -Method Get -Headers $headers `
|
|
-Uri "$api/releases/tags/latest" -SkipHttpErrorCheck
|
|
if ($existing.id) {
|
|
Invoke-RestMethod -Method Delete -Headers $headers -Uri "$api/releases/$($existing.id)" | Out-Null
|
|
# The tag outlives its release and would block recreation.
|
|
Invoke-RestMethod -Method Delete -Headers $headers -Uri "$api/tags/latest" -SkipHttpErrorCheck | Out-Null
|
|
Write-Host "removed the previous latest pointer"
|
|
}
|
|
|
|
$body = @{
|
|
tag_name = 'latest'
|
|
name = "Update feed -> $env:TAG"
|
|
body = "Pointer the launcher polls. The downloads live in the ``$env:TAG`` release."
|
|
draft = $false
|
|
prerelease = $false
|
|
target_commitish = 'main'
|
|
} | ConvertTo-Json
|
|
$pointer = Invoke-RestMethod -Method Post -Uri "$api/releases" -Headers $headers `
|
|
-ContentType 'application/json' -Body $body
|
|
|
|
Invoke-RestMethod -Method Post -Headers $headers `
|
|
-Uri "$api/releases/$($pointer.id)/assets?name=manifest.json" `
|
|
-Form @{ attachment = Get-Item bin/manifest.json } | Out-Null
|
|
Write-Host "latest pointer now advertises $env:TAG"
|