112 lines
11 KiB
PowerShell
112 lines
11 KiB
PowerShell
#Requires -Version 7.0
|
|
param([Parameter(Mandatory=$true)][ValidateSet('list','focused','all','clippy','build')][string]$Stage)
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference='Stop'
|
|
$PSNativeCommandUseErrorActionPreference=$false
|
|
$taskWorkspace='C:\Users\User\Documents\Git\ARCADIA-Studio\.local\scenario-workspace-ytr83r-20260913'
|
|
$taskTarget='C:\Users\User\Documents\Git\ARCADIA-Studio\target'
|
|
$taskPin='2c9cd2b3e661ef85f11590c7128dcb2f502d0c26'
|
|
$toolchainEnv='C:\Users\User\Documents\Git\ARCADIA-Studio\.local\toolchain\env.ps1'
|
|
function Hash-Input([string]$Path){(Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()}
|
|
function Same-Names($Left,$Right){$a=@($Left|Sort-Object);$b=@($Right|Sort-Object);return $a.Count -eq $b.Count -and @(Compare-Object $a $b).Count -eq 0}
|
|
$taskLog=Join-Path $PSScriptRoot ($Stage+'.log')
|
|
$taskReceipt=Join-Path $PSScriptRoot ($Stage+'-receipt.json')
|
|
if((Test-Path -LiteralPath $taskLog)-or(Test-Path -LiteralPath $taskReceipt)){throw 'Do not replace evidence; prepare a new gate directory for retries.'}
|
|
$clearancePath=Join-Path $PSScriptRoot 'package-clearance.json'
|
|
$clearance=Get-Content -Raw -LiteralPath $clearancePath|ConvertFrom-Json
|
|
if($clearance.base -cne $taskPin -or $clearance.cargoExitCode -ne 0 -or [IO.Path]::GetFullPath($clearance.target) -ine $taskTarget -or -not $clearance.dependenciesRetained){throw 'Parent-owned successful current-pin package clearance is required.'}
|
|
$selection=Get-Content -Raw -LiteralPath (Join-Path $PSScriptRoot 'expected-tests.json')|ConvertFrom-Json
|
|
$expected=@($selection.tests|Sort-Object -Unique)
|
|
if($selection.source_commit -cne $taskPin -or $selection.expected_count -ne 224 -or $expected.Count -ne 224 -or @($selection.tests).Count -ne 224){throw 'Wrong exact focused membership.'}
|
|
$expectedCount=224
|
|
$commonFiles=@('run.ps1','capture.ps1','expected-tests.json','expected-inputs.json','enumeration.json','external-inputs.json','inputs-complete-before.json','package-clearance.json','server-provenance.json')
|
|
$boundScripts=@{}
|
|
foreach($file in $commonFiles){$boundScripts[$file]=Hash-Input (Join-Path $PSScriptRoot $file)}
|
|
$previous=@{focused='list';all='focused';clippy='all';build='clippy'}
|
|
$priorBinding=$null
|
|
if($previous.ContainsKey($Stage)){
|
|
$priorName=$previous[$Stage]
|
|
$priorPath=Join-Path $PSScriptRoot ($priorName+'-receipt.json')
|
|
$prior=Get-Content -Raw -LiteralPath $priorPath|ConvertFrom-Json
|
|
if($prior.stage -cne $priorName -or $prior.base -cne $taskPin -or $prior.workspace -ine $taskWorkspace -or $prior.target -ine $taskTarget -or $prior.cargoExitCode -ne 0 -or -not $prior.selectionValid -or -not $prior.inputsUnchanged){throw 'Exact prior sequential stage did not pass.'}
|
|
foreach($file in $commonFiles){if($prior.boundScriptAndInputHashes.$file -cne $boundScripts[$file]){throw ('Prior recipe/input binding changed: '+$file)}}
|
|
foreach($property in $prior.boundScriptAndInputHashes.PSObject.Properties){if((Hash-Input (Join-Path $PSScriptRoot $property.Name)) -cne $property.Value){throw ('Prior transitive binding changed: '+$property.Name)};$boundScripts[$property.Name]=$property.Value}
|
|
if((Hash-Input (Join-Path $PSScriptRoot ($priorName+'.log'))) -cne $prior.rawLogSha256){throw 'Prior raw output changed.'}
|
|
$priorBinding=[ordered]@{stage=$priorName;receiptSha256=(Hash-Input $priorPath);rawLogSha256=$prior.rawLogSha256}
|
|
}
|
|
Push-Location -LiteralPath $taskWorkspace
|
|
try {
|
|
& (Join-Path $PSScriptRoot 'capture.ps1') -Stage ($Stage+'-before')
|
|
. $toolchainEnv
|
|
$env:CARGO_TARGET_DIR=$taskTarget
|
|
$env:CARGO_TERM_COLOR='never'
|
|
$PSNativeCommandUseErrorActionPreference=$false
|
|
$versionPath=Join-Path $PSScriptRoot 'toolchain-versions.json'
|
|
if($Stage -eq 'list'){
|
|
if(Test-Path -LiteralPath $versionPath){throw 'Preserve prior toolchain version evidence.'}
|
|
$cargoVersion=@(& cargo --version --verbose 2>&1);$cargoVersionExit=$LASTEXITCODE
|
|
$rustcVersion=@(& rustc --version --verbose 2>&1);$rustcVersionExit=$LASTEXITCODE
|
|
$clippyVersion=@(& cargo clippy --version 2>&1);$clippyVersionExit=$LASTEXITCODE
|
|
$clangVersion=@(& clang --version 2>&1);$clangVersionExit=$LASTEXITCODE
|
|
[ordered]@{base=$taskPin;capturedUtc=[DateTime]::UtcNow.ToString('o');externalEnvironmentPath=$toolchainEnv;externalEnvironmentSha256=(Hash-Input $toolchainEnv);cargo=@($cargoVersion|ForEach-Object {$_.ToString()});cargoExitCode=$cargoVersionExit;rustc=@($rustcVersion|ForEach-Object {$_.ToString()});rustcExitCode=$rustcVersionExit;clippy=@($clippyVersion|ForEach-Object {$_.ToString()});clippyExitCode=$clippyVersionExit;clang=@($clangVersion|ForEach-Object {$_.ToString()});clangExitCode=$clangVersionExit;cargoPath=(Get-Command cargo).Source;rustcPath=(Get-Command rustc).Source;clangPath=(Get-Command clang).Source;cargoHome=$env:CARGO_HOME;rustupHome=$env:RUSTUP_HOME;rustupToolchain=$env:RUSTUP_TOOLCHAIN;buildJobs=$env:CARGO_BUILD_JOBS;incremental=$env:CARGO_INCREMENTAL}|ConvertTo-Json -Depth 8|Set-Content -LiteralPath $versionPath -Encoding utf8
|
|
if($cargoVersionExit -ne 0 -or $rustcVersionExit -ne 0 -or $clippyVersionExit -ne 0 -or $clangVersionExit -ne 0){throw 'Toolchain version query failed.'}
|
|
}
|
|
$versionsHash=Hash-Input $versionPath
|
|
if($priorBinding -and $prior.toolchainVersionsSha256 -cne $versionsHash){throw 'Toolchain version receipt changed.'}
|
|
$taskArgs=switch($Stage){
|
|
'list' {@('test','-p','oa-desktop','--all-targets','--locked','--offline','--','--list')}
|
|
'focused' {@('test','-p','oa-desktop','--bin','arcadia-studio','--locked','--offline','--','--exact')+$expected+@('--test-threads=1')}
|
|
'all' {@('test','-p','oa-desktop','--all-targets','--locked','--offline','--','--test-threads=1')}
|
|
'clippy' {@('clippy','-p','oa-desktop','--all-targets','--locked','--offline','--','-D','warnings')}
|
|
'build' {@('build','-p','oa-desktop','--bin','arcadia-studio','--locked','--offline')}
|
|
}
|
|
$taskStart=[DateTime]::UtcNow.ToString('o')
|
|
& cargo @taskArgs 2>&1|Tee-Object -FilePath $taskLog
|
|
$taskExit=$LASTEXITCODE
|
|
$taskEnd=[DateTime]::UtcNow.ToString('o')
|
|
$taskText=Get-Content -Raw -LiteralPath $taskLog
|
|
$taskValid=$true;$taskFailure=$null
|
|
$listed=@();$executed=@();$matched=@();$ignored=@();$summaries=@();$listSummaries=@()
|
|
if($Stage -eq 'list'){
|
|
$listed=@([regex]::Matches($taskText,'(?m)^([^\r\n]+): test\r?$')|ForEach-Object {$_.Groups[1].Value})
|
|
$matched=@($listed|Where-Object {$_ -cin $expected}|Sort-Object)
|
|
$listSummaries=@([regex]::Matches($taskText,'(?m)^(\d+) tests, (\d+) benchmarks\r?$')|ForEach-Object {[ordered]@{tests=[int]$_.Groups[1].Value;benchmarks=[int]$_.Groups[2].Value}})
|
|
$taskValid=$listed.Count -ge $expectedCount -and @($listed|Sort-Object -Unique).Count -eq $listed.Count -and (Same-Names $expected $matched) -and (Same-Names @($selection.desktop_tests) $listed) -and $listSummaries.Count -gt 0 -and ($listSummaries|ForEach-Object {$_.tests}|Measure-Object -Sum).Sum -eq $listed.Count -and ($listSummaries|ForEach-Object {$_.benchmarks}|Measure-Object -Sum).Sum -eq 0
|
|
}
|
|
if($Stage -in @('focused','all')){
|
|
$executed=@([regex]::Matches($taskText,'(?m)^test ([^\r\n]+?) \.\.\. (ok|FAILED|ignored)[^\r\n]*\r?$')|ForEach-Object {[ordered]@{name=$_.Groups[1].Value;result=$_.Groups[2].Value}})
|
|
$summaries=@([regex]::Matches($taskText,'test result: (ok|FAILED)\. (\d+) passed; (\d+) failed; (\d+) ignored; (\d+) measured; (\d+) filtered out;')|ForEach-Object {[ordered]@{status=$_.Groups[1].Value;passed=[int]$_.Groups[2].Value;failed=[int]$_.Groups[3].Value;ignored=[int]$_.Groups[4].Value;measured=[int]$_.Groups[5].Value;filtered=[int]$_.Groups[6].Value}})
|
|
$ignored=@($executed|Where-Object {$_.result -eq 'ignored'}|ForEach-Object {$_.name})
|
|
$passedCount=@($executed|Where-Object {$_.result -eq 'ok'}).Count
|
|
$failedCount=@($executed|Where-Object {$_.result -eq 'FAILED'}).Count
|
|
$taskValid=$summaries.Count -gt 0 -and $failedCount -eq 0 -and @($summaries|Where-Object {$_.status -ne 'ok' -or $_.failed -ne 0 -or $_.measured -ne 0}).Count -eq 0 -and ($summaries|ForEach-Object {$_. passed}|Measure-Object -Sum).Sum -eq $passedCount -and ($summaries|ForEach-Object {$_. ignored}|Measure-Object -Sum).Sum -eq $ignored.Count
|
|
if($Stage -in @('focused','all')){
|
|
$matched=@($executed|Where-Object {$_.name -cin $expected -and $_.result -eq 'ok'}|ForEach-Object {$_.name}|Sort-Object)
|
|
$taskValid=$taskValid -and (Same-Names $expected $matched)
|
|
}
|
|
if($Stage -eq 'focused'){$taskValid=$taskValid -and $executed.Count -eq $expectedCount -and $passedCount -eq $expectedCount -and $ignored.Count -eq 0 -and $summaries.Count -eq 1 -and $summaries[0].filtered -eq (@($prior.listedTests).Count - $expectedCount)}
|
|
if($Stage -eq 'all'){
|
|
$listPath=Join-Path $PSScriptRoot 'list-receipt.json'
|
|
$listHash=Hash-Input $listPath
|
|
$listReceipt=Get-Content -Raw -LiteralPath $listPath|ConvertFrom-Json
|
|
if($prior.priorStage.stage -cne 'list' -or $prior.priorStage.receiptSha256 -cne $listHash){throw 'Desktop listing changed after focused execution.'}
|
|
$boundScripts['list-receipt.json']=$listHash
|
|
$taskValid=$taskValid -and (Same-Names @($listReceipt.listedTests) @($executed|ForEach-Object {$_.name})) -and ($summaries|ForEach-Object {$_. filtered}|Measure-Object -Sum).Sum -eq 0 -and (Same-Names @($selection.ignored_tests) $ignored)
|
|
}
|
|
|
|
}
|
|
$inputsUnchanged=$false
|
|
try {
|
|
& (Join-Path $PSScriptRoot 'capture.ps1') -Stage ($Stage+'-after')
|
|
foreach($file in $boundScripts.Keys){if((Hash-Input (Join-Path $PSScriptRoot $file)) -cne $boundScripts[$file]){throw ('Gate recipe/input changed: '+$file)}}
|
|
if((Hash-Input $versionPath) -cne $versionsHash){throw 'Toolchain version receipt changed.'}
|
|
if($priorBinding -and (Hash-Input (Join-Path $PSScriptRoot ($priorBinding.stage+'-receipt.json'))) -cne $priorBinding.receiptSha256){throw 'Prior receipt changed during gate.'}
|
|
$inputsUnchanged=$true
|
|
}catch{$taskFailure=$_.Exception.Message}
|
|
$binary=$null
|
|
if($Stage -eq 'build' -and $taskExit -eq 0){$path=Join-Path $taskTarget 'debug/arcadia-studio.exe';$item=Get-Item -LiteralPath $path;$binary=[ordered]@{path=$item.FullName;bytes=$item.Length;sha256=(Hash-Input $path);mtimeUtc=$item.LastWriteTimeUtc.ToString('o')}}
|
|
[ordered]@{stage=$Stage;command=('cargo '+($taskArgs -join ' '));arguments=$taskArgs;base=$taskPin;workspace=$taskWorkspace;target=$taskTarget;startUtc=$taskStart;endUtc=$taskEnd;cargoExitCode=$taskExit;selectionValid=$taskValid;inputsUnchanged=$inputsUnchanged;postflightFailure=$taskFailure;boundScriptAndInputHashes=$boundScripts;priorStage=$priorBinding;externalEnvironmentPath=$toolchainEnv;externalEnvironmentSha256=(Hash-Input $toolchainEnv);toolchainVersionsSha256=$versionsHash;rawLogSha256=(Hash-Input $taskLog);beforeCaptureSha256=(Hash-Input (Join-Path $PSScriptRoot ('inputs-complete-'+$Stage+'-before.json')));afterCaptureSha256=if(Test-Path -LiteralPath (Join-Path $PSScriptRoot ('inputs-complete-'+$Stage+'-after.json'))){Hash-Input (Join-Path $PSScriptRoot ('inputs-complete-'+$Stage+'-after.json'))}else{$null};listedTests=$listed;executedTests=$executed;testResultSummaries=$summaries;matchedRegressionTests=$matched;ignoredTests=$ignored;listSummaries=$listSummaries;binary=$binary}|ConvertTo-Json -Depth 14|Set-Content -LiteralPath $taskReceipt -Encoding utf8
|
|
if($taskExit -ne 0){exit $taskExit}
|
|
if(-not $taskValid -or -not $inputsUnchanged){throw 'Gate membership or input binding failed; inspect receipt and raw output.'}
|
|
} finally {Pop-Location}
|