Files
android-assistant/scripts/windows/run_vagrant_tests.ps1
T

335 lines
15 KiB
PowerShell

# run_vagrant_tests.ps1 — Host-side orchestrator for Vagrant Windows 11 test cycle.
#
# Workflow:
# 1. Build the installer locally (PyInstaller + Inno Setup)
# 2. Ensure the Vagrant VM is running and has a "clean" snapshot
# 3. Restore to the clean snapshot
# 4. Transfer build assets to the VM and compile the installer (Inno Setup)
# 5. Run the test script inside the VM via PSSession
# 6. Retrieve screenshots and results to the host
# 7. Print results summary to CLI
# 8. Revert VM back to clean snapshot
#
# Usage (from repo root):
# .\scripts\windows\run_vagrant_tests.ps1
# .\scripts\windows\run_vagrant_tests.ps1 -SkipBuild
# .\scripts\windows\run_vagrant_tests.ps1 -SkipRevert
#
# Prerequisites:
# - Vagrant and Hyper-V enabled on the host
# - Poetry environment set up
# - The VM created and "clean" snapshot saved:
# cd vagrant && vagrant up --provider=hyperv && vagrant snapshot save clean
# Note: Inno Setup 6.7.1 is installed inside the VM by provision.ps1 — no host install needed.
[CmdletBinding()]
param(
[switch]$SkipBuild,
[switch]$SkipRevert,
[string]$SnapshotName = "clean",
[string]$LocalResultsDir = ""
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path
$vmSession = $null # PSSession — closed in finally block
$VagrantDir = Join-Path $RepoRoot "vagrant"
$Timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
if (-not $LocalResultsDir) {
$LocalResultsDir = Join-Path $RepoRoot "vagrant_test_results\$Timestamp"
}
function Write-Step {
param([string]$Message)
Write-Host "`n>>> $Message" -ForegroundColor Cyan
}
# ── Ensure we're in the vagrant directory ────────────────────────────────────
Push-Location $VagrantDir
try {
# ── Step 1: Build the Windows executable (host, PyInstaller only) ────────
# Inno Setup runs inside the VM — no host install of Inno Setup required.
$pyinstallerExe = Join-Path $RepoRoot "dist\android-file-handler-windows.exe"
$AppVersion = ""
if ($SkipBuild) {
Write-Step "Skipping PyInstaller build (-SkipBuild specified)"
if (-not (Test-Path $pyinstallerExe)) {
Write-Host "ERROR: -SkipBuild was specified but exe not found at $pyinstallerExe" -ForegroundColor Red
exit 1
}
}
else {
Write-Step "Building Windows executable (PyInstaller)"
Push-Location $RepoRoot
try {
Write-Host " Running PyInstaller..." -ForegroundColor Yellow
poetry run pyinstaller scripts\spec_scripts\android-file-handler-windows.spec --noconfirm
if ($LASTEXITCODE -ne 0) {
Write-Host "ERROR: PyInstaller failed with exit code $LASTEXITCODE" -ForegroundColor Red
exit 1
}
if (-not (Test-Path $pyinstallerExe)) {
Write-Host "ERROR: PyInstaller output not found at $pyinstallerExe" -ForegroundColor Red
exit 1
}
Write-Host " PyInstaller output: $pyinstallerExe" -ForegroundColor Green
}
finally {
Pop-Location
}
}
# Capture version to pass to Inno Setup inside the VM
Push-Location $RepoRoot
try {
$AppVersion = (poetry version -s).Trim()
Write-Host " App version: $AppVersion" -ForegroundColor DarkGray
}
finally {
Pop-Location
}
# ── Step 2: Verify VM and snapshot exist ─────────────────────────────────
Write-Step "Checking Vagrant VM status"
$status = vagrant status --machine-readable 2>&1 |
Select-String "state,running" -Quiet
if (-not $status) {
Write-Host "VM is not running. Starting it..." -ForegroundColor Yellow
vagrant up --provider=hyperv
}
Write-Step "Verifying '$SnapshotName' snapshot exists"
$snapshots = vagrant snapshot list 2>&1
if ($snapshots -notmatch [regex]::Escape($SnapshotName)) {
Write-Host "Snapshot '$SnapshotName' not found. Creating it now..." -ForegroundColor Yellow
vagrant snapshot save $SnapshotName
}
# ── Step 3: Restore to clean snapshot ────────────────────────────────────
Write-Step "Restoring VM to '$SnapshotName' snapshot"
vagrant snapshot restore $SnapshotName
# ── Establish direct PSSession ────────────────────────────────────────────
# vagrant winrm uses a subprocess whose args are subject to the Windows
# command-line length limit (~32 KB). Large file transfers exceed that limit.
# A direct PSSession has no such restriction and supports
# Copy-Item -ToSession / -FromSession for binary files of any size.
Write-Step "Establishing PSSession to VM"
# Ensure host WinRM client is configured to allow Basic authentication
# This is required for the connection to succeed when using -Authentication Basic
if ((Get-Item WSMan:\localhost\Client\Auth\Basic).Value -ne "true") {
Write-Host " Enabling WinRM Client Basic authentication on host..." -ForegroundColor Yellow
Set-Item WSMan:\localhost\Client\Auth\Basic -Value $true -Force
}
$remoteBuildRoot = "C:\vagrant_build"
$remoteInstallerPath = "$remoteBuildRoot\dist\android-file-handler-setup.exe"
$remoteResultsDir = "C:\vagrant_test_results"
# vagrant winrm-config reports the plaintext port (5985) used by Vagrant itself.
# We extract the host and port, then derive the HTTPS port (typically +1).
# If the direct IP fails, we fallback to the forwarded port on localhost.
$winrmConfig = & vagrant winrm-config 2>$null | Out-String
$vmHost = if ($winrmConfig -match 'HostName\s+(\S+)') { $Matches[1] } else { '127.0.0.1' }
$vmPort = if ($winrmConfig -match 'Port\s+(\d+)') { [int]$Matches[1] + 1 } else { 5986 }
# If we have a direct IP but it's not reachable, fallback to the forwarded port
if ($vmHost -ne '127.0.0.1' -and -not (Test-NetConnection -ComputerName $vmHost -Port $vmPort -InformationLevel Quiet)) {
Write-Host " Direct IP $vmHost`:$vmPort unreachable. Falling back to localhost..." -ForegroundColor Yellow
$vmHost = '127.0.0.1'
$vmPort = 55986 # The host-side forwarded port for 5986
}
# Verify connectivity before trying PSSession
if (-not (Test-NetConnection -ComputerName $vmHost -Port $vmPort -InformationLevel Quiet)) {
Write-Host "ERROR: Could not reach VM at $vmHost`:$vmPort. Ensure 'vagrant up' and provisioning completed successfully." -ForegroundColor Red
exit 1
}
$vagrantCred = New-Object PSCredential 'vagrant',
(ConvertTo-SecureString 'vagrant' -AsPlainText -Force)
# SkipCACheck / SkipCNCheck accept the self-signed cert created by provision.ps1.
$sessionOpts = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
$vmSession = New-PSSession -ComputerName $vmHost -Port $vmPort `
-Credential $vagrantCred -Authentication Basic `
-UseSSL -SessionOption $sessionOpts
Write-Host " Connected to VM at $vmHost`:$vmPort" -ForegroundColor DarkGray
# ── Step 4: Transfer build assets to VM and compile installer ────────────
Write-Step "Transferring build assets to VM"
Invoke-Command -Session $vmSession -ScriptBlock {
param($root)
@("$root\dist", "$root\scripts\windows", "$root\icon_media") |
ForEach-Object { New-Item -ItemType Directory -Force -Path $_ | Out-Null }
} -ArgumentList $remoteBuildRoot
$transfers = @(
@{ L = Join-Path $RepoRoot "dist\android-file-handler-windows.exe"; R = "$remoteBuildRoot\dist\android-file-handler-windows.exe" },
@{ L = Join-Path $RepoRoot "scripts\windows\android-file-handler-setup.iss"; R = "$remoteBuildRoot\scripts\windows\android-file-handler-setup.iss" },
@{ L = Join-Path $RepoRoot "LICENSE.txt"; R = "$remoteBuildRoot\LICENSE.txt" },
@{ L = Join-Path $RepoRoot "icon_media\robot_files_256.ico"; R = "$remoteBuildRoot\icon_media\robot_files_256.ico" },
@{ L = Join-Path $RepoRoot "vagrant\test_installer.ps1"; R = "$remoteBuildRoot\test_installer.ps1" }
)
foreach ($t in $transfers) {
Copy-Item -Path $t.L -Destination $t.R -ToSession $vmSession -Force
$sizeMB = [math]::Round((Get-Item $t.L).Length / 1MB, 1)
Write-Host " Sent: $(Split-Path -Leaf $t.L) (${sizeMB} MB)" -ForegroundColor DarkGray
}
# Unblock transferred files — Copy-Item -ToSession can mark files with a
# Zone.Identifier ADS that execution policy may block even under -Scope Process Bypass.
Invoke-Command -Session $vmSession -ScriptBlock {
param($root)
Get-ChildItem -Path $root -Recurse -File |
ForEach-Object { Unblock-File -Path $_.FullName -ErrorAction SilentlyContinue }
} -ArgumentList $remoteBuildRoot
Write-Host " Files unblocked on VM." -ForegroundColor DarkGray
Write-Step "Building installer inside the VM (Inno Setup 6.7.1)"
Invoke-Command -Session $vmSession -ScriptBlock {
param($buildRoot, $version)
& 'C:\Program Files (x86)\Inno Setup 6\ISCC.exe' `
"$buildRoot\scripts\windows\android-file-handler-setup.iss" `
"/DMyAppVersion=$version"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
} -ArgumentList $remoteBuildRoot, $AppVersion
Write-Host " Installer built: $remoteInstallerPath" -ForegroundColor Green
# ── Step 5: Run tests inside the VM ──────────────────────────────────────
Write-Step "Running test suite inside the VM"
$testExitCode = 0
try {
Invoke-Command -Session $vmSession -ScriptBlock {
param($buildRoot, $installer)
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force
& "$buildRoot\test_installer.ps1" -InstallerPath $installer
# Propagate the test script's exit code as output so the host can inspect it.
$LASTEXITCODE
} -ArgumentList $remoteBuildRoot, $remoteInstallerPath |
ForEach-Object {
if ($_ -is [int]) { $testExitCode = $_ }
}
}
catch {
$testExitCode = 1
Write-Host "Test script encountered an error:" -ForegroundColor Yellow
Write-Host " $($_.Exception.Message)" -ForegroundColor Red
Write-Host " Category : $($_.CategoryInfo.Category)" -ForegroundColor Red
Write-Host " FullError: $_" -ForegroundColor Red
}
# Re-establish the PSSession if the test run left it in a non-Opened state.
# An unhandled exception inside Invoke-Command can terminate the remote runspace.
if ($vmSession.State -ne 'Opened') {
Write-Host " PSSession is '$($vmSession.State)' — re-establishing for results retrieval..." -ForegroundColor Yellow
Remove-PSSession $vmSession -ErrorAction SilentlyContinue
$vmSession = New-PSSession -ComputerName $vmHost -Port $vmPort `
-Credential $vagrantCred -Authentication Basic `
-UseSSL -SessionOption $sessionOpts
Write-Host " Session re-established." -ForegroundColor DarkGray
}
# ── Step 6: Retrieve results and screenshots ─────────────────────────────
Write-Step "Retrieving test results and screenshots"
if (-not (Test-Path $LocalResultsDir)) {
New-Item -ItemType Directory -Path $LocalResultsDir -Force | Out-Null
}
try {
$hasResults = Invoke-Command -Session $vmSession -ScriptBlock {
param($dir) Test-Path $dir
} -ArgumentList $remoteResultsDir
if ($hasResults) {
Copy-Item -Path "$remoteResultsDir\*" -Destination $LocalResultsDir `
-FromSession $vmSession -Recurse -Force
Write-Host " Retrieved results from $remoteResultsDir" -ForegroundColor DarkGray
}
else {
Write-Host " No results directory found on VM." -ForegroundColor Yellow
}
}
catch {
Write-Host " Warning: Could not retrieve some result files: $_" -ForegroundColor Yellow
}
# ── Step 7: Print local summary ──────────────────────────────────────────
Write-Step "Test Results"
$csvPath = Join-Path $LocalResultsDir "test_results.csv"
if (Test-Path $csvPath) {
$results = Import-Csv $csvPath
$results | Format-Table -AutoSize
$passCount = @($results | Where-Object { $_.Status -eq "PASS" }).Count
$failCount = @($results | Where-Object { $_.Status -eq "FAIL" }).Count
Write-Host " Passed: $passCount | Failed: $failCount | Total: $($passCount + $failCount)" -ForegroundColor $(if ($failCount -gt 0) { "Red" } else { "Green" })
}
else {
Write-Host " No results CSV found. Check VM output above." -ForegroundColor Yellow
}
Write-Host "`n Results directory: $LocalResultsDir" -ForegroundColor White
# List screenshots
$screenshots = Get-ChildItem $LocalResultsDir -Filter "*.png" -ErrorAction SilentlyContinue
if ($screenshots) {
Write-Host " Screenshots:" -ForegroundColor White
foreach ($shot in $screenshots) {
Write-Host " - $($shot.FullName)" -ForegroundColor DarkGray
}
}
# ── Step 8: Revert to clean snapshot ─────────────────────────────────────
if (-not $SkipRevert) {
Write-Step "Reverting VM to '$SnapshotName' snapshot"
vagrant snapshot restore $SnapshotName
Write-Host "VM reverted to clean state." -ForegroundColor Green
}
else {
Write-Host "`n -SkipRevert specified, VM left in current state." -ForegroundColor Yellow
}
# ── Exit ─────────────────────────────────────────────────────────────────
Write-Host ""
if ($testExitCode -ne 0) {
Write-Host "One or more tests FAILED." -ForegroundColor Red
exit 1
}
else {
Write-Host "All tests PASSED." -ForegroundColor Green
exit 0
}
}
finally {
if ($vmSession) { Remove-PSSession $vmSession -ErrorAction SilentlyContinue }
Pop-Location
}