#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}