param([switch]$BrowserDom)
$ErrorActionPreference = 'Stop'
$taskRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
if (([IO.DriveInfo]::new([IO.Path]::GetPathRoot($taskRoot))).AvailableFreeSpace -lt 10GB) {
throw 'At least 10 GiB of free disk space is required before host tests; no artifacts were created.'
}
$artifacts = Join-Path $taskRoot 'build/CSharp/HostArtifacts'
[void][IO.Directory]::CreateDirectory((Split-Path -Parent $artifacts))
# Hold the lock for the entire run: shared output must never be rebuilt by two
# host test runs simultaneously. Logs and receipts remain unique per run.
$artifactsLock = [IO.File]::Open(($artifacts + '.lock'), [IO.FileMode]::OpenOrCreate, [IO.FileAccess]::ReadWrite, [IO.FileShare]::None)
try {
$testDirectory = Join-Path $taskRoot ('build/CSharp/HostTests-' + [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds())
[void][IO.Directory]::CreateDirectory($testDirectory)
# Cosmos adapter/USB-driver regressions remain in XFESystem.Legacy.slnx. The
# active platform suite exercises this kernel's own PS/2 and framebuffer path.
$suites = @('Boot','Core','Platform','Desktop','Console','Documents','Browser','ManagedRuntime','NativeInterop','HttpProtocol','JintCleanupPatch','Gpu.Nvidia','Network','NativeTls')
$checks = @()
function Get-SourceHashes {
$hashes = [ordered]@{}
$hashes['Tools/TestCSharp.ps1'] = (Get-FileHash -LiteralPath (Join-Path $taskRoot 'Tools/TestCSharp.ps1') -Algorithm SHA256).Hash.ToLowerInvariant()
$hashes['Directory.Build.targets'] = (Get-FileHash -LiteralPath (Join-Path $taskRoot 'Directory.Build.targets') -Algorithm SHA256).Hash.ToLowerInvariant()
foreach ($directory in @('Source','Samples','Tests','Tools/NativeInputPatch','Tools/JintCleanupPatch','Tools/TlsEntropyPatch','Tools/GcRuntimePatch','assets/Browser','assets/fonts','assets/branding/console')) {
foreach ($file in Get-ChildItem -LiteralPath (Join-Path $taskRoot $directory) -File -Recurse | Sort-Object FullName) {
if ($file.FullName -match '[\\/](bin|obj|build|TestResults|__pycache__)[\\/]' -or $file.Extension -in @('.pyc','.pyo')) { continue }
$relative = $file.FullName.Substring($taskRoot.Length + 1).Replace('\','/')
# The host suites compile these native modules by linking their
# sources. Other kernel files are covered by the separate guest receipt.
if ($relative.StartsWith('Source/XFESystem.Kernel/') -and $file.Name -notin @('NativeWebTransport.cs','VectorFileIo.cs','NativeDesktopInput.cs','NativeKeyboardCapture.cs','NativePointerCapture.cs','NativeNetworkBootWait.cs','NativePointerFrameProbe.cs')) { continue }
$hashes[$relative] = (Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant()
}
}
return $hashes
}
$sourceHashes = Get-SourceHashes
Push-Location $taskRoot
try {
# Shared project dependencies cannot be compiled concurrently while ILC maps
# their PDBs on Windows. Reuse one locked artifacts tree and run in order.
foreach ($suite in $suites) {
$project = 'Tests/XFESystem.' + $suite + '.Tests/XFESystem.' + $suite + '.Tests.csproj'
$log = Join-Path $testDirectory ($suite + '.log')
# Windows PowerShell 5 turns native stderr into ErrorRecords. Keep the
# complete diagnostic/TRX output, then judge the process exit code;
# otherwise xUnit's first failure line aborts the process mid-report.
$savedPreference = $ErrorActionPreference
try {
$ErrorActionPreference = 'Continue'
& dotnet test $project -c Release --artifacts-path $artifacts --nologo --logger ('trx;LogFileName=' + $suite + '.trx') --results-directory $testDirectory *> $log
$testExitCode = $LASTEXITCODE
} finally { $ErrorActionPreference = $savedPreference }
if ($testExitCode -ne 0) { throw ('Host test failed: ' + $suite + '. See ' + $log) }
[xml]$trx = Get-Content -LiteralPath (Join-Path $testDirectory ($suite + '.trx')) -Raw -Encoding UTF8
$counter = $trx.SelectSingleNode("//*[local-name()='Counters']")
if ($null -eq $counter -or [int]$counter.failed -ne 0) { throw ('Missing or failed test counters: ' + $suite) }
$checks += [ordered]@{Suite=$suite;Passed=[int]$counter.passed;Failed=[int]$counter.failed;Log=$log;Trx=(Join-Path $testDirectory ($suite + '.trx'))}
Write-Host ($suite + ': ' + $counter.passed + ' passed')
}
$nativeLog=Join-Path $testDirectory 'NativeContracts.log'
$nativeRunner=Join-Path $taskRoot 'Tests/XFESystem.Platform.Tests/TestSystemNativeExtras.ps1'
& $nativeRunner *> $nativeLog
$nativeDirectory=Get-ChildItem -LiteralPath (Join-Path $taskRoot 'build/IndependentKernel') -Directory -Filter 'ExtrasContract-*' |
Sort-Object Name -Descending | Where-Object {Test-Path -LiteralPath (Join-Path $_.FullName 'Receipt.json')} | Select-Object -First 1
$nativeReceipt=Join-Path $nativeDirectory.FullName 'Receipt.json'
$native=Get-Content -LiteralPath $nativeReceipt -Raw -Encoding UTF8 | ConvertFrom-Json
if($native.Passed -ne $true -or $native.ExitCode -ne 0 -or $null -eq $native.Failed -or $native.Failed -ne 0 -or $native.PassedCount -lt 1){throw 'Native runtime contract verification failed.'}
$checks+=[ordered]@{Suite='NativeContracts';Passed=[int]$native.PassedCount;Failed=[int]$native.Failed;
Log=$native.Log;LogSha256=$native.LogSha256;Receipt=$nativeReceipt;
ReceiptSha256=(Get-FileHash -LiteralPath $nativeReceipt -Algorithm SHA256).Hash.ToLowerInvariant()}
Write-Host ('NativeContracts: '+$native.PassedCount+' passed')
if ($BrowserDom) {
& node scripts/TestDocumentBrowser.mjs
if ($LASTEXITCODE -ne 0) { throw 'Independent browser DOM verification failed.' }
}
$afterHashes = Get-SourceHashes
if (($sourceHashes | ConvertTo-Json -Compress -Depth 4) -ne ($afterHashes | ConvertTo-Json -Compress -Depth 4)) { throw 'Source files changed during host verification; run the final checks against frozen sources.' }
$result = [ordered]@{HostOnly=$true;GuestExecution=$false;Passed=$true;Suites=$checks;SourceHashes=$sourceHashes;ArtifactsDirectory=$artifacts;ArtifactsImmutable=$false;ArtifactPolicy='SharedLockedCompilerCache';BrowserDomRequested=[bool]$BrowserDom;FinishedUtc=[DateTime]::UtcNow.ToString('o')}
$result | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Join-Path $testDirectory 'Result.json') -Encoding UTF8
Write-Host ('Host verification: ' + (Join-Path $testDirectory 'Result.json'))
} catch {
[ordered]@{HostOnly=$true;GuestExecution=$false;Passed=$false;Error=$_.Exception.Message;Suites=$checks} |
ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Join-Path $testDirectory 'Failure.json') -Encoding UTF8
throw
} finally { Pop-Location }
} finally { $artifactsLock.Dispose() }
param([switch]$BrowserDom)
$ErrorActionPreference = 'Stop'
$taskRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
if (([IO.DriveInfo]::new([IO.Path]::GetPathRoot($taskRoot))).AvailableFreeSpace -lt 10GB) {
throw 'At least 10 GiB of free disk space is required before host tests; no artifacts were created.'
}
$artifacts = Join-Path $taskRoot 'build/CSharp/HostArtifacts'
[void][IO.Directory]::CreateDirectory((Split-Path -Parent $artifacts))
# Hold the lock for the entire run: shared output must never be rebuilt by two
# host test runs simultaneously. Logs and receipts remain unique per run.
$artifactsLock = [IO.File]::Open(($artifacts + '.lock'), [IO.FileMode]::OpenOrCreate, [IO.FileAccess]::ReadWrite, [IO.FileShare]::None)
try {
$testDirectory = Join-Path $taskRoot ('build/CSharp/HostTests-' + [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds())
[void][IO.Directory]::CreateDirectory($testDirectory)
# Cosmos adapter/USB-driver regressions remain in XFESystem.Legacy.slnx. The
# active platform suite exercises this kernel's own PS/2 and framebuffer path.
$suites = @('Boot','Core','Platform','Desktop','Console','Documents','Browser','ManagedRuntime','NativeInterop','HttpProtocol','JintCleanupPatch','Gpu.Nvidia','Network','NativeTls')
$checks = @()
function Get-SourceHashes {
$hashes = [ordered]@{}
$hashes['Tools/TestCSharp.ps1'] = (Get-FileHash -LiteralPath (Join-Path $taskRoot 'Tools/TestCSharp.ps1') -Algorithm SHA256).Hash.ToLowerInvariant()
$hashes['Directory.Build.targets'] = (Get-FileHash -LiteralPath (Join-Path $taskRoot 'Directory.Build.targets') -Algorithm SHA256).Hash.ToLowerInvariant()
foreach ($directory in @('Source','Samples','Tests','Tools/NativeInputPatch','Tools/JintCleanupPatch','Tools/TlsEntropyPatch','Tools/GcRuntimePatch','assets/Browser','assets/fonts','assets/branding/console')) {
foreach ($file in Get-ChildItem -LiteralPath (Join-Path $taskRoot $directory) -File -Recurse | Sort-Object FullName) {
if ($file.FullName -match '[\\/](bin|obj|build|TestResults|__pycache__)[\\/]' -or $file.Extension -in @('.pyc','.pyo')) { continue }
$relative = $file.FullName.Substring($taskRoot.Length + 1).Replace('\','/')
# The host suites compile these native modules by linking their
# sources. Other kernel files are covered by the separate guest receipt.
if ($relative.StartsWith('Source/XFESystem.Kernel/') -and $file.Name -notin @('NativeWebTransport.cs','VectorFileIo.cs','NativeDesktopInput.cs','NativeKeyboardCapture.cs','NativePointerCapture.cs','NativeNetworkBootWait.cs','NativePointerFrameProbe.cs')) { continue }
$hashes[$relative] = (Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant()
}
}
return $hashes
}
$sourceHashes = Get-SourceHashes
Push-Location $taskRoot
try {
# Shared project dependencies cannot be compiled concurrently while ILC maps
# their PDBs on Windows. Reuse one locked artifacts tree and run in order.
foreach ($suite in $suites) {
$project = 'Tests/XFESystem.' + $suite + '.Tests/XFESystem.' + $suite + '.Tests.csproj'
$log = Join-Path $testDirectory ($suite + '.log')
# Windows PowerShell 5 turns native stderr into ErrorRecords. Keep the
# complete diagnostic/TRX output, then judge the process exit code;
# otherwise xUnit's first failure line aborts the process mid-report.
$savedPreference = $ErrorActionPreference
try {
$ErrorActionPreference = 'Continue'
& dotnet test $project -c Release --artifacts-path $artifacts --nologo --logger ('trx;LogFileName=' + $suite + '.trx') --results-directory $testDirectory *> $log
$testExitCode = $LASTEXITCODE
} finally { $ErrorActionPreference = $savedPreference }
if ($testExitCode -ne 0) { throw ('Host test failed: ' + $suite + '. See ' + $log) }
[xml]$trx = Get-Content -LiteralPath (Join-Path $testDirectory ($suite + '.trx')) -Raw -Encoding UTF8
$counter = $trx.SelectSingleNode("//*[local-name()='Counters']")
if ($null -eq $counter -or [int]$counter.failed -ne 0) { throw ('Missing or failed test counters: ' + $suite) }
$checks += [ordered]@{Suite=$suite;Passed=[int]$counter.passed;Failed=[int]$counter.failed;Log=$log;Trx=(Join-Path $testDirectory ($suite + '.trx'))}
Write-Host ($suite + ': ' + $counter.passed + ' passed')
}
$nativeLog=Join-Path $testDirectory 'NativeContracts.log'
$nativeRunner=Join-Path $taskRoot 'Tests/XFESystem.Platform.Tests/TestSystemNativeExtras.ps1'
& $nativeRunner *> $nativeLog
$nativeDirectory=Get-ChildItem -LiteralPath (Join-Path $taskRoot 'build/IndependentKernel') -Directory -Filter 'ExtrasContract-*' |
Sort-Object Name -Descending | Where-Object {Test-Path -LiteralPath (Join-Path $_.FullName 'Receipt.json')} | Select-Object -First 1
$nativeReceipt=Join-Path $nativeDirectory.FullName 'Receipt.json'
$native=Get-Content -LiteralPath $nativeReceipt -Raw -Encoding UTF8 | ConvertFrom-Json
if($native.Passed -ne $true -or $native.ExitCode -ne 0 -or $null -eq $native.Failed -or $native.Failed -ne 0 -or $native.PassedCount -lt 1){throw 'Native runtime contract verification failed.'}
$checks+=[ordered]@{Suite='NativeContracts';Passed=[int]$native.PassedCount;Failed=[int]$native.Failed;
Log=$native.Log;LogSha256=$native.LogSha256;Receipt=$nativeReceipt;
ReceiptSha256=(Get-FileHash -LiteralPath $nativeReceipt -Algorithm SHA256).Hash.ToLowerInvariant()}
Write-Host ('NativeContracts: '+$native.PassedCount+' passed')
if ($BrowserDom) {
& node scripts/TestDocumentBrowser.mjs
if ($LASTEXITCODE -ne 0) { throw 'Independent browser DOM verification failed.' }
}
$afterHashes = Get-SourceHashes
if (($sourceHashes | ConvertTo-Json -Compress -Depth 4) -ne ($afterHashes | ConvertTo-Json -Compress -Depth 4)) { throw 'Source files changed during host verification; run the final checks against frozen sources.' }
$result = [ordered]@{HostOnly=$true;GuestExecution=$false;Passed=$true;Suites=$checks;SourceHashes=$sourceHashes;ArtifactsDirectory=$artifacts;ArtifactsImmutable=$false;ArtifactPolicy='SharedLockedCompilerCache';BrowserDomRequested=[bool]$BrowserDom;FinishedUtc=[DateTime]::UtcNow.ToString('o')}
$result | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Join-Path $testDirectory 'Result.json') -Encoding UTF8
Write-Host ('Host verification: ' + (Join-Path $testDirectory 'Result.json'))
} catch {
[ordered]@{HostOnly=$true;GuestExecution=$false;Passed=$false;Error=$_.Exception.Message;Suites=$checks} |
ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Join-Path $testDirectory 'Failure.json') -Encoding UTF8
throw
} finally { Pop-Location }
} finally { $artifactsLock.Dispose() }