commit
This commit is contained in:
@@ -16,8 +16,8 @@ Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Coverage-harness orchestrator. Drives the Vagrant Windows VM through every
|
||||
# scenario directory under vm\<scenario>\install.toml using the per-scenario
|
||||
# guest scripts under scripts\windows-vm\coverage\<scenario>.ps1.
|
||||
# scenario directory under vm\<scenario>\<AppName>-install.toml using the
|
||||
# per-scenario guest scripts under scripts\windows-vm\coverage\<scenario>.ps1.
|
||||
#
|
||||
# All install/uninstall side effects happen INSIDE the VM. The host only:
|
||||
# 1. Builds covenant-setup.exe (release).
|
||||
@@ -73,6 +73,21 @@ function Open-HyperVViewer {
|
||||
Start-Process -FilePath $vmConnect -ArgumentList @("localhost", $VmName) | Out-Null
|
||||
}
|
||||
|
||||
# The engine enforces the <AppName>-install.toml naming convention, so each
|
||||
# scenario directory carries its own manifest file name; discover it instead
|
||||
# of hardcoding install.toml.
|
||||
function Get-ScenarioManifest {
|
||||
param([Parameter(Mandatory)][string]$ScenarioDir)
|
||||
$candidates = @(Get-ChildItem -LiteralPath $ScenarioDir -Filter "*install.toml" -File | Sort-Object Name)
|
||||
if ($candidates.Count -eq 0) {
|
||||
throw "Scenario manifest (*install.toml) not found in: $ScenarioDir"
|
||||
}
|
||||
if ($candidates.Count -gt 1) {
|
||||
throw "Multiple *install.toml manifests found in: $ScenarioDir"
|
||||
}
|
||||
return $candidates[0]
|
||||
}
|
||||
|
||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||
$releaseExe = Join-Path $repoRoot "target\release\covenant-setup.exe"
|
||||
$outputRoot = Join-Path $repoRoot "dist\vagrant-coverage"
|
||||
@@ -98,10 +113,7 @@ if (-not (Test-Path -LiteralPath $releaseExe)) {
|
||||
# Stage the payload tree for each scenario manifest that references
|
||||
# payload\covenant-setup.exe (relative to the manifest dir).
|
||||
foreach ($scenario in $Scenarios) {
|
||||
$manifest = Join-Path $repoRoot "vm\$scenario\install.toml"
|
||||
if (-not (Test-Path -LiteralPath $manifest)) {
|
||||
throw "Scenario manifest not found: $manifest"
|
||||
}
|
||||
$manifest = (Get-ScenarioManifest -ScenarioDir (Join-Path $repoRoot "vm\$scenario")).FullName
|
||||
$payloadDir = Join-Path $repoRoot "vm\$scenario\payload"
|
||||
$manifestText = Get-Content -LiteralPath $manifest -Raw
|
||||
if ($manifestText -match 'payload\\\\covenant-setup\.exe' -or $manifestText -match 'payload[\\/]covenant-setup\.exe') {
|
||||
@@ -122,7 +134,16 @@ try {
|
||||
Open-HyperVViewer -VmName $VmName
|
||||
}
|
||||
|
||||
$waitForShellCommand = "for (`$i = 0; `$i -lt 90; `$i++) { if (Get-Process -Name explorer -ErrorAction SilentlyContinue) { exit 0 }; Start-Sleep -Seconds 2 }; Write-Error 'Explorer shell did not start in time.'; exit 1"
|
||||
$waitForShellCommand = @(
|
||||
"for (`$i = 0; `$i -lt 90; `$i++) { if (Get-Process -Name explorer -ErrorAction SilentlyContinue) { exit 0 }; Start-Sleep -Seconds 2 }"
|
||||
"`$winlogon = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'"
|
||||
"Write-Output ('consoleUser=' + (Get-CimInstance Win32_ComputerSystem).UserName)"
|
||||
"Write-Output ('computerName=' + `$env:COMPUTERNAME)"
|
||||
"foreach (`$name in 'AutoAdminLogon','ForceAutoLogon','DefaultUserName','DefaultDomainName','AutoLogonCount') { `$value = `$winlogon.PSObject.Properties[`$name]; Write-Output (`$name + '=' + `$(if (`$value) { `$value.Value } else { '<absent>' })) }"
|
||||
"Write-Output ('DefaultPassword=' + `$(if (`$winlogon.PSObject.Properties['DefaultPassword']) { '<set>' } else { '<absent>' }))"
|
||||
"Write-Error 'Explorer shell did not start in time. Auto-logon state is printed above.'"
|
||||
"exit 1"
|
||||
) -join "; "
|
||||
Invoke-Vagrant -Arguments @("winrm", "-s", "powershell", "-c", $waitForShellCommand)
|
||||
|
||||
# Prepare guest layout.
|
||||
@@ -156,7 +177,8 @@ try {
|
||||
Write-Host ""
|
||||
Write-Host "[coverage] -> $scenario" -ForegroundColor Cyan
|
||||
$remoteScript = Join-Path $guestScriptRoot "$scenario.ps1"
|
||||
$remoteManifest = Join-Path $guestScenarioRoot "$scenario\install.toml"
|
||||
$manifestName = (Get-ScenarioManifest -ScenarioDir (Join-Path $repoRoot "vm\$scenario")).Name
|
||||
$remoteManifest = Join-Path $guestScenarioRoot "$scenario\$manifestName"
|
||||
$remoteWork = Join-Path $guestWorkRoot $scenario
|
||||
$logRel = "$scenario\guest.log"
|
||||
$remoteLog = Join-Path $guestWorkRoot $logRel
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$Provider = "hyperv",
|
||||
[string]$ManifestPath = "vm\self-test\install.toml",
|
||||
[string]$ManifestPath = "vm\self-test\CovenantSetupSelfTest-install.toml",
|
||||
[string]$OutputRoot = "dist\vagrant-self-test",
|
||||
[string]$VmName = $(if ($env:COVENANT_VM_NAME) { $env:COVENANT_VM_NAME } else { "covenant-setup-windows" }),
|
||||
[string]$GuestUsername = $(if ($env:COVENANT_WINRM_USERNAME) { $env:COVENANT_WINRM_USERNAME } else { "vagrant" }),
|
||||
@@ -244,7 +244,16 @@ try {
|
||||
Open-HyperVViewer -VmName $VmName
|
||||
}
|
||||
|
||||
$waitForShellCommand = "for (`$i = 0; `$i -lt 90; `$i++) { if (Get-Process -Name explorer -ErrorAction SilentlyContinue) { exit 0 }; Start-Sleep -Seconds 2 }; Write-Error 'Explorer shell did not start in time.'; exit 1"
|
||||
$waitForShellCommand = @(
|
||||
"for (`$i = 0; `$i -lt 90; `$i++) { if (Get-Process -Name explorer -ErrorAction SilentlyContinue) { exit 0 }; Start-Sleep -Seconds 2 }"
|
||||
"`$winlogon = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'"
|
||||
"Write-Output ('consoleUser=' + (Get-CimInstance Win32_ComputerSystem).UserName)"
|
||||
"Write-Output ('computerName=' + `$env:COMPUTERNAME)"
|
||||
"foreach (`$name in 'AutoAdminLogon','ForceAutoLogon','DefaultUserName','DefaultDomainName','AutoLogonCount') { `$value = `$winlogon.PSObject.Properties[`$name]; Write-Output (`$name + '=' + `$(if (`$value) { `$value.Value } else { '<absent>' })) }"
|
||||
"Write-Output ('DefaultPassword=' + `$(if (`$winlogon.PSObject.Properties['DefaultPassword']) { '<set>' } else { '<absent>' }))"
|
||||
"Write-Error 'Explorer shell did not start in time. Auto-logon state is printed above.'"
|
||||
"exit 1"
|
||||
) -join "; "
|
||||
Invoke-Vagrant -Arguments @("winrm", "-s", "powershell", "-c", $waitForShellCommand)
|
||||
|
||||
Invoke-Vagrant -Arguments @("winrm", "-s", "powershell", "-c", "New-Item -ItemType Directory -Force -Path '$guestRoot' | Out-Null; New-Item -ItemType Directory -Force -Path '$guestScriptRoot' | Out-Null")
|
||||
@@ -257,6 +266,38 @@ try {
|
||||
) -join "; "
|
||||
Invoke-Vagrant -Arguments @("winrm", "-s", "powershell", "-c", $clearGuestTraceCommand)
|
||||
Invoke-Vagrant -Arguments @("upload", $installerPath, $guestInstallerPath)
|
||||
|
||||
# Zip the ui folder, excluding bin/obj directories to keep it small
|
||||
$uiZipPath = Join-Path $outputRootAbs "ui.zip"
|
||||
Remove-Item -LiteralPath $uiZipPath -Force -ErrorAction SilentlyContinue
|
||||
|
||||
$zipScript = @(
|
||||
"& {"
|
||||
" `$uiPath = '$repoRoot\ui'"
|
||||
" `$zipPath = '$uiZipPath'"
|
||||
" `$tempDir = Join-Path '$outputRootAbs' 'ui-temp'"
|
||||
" Remove-Item -LiteralPath `$tempDir -Recurse -Force -ErrorAction SilentlyContinue"
|
||||
" New-Item -ItemType Directory -Force -Path `$tempDir | Out-Null"
|
||||
" Get-ChildItem -Path `$uiPath -Recurse | Where-Object { `$_.FullName -notmatch '\\(bin|obj|TestResults|\.vs)($|\\)' } | ForEach-Object {"
|
||||
" `$relative = `$_.FullName.Substring(`$uiPath.Length + 1)"
|
||||
" `$target = Join-Path `$tempDir `$relative"
|
||||
" if (`$_.PSIsContainer) {"
|
||||
" New-Item -ItemType Directory -Force -Path `$target | Out-Null"
|
||||
" } else {"
|
||||
" `$targetParent = Split-Path -Parent `$target"
|
||||
" New-Item -ItemType Directory -Force -Path `$targetParent -ErrorAction SilentlyContinue | Out-Null"
|
||||
" Copy-Item -LiteralPath `$_.FullName -Destination `$target -Force"
|
||||
" }"
|
||||
" }"
|
||||
" Compress-Archive -Path (Join-Path `$tempDir '*') -DestinationPath `$zipPath -Force"
|
||||
" Remove-Item -LiteralPath `$tempDir -Recurse -Force -ErrorAction SilentlyContinue"
|
||||
"}"
|
||||
) -join "`n"
|
||||
powershell.exe -NoProfile -Command $zipScript
|
||||
|
||||
Invoke-Vagrant -Arguments @("upload", $uiZipPath, "$guestRoot\ui.zip")
|
||||
Invoke-Vagrant -Arguments @("winrm", "-s", "powershell", "-c", "if (Test-Path -LiteralPath '$guestRoot\ui') { Remove-Item -LiteralPath '$guestRoot\ui' -Recurse -Force -ErrorAction SilentlyContinue }; Expand-Archive -Path '$guestRoot\ui.zip' -DestinationPath '$guestRoot\ui' -Force")
|
||||
|
||||
Invoke-Vagrant -Arguments @("upload", (Join-Path $repoRoot "scripts\windows-vm\Invoke-InteractiveInstaller.ps1"), (Join-Path $guestScriptRoot "Invoke-InteractiveInstaller.ps1"))
|
||||
Invoke-Vagrant -Arguments @("upload", (Join-Path $repoRoot "scripts\windows-vm\Start-InteractiveSelfInstall.ps1"), (Join-Path $guestScriptRoot "Start-InteractiveSelfInstall.ps1"))
|
||||
|
||||
|
||||
@@ -129,51 +129,95 @@ try {
|
||||
$env:COVENANT_SETUP_TRACE_DIR = $TracePath
|
||||
Write-TraceEvent -Phase "trace_environment_set" -Detail @{ name = "COVENANT_SETUP_TRACE_DIR"; value = $TracePath }
|
||||
|
||||
$installer = Start-Process -FilePath $InstallerPath -ArgumentList $InstallerArguments -PassThru
|
||||
Write-TraceEvent -Phase "installer_process_started" -Detail @{ pid = $installer.Id; operationName = $OperationName }
|
||||
|
||||
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
||||
$lastPoll = Get-Date "2000-01-01"
|
||||
while (-not $installer.HasExited) {
|
||||
if ((Get-Date) -ge $deadline) {
|
||||
Export-InstallerDiagnostics -Reason "installer_timeout" -InstallerProcess $installer
|
||||
Stop-Process -Id $installer.Id -Force -ErrorAction SilentlyContinue
|
||||
throw "Installer timed out after $TimeoutSeconds seconds."
|
||||
$testProjectDir = "C:\Users\vagrant\AppData\Local\Temp\covenant-setup-smoke\ui\Covenant.Setup.Ui.Tests"
|
||||
if (Test-Path -LiteralPath "$testProjectDir\Covenant.Setup.Ui.Tests.csproj") {
|
||||
$env:COVENANT_REAL_INSTALLER_PATH = $InstallerPath
|
||||
$env:COVENANT_REAL_INSTALLER_ARGS = $InstallerArguments -join ' '
|
||||
Write-TraceEvent -Phase "running_ui_automation_tests" -Detail @{
|
||||
installerPath = $InstallerPath
|
||||
arguments = $InstallerArguments
|
||||
}
|
||||
|
||||
$dotnetPath = "C:\Users\vagrant\dotnet\dotnet.exe"
|
||||
if (-not (Test-Path -LiteralPath $dotnetPath)) {
|
||||
$dotnetPath = "dotnet"
|
||||
}
|
||||
|
||||
Write-TraceEvent -Phase "executing_dotnet_test" -Detail @{
|
||||
dotnetPath = $dotnetPath
|
||||
project = "$testProjectDir\Covenant.Setup.Ui.Tests.csproj"
|
||||
}
|
||||
|
||||
$stdoutLog = Join-Path $TracePath "dotnet-test.log"
|
||||
$stderrLog = Join-Path $TracePath "dotnet-test-error.log"
|
||||
|
||||
$process = Start-Process -FilePath $dotnetPath -ArgumentList @("test", "$testProjectDir\Covenant.Setup.Ui.Tests.csproj", "--filter", "FullyQualifiedName=Covenant.Setup.Ui.Tests.InstallerUiAutomationTests.TestRealInstallUninstallFlow") -PassThru -NoNewWindow -Wait -RedirectStandardOutput $stdoutLog -RedirectStandardError $stderrLog
|
||||
|
||||
Write-TraceEvent -Phase "dotnet_test_finished" -Detail @{ exitCode = $process.ExitCode }
|
||||
|
||||
$result = [ordered]@{
|
||||
success = ($process.ExitCode -eq 0)
|
||||
exitCode = [int]$process.ExitCode
|
||||
installerPath = $InstallerPath
|
||||
installerArgs = $InstallerArguments
|
||||
operationName = $OperationName
|
||||
tracePath = $TracePath
|
||||
startedAt = $startedAt.ToString("o")
|
||||
finishedAt = (Get-Date).ToString("o")
|
||||
}
|
||||
$result | ConvertTo-Json | Set-Content -LiteralPath $ResultPath -Encoding UTF8
|
||||
|
||||
if ($process.ExitCode -ne 0) {
|
||||
exit $process.ExitCode
|
||||
}
|
||||
}
|
||||
else {
|
||||
$installer = Start-Process -FilePath $InstallerPath -ArgumentList $InstallerArguments -PassThru
|
||||
Write-TraceEvent -Phase "installer_process_started" -Detail @{ pid = $installer.Id; operationName = $OperationName }
|
||||
|
||||
if (((Get-Date) - $lastPoll).TotalSeconds -ge 10) {
|
||||
$lastPoll = Get-Date
|
||||
$installer.Refresh()
|
||||
Write-TraceEvent -Phase "installer_still_running" -Detail @{
|
||||
pid = $installer.Id
|
||||
operationName = $OperationName
|
||||
elapsedSeconds = [int]((Get-Date) - $startedAt).TotalSeconds
|
||||
responding = $installer.Responding
|
||||
mainWindow = $installer.MainWindowTitle
|
||||
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
||||
$lastPoll = Get-Date "2000-01-01"
|
||||
while (-not $installer.HasExited) {
|
||||
if ((Get-Date) -ge $deadline) {
|
||||
Export-InstallerDiagnostics -Reason "installer_timeout" -InstallerProcess $installer
|
||||
Stop-Process -Id $installer.Id -Force -ErrorAction SilentlyContinue
|
||||
throw "Installer timed out after $TimeoutSeconds seconds."
|
||||
}
|
||||
|
||||
if (((Get-Date) - $lastPoll).TotalSeconds -ge 10) {
|
||||
$lastPoll = Get-Date
|
||||
$installer.Refresh()
|
||||
Write-TraceEvent -Phase "installer_still_running" -Detail @{
|
||||
pid = $installer.Id
|
||||
operationName = $OperationName
|
||||
elapsedSeconds = [int]((Get-Date) - $startedAt).TotalSeconds
|
||||
responding = $installer.Responding
|
||||
mainWindow = $installer.MainWindowTitle
|
||||
}
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds 2
|
||||
$installer.Refresh()
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds 2
|
||||
$installer.Refresh()
|
||||
}
|
||||
Write-TraceEvent -Phase "installer_process_exited" -Detail @{ pid = $installer.Id; exitCode = $installer.ExitCode; operationName = $OperationName }
|
||||
Export-InstallerDiagnostics -Reason "installer_exit" -InstallerProcess $installer
|
||||
|
||||
Write-TraceEvent -Phase "installer_process_exited" -Detail @{ pid = $installer.Id; exitCode = $installer.ExitCode; operationName = $OperationName }
|
||||
Export-InstallerDiagnostics -Reason "installer_exit" -InstallerProcess $installer
|
||||
$result = [ordered]@{
|
||||
success = ($installer.ExitCode -eq 0)
|
||||
exitCode = [int]$installer.ExitCode
|
||||
installerPath = $InstallerPath
|
||||
installerArgs = $InstallerArguments
|
||||
operationName = $OperationName
|
||||
tracePath = $TracePath
|
||||
startedAt = $startedAt.ToString("o")
|
||||
finishedAt = (Get-Date).ToString("o")
|
||||
}
|
||||
$result | ConvertTo-Json | Set-Content -LiteralPath $ResultPath -Encoding UTF8
|
||||
|
||||
$result = [ordered]@{
|
||||
success = ($installer.ExitCode -eq 0)
|
||||
exitCode = [int]$installer.ExitCode
|
||||
installerPath = $InstallerPath
|
||||
installerArgs = $InstallerArguments
|
||||
operationName = $OperationName
|
||||
tracePath = $TracePath
|
||||
startedAt = $startedAt.ToString("o")
|
||||
finishedAt = (Get-Date).ToString("o")
|
||||
}
|
||||
$result | ConvertTo-Json | Set-Content -LiteralPath $ResultPath -Encoding UTF8
|
||||
|
||||
if ($installer.ExitCode -ne 0) {
|
||||
exit $installer.ExitCode
|
||||
if ($installer.ExitCode -ne 0) {
|
||||
exit $installer.ExitCode
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
@@ -321,6 +321,43 @@ try {
|
||||
timeoutSeconds = $TimeoutSeconds
|
||||
}
|
||||
|
||||
# Install VC++ Redistributable if missing
|
||||
$vcredistInstalled = $false
|
||||
$vcredistKey = "HKLM:\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64"
|
||||
if (Test-Path -Path $vcredistKey) {
|
||||
$vcredistVal = Get-ItemProperty -Path $vcredistKey -Name "Installed" -ErrorAction SilentlyContinue
|
||||
if ($vcredistVal -and $vcredistVal.Installed -eq 1) {
|
||||
$vcredistInstalled = $true
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $vcredistInstalled) {
|
||||
Write-TraceEvent -Phase "vcredist_install_start"
|
||||
try {
|
||||
$vcredistPath = Join-Path $env:TEMP "vc_redist.x64.exe"
|
||||
Remove-Item -Path $vcredistPath -Force -ErrorAction SilentlyContinue
|
||||
Write-Host "Downloading VC++ Redistributable..."
|
||||
$oldProgress = $ProgressPreference
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
Invoke-WebRequest -Uri "https://aka.ms/vs/17/release/vc_redist.x64.exe" -OutFile $vcredistPath
|
||||
$ProgressPreference = $oldProgress
|
||||
|
||||
Write-Host "Installing VC++ Redistributable..."
|
||||
$proc = Start-Process -FilePath $vcredistPath -ArgumentList "/install", "/quiet", "/norestart" -PassThru -Wait
|
||||
if ($proc.ExitCode -ne 0 -and $proc.ExitCode -ne 3010) { # 3010 is success but reboot required
|
||||
throw "vc_redist installation failed with exit code $($proc.ExitCode)"
|
||||
}
|
||||
Write-TraceEvent -Phase "vcredist_install_success" -Detail @{ exitCode = $proc.ExitCode }
|
||||
}
|
||||
catch {
|
||||
Write-TraceEvent -Phase "vcredist_install_error" -Detail @{ error = $_.Exception.Message }
|
||||
Write-Warning "Failed to install VC++ Redistributable: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-TraceEvent -Phase "vcredist_already_installed"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $InstallerPath)) {
|
||||
throw "Installer not found in guest: $InstallerPath"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user