From f62e02e2a2875f9d6f8f686647e179f307730e79 Mon Sep 17 00:00:00 2001 From: Clint Branham Date: Wed, 25 Mar 2026 11:53:24 -0500 Subject: [PATCH] refactor: redesign dev.ps1 with switch-based CLI and -Tests filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace positional $Command parameter with switches that can be combined: -Provision -Integration -Cleanup -DockerHost 172.16.40.113 New -Tests parameter filters integration tests by area name: -Tests Connection,VMs,Snapshots New -Version parameter (alias for old PveVersion): -Version 9 Integration without Provision checks for config.json and errors if environment is not ready. Actions execute in logical order: Stop → Rebuild → Shell → Build → Test → Provision → Integration → Cleanup. Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/dev.ps1 | 250 ++++++++++++++++++++++++++------------------------ 1 file changed, 131 insertions(+), 119 deletions(-) diff --git a/tests/dev.ps1 b/tests/dev.ps1 index d08c13a..1ce3361 100644 --- a/tests/dev.ps1 +++ b/tests/dev.ps1 @@ -7,87 +7,111 @@ Manages Docker-based dev containers for building, testing, and running integration tests for PSProxmoxVE. Works on Windows, macOS, and Linux. + Use switches to compose actions: -Provision -Integration -Cleanup can + be combined in a single invocation. + For x86-only commands (integration, provision, cleanup), use -DockerHost to run containers on a remote Docker host via SSH. The script syncs the repo to the remote host automatically. -.PARAMETER Command - The action to perform: - shell - Open pwsh in the dev container (default) - build - Build the module inside the container - test - Run unit tests (ARM + x86) - integration - Provision nested PVE VMs, run integration tests, cleanup (x86 only) - provision - Provision nested PVE VMs only, no tests (x86 only) - cleanup - Destroy provisioned VMs (x86 only) - stop - Stop all containers - rebuild - Rebuild container image(s) +.PARAMETER Shell + Open an interactive pwsh shell in the dev container. -.PARAMETER PveVersion +.PARAMETER Build + Build the module inside the container. + +.PARAMETER Test + Run unit tests (Pester, excluding Integration tag). + +.PARAMETER Provision + Provision nested PVE VMs (x86 only). Required before -Integration + unless VMs are already running. + +.PARAMETER Integration + Run integration tests against provisioned PVE VMs. + +.PARAMETER Cleanup + Destroy provisioned VMs (x86 only). + +.PARAMETER Stop + Stop all containers. + +.PARAMETER Rebuild + Rebuild container images from scratch. + +.PARAMETER Tests + Filter integration tests by area name. Comma-separated list of test + area names that match the numbered file prefixes. Examples: + -Tests Connection,Nodes # runs 00_Connection + 01_Nodes + -Tests VMs,Snapshots # runs 06_VMs + 07_Snapshots + -Tests Cluster # runs 16_Cluster + When omitted, all integration test files are run. + +.PARAMETER Version PVE version to test against (8, 9, or all). Default: all. - Only used with 'integration' and 'provision' commands. - -.PARAMETER NoCleanup - When used with 'integration', skips cleanup after tests complete. - Nested PVE VMs are left running for inspection or re-testing. - Run './tests/dev.ps1 cleanup' to destroy them later. .PARAMETER DockerHost - SSH destination for a remote Docker host (e.g. user@runner-vm). - When set, the repo is rsynced to the remote host and all Docker - commands run against the remote daemon. Useful for running x86 - containers from an ARM Mac. + SSH destination for a remote Docker host (e.g. 172.16.40.113). - Prerequisites on the remote host: - - Docker installed - - SSH user in the 'docker' group (sudo usermod -aG docker $USER) - - SSH key-based auth from the local machine +.PARAMETER NoCleanup + When used with -Integration, skips cleanup after tests complete. .EXAMPLE - ./tests/dev.ps1 + ./tests/dev.ps1 -Shell # Opens a pwsh shell in the dev container .EXAMPLE - ./tests/dev.ps1 test + ./tests/dev.ps1 -Build -Test # Builds the module and runs unit tests .EXAMPLE - ./tests/dev.ps1 integration -DockerHost user@runner-vm - # Syncs repo to runner-vm, provisions nested PVE, runs tests, cleans up + ./tests/dev.ps1 -Provision -Integration -Cleanup -DockerHost 172.16.40.113 + # Full lifecycle: provision, test, cleanup on remote host .EXAMPLE - ./tests/dev.ps1 integration 9 -DockerHost user@runner-vm - # Same but only for PVE 9 + ./tests/dev.ps1 -Integration -Tests Connection,VMs -Version 9 -DockerHost 172.16.40.113 + # Run only Connection and VMs integration tests for PVE 9 + +.EXAMPLE + ./tests/dev.ps1 -Provision -Integration -Tests Cluster,HA -Version 9 -Cleanup -DockerHost 172.16.40.113 + # Provision, run cluster+HA tests for PVE 9, cleanup #> [CmdletBinding()] param( - [Parameter(Position = 0)] - [ValidateSet('shell', 'build', 'test', 'integration', 'provision', 'cleanup', 'stop', 'rebuild')] - [string] $Command = 'shell', + [switch] $Shell, + [switch] $Build, + [switch] $Test, + [switch] $Provision, + [switch] $Integration, + [switch] $Cleanup, + [switch] $Stop, + [switch] $Rebuild, - [Parameter(Position = 1)] - [string] $PveVersion = 'all', + [string[]] $Tests, + + [Alias('PveVersion')] + [string] $Version = 'all', - [Parameter()] [string] $DockerHost, - [Parameter()] [Alias('k')] [switch] $NoCleanup ) $ErrorActionPreference = 'Stop' +# If no switches specified, default to -Shell +$anySwitchSet = $Shell -or $Build -or $Test -or $Provision -or $Integration -or $Cleanup -or $Stop -or $Rebuild +if (-not $anySwitchSet) { + $Shell = $true +} + # Resolve repo root (parent of tests/) $RepoRoot = Split-Path -Parent $PSScriptRoot Push-Location $RepoRoot try { # ── Remote Docker host support ──────────────────────────────────────── -# When -DockerHost is specified, we rsync the repo (including .env.test) -# to the remote host and set DOCKER_HOST so all docker/compose commands -# execute on the remote daemon. The compose file's volume mount (..:/repo) -# and build context reference the remote copy. - $RemoteRepoPath = $null if ($DockerHost) { @@ -95,11 +119,9 @@ if ($DockerHost) { $env:DOCKER_HOST = "ssh://$DockerHost" Write-Host "Syncing repo to ${DockerHost}:${RemoteRepoPath}..." - # Create remote directory ssh $DockerHost "mkdir -p $RemoteRepoPath" if ($LASTEXITCODE -ne 0) { throw "Failed to create remote directory" } - # Rsync repo to remote host, excluding build artifacts rsync -az --delete ` --exclude 'bin/' ` --exclude 'obj/' ` @@ -114,19 +136,12 @@ if ($DockerHost) { Write-Host "Using remote Docker host: $DockerHost" } -# When running remotely, docker compose reads the compose file locally but -# volume mounts resolve on the remote host. We generate a temporary override -# file that remaps volume mounts to the rsynced remote path. $ComposeFile = 'tests/docker-compose.test.yml' $ComposeArgs = @('-f', $ComposeFile) $OverrideFile = $null if ($RemoteRepoPath) { $OverrideFile = Join-Path ([System.IO.Path]::GetTempPath()) 'docker-compose.remote-override.yml' - - # Override volume mounts to point at the rsynced remote repo path. - # env_file is NOT overridden — compose reads it locally and injects - # the values as container env vars, which is what we want. @" services: dev: @@ -146,7 +161,6 @@ $InfraContainer = 'psproxmoxve-dev-infra' $RunIntegration = 'tests/infrastructure/scripts/run-integration.sh' function Start-DevContainer { - # 'up -d' is idempotent: starts if stopped, recreates if config/env changed, no-ops if current docker compose @ComposeArgs up -d dev if ($LASTEXITCODE -ne 0) { throw 'Failed to start dev container' } } @@ -166,78 +180,76 @@ echo 'Module installed to /usr/local/share/powershell/Modules/PSProxmoxVE' if ($LASTEXITCODE -ne 0) { throw "Module build failed (exit code $LASTEXITCODE)" } } -switch ($Command) { - 'shell' { - if ($DockerHost) { - Write-Warning "Interactive shell over remote Docker is not supported. Use: ssh $DockerHost 'docker exec -it $DevContainer pwsh -NoProfile'" - return - } - Start-DevContainer - docker exec -it $DevContainer pwsh -NoProfile - } +# Build test filter argument for run-integration.sh +$TestFilter = '' +if ($Tests) { + $TestFilter = ($Tests -join ',') +} - 'build' { - Start-DevContainer - Invoke-BuildModule $DevContainer - } +# ── Execute actions in order ────────────────────────────────────────── - 'test' { - Start-DevContainer - Invoke-BuildModule $DevContainer - docker exec $DevContainer pwsh -NoProfile -Command @' - $config = New-PesterConfiguration - $config.Run.Path = 'tests/PSProxmoxVE.Tests' - $config.Run.Exit = $true - $config.Filter.ExcludeTag = @('Integration') - $config.Output.Verbosity = 'Detailed' - Invoke-Pester -Configuration $config +if ($Stop) { + docker compose @ComposeArgs --profile infra down +} + +if ($Rebuild) { + docker compose @ComposeArgs --profile infra down + docker compose @ComposeArgs build --no-cache dev + docker compose @ComposeArgs --profile infra build --no-cache dev-infra + docker compose @ComposeArgs up -d dev +} + +if ($Shell) { + if ($DockerHost) { + Write-Warning "Interactive shell over remote Docker is not supported. Use: ssh $DockerHost 'docker exec -it $InfraContainer pwsh -NoProfile'" + return + } + Start-DevContainer + docker exec -it $DevContainer pwsh -NoProfile +} + +if ($Build) { + Start-DevContainer + Invoke-BuildModule $DevContainer +} + +if ($Test) { + Start-DevContainer + Invoke-BuildModule $DevContainer + docker exec $DevContainer pwsh -NoProfile -Command @' + $config = New-PesterConfiguration + $config.Run.Path = 'tests/PSProxmoxVE.Tests' + $config.Run.Exit = $true + $config.Filter.ExcludeTag = @('Integration') + $config.Output.Verbosity = 'Detailed' + Invoke-Pester -Configuration $config '@ - if ($LASTEXITCODE -ne 0) { throw "Unit tests failed (exit code $LASTEXITCODE)" } + if ($LASTEXITCODE -ne 0) { throw "Unit tests failed (exit code $LASTEXITCODE)" } +} + +if ($Provision) { + Start-InfraContainer + docker exec $InfraContainer bash $RunIntegration provision + if ($LASTEXITCODE -ne 0) { throw "Provisioning failed (exit code $LASTEXITCODE)" } +} + +if ($Integration) { + Start-InfraContainer + + # Verify environment is ready (config file exists from provisioning) + $configCheck = docker exec $InfraContainer bash -c 'test -f /tmp/pve-integration/config.json && echo OK || echo MISSING' + if ($configCheck.Trim() -eq 'MISSING' -and -not $Provision) { + throw "Integration environment not ready. Run with -Provision first, or use -Provision -Integration together." } - 'integration' { - # Full lifecycle: provision nested PVE VMs -> test -> cleanup (x86 only) - # Requires PVE_ENDPOINT, PVE_API_TOKEN, PVE_TARGET_NODE, PVE_PASSWORD in .env.test - # run-integration.sh handles module build if no pre-built artifact exists - Start-InfraContainer - if ($NoCleanup) { - # Provision and test separately — leave VMs running for inspection - docker exec $InfraContainer bash $RunIntegration provision - if ($LASTEXITCODE -ne 0) { throw "Provisioning failed (exit code $LASTEXITCODE)" } - docker exec $InfraContainer bash $RunIntegration test $PveVersion - if ($LASTEXITCODE -ne 0) { throw "Integration tests failed (exit code $LASTEXITCODE)" } - Write-Host 'VMs left running (-NoCleanup). Run ./tests/dev.ps1 cleanup to destroy them.' - } else { - docker exec $InfraContainer bash $RunIntegration all $PveVersion - if ($LASTEXITCODE -ne 0) { throw "Integration tests failed (exit code $LASTEXITCODE)" } - } - } + docker exec $InfraContainer bash $RunIntegration test $Version $TestFilter + if ($LASTEXITCODE -ne 0) { throw "Integration tests failed (exit code $LASTEXITCODE)" } +} - 'provision' { - # Provision nested PVE VMs only, without running tests (x86 only) - # Useful for iterating: provision once, then shell in and run tests manually - Start-InfraContainer - docker exec $InfraContainer bash $RunIntegration provision - if ($LASTEXITCODE -ne 0) { throw "Provisioning failed (exit code $LASTEXITCODE)" } - } - - 'cleanup' { - # Destroy provisioned VMs - Start-InfraContainer - docker exec $InfraContainer bash $RunIntegration cleanup - if ($LASTEXITCODE -ne 0) { throw "Cleanup failed (exit code $LASTEXITCODE)" } - } - - 'stop' { - docker compose @ComposeArgs --profile infra down - } - - 'rebuild' { - docker compose @ComposeArgs --profile infra down - docker compose @ComposeArgs build --no-cache dev - docker compose @ComposeArgs --profile infra build --no-cache dev-infra - docker compose @ComposeArgs up -d dev - } +if ($Cleanup) { + Start-InfraContainer + docker exec $InfraContainer bash $RunIntegration cleanup + if ($LASTEXITCODE -ne 0) { throw "Cleanup failed (exit code $LASTEXITCODE)" } } } finally {