From f92479ac3ee31c6bb667d6e1b333f2d6c55113d6 Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 22 Aug 2026 13:53:29 +0200 Subject: [PATCH] test(render): add connected package lifecycle gate --- .../ConnectedRenderPackGateContractTests.cs | 43 +- tools/connected-render-pack-gate-common.ps1 | 42 +- ...ed-render-pack-package-lifecycle.route.txt | 10 + ...onnected-render-pack-package-lifecycle.ps1 | 424 ++++++++++++++++++ 4 files changed, 507 insertions(+), 12 deletions(-) create mode 100644 tools/connected-render-pack-package-lifecycle.route.txt create mode 100644 tools/run-connected-render-pack-package-lifecycle.ps1 diff --git a/tests/AcDream.App.Tests/Diagnostics/ConnectedRenderPackGateContractTests.cs b/tests/AcDream.App.Tests/Diagnostics/ConnectedRenderPackGateContractTests.cs index 5130f489..035b55c2 100644 --- a/tests/AcDream.App.Tests/Diagnostics/ConnectedRenderPackGateContractTests.cs +++ b/tests/AcDream.App.Tests/Diagnostics/ConnectedRenderPackGateContractTests.cs @@ -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() { diff --git a/tools/connected-render-pack-gate-common.ps1 b/tools/connected-render-pack-gate-common.ps1 index 6264bbb9..85126f7b 100644 --- a/tools/connected-render-pack-gate-common.ps1 +++ b/tools/connected-render-pack-gate-common.ps1 @@ -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 diff --git a/tools/connected-render-pack-package-lifecycle.route.txt b/tools/connected-render-pack-package-lifecycle.route.txt new file mode 100644 index 00000000..01c841c7 --- /dev/null +++ b/tools/connected-render-pack-package-lifecycle.route.txt @@ -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 diff --git a/tools/run-connected-render-pack-package-lifecycle.ps1 b/tools/run-connected-render-pack-package-lifecycle.ps1 new file mode 100644 index 00000000..72fc3c60 --- /dev/null +++ b/tools/run-connected-render-pack-package-lifecycle.ps1 @@ -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" +}