99 lines
7.9 KiB
PowerShell
99 lines
7.9 KiB
PowerShell
#Requires -Version 7.0
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory=$true)][ValidateSet('list','normal','font','ffi','auto-transition','timing')][string]$Stage,
|
|
[Parameter(Mandatory=$true)][string]$Output
|
|
)
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference='Stop'
|
|
$PSNativeCommandUseErrorActionPreference=$false
|
|
$Output=(Resolve-Path -LiteralPath $Output).Path
|
|
function Hash([string]$p){(Get-FileHash -LiteralPath $p -Algorithm SHA256).Hash.ToLowerInvariant()}
|
|
function Json([string]$p){Get-Content -LiteralPath $p -Raw|ConvertFrom-Json}
|
|
function Save([string]$p,$v){$b=[Text.UTF8Encoding]::new($false).GetBytes(($v|ConvertTo-Json -Depth 16)+[Environment]::NewLine);$f=[IO.File]::Open($p,'CreateNew','Write','Read');try{$f.Write($b,0,$b.Length)}finally{$f.Dispose()}}
|
|
$receiptPath=Join-Path $Output 'build-receipt.json'
|
|
$r=Json $receiptPath
|
|
if($r.exit_code -ne 0 -or -not $r.inputs_unchanged -or $null -ne $r.postflight_failure -or $r.tests_executed){throw 'Closed successful compile-only receipt required'}
|
|
$expectedPath=Join-Path $PSScriptRoot 'expected-tests.json'
|
|
$e=Json $expectedPath
|
|
if($r.source_commit -cne '4c9bc87e4e66bba573dd63b23df5ea039f85d0ca' -or $r.source_commit -cne $e.source_commit){throw 'Wrong source pin'}
|
|
if(@($e.tests).Count -ne 25 -or @($e.normal).Count -ne 21 -or @($e.isolated).Count -ne 4 -or @($e.tests|Sort-Object -Unique).Count -ne 25 -or (Compare-Object ($e.tests|Sort-Object) (@($e.normal)+@($e.isolated)|Sort-Object))){throw 'Expected inventory is not a disjoint complete 25-test partition'}
|
|
$exe=$r.binary.path
|
|
$dll=Join-Path $Output 'libunwind.dll'
|
|
if((Hash $exe) -cne $r.binary.sha256 -or (Get-Item -LiteralPath $exe).Length -ne $r.binary.bytes -or (Hash $dll) -cne $r.runtime_sha256){throw 'Executable or runtime differs from build'}
|
|
$manifestPath=Join-Path $Output 'inputs-after.json'
|
|
if((Hash $manifestPath) -cne $r.after_manifest_sha256){throw 'Build input capture changed'}
|
|
$sourceInputs=@((Json $manifestPath)|Where-Object {$_.path -match 'scenario-label-fields-(harness|ytr83r)'})
|
|
if(-not @($sourceInputs|Where-Object {$_.path -like '*fixtures*NotoSans-Regular.ttf'}).Count){throw 'Build capture omitted the font fixture'}
|
|
foreach($s in $sourceInputs){if((Hash $s.path) -cne $s.sha256 -or (Get-Item -LiteralPath $s.path).Length -ne $s.bytes){throw ('Source/recipe changed: '+$s.path)}}
|
|
$prior=switch($Stage){normal{'list'} font{'normal'} ffi{'font'} auto-transition{'ffi'} timing{'auto-transition'} default{$null}}
|
|
$paths=@($PSCommandPath,$receiptPath,$manifestPath,$expectedPath,$exe,$dll)+@($sourceInputs.path)
|
|
$priorReceipt=$null
|
|
if($prior){
|
|
$p=Join-Path $Output ($prior+'-run-receipt.json')
|
|
$previous=Json $p
|
|
if($previous.stage -cne $prior -or -not $previous.passed -or $previous.source_commit -cne $r.source_commit -or $previous.build_receipt_sha256 -cne (Hash $receiptPath) -or $previous.expected_tests_sha256 -cne (Hash $expectedPath) -or $previous.binary_sha256 -cne $r.binary.sha256 -or $previous.runtime_sha256 -cne $r.runtime_sha256){throw 'Required exact previous stage did not pass'}
|
|
$priorLog=Join-Path $Output ($prior+'.log')
|
|
if((Hash $priorLog) -cne $previous.log_sha256){throw 'Previous raw log changed'}
|
|
$paths+=@($p,$priorLog)
|
|
$priorReceipt=[ordered]@{stage=$prior;path=$p;sha256=(Hash $p);log_sha256=(Hash $priorLog)}
|
|
}
|
|
function Capture {@($paths|Sort-Object -Unique|ForEach-Object {[ordered]@{path=$_;sha256=(Hash $_);bytes=(Get-Item -LiteralPath $_).Length}})}
|
|
$log=Join-Path $Output ($Stage+'.log')
|
|
$receipt=Join-Path $Output ($Stage+'-run-receipt.json')
|
|
if((Test-Path -LiteralPath $log) -or (Test-Path -LiteralPath $receipt)){throw 'Preserve existing stage evidence; use a new recipe/output for retry'}
|
|
$before=@(Capture)
|
|
$font=$e.stages.font
|
|
$ffi=$e.stages.ffi
|
|
$auto=$e.stages.'auto-transition'
|
|
$timing=$e.stages.timing
|
|
$argsForExe=@(switch($Stage){list{'--list'} normal{'--test-threads=1';'--show-output'} font{'--exact';$font;'--ignored';'--show-output';'--test-threads=1'} ffi{'--exact';$ffi;'--ignored';'--show-output';'--test-threads=1'} auto-transition{'--exact';$auto;'--ignored';'--show-output';'--test-threads=1'} timing{'--exact';$timing;'--ignored';'--show-output';'--test-threads=1'}})
|
|
$oldPin=$env:SCENARIO_LABEL_FIELDS_SOURCE_PIN
|
|
$start=[DateTime]::UtcNow.ToString('o')
|
|
try{
|
|
$env:SCENARIO_LABEL_FIELDS_SOURCE_PIN=$r.source_commit
|
|
# Explicit native arguments are intentional: the former scalar --list wrapper
|
|
# accidentally executed default tests. Do not replace these with that dispatch.
|
|
switch($Stage){
|
|
list {& $exe --list *> $log}
|
|
normal {& $exe --test-threads=1 --show-output *> $log}
|
|
font {& $exe --exact $font --ignored --show-output --test-threads=1 *> $log}
|
|
ffi {& $exe --exact $ffi --ignored --show-output --test-threads=1 *> $log}
|
|
auto-transition {& $exe --exact $auto --ignored --show-output --test-threads=1 *> $log}
|
|
timing {& $exe --exact $timing --ignored --show-output --test-threads=1 *> $log}
|
|
}
|
|
$code=$LASTEXITCODE
|
|
}finally{$env:SCENARIO_LABEL_FIELDS_SOURCE_PIN=$oldPin}
|
|
$end=[DateTime]::UtcNow.ToString('o')
|
|
$failure=$null;$passed=$false;$after=@();$inventory=@();$results=@();$summary=$null
|
|
try{
|
|
$after=@(Capture)
|
|
if(($before|ConvertTo-Json -Depth 6 -Compress) -cne ($after|ConvertTo-Json -Depth 6 -Compress)){throw 'Run inputs changed'}
|
|
$raw=Get-Content -LiteralPath $log -Raw
|
|
if($Stage -eq 'list'){
|
|
$inventory=@([regex]::Matches($raw,'(?m)^(.+): test\r?$')|ForEach-Object {$_.Groups[1].Value})
|
|
if($inventory.Count -ne 25 -or @($inventory|Sort-Object -Unique).Count -ne 25 -or (Compare-Object ($e.tests|Sort-Object) ($inventory|Sort-Object)) -or $raw -notmatch '(?m)^25 tests, 0 benchmarks\r?$' -or $raw -match '(?m)^test result:|^running \d+ tests'){throw 'Actual list differs or unexpectedly executed tests'}
|
|
}else{
|
|
$rows=@([regex]::Matches($raw,'(?m)^test (.+?) \.\.\. (ok|FAILED|ignored)(?:, [^\r\n]*)?\r?$'))
|
|
$inventory=@($rows|ForEach-Object {$_.Groups[1].Value})
|
|
$want=@(switch($Stage){normal{$e.tests} font{$font} ffi{$ffi} auto-transition{$auto} timing{$timing}})
|
|
if($inventory.Count -ne $want.Count -or @($inventory|Sort-Object -Unique).Count -ne $want.Count -or (Compare-Object ($want|Sort-Object) ($inventory|Sort-Object))){throw 'Executed membership differs'}
|
|
foreach($row in $rows){
|
|
$status=if($Stage -eq 'normal' -and $row.Groups[1].Value -in $e.isolated){'ignored'}else{'ok'}
|
|
$results+=[ordered]@{name=$row.Groups[1].Value;result=$row.Groups[2].Value}
|
|
if($row.Groups[2].Value -cne $status){throw ('Unexpected outcome: '+$row.Value)}
|
|
}
|
|
$summaries=[regex]::Matches($raw,'(?m)^test result: ok\. (\d+) passed; (\d+) failed; (\d+) ignored; (\d+) measured; (\d+) filtered out; finished in ([\d.]+)s\r?$')
|
|
if($summaries.Count -ne 1){throw 'Missing or duplicate successful test summary'}
|
|
$match=$summaries[0]
|
|
$wantSummary=if($Stage -eq 'normal'){@('21','0','4','0','0')}else{@('1','0','0','0','24')}
|
|
for($i=0;$i -lt 5;$i++){if($match.Groups[$i+1].Value -cne $wantSummary[$i]){throw 'Unexpected test summary'}}
|
|
$summary=[ordered]@{passed=[int]$match.Groups[1].Value;failed=[int]$match.Groups[2].Value;ignored=[int]$match.Groups[3].Value;measured=[int]$match.Groups[4].Value;filtered=[int]$match.Groups[5].Value;seconds=$match.Groups[6].Value;raw=$match.Value}
|
|
}
|
|
if($code -ne 0){throw ('Executable exit '+$code)}
|
|
$passed=$true
|
|
}catch{$failure=$_.Exception.Message}
|
|
Save $receipt ([ordered]@{schema='scenario_label_field_run_v3';stage=$Stage;source_commit=$r.source_commit;command=@($exe)+$argsForExe;start_utc=$start;end_utc=$end;exit_code=$code;passed=$passed;failure=$failure;build_receipt_sha256=(Hash $receiptPath);expected_tests_sha256=(Hash $expectedPath);run_script_sha256=(Hash $PSCommandPath);binary_sha256=$r.binary.sha256;runtime_sha256=$r.runtime_sha256;prior=$priorReceipt;log_sha256=(Hash $log);inventory=$inventory;results=$results;summary=$summary;before=$before;after=$after;scope='Isolated CPU Iced/Cosmic label checks; timing optional; no native renderer or navigation acceptance'})
|
|
Write-Output ($Stage+': passed='+$passed+'; exit='+$code)
|
|
if(-not $passed){throw $failure}
|