test(render): add connected package lifecycle gate

This commit is contained in:
Erik 2026-08-22 13:53:29 +02:00
parent 1f75ce0505
commit f92479ac3e
4 changed files with 507 additions and 12 deletions

View file

@ -10,6 +10,8 @@ public sealed class ConnectedRenderPackGateContractTests
{
private const string LifecycleScript = "run-connected-world-lifecycle-gate.ps1";
private const string SoakScript = "run-connected-r6-soak.ps1";
private const string PackageLifecycleScript =
"run-connected-render-pack-package-lifecycle.ps1";
[Fact]
public void ScreenshotJsonCarriesAuthoritativeCasterClassCounters()
@ -125,7 +127,8 @@ foreach ($name in $script:ConnectedGateEnvironmentNames) {{
}}
[Environment]::SetEnvironmentVariable('ACDREAM_FUTURE_GATE_KNOB', 'sentinel-future', 'Process')
$state = New-ConnectedRenderPackGateState -Root {rootQuoted} -Preset medium
$sessionConfigPath = New-ConnectedGraphicalSessionConfig -State $state -Account 'fixture-account'
$statusFile = Join-Path $state.StateDirectory 'status\fixture.jsonl'
$sessionConfigPath = New-ConnectedGraphicalSessionConfig -State $state -Account 'fixture-account' -Plugins @('fixture.render') -StatusFile $statusFile
$sessionConfig = Get-Content -Raw -LiteralPath $sessionConfigPath | ConvertFrom-Json
$isolated=@($state.PreviousEnvironment.Keys | Where-Object {{
$_ -notin @('ACDREAM_CONFIG_DIR', 'ACDREAM_DATA_DIR', 'ACDREAM_CACHE_DIR') -and
@ -153,6 +156,8 @@ Remove-ConnectedGraphicalSessionConfig -State $state -Path $sessionConfigPath
SessionCharacterIndex=$sessionConfig.sessions[0].character.index;
SessionCredentialProvider=$sessionConfig.sessions[0].credential.provider;
SessionCredentialReference=$sessionConfig.sessions[0].credential.reference;
SessionPlugins=@($sessionConfig.sessions[0].plugins);
SessionStatusFile=$sessionConfig.sessions[0].statusFile;
Settings=(Get-Content -Raw -LiteralPath (Join-Path $state.ConfigDirectory 'settings.json') | ConvertFrom-Json).display.renderPack.presetId
}} | ConvertTo-Json -Compress";
using JsonDocument result = JsonDocument.Parse(RunPowerShell(command));
@ -170,6 +175,13 @@ Remove-ConnectedGraphicalSessionConfig -State $state -Path $sessionConfigPath
Assert.Equal(0, rootElement.GetProperty("SessionCharacterIndex").GetInt32());
Assert.Equal("Environment", rootElement.GetProperty("SessionCredentialProvider").GetString());
Assert.Equal("ACDREAM_TEST_PASS", rootElement.GetProperty("SessionCredentialReference").GetString());
Assert.Equal(
new[] { "fixture.render" },
rootElement.GetProperty("SessionPlugins").EnumerateArray()
.Select(static value => value.GetString()!).ToArray());
Assert.Equal(
Path.GetFullPath(Path.Combine(root, "isolated-state", "status", "fixture.jsonl")),
rootElement.GetProperty("SessionStatusFile").GetString());
Assert.Equal("medium", rootElement.GetProperty("Settings").GetString());
}
finally { Directory.Delete(root, recursive: true); }
@ -192,6 +204,35 @@ Remove-ConnectedGraphicalSessionConfig -State $state -Path $sessionConfigPath
Assert.Contains("reference = $CredentialEnvironmentVariable", common, StringComparison.Ordinal);
}
[Fact]
public void ConnectedPackageLifecycleRunsSixFreshGraphicalHostScenariosFailClosed()
{
string source = ReadTool(PackageLifecycleScript);
Assert.Contains("Get-ConnectedGateBinaryIdentity", source, StringComparison.Ordinal);
Assert.Contains("New-ConnectedRenderPackGateState -Root $root -Preset retail", source, StringComparison.Ordinal);
Assert.Contains("-Plugins @($packageId)", source, StringComparison.Ordinal);
Assert.Contains("-StatusFile $status", source, StringComparison.Ordinal);
Assert.Contains("pluginLoaded", source, StringComparison.Ordinal);
Assert.Contains("pluginFailed", source, StringComparison.Ordinal);
Assert.Contains("'01-installed-v1-selected'", source, StringComparison.Ordinal);
Assert.Contains("'02-updated-v2-stale-selection'", source, StringComparison.Ordinal);
Assert.Contains("'03-updated-v2-selected'", source, StringComparison.Ordinal);
Assert.Contains("'04-package-removed'", source, StringComparison.Ordinal);
Assert.Contains("'05-registration-failed'", source, StringComparison.Ordinal);
Assert.Contains("'06-corrected-and-recovered'", source, StringComparison.Ordinal);
Assert.Contains("BinaryMatchesSource", source, StringComparison.Ordinal);
Assert.Contains("TrackedSourceStatus", source, StringComparison.Ordinal);
Assert.Contains("Restore-ConnectedRenderPackGateEnvironment", source, StringComparison.Ordinal);
string route = ReadTool("connected-render-pack-package-lifecycle.route.txt");
AssertAppearsInOrder(
route,
"wait world-ready 90000",
"wait world-visible 30000",
"screenshot package-lifecycle 15000",
"checkpoint package-lifecycle");
}
[Fact]
public void EveryConnectedScreenshotMustProveExactFailureFreeActivation()
{

View file

@ -168,7 +168,9 @@ function New-ConnectedGraphicalSessionConfig {
[string]$HostName = '127.0.0.1',
[ValidateRange(1, 65535)][int]$Port = 9000,
[ValidateRange(0, [int]::MaxValue)][int]$CharacterIndex = 0,
[string]$CredentialEnvironmentVariable = 'ACDREAM_TEST_PASS'
[string]$CredentialEnvironmentVariable = 'ACDREAM_TEST_PASS',
[string[]]$Plugins = @(),
[string]$StatusFile
)
if ([string]::IsNullOrWhiteSpace($Account)) {
@ -187,18 +189,36 @@ function New-ConnectedGraphicalSessionConfig {
throw "Connected graphical session config already exists: '$path'."
}
$session = [ordered]@{
id = 'connected-gate'
endpoint = [ordered]@{ host = $HostName; port = $Port }
account = $Account
character = [ordered]@{ index = $CharacterIndex }
credential = [ordered]@{
provider = 'Environment'
reference = $CredentialEnvironmentVariable
}
}
if ($PSBoundParameters.ContainsKey('Plugins')) {
$orderedPlugins = @($Plugins | ForEach-Object {
if ([string]::IsNullOrWhiteSpace($_)) {
throw 'Connected graphical session plugin IDs cannot be empty.'
}
$_
} | Sort-Object -Unique)
$session['plugins'] = $orderedPlugins
}
if ($PSBoundParameters.ContainsKey('StatusFile')) {
if ([string]::IsNullOrWhiteSpace($StatusFile)) {
throw 'Connected graphical session status file cannot be empty.'
}
$session['statusFile'] = Assert-ConnectedGateContainedPath `
$State.StateDirectory $StatusFile
}
[ordered]@{
version = 1
sessions = @([ordered]@{
id = 'connected-gate'
endpoint = [ordered]@{ host = $HostName; port = $Port }
account = $Account
character = [ordered]@{ index = $CharacterIndex }
credential = [ordered]@{
provider = 'Environment'
reference = $CredentialEnvironmentVariable
}
})
sessions = @($session)
} | ConvertTo-Json -Depth 8 |
Set-Content -LiteralPath $path -Encoding utf8
return $path

View file

@ -0,0 +1,10 @@
# One short, repeatable connected graphical-host observation. The package
# lifecycle driver reuses this route across fresh processes while it mutates
# only its disposable plugin directory and persisted render-pack selection.
wait world-ready 90000
wait world-visible 30000
sleep 3000
screenshot package-lifecycle 15000
checkpoint package-lifecycle
sleep 1000

View file

@ -0,0 +1,424 @@
[CmdletBinding()]
param(
[string]$Repository = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path,
[string]$Account = $env:ACDREAM_TEST_USER,
[string]$Password = $env:ACDREAM_TEST_PASS,
[switch]$SkipBuild,
[int]$LoginTimeoutSeconds = 90
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
. (Join-Path $PSScriptRoot 'connected-render-pack-gate-common.ps1')
if ([string]::IsNullOrWhiteSpace($Account)) { $Account = 'testaccount' }
if ([string]::IsNullOrWhiteSpace($Password)) { $Password = 'testpassword' }
$packageId = 'acdream.test.external-render-pack-package'
$packId = 'acdream.test.external-render-pack'
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$root = Join-Path $Repository `
"artifacts\atmospheric-rendering\connected-package-lifecycle-$stamp"
$route = Join-Path $Repository `
'tools\connected-render-pack-package-lifecycle.route.txt'
$exe = Join-Path $Repository `
'src\AcDream.App\bin\Release\net10.0\AcDream.App.exe'
$fixture = Join-Path $Repository `
'tests\AcDream.Plugin.Tests.Fixtures.HostPlugin\bin\Release\net10.0\AcDream.Plugin.Tests.Fixtures.HostPlugin.dll'
$startedUtc = [DateTime]::UtcNow.ToString('O')
$results = [Collections.Generic.List[object]]::new()
$renderPackGate = $null
$sessionConfigPath = $null
$failure = $null
$binaryIdentity = $null
function Wait-ForLogPattern {
param(
[Parameter(Mandatory = $true)][Diagnostics.Process]$Client,
[Parameter(Mandatory = $true)][string]$Path,
[Parameter(Mandatory = $true)][string]$Pattern,
[Parameter(Mandatory = $true)][int]$TimeoutSeconds
)
$deadline = [DateTime]::UtcNow.AddSeconds($TimeoutSeconds)
while ([DateTime]::UtcNow -lt $deadline) {
$Client.Refresh()
if ($Client.HasExited) {
throw "client exited with code $($Client.ExitCode) while waiting for '$Pattern'"
}
if (Test-Path -LiteralPath $Path) {
if (Select-String -LiteralPath $Path -SimpleMatch $Pattern -Quiet) {
return
}
}
Start-Sleep -Milliseconds 250
}
throw "timed out after $TimeoutSeconds seconds waiting for '$Pattern'"
}
function Close-ClientGracefully {
param([Parameter(Mandatory = $true)][Diagnostics.Process]$Client)
$Client.Refresh()
if ($Client.HasExited) { return }
if (-not $Client.CloseMainWindow() -or -not $Client.WaitForExit(30000)) {
throw "client $($Client.Id) did not accept graceful close within 30 seconds"
}
$Client.WaitForExit()
}
function Write-Selection {
param(
[Parameter(Mandatory = $true)][string]$Version,
[Parameter(Mandatory = $true)][string]$Preset
)
[ordered]@{
display = [ordered]@{
renderPack = [ordered]@{
packId = $packId
packVersion = $Version
presetId = $Preset
settingOverrides = [ordered]@{}
}
}
version = 3
} | ConvertTo-Json -Depth 8 |
Set-Content -Encoding utf8 -LiteralPath (
Join-Path $renderPackGate.ConfigDirectory 'settings.json')
}
function Write-PackageVersion {
param(
[Parameter(Mandatory = $true)][string]$Directory,
[Parameter(Mandatory = $true)][string]$Version
)
Set-Content -Encoding utf8 -NoNewline -LiteralPath (
Join-Path $Directory 'render-pack-version.txt') -Value $Version
[ordered]@{
id = $packageId
displayName = 'Connected external render-pack lifecycle fixture'
version = $Version
entryDll = [IO.Path]::GetFileName($fixture)
apiVersion = 1
kinds = @('renderPack')
} | ConvertTo-Json -Depth 4 |
Set-Content -Encoding utf8 -LiteralPath (Join-Path $Directory 'plugin.json')
}
function Install-Package {
param(
[Parameter(Mandatory = $true)][string]$Directory,
[Parameter(Mandatory = $true)][string]$Version
)
$null = New-Item -ItemType Directory -Path $Directory
Assert-ConnectedGateNoReparsePoint $Directory
Copy-Item -LiteralPath $fixture -Destination (
Join-Path $Directory ([IO.Path]::GetFileName($fixture)))
Write-PackageVersion -Directory $Directory -Version $Version
}
function Read-StatusEvents {
param([Parameter(Mandatory = $true)][string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
throw "session status stream is missing: '$Path'"
}
return @(Get-Content -LiteralPath $Path | ForEach-Object {
$_ | ConvertFrom-Json
})
}
function Assert-SingleStatusEvent {
param(
[Parameter(Mandatory = $true)][object[]]$Events,
[Parameter(Mandatory = $true)][string]$Name,
[string]$Plugin
)
$matching = @($Events | Where-Object {
$_.e -ceq $Name -and (
-not $PSBoundParameters.ContainsKey('Plugin') -or
$_.plugin -ceq $Plugin)
})
if ($matching.Count -ne 1) {
throw "expected one '$Name' status event, observed $($matching.Count)"
}
return $matching[0]
}
function Invoke-Scenario {
param(
[Parameter(Mandatory = $true)][string]$Name,
[Parameter(Mandatory = $true)][int]$ExpectedState,
[Parameter(Mandatory = $true)][string]$ExpectedPackId,
[AllowNull()][string]$ExpectedPackVersion,
[Parameter(Mandatory = $true)][string]$ExpectedPreset,
[Parameter(Mandatory = $true)]
[ValidateSet('pluginLoaded', 'pluginFailed')]
[string]$ExpectedPluginEvent,
[string]$ExpectedFailureFragment,
[Parameter(Mandatory = $true)][string]$ExpectedPersistedPackId
)
Assert-ConnectedGateSafeLeafName $Name
$scenarioRoot = Assert-ConnectedGateContainedPath $root (Join-Path $root $Name)
$null = New-Item -ItemType Directory -Path $scenarioRoot
Assert-ConnectedGateNoReparsePoint $scenarioRoot
$stdout = Join-Path $scenarioRoot 'client.stdout.log'
$stderr = Join-Path $scenarioRoot 'client.stderr.log'
$status = Assert-ConnectedGateContainedPath $renderPackGate.StateDirectory (
Join-Path $renderPackGate.StateDirectory "status\$Name.jsonl")
$sessionConfigPath = New-ConnectedGraphicalSessionConfig `
-State $renderPackGate `
-Account $Account `
-Plugins @($packageId) `
-StatusFile $status
$client = $null
try {
$env:ACDREAM_AUTOMATION_ARTIFACT_DIR = $scenarioRoot
$client = Start-Process -FilePath $exe -WorkingDirectory $Repository `
-ArgumentList @('--session-config', ('"' + $sessionConfigPath + '"')) `
-RedirectStandardOutput $stdout -RedirectStandardError $stderr -PassThru
Wait-ForLogPattern $client $stdout 'live: in world' $LoginTimeoutSeconds
Wait-ForLogPattern $client $stdout `
'[world-gate] screenshot-complete name=package-lifecycle' 120
Wait-ForLogPattern $client $stdout `
'[world-gate] checkpoint name=package-lifecycle' 30
Close-ClientGracefully $client
if ($client.ExitCode -ne 0) {
throw "client exited with code $($client.ExitCode)"
}
$metadataPath = Join-Path $scenarioRoot `
'screenshots\package-lifecycle.metadata.json'
if (-not (Test-Path -LiteralPath $metadataPath)) {
throw "render-pack metadata is missing: '$metadataPath'"
}
$metadata = Get-Content -Raw -LiteralPath $metadataPath | ConvertFrom-Json
$actual = $metadata.RenderPack
if ([int]$metadata.SchemaVersion -ne 1) {
throw "metadata schema was $($metadata.SchemaVersion), expected 1"
}
foreach ($comparison in @(
@('State', [int]$ExpectedState, [int]$actual.State),
@('PackId', $ExpectedPackId, [string]$actual.PackId),
@('PackVersion', [string]$ExpectedPackVersion, [string]$actual.PackVersion),
@('PresetId', $ExpectedPreset, [string]$actual.PresetId))) {
if ($comparison[1] -cne $comparison[2]) {
throw "$($comparison[0]) '$($comparison[2])', expected '$($comparison[1])'"
}
}
if ([string]::IsNullOrWhiteSpace($ExpectedFailureFragment)) {
if (-not [string]::IsNullOrWhiteSpace([string]$actual.FailureReason)) {
throw "unexpected render-pack failure: $($actual.FailureReason)"
}
}
elseif ([string]$actual.FailureReason -notlike "*$ExpectedFailureFragment*") {
throw "failure '$($actual.FailureReason)' did not contain '$ExpectedFailureFragment'"
}
$events = @(Read-StatusEvents $status)
$pluginEvent = Assert-SingleStatusEvent `
-Events $events -Name $ExpectedPluginEvent -Plugin $packageId
foreach ($eventName in @('started', 'connected', 'characterList',
'enteredWorld', 'disconnected', 'exited')) {
$null = Assert-SingleStatusEvent -Events $events -Name $eventName
}
$exitEvent = Assert-SingleStatusEvent -Events $events -Name 'exited'
if ([int]$exitEvent.code -ne 0) {
throw "terminal status exit code was $($exitEvent.code)"
}
$persisted = (Get-Content -Raw -LiteralPath (
Join-Path $renderPackGate.ConfigDirectory 'settings.json') |
ConvertFrom-Json).display.renderPack
if ([string]$persisted.packId -cne $ExpectedPersistedPackId) {
throw "persisted pack '$($persisted.packId)', expected '$ExpectedPersistedPackId'"
}
$pluginErrorProperty = $pluginEvent.PSObject.Properties['error']
$result = [pscustomobject][ordered]@{
Name = $Name
Passed = $true
ProcessId = $client.Id
ExitCode = $client.ExitCode
PluginEvent = $pluginEvent.e
PluginError = if ($null -eq $pluginErrorProperty) {
$null
}
else {
$pluginErrorProperty.Value
}
RenderPack = [pscustomobject][ordered]@{
State = $actual.State
PackId = $actual.PackId
PackVersion = $actual.PackVersion
PresetId = $actual.PresetId
EffectiveQuality = $actual.EffectiveQuality
FailureReason = $actual.FailureReason
ActivationGeneration = $actual.ActivationGeneration
RetainedGpuBytes = $actual.RetainedGpuBytes
TransientGpuBytes = $actual.TransientGpuBytes
}
PersistedPackId = $persisted.packId
StatusFile = $status
MetadataFile = $metadataPath
}
$results.Add($result)
Write-Output (
"PASS scenario=$Name plugin=$($pluginEvent.e) " +
"renderPack=$($actual.PackId) state=$($actual.State)")
}
finally {
if ($null -ne $client) {
$client.Refresh()
if (-not $client.HasExited) {
$closed = $client.CloseMainWindow()
if (-not $closed -or -not $client.WaitForExit(10000)) {
Stop-Process -Id $client.Id -Force -ErrorAction SilentlyContinue
}
}
$client.Dispose()
}
if ($null -ne $sessionConfigPath) {
Remove-ConnectedGraphicalSessionConfig `
-State $renderPackGate -Path $sessionConfigPath
$sessionConfigPath = $null
}
}
}
if (@(Get-Process -Name AcDream.App -ErrorAction SilentlyContinue).Count -gt 0) {
throw 'an AcDream.App client is already running; close it gracefully before the package gate'
}
if (@(Get-NetUDPEndpoint -LocalPort 9000 -ErrorAction SilentlyContinue).Count -eq 0) {
throw 'local ACE is not listening on UDP port 9000'
}
if (-not (Test-Path -LiteralPath $route -PathType Leaf)) {
throw "route file not found: '$route'"
}
if (-not $SkipBuild) {
Write-Output 'Building Release solution...'
& dotnet build (Join-Path $Repository 'AcDream.slnx') -c Release --no-restore
if ($LASTEXITCODE -ne 0) { throw "Release build failed with exit code $LASTEXITCODE" }
}
foreach ($required in @($exe, $fixture)) {
if (-not (Test-Path -LiteralPath $required -PathType Leaf)) {
throw "required gate input not found: '$required'"
}
}
$null = New-Item -ItemType Directory -Path $root
Assert-ConnectedGateNoReparsePoint $root
$renderPackGate = New-ConnectedRenderPackGateState -Root $root -Preset retail
try {
$binaryIdentity = Get-ConnectedGateBinaryIdentity `
-Repository $Repository -Executable $exe -SkipBuild:$SkipBuild
$env:ACDREAM_DAT_DIR = "$env:USERPROFILE\Documents\Asheron's Call"
$env:ACDREAM_LIVE = '1'
$env:ACDREAM_TEST_HOST = '127.0.0.1'
$env:ACDREAM_TEST_PORT = '9000'
$env:ACDREAM_TEST_USER = $Account
$env:ACDREAM_TEST_PASS = $Password
$env:ACDREAM_RETAIL_UI = '1'
$env:ACDREAM_DEVTOOLS = '0'
$env:ACDREAM_UI_PROBE_DUMP = '0'
$env:ACDREAM_UI_PROBE_SCRIPT = $route
$env:ACDREAM_NO_AUDIO = '1'
$packageDirectory = Assert-ConnectedGateContainedPath `
$renderPackGate.DataDirectory (
Join-Path $renderPackGate.DataDirectory "plugins\$packageId")
$withdrawnDirectory = Assert-ConnectedGateContainedPath $root (
Join-Path $root 'withdrawn-package')
Install-Package -Directory $packageDirectory -Version '1.0.0'
Write-Selection -Version '1.0.0' -Preset 'low'
Invoke-Scenario -Name '01-installed-v1-selected' `
-ExpectedState 2 -ExpectedPackId $packId -ExpectedPackVersion '1.0.0' `
-ExpectedPreset 'low' -ExpectedPluginEvent pluginLoaded `
-ExpectedPersistedPackId $packId
Write-PackageVersion -Directory $packageDirectory -Version '2.0.0'
Write-Selection -Version '1.0.0' -Preset 'low'
Invoke-Scenario -Name '02-updated-v2-stale-selection' `
-ExpectedState 3 -ExpectedPackId 'retail' -ExpectedPackVersion $null `
-ExpectedPreset 'off' -ExpectedPluginEvent pluginLoaded `
-ExpectedFailureFragment 'version 1.0.0 was selected, but version 2.0.0 is installed' `
-ExpectedPersistedPackId 'retail'
Write-Selection -Version '2.0.0' -Preset 'high'
Invoke-Scenario -Name '03-updated-v2-selected' `
-ExpectedState 2 -ExpectedPackId $packId -ExpectedPackVersion '2.0.0' `
-ExpectedPreset 'high' -ExpectedPluginEvent pluginLoaded `
-ExpectedPersistedPackId $packId
Move-Item -LiteralPath $packageDirectory -Destination $withdrawnDirectory
Write-Selection -Version '2.0.0' -Preset 'high'
Invoke-Scenario -Name '04-package-removed' `
-ExpectedState 3 -ExpectedPackId 'retail' -ExpectedPackVersion $null `
-ExpectedPreset 'off' -ExpectedPluginEvent pluginFailed `
-ExpectedFailureFragment 'is not installed' `
-ExpectedPersistedPackId 'retail'
Move-Item -LiteralPath $withdrawnDirectory -Destination $packageDirectory
Set-Content -NoNewline -LiteralPath (
Join-Path $packageDirectory 'throw-after-render-pack-register') -Value ''
Write-Selection -Version '2.0.0' -Preset 'high'
Invoke-Scenario -Name '05-registration-failed' `
-ExpectedState 3 -ExpectedPackId 'retail' -ExpectedPackVersion $null `
-ExpectedPreset 'off' -ExpectedPluginEvent pluginFailed `
-ExpectedFailureFragment 'is not installed' `
-ExpectedPersistedPackId 'retail'
Remove-Item -LiteralPath (
Join-Path $packageDirectory 'throw-after-render-pack-register')
Write-Selection -Version '2.0.0' -Preset 'high'
Invoke-Scenario -Name '06-corrected-and-recovered' `
-ExpectedState 2 -ExpectedPackId $packId -ExpectedPackVersion '2.0.0' `
-ExpectedPreset 'high' -ExpectedPluginEvent pluginLoaded `
-ExpectedPersistedPackId $packId
}
catch {
$failure = $_.Exception.ToString()
}
finally {
if ($null -ne $sessionConfigPath -and $null -ne $renderPackGate) {
Remove-ConnectedGraphicalSessionConfig `
-State $renderPackGate -Path $sessionConfigPath
}
if ($null -ne $renderPackGate) {
Restore-ConnectedRenderPackGateEnvironment $renderPackGate
}
$report = [pscustomobject][ordered]@{
Passed = [string]::IsNullOrWhiteSpace($failure) -and $results.Count -eq 6
StartedUtc = $startedUtc
FinishedUtc = [DateTime]::UtcNow.ToString('O')
SourceCommit = if ($null -eq $binaryIdentity) {
$null
} else { $binaryIdentity.SourceCommit }
BinaryProductVersion = if ($null -eq $binaryIdentity) {
$null
} else { $binaryIdentity.BinaryProductVersion }
BinaryCommit = if ($null -eq $binaryIdentity) {
$null
} else { $binaryIdentity.BinaryCommit }
BinaryMatchesSource = if ($null -eq $binaryIdentity) {
$false
} else { $binaryIdentity.BinaryMatchesSource }
TrackedSourceStatus = if ($null -eq $binaryIdentity) {
@()
} else { @($binaryIdentity.SourceTrackedStatus) }
PackageId = $packageId
RenderPackId = $packId
ScenarioCount = $results.Count
Scenarios = @($results)
Failure = $failure
}
$reportPath = Join-Path $root 'report.json'
$report | ConvertTo-Json -Depth 12 |
Set-Content -Encoding utf8 -LiteralPath $reportPath
Write-Output "REPORT=$reportPath"
Write-Output "RESULT=$(if ($report.Passed) { 'PASS' } else { 'FAIL' })"
}
if (-not [string]::IsNullOrWhiteSpace($failure)) {
throw $failure
}
if ($results.Count -ne 6) {
throw "package lifecycle completed $($results.Count) of 6 scenarios"
}