build: make release restore reproducible

This commit is contained in:
Erik 2026-08-18 10:29:00 +02:00
parent b459e0cf0c
commit c38f6b8852
129 changed files with 16810 additions and 188 deletions

View file

@ -3,12 +3,14 @@
Runs the complete portable Release gate with bounded child processes.
.DESCRIPTION
Discovers every test project under tests/ that declares itself through
Microsoft.NET.Test.Sdk or IsTestProject, verifies that AcDream.slnx owns it,
restores and builds the solution, and then runs each test assembly in a
fresh process. Every process has an outer hard timeout. Test processes also
use VSTest blame-hang collection, so a stuck test host is terminated with a
mini dump before the outer watchdog is allowed to kill the process tree.
Verifies that AcDream.slnx owns every project under src/, tests/, and tools/;
performs a locked restore; builds that complete supported graph; discovers
every test project under tests/ that declares itself through
Microsoft.NET.Test.Sdk or IsTestProject; and then runs each test assembly in
a fresh process. Every process has an outer hard timeout. Test processes
also use VSTest blame-hang collection, so a stuck test host is terminated
with a mini dump before the outer watchdog is allowed to kill the process
tree.
The evidence directory contains process logs, one TRX per test assembly,
any blame sequence/dump files, an environment report, an aggregate JSON
@ -171,6 +173,48 @@ function Invoke-BoundedDotnet {
return $result
}
function Get-SolutionProjects {
[xml]$solution = Get-Content -LiteralPath $solutionPath
$projectPaths = @(
$solution.SelectNodes('//Project') |
ForEach-Object { [string]$_.Path } |
Sort-Object -Unique
)
if ($projectPaths.Count -eq 0) {
throw 'AcDream.slnx contains no projects.'
}
$projects = @(
foreach ($relativePath in $projectPaths) {
$fullPath = [IO.Path]::GetFullPath((Join-Path $repoRoot $relativePath))
if (-not (Test-Path -LiteralPath $fullPath -PathType Leaf)) {
throw "Solution project '$relativePath' does not exist."
}
Get-Item -LiteralPath $fullPath
}
)
$maintainedProjects = @(
foreach ($directoryName in @('src', 'tests', 'tools')) {
Get-ChildItem -LiteralPath (Join-Path $repoRoot $directoryName) -Recurse -Filter '*.csproj' -File
}
)
$solutionSet = [Collections.Generic.HashSet[string]]::new(
[StringComparer]::OrdinalIgnoreCase)
foreach ($project in $projects) {
[void]$solutionSet.Add($project.FullName)
}
$missing = @($maintainedProjects | Where-Object { -not $solutionSet.Contains($_.FullName) })
if ($missing.Count -gt 0) {
$missingText = $missing |
ForEach-Object { [IO.Path]::GetRelativePath($repoRoot, $_.FullName).Replace('\', '/') } |
Sort-Object
throw "Maintained projects missing from AcDream.slnx: $($missingText -join ', ')"
}
return $projects
}
function Get-TestProjects {
$solutionText = Get-Content -LiteralPath $solutionPath -Raw
$projects = @(
@ -240,6 +284,7 @@ function Write-ArtifactHashes {
Push-Location $repoRoot
try {
$gateStartedUtc = [DateTimeOffset]::UtcNow
$solutionProjects = @(Get-SolutionProjects)
$testProjects = @(Get-TestProjects)
$processes = [Collections.Generic.List[object]]::new()
$testReports = [Collections.Generic.List[object]]::new()
@ -263,6 +308,29 @@ try {
(dotnet nuget list source | Out-String)
'--- worktree status at gate start ---'
($worktreeStatus -join [Environment]::NewLine)
'--- supported solution projects ---'
($solutionProjects | ForEach-Object {
[IO.Path]::GetRelativePath($repoRoot, $_.FullName).Replace('\', '/')
})
'--- package lock files ---'
($solutionProjects | ForEach-Object {
$relativeProject = [IO.Path]::GetRelativePath($repoRoot, $_.FullName).Replace('\', '/')
$lockNames = [Collections.Generic.List[string]]::new()
$lockNames.Add('packages.neutral.lock.json')
if ($relativeProject.StartsWith('src/', [StringComparison]::OrdinalIgnoreCase)) {
$lockNames.Add('packages.win-x64.lock.json')
$lockNames.Add('packages.linux-x64.lock.json')
}
foreach ($lockName in $lockNames) {
$lockPath = Join-Path $_.DirectoryName $lockName
if (-not (Test-Path -LiteralPath $lockPath -PathType Leaf)) {
throw "Supported project '$($_.FullName)' has no $lockName."
}
$relative = [IO.Path]::GetRelativePath($repoRoot, $lockPath).Replace('\', '/')
$hash = (Get-FileHash -LiteralPath $lockPath -Algorithm SHA256).Hash.ToLowerInvariant()
"$hash $relative"
}
})
'--- discovered test projects ---'
($testProjects | ForEach-Object {
[IO.Path]::GetRelativePath($repoRoot, $_.FullName).Replace('\', '/')
@ -272,7 +340,7 @@ try {
if (-not $SkipRestore) {
$restore = Invoke-BoundedDotnet -Label 'restore' -TimeoutSeconds $RestoreTimeoutSeconds -Arguments @(
'restore', $solutionPath, '--nologo'
'restore', $solutionPath, '--locked-mode', '--force-evaluate', '--nologo'
)
$processes.Add($restore)
if ($restore.ExitCode -ne 0) {