mirror of
https://github.com/GoodOlClint/PSProxmoxVE.git
synced 2026-07-26 07:58:14 +00:00
feat(ci): unify CI and local integration test infrastructure
- Replace tests/infrastructure/Dockerfile with tests/Dockerfile.test (single multi-stage Dockerfile for both CI and local dev) - CI container-image job now builds from Dockerfile.test target dev-infra - Add ARM support: PowerShell installed via dotnet tool on arm64, APT package on amd64 - Replace tests/dev.sh (bash) with tests/dev.ps1 (PowerShell) for cross-platform support (Windows, macOS, Linux) - Add -DockerHost parameter for running x86 containers on a remote Docker host from ARM Macs (rsyncs repo, uses SSH Docker transport) - Add -NoCleanup switch to keep nested PVE VMs after integration tests - integration command now provisions nested PVE VMs instead of testing against a pre-existing PVE directly - Share /opt/pve-isos host path between CI and local dev (was separate Docker named volume) - Delete tools/Invoke-Tests.ps1 (unused, overlapped with run-integration.sh) - Add .gitignore entries for Terraform state/artifacts - Update all documentation references Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -96,8 +96,9 @@ jobs:
|
|||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v7
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: tests/infrastructure
|
context: .
|
||||||
file: tests/infrastructure/Dockerfile
|
file: tests/Dockerfile.test
|
||||||
|
target: dev-infra
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.TEST_IMAGE }}:${{ github.sha }},${{ env.TEST_IMAGE }}:latest
|
tags: ${{ env.TEST_IMAGE }}:${{ github.sha }},${{ env.TEST_IMAGE }}:latest
|
||||||
|
|
||||||
|
|||||||
@@ -15,3 +15,7 @@ BenchmarkDotNet.Artifacts/
|
|||||||
.env.*
|
.env.*
|
||||||
!.env.*.example
|
!.env.*.example
|
||||||
tests/.env.test
|
tests/.env.test
|
||||||
|
tests/infrastructure/.terraform/
|
||||||
|
tests/infrastructure/.terraform.lock.hcl
|
||||||
|
tests/infrastructure/terraform.tfstate*
|
||||||
|
tests/infrastructure/*.tfvars.json
|
||||||
|
|||||||
@@ -32,15 +32,15 @@ gh pr create
|
|||||||
A Docker-based dev environment replicates the full CI setup locally. Works on ARM Macs
|
A Docker-based dev environment replicates the full CI setup locally. Works on ARM Macs
|
||||||
(build + test) and x86 (full provisioning flow).
|
(build + test) and x86 (full provisioning flow).
|
||||||
|
|
||||||
```bash
|
```powershell
|
||||||
./tests/dev.sh # Open pwsh shell in dev container
|
./tests/dev.ps1 # Open pwsh shell in dev container
|
||||||
./tests/dev.sh build # Build the module
|
./tests/dev.ps1 build # Build the module
|
||||||
./tests/dev.sh test # Run unit tests
|
./tests/dev.ps1 test # Run unit tests (ARM + x86)
|
||||||
./tests/dev.sh integration # Run integration tests against existing PVE
|
./tests/dev.ps1 integration # Provision nested PVE, run tests, cleanup (x86 only)
|
||||||
./tests/dev.sh provision # Full CI flow: provision → test → cleanup (x86 only)
|
./tests/dev.ps1 provision # Provision nested PVE only, no tests (x86 only)
|
||||||
```
|
```
|
||||||
|
|
||||||
Configure integration test targets by copying `tests/.env.test.example` to `tests/.env.test`.
|
Configure parent PVE credentials by copying `tests/.env.test.example` to `tests/.env.test`.
|
||||||
|
|
||||||
### Build & test without container
|
### Build & test without container
|
||||||
|
|
||||||
@@ -54,8 +54,8 @@ dotnet test tests/PSProxmoxVE.Core.Tests/
|
|||||||
# Pester tests (requires pwsh)
|
# Pester tests (requires pwsh)
|
||||||
pwsh -Command "Invoke-Pester tests/PSProxmoxVE.Tests/ -Output Detailed"
|
pwsh -Command "Invoke-Pester tests/PSProxmoxVE.Tests/ -Output Detailed"
|
||||||
|
|
||||||
# Run all tests via helper
|
# Run all tests via dev container
|
||||||
pwsh -File tools/Invoke-Tests.ps1
|
./tests/dev.ps1 test
|
||||||
```
|
```
|
||||||
|
|
||||||
## Key Conventions
|
## Key Conventions
|
||||||
|
|||||||
@@ -483,8 +483,8 @@ SDN management requires Proxmox VE 8.0 or later. Connected server is version 7.4
|
|||||||
1. Clone the repository
|
1. Clone the repository
|
||||||
2. Open `PSProxmoxVE.sln` in your IDE
|
2. Open `PSProxmoxVE.sln` in your IDE
|
||||||
3. Build: `dotnet build`
|
3. Build: `dotnet build`
|
||||||
4. Run unit tests: `./tools/Invoke-Tests.ps1`
|
4. Run unit tests: `./tests/dev.ps1 test`
|
||||||
5. Run integration tests (requires live PVE node): `./tools/Invoke-Tests.ps1 -Tier Integration`
|
5. Run integration tests (provisions nested PVE, x86 only): `./tests/dev.ps1 integration`
|
||||||
|
|
||||||
### Commit Convention
|
### Commit Convention
|
||||||
|
|
||||||
|
|||||||
+10
-14
@@ -1,21 +1,17 @@
|
|||||||
# PSProxmoxVE integration test configuration.
|
# PSProxmoxVE integration test configuration.
|
||||||
# Copy to .env.test and fill in values. This file is gitignored.
|
# Copy to .env.test and fill in values. This file is gitignored.
|
||||||
|
#
|
||||||
|
# These credentials point to the PARENT PVE host where nested test VMs
|
||||||
|
# will be provisioned. Integration tests run against the nested VMs,
|
||||||
|
# not against this host directly.
|
||||||
|
|
||||||
# ── For testing against a pre-existing PVE (./tests/dev.sh integration) ──
|
# ── Required: parent PVE for provisioning ─────────────────────────────
|
||||||
PVETEST_HOST=pve.example.com
|
PVE_ENDPOINT=https://pve.example.com:8006
|
||||||
PVETEST_PORT=8006
|
PVE_API_TOKEN=user@realm!tokenid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
PVETEST_APITOKEN=user@realm!tokenid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
PVE_TARGET_NODE=pve1
|
||||||
PVETEST_NODE=pve1
|
PVE_PASSWORD=<root-password-for-nested-pve>
|
||||||
PVETEST_STORAGE=local
|
|
||||||
PVETEST_PASSWORD=<your-test-password>
|
|
||||||
|
|
||||||
# ── For full provisioning (./tests/dev.sh provision, x86 only) ───────────
|
# ── Optional overrides ────────────────────────────────────────────────
|
||||||
# PVE_ENDPOINT=https://pve.example.com:8006
|
|
||||||
# PVE_API_TOKEN=user@realm!tokenid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
||||||
# PVE_TARGET_NODE=pve1
|
|
||||||
# PVE_PASSWORD=<root-password-for-nested-pve>
|
|
||||||
|
|
||||||
# ── Optional overrides ──────────────────────────────────────────────────
|
|
||||||
# CACHE_DIR=/opt/pve-isos
|
# CACHE_DIR=/opt/pve-isos
|
||||||
# WORK_DIR=/tmp/pve-integration
|
# WORK_DIR=/tmp/pve-integration
|
||||||
# PVE_VERSIONS=9 8
|
# PVE_VERSIONS=9 8
|
||||||
|
|||||||
+16
-4
@@ -16,16 +16,28 @@ FROM mcr.microsoft.com/dotnet/sdk:10.0-noble AS dev
|
|||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
# Install base packages
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
curl jq openssh-client ca-certificates apt-transport-https gnupg \
|
curl jq openssh-client ca-certificates apt-transport-https gnupg \
|
||||||
&& curl -fsSL https://packages.microsoft.com/keys/microsoft.asc \
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install PowerShell: APT package on amd64, dotnet global tool on arm64.
|
||||||
|
# The Microsoft APT repo does not publish arm64 packages for PowerShell.
|
||||||
|
RUN if [ "$(dpkg --print-architecture)" = "amd64" ]; then \
|
||||||
|
curl -fsSL https://packages.microsoft.com/keys/microsoft.asc \
|
||||||
| gpg --dearmor -o /usr/share/keyrings/microsoft-archive-keyring.gpg \
|
| gpg --dearmor -o /usr/share/keyrings/microsoft-archive-keyring.gpg \
|
||||||
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] \
|
&& echo "deb [arch=amd64 signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] \
|
||||||
https://packages.microsoft.com/ubuntu/24.04/prod noble main" \
|
https://packages.microsoft.com/ubuntu/24.04/prod noble main" \
|
||||||
> /etc/apt/sources.list.d/microsoft-prod.list \
|
> /etc/apt/sources.list.d/microsoft-prod.list \
|
||||||
&& apt-get update && apt-get install -y --no-install-recommends powershell \
|
&& apt-get update && apt-get install -y --no-install-recommends powershell \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/*; \
|
||||||
&& pwsh -NoProfile -Command \
|
else \
|
||||||
|
dotnet tool install --global PowerShell \
|
||||||
|
&& ln -s /root/.dotnet/tools/pwsh /usr/local/bin/pwsh; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install Pester and prepare module directory
|
||||||
|
RUN pwsh -NoProfile -Command \
|
||||||
'Set-PSRepository -Name PSGallery -InstallationPolicy Trusted; \
|
'Set-PSRepository -Name PSGallery -InstallationPolicy Trusted; \
|
||||||
Install-Module -Name Pester -MinimumVersion 5.0 -Scope AllUsers -Force' \
|
Install-Module -Name Pester -MinimumVersion 5.0 -Scope AllUsers -Force' \
|
||||||
&& mkdir -p /usr/local/share/powershell/Modules/PSProxmoxVE
|
&& mkdir -p /usr/local/share/powershell/Modules/PSProxmoxVE
|
||||||
|
|||||||
@@ -24,8 +24,8 @@
|
|||||||
|
|
||||||
To run:
|
To run:
|
||||||
Invoke-Pester -Path ./tests/PSProxmoxVE.Tests -Tag Integration
|
Invoke-Pester -Path ./tests/PSProxmoxVE.Tests -Tag Integration
|
||||||
# or use the project's helper script:
|
# or use the dev container:
|
||||||
./Invoke-Tests.ps1 -Tier Integration
|
./tests/dev.ps1 integration
|
||||||
#>
|
#>
|
||||||
|
|
||||||
BeforeAll {
|
BeforeAll {
|
||||||
|
|||||||
@@ -119,10 +119,10 @@ env:
|
|||||||
The integration suite is tagged `Integration`. Use the `-Tag` filter so that the unit
|
The integration suite is tagged `Integration`. Use the `-Tag` filter so that the unit
|
||||||
tests and integration tests can be run independently.
|
tests and integration tests can be run independently.
|
||||||
|
|
||||||
### Via the project helper script (recommended)
|
### Via the dev container (recommended)
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
./Invoke-Tests.ps1 -Tier Integration
|
./tests/dev.ps1 integration
|
||||||
```
|
```
|
||||||
|
|
||||||
### Directly with Invoke-Pester
|
### Directly with Invoke-Pester
|
||||||
|
|||||||
+251
@@ -0,0 +1,251 @@
|
|||||||
|
#Requires -Version 5.1
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Helper script for the dev/test containers.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Manages Docker-based dev containers for building, testing, and running
|
||||||
|
integration tests for PSProxmoxVE. Works on Windows, macOS, and Linux.
|
||||||
|
|
||||||
|
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 PveVersion
|
||||||
|
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.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
./tests/dev.ps1
|
||||||
|
# Opens a pwsh shell in the dev container
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
./tests/dev.ps1 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
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
./tests/dev.ps1 integration 9 -DockerHost user@runner-vm
|
||||||
|
# Same but only for PVE 9
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Position = 0)]
|
||||||
|
[ValidateSet('shell', 'build', 'test', 'integration', 'provision', 'cleanup', 'stop', 'rebuild')]
|
||||||
|
[string] $Command = 'shell',
|
||||||
|
|
||||||
|
[Parameter(Position = 1)]
|
||||||
|
[string] $PveVersion = 'all',
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[string] $DockerHost,
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[Alias('k')]
|
||||||
|
[switch] $NoCleanup
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
# 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) {
|
||||||
|
$RemoteRepoPath = "/tmp/psproxmoxve-dev"
|
||||||
|
$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/' `
|
||||||
|
--exclude 'publish/' `
|
||||||
|
--exclude 'TestResults/' `
|
||||||
|
--exclude '.terraform/' `
|
||||||
|
--exclude 'terraform.tfstate*' `
|
||||||
|
--include '.env.test' `
|
||||||
|
./ "${DockerHost}:${RemoteRepoPath}/"
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Failed to sync repo to remote host" }
|
||||||
|
|
||||||
|
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:
|
||||||
|
volumes:
|
||||||
|
- ${RemoteRepoPath}:/repo
|
||||||
|
dev-infra:
|
||||||
|
volumes:
|
||||||
|
- ${RemoteRepoPath}:/repo
|
||||||
|
- /opt/pve-isos:/opt/pve-isos
|
||||||
|
"@ | Set-Content -Path $OverrideFile -Encoding utf8
|
||||||
|
|
||||||
|
$ComposeArgs = @('-f', $ComposeFile, '-f', $OverrideFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
$DevContainer = 'psproxmoxve-dev'
|
||||||
|
$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' }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Start-InfraContainer {
|
||||||
|
docker compose @ComposeArgs --profile infra up -d dev-infra
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw 'Failed to start infra container (x86 only)' }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-BuildModule {
|
||||||
|
param([string] $Container)
|
||||||
|
docker exec $Container bash -c @"
|
||||||
|
dotnet publish src/PSProxmoxVE/PSProxmoxVE.csproj -c Release -f netstandard2.0 -o /tmp/publish 2>&1 | tail -1 && \
|
||||||
|
cp -r /tmp/publish/* /usr/local/share/powershell/Modules/PSProxmoxVE/ && \
|
||||||
|
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' {
|
||||||
|
Start-DevContainer
|
||||||
|
Invoke-BuildModule $DevContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
'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)" }
|
||||||
|
}
|
||||||
|
|
||||||
|
'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)" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
'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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if ($OverrideFile -and (Test-Path $OverrideFile)) {
|
||||||
|
Remove-Item $OverrideFile -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
if ($DockerHost) {
|
||||||
|
Remove-Item Env:\DOCKER_HOST -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
Pop-Location
|
||||||
|
}
|
||||||
-117
@@ -1,117 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Helper script for the dev/test containers.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# ./tests/dev.sh # Start dev container and open pwsh shell
|
|
||||||
# ./tests/dev.sh build # Build the module inside the container
|
|
||||||
# ./tests/dev.sh test # Run unit tests
|
|
||||||
# ./tests/dev.sh integration # Run integration tests against existing PVE (needs .env.test)
|
|
||||||
# ./tests/dev.sh provision # Full CI flow: provision → test → cleanup (x86 only)
|
|
||||||
# ./tests/dev.sh cleanup # Destroy provisioned VMs (x86 only)
|
|
||||||
# ./tests/dev.sh stop # Stop all containers
|
|
||||||
# ./tests/dev.sh rebuild # Rebuild container image(s)
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
|
|
||||||
COMPOSE="docker compose -f tests/docker-compose.test.yml"
|
|
||||||
DEV_CONTAINER="psproxmoxve-dev"
|
|
||||||
INFRA_CONTAINER="psproxmoxve-dev-infra"
|
|
||||||
MODULE_PATH="/usr/local/share/powershell/Modules/PSProxmoxVE"
|
|
||||||
RUN_INTEGRATION="tests/infrastructure/scripts/run-integration.sh"
|
|
||||||
|
|
||||||
ensure_dev() {
|
|
||||||
if ! docker inspect "$DEV_CONTAINER" --format '{{.State.Running}}' 2>/dev/null | grep -q true; then
|
|
||||||
echo "Starting dev container..."
|
|
||||||
$COMPOSE up -d dev
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_infra() {
|
|
||||||
if ! docker inspect "$INFRA_CONTAINER" --format '{{.State.Running}}' 2>/dev/null | grep -q true; then
|
|
||||||
echo "Starting infra container (x86 only)..."
|
|
||||||
$COMPOSE --profile infra up -d dev-infra
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
build_module() {
|
|
||||||
local container="$1"
|
|
||||||
docker exec "$container" bash -c "
|
|
||||||
dotnet publish src/PSProxmoxVE/PSProxmoxVE.csproj \
|
|
||||||
-c Release -f netstandard2.0 -o /tmp/publish 2>&1 | tail -1 && \
|
|
||||||
cp -r /tmp/publish/* $MODULE_PATH/ && \
|
|
||||||
echo "Module installed to $MODULE_PATH"
|
|
||||||
"
|
|
||||||
}
|
|
||||||
|
|
||||||
case "${1:-shell}" in
|
|
||||||
shell)
|
|
||||||
ensure_dev
|
|
||||||
docker exec -it "$DEV_CONTAINER" pwsh -NoProfile
|
|
||||||
;;
|
|
||||||
|
|
||||||
build)
|
|
||||||
ensure_dev
|
|
||||||
build_module "$DEV_CONTAINER"
|
|
||||||
;;
|
|
||||||
|
|
||||||
test)
|
|
||||||
ensure_dev
|
|
||||||
build_module "$DEV_CONTAINER"
|
|
||||||
docker exec "$DEV_CONTAINER" 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
|
|
||||||
"
|
|
||||||
;;
|
|
||||||
|
|
||||||
integration)
|
|
||||||
# Run integration tests against a pre-existing PVE (set via .env.test)
|
|
||||||
ensure_dev
|
|
||||||
build_module "$DEV_CONTAINER"
|
|
||||||
docker exec "$DEV_CONTAINER" bash -c "
|
|
||||||
SKIP_PROVISION=true bash $RUN_INTEGRATION test ${2:-all}
|
|
||||||
"
|
|
||||||
;;
|
|
||||||
|
|
||||||
provision)
|
|
||||||
# Full CI lifecycle: provision → test → cleanup (x86 infra container)
|
|
||||||
ensure_infra
|
|
||||||
build_module "$INFRA_CONTAINER"
|
|
||||||
docker exec "$INFRA_CONTAINER" bash "$RUN_INTEGRATION" all "${2:-all}"
|
|
||||||
;;
|
|
||||||
|
|
||||||
cleanup)
|
|
||||||
# Destroy provisioned VMs
|
|
||||||
ensure_infra
|
|
||||||
docker exec "$INFRA_CONTAINER" bash "$RUN_INTEGRATION" cleanup
|
|
||||||
;;
|
|
||||||
|
|
||||||
stop)
|
|
||||||
$COMPOSE --profile infra down
|
|
||||||
;;
|
|
||||||
|
|
||||||
rebuild)
|
|
||||||
$COMPOSE --profile infra down
|
|
||||||
$COMPOSE build --no-cache dev
|
|
||||||
$COMPOSE --profile infra build --no-cache dev-infra
|
|
||||||
$COMPOSE up -d dev
|
|
||||||
;;
|
|
||||||
|
|
||||||
*)
|
|
||||||
echo "Usage: $0 {shell|build|test|integration|provision|cleanup|stop|rebuild}"
|
|
||||||
echo ""
|
|
||||||
echo " shell Open pwsh in the dev container"
|
|
||||||
echo " build Build the module"
|
|
||||||
echo " test Run unit tests"
|
|
||||||
echo " integration Run integration tests against existing PVE (.env.test)"
|
|
||||||
echo " provision Full CI flow: provision → test → cleanup (x86 only)"
|
|
||||||
echo " cleanup Destroy provisioned VMs (x86 only)"
|
|
||||||
echo " stop Stop all containers"
|
|
||||||
echo " rebuild Rebuild container images"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
@@ -37,7 +37,7 @@ services:
|
|||||||
profiles: [infra]
|
profiles: [infra]
|
||||||
volumes:
|
volumes:
|
||||||
- ..:/repo
|
- ..:/repo
|
||||||
- pve-isos:/opt/pve-isos
|
- /opt/pve-isos:/opt/pve-isos
|
||||||
working_dir: /repo
|
working_dir: /repo
|
||||||
stdin_open: true
|
stdin_open: true
|
||||||
tty: true
|
tty: true
|
||||||
@@ -45,6 +45,3 @@ services:
|
|||||||
env_file:
|
env_file:
|
||||||
- path: .env.test
|
- path: .env.test
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
volumes:
|
|
||||||
pve-isos:
|
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
FROM ubuntu:24.04
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
|
||||||
|
|
||||||
# ── Base packages ──────────────────────────────────────────────────────
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
curl \
|
|
||||||
jq \
|
|
||||||
dosfstools \
|
|
||||||
mtools \
|
|
||||||
openssh-client \
|
|
||||||
sshpass \
|
|
||||||
apt-transport-https \
|
|
||||||
gnupg \
|
|
||||||
lsb-release \
|
|
||||||
ca-certificates \
|
|
||||||
python3 \
|
|
||||||
xorriso \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# ── Microsoft repo (PowerShell) ───────────────────────────────────────
|
|
||||||
RUN curl -fsSL https://packages.microsoft.com/keys/microsoft.asc \
|
|
||||||
| gpg --dearmor -o /usr/share/keyrings/microsoft-archive-keyring.gpg && \
|
|
||||||
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] \
|
|
||||||
https://packages.microsoft.com/ubuntu/24.04/prod noble main" \
|
|
||||||
> /etc/apt/sources.list.d/microsoft-prod.list
|
|
||||||
|
|
||||||
# ── HashiCorp repo (Terraform) ────────────────────────────────────────
|
|
||||||
RUN curl -fsSL https://apt.releases.hashicorp.com/gpg \
|
|
||||||
| gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg && \
|
|
||||||
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
|
|
||||||
https://apt.releases.hashicorp.com noble main" \
|
|
||||||
> /etc/apt/sources.list.d/hashicorp.list
|
|
||||||
|
|
||||||
# ── Proxmox repo (proxmox-auto-install-assistant) ─────────────────────
|
|
||||||
RUN curl -fsSL https://enterprise.proxmox.com/debian/proxmox-release-bookworm.gpg \
|
|
||||||
-o /usr/share/keyrings/proxmox-release-bookworm.gpg && \
|
|
||||||
echo "deb [signed-by=/usr/share/keyrings/proxmox-release-bookworm.gpg] \
|
|
||||||
http://download.proxmox.com/debian/pve bookworm pve-no-subscription" \
|
|
||||||
> /etc/apt/sources.list.d/proxmox-pve.list
|
|
||||||
|
|
||||||
# ── Install tooling ───────────────────────────────────────────────────
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
powershell \
|
|
||||||
terraform \
|
|
||||||
proxmox-auto-install-assistant \
|
|
||||||
qemu-utils \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# ── Install Pester ────────────────────────────────────────────────────
|
|
||||||
RUN pwsh -NoProfile -Command \
|
|
||||||
'Set-PSRepository -Name PSGallery -InstallationPolicy Trusted; \
|
|
||||||
Install-Module -Name Pester -MinimumVersion 5.0 -Scope AllUsers -Force'
|
|
||||||
|
|
||||||
# ── Verify all tools are present ──────────────────────────────────────
|
|
||||||
RUN echo "=== Tool verification ===" && \
|
|
||||||
pwsh --version && \
|
|
||||||
terraform --version && \
|
|
||||||
proxmox-auto-install-assistant --version && \
|
|
||||||
sshpass -V | head -1 && \
|
|
||||||
xorriso --version 2>&1 | head -1 && \
|
|
||||||
qemu-img --version | head -1 && \
|
|
||||||
curl --version | head -1 && \
|
|
||||||
jq --version && \
|
|
||||||
python3 --version && \
|
|
||||||
ssh -V 2>&1 && \
|
|
||||||
echo "=== All tools verified ==="
|
|
||||||
@@ -1,390 +0,0 @@
|
|||||||
#Requires -Version 5.1
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Test runner for the PSProxmoxVE project.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Runs unit and/or integration tests for PSProxmoxVE. Unit tests include both
|
|
||||||
xUnit (.NET) and Pester (PowerShell) suites. Integration tests require a live
|
|
||||||
or mocked PVE endpoint and are executed via Pester with the Integration tag.
|
|
||||||
|
|
||||||
.PARAMETER Tier
|
|
||||||
Which tier of tests to run. Valid values: Unit, Integration, All. Default: Unit.
|
|
||||||
|
|
||||||
.PARAMETER Framework
|
|
||||||
Dotnet target framework moniker to pass to 'dotnet test' (e.g. net8.0, net9.0).
|
|
||||||
When omitted the framework is auto-detected from the first *.csproj found under
|
|
||||||
tests/PSProxmoxVE.Core.Tests.
|
|
||||||
|
|
||||||
.PARAMETER FromTerraform
|
|
||||||
Read PVE connection details from 'terraform output -json' in
|
|
||||||
tests/infrastructure/. Requires Terraform to be installed and
|
|
||||||
'terraform apply' to have been run. Implies -Tier Integration.
|
|
||||||
|
|
||||||
.PARAMETER PveHost
|
|
||||||
Hostname or IP of the PVE test node. Sets PVETEST_HOST.
|
|
||||||
|
|
||||||
.PARAMETER PvePort
|
|
||||||
API port. Default 8006. Sets PVETEST_PORT.
|
|
||||||
|
|
||||||
.PARAMETER PveApiToken
|
|
||||||
API token in USER@REALM!TOKENID=UUID format. Sets PVETEST_APITOKEN.
|
|
||||||
|
|
||||||
.PARAMETER PveNode
|
|
||||||
PVE node name (e.g. pve). Sets PVETEST_NODE.
|
|
||||||
|
|
||||||
.PARAMETER PveStorage
|
|
||||||
Storage pool for disk/ISO operations (e.g. local). Sets PVETEST_STORAGE.
|
|
||||||
|
|
||||||
.PARAMETER PveIsoPath
|
|
||||||
Local filesystem path to a small .iso for upload tests. Sets PVETEST_ISO_PATH.
|
|
||||||
|
|
||||||
.EXAMPLE
|
|
||||||
./tools/Invoke-Tests.ps1
|
|
||||||
|
|
||||||
.EXAMPLE
|
|
||||||
./tools/Invoke-Tests.ps1 -Tier All
|
|
||||||
|
|
||||||
.EXAMPLE
|
|
||||||
./tools/Invoke-Tests.ps1 -FromTerraform
|
|
||||||
|
|
||||||
.EXAMPLE
|
|
||||||
./tools/Invoke-Tests.ps1 -Tier Integration `
|
|
||||||
-PveHost 192.168.1.200 -PveApiToken "root@pam!integration=abc123..." `
|
|
||||||
-PveNode pve -PveStorage local -PveIsoPath /tmp/test.iso
|
|
||||||
|
|
||||||
.EXAMPLE
|
|
||||||
./tools/Invoke-Tests.ps1 -Tier Unit -Framework net9.0
|
|
||||||
#>
|
|
||||||
[CmdletBinding(DefaultParameterSetName = 'Explicit')]
|
|
||||||
param(
|
|
||||||
[Parameter()]
|
|
||||||
[ValidateSet('Unit', 'Integration', 'All')]
|
|
||||||
[string] $Tier = 'Unit',
|
|
||||||
|
|
||||||
[Parameter()]
|
|
||||||
[string] $Framework,
|
|
||||||
|
|
||||||
# --- Terraform-sourced connection ---
|
|
||||||
[Parameter(ParameterSetName = 'Terraform')]
|
|
||||||
[switch] $FromTerraform,
|
|
||||||
|
|
||||||
# --- Explicit connection params ---
|
|
||||||
[Parameter(ParameterSetName = 'Explicit')]
|
|
||||||
[string] $PveHost,
|
|
||||||
|
|
||||||
[Parameter(ParameterSetName = 'Explicit')]
|
|
||||||
[int] $PvePort = 8006,
|
|
||||||
|
|
||||||
[Parameter(ParameterSetName = 'Explicit')]
|
|
||||||
[string] $PveApiToken,
|
|
||||||
|
|
||||||
[Parameter(ParameterSetName = 'Explicit')]
|
|
||||||
[string] $PveNode,
|
|
||||||
|
|
||||||
[Parameter(ParameterSetName = 'Explicit')]
|
|
||||||
[string] $PveStorage,
|
|
||||||
|
|
||||||
[Parameter(ParameterSetName = 'Explicit')]
|
|
||||||
[string] $PveIsoPath
|
|
||||||
)
|
|
||||||
|
|
||||||
Set-StrictMode -Version Latest
|
|
||||||
$ErrorActionPreference = 'Stop'
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Integration connection setup
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
if ($FromTerraform) {
|
|
||||||
$Tier = 'Integration'
|
|
||||||
$infraDir = Join-Path $PSScriptRoot '../tests/infrastructure'
|
|
||||||
|
|
||||||
if (-not (Get-Command terraform -ErrorAction SilentlyContinue)) {
|
|
||||||
throw 'terraform not found on PATH. Install Terraform >= 1.5 first.'
|
|
||||||
}
|
|
||||||
if (-not (Test-Path $infraDir)) {
|
|
||||||
throw "Infrastructure directory not found: $infraDir"
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host 'Reading connection details from terraform output...' -ForegroundColor Cyan
|
|
||||||
$tfOutputJson = terraform -chdir:$infraDir output -json 2>&1
|
|
||||||
if ($LASTEXITCODE -ne 0) {
|
|
||||||
throw "terraform output failed. Have you run 'terraform apply' in $infraDir?`n$tfOutputJson"
|
|
||||||
}
|
|
||||||
|
|
||||||
$tfOutput = $tfOutputJson | ConvertFrom-Json
|
|
||||||
$env:PVETEST_HOST = $tfOutput.pve_test_host.value
|
|
||||||
$env:PVETEST_PORT = $tfOutput.pve_test_port.value
|
|
||||||
$env:PVETEST_NODE = $tfOutput.pve_test_node_name.value
|
|
||||||
$env:PVETEST_APITOKEN = terraform -chdir:$infraDir output -raw pve_test_api_token 2>&1
|
|
||||||
|
|
||||||
# Storage and ISO path are not provisioned by Terraform; require env vars or defaults
|
|
||||||
if (-not $env:PVETEST_STORAGE) { $env:PVETEST_STORAGE = 'local' }
|
|
||||||
if (-not $env:PVETEST_ISO_PATH) {
|
|
||||||
Write-Warning 'PVETEST_ISO_PATH not set — ISO upload tests will be skipped.'
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host " PVE host : $($env:PVETEST_HOST):$($env:PVETEST_PORT)" -ForegroundColor DarkGray
|
|
||||||
Write-Host " PVE node : $($env:PVETEST_NODE)" -ForegroundColor DarkGray
|
|
||||||
Write-Host " Storage : $($env:PVETEST_STORAGE)" -ForegroundColor DarkGray
|
|
||||||
}
|
|
||||||
elseif ($PSBoundParameters.ContainsKey('PveHost') -or $PSBoundParameters.ContainsKey('PveApiToken')) {
|
|
||||||
# Explicit params override env vars
|
|
||||||
if ($PveHost) { $env:PVETEST_HOST = $PveHost }
|
|
||||||
if ($PvePort) { $env:PVETEST_PORT = $PvePort }
|
|
||||||
if ($PveApiToken) { $env:PVETEST_APITOKEN = $PveApiToken }
|
|
||||||
if ($PveNode) { $env:PVETEST_NODE = $PveNode }
|
|
||||||
if ($PveStorage) { $env:PVETEST_STORAGE = $PveStorage }
|
|
||||||
if ($PveIsoPath) { $env:PVETEST_ISO_PATH = $PveIsoPath }
|
|
||||||
|
|
||||||
if ($Tier -eq 'Unit') { $Tier = 'Integration' }
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Helpers
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function Write-Header {
|
|
||||||
param([string] $Text)
|
|
||||||
$line = '-' * 70
|
|
||||||
Write-Host ''
|
|
||||||
Write-Host $line -ForegroundColor Cyan
|
|
||||||
Write-Host " $Text" -ForegroundColor Cyan
|
|
||||||
Write-Host $line -ForegroundColor Cyan
|
|
||||||
}
|
|
||||||
|
|
||||||
function Assert-Command {
|
|
||||||
param([string] $Name)
|
|
||||||
if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
|
|
||||||
throw "Required command '$Name' was not found on PATH."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Path resolution
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
|
||||||
|
|
||||||
$coreTestProject = Join-Path $repoRoot 'tests' 'PSProxmoxVE.Core.Tests'
|
|
||||||
$pesterTestDir = Join-Path $repoRoot 'tests' 'PSProxmoxVE.Tests'
|
|
||||||
$integrationDir = Join-Path $pesterTestDir 'Integration'
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Auto-detect framework when not supplied
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
if (-not $Framework) {
|
|
||||||
$csproj = Get-ChildItem -Path $coreTestProject -Filter '*.csproj' -ErrorAction SilentlyContinue |
|
|
||||||
Select-Object -First 1
|
|
||||||
|
|
||||||
if ($csproj) {
|
|
||||||
# Try singular TargetFramework first, then first entry from TargetFrameworks
|
|
||||||
$tfmNode = Select-Xml -Path $csproj.FullName -XPath '//*[local-name()="TargetFramework" or local-name()="TargetFrameworks"]' |
|
|
||||||
Select-Object -First 1
|
|
||||||
|
|
||||||
if ($tfmNode) {
|
|
||||||
# TargetFrameworks may be semicolon-separated; pick the first .NET Core/5+ TFM
|
|
||||||
# On non-Windows, skip net48 since .NET Framework is not available
|
|
||||||
$allTfms = $tfmNode.Node.InnerText.Trim() -split ';' | ForEach-Object { $_.Trim() }
|
|
||||||
$onWindows = $PSVersionTable.Platform -eq 'Win32NT' -or $PSVersionTable.PSEdition -eq 'Desktop'
|
|
||||||
$Framework = $allTfms | Where-Object { $_ -notmatch 'net4' -or $onWindows } | Select-Object -First 1
|
|
||||||
Write-Verbose "Auto-detected target framework: $Framework"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Result tracking
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
$results = [ordered] @{}
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# xUnit tests
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function Invoke-XUnitTests {
|
|
||||||
Write-Header 'xUnit Tests (PSProxmoxVE.Core.Tests)'
|
|
||||||
|
|
||||||
Assert-Command 'dotnet'
|
|
||||||
|
|
||||||
if (-not (Test-Path $coreTestProject)) {
|
|
||||||
Write-Warning "xUnit test project not found at: $coreTestProject -- skipping."
|
|
||||||
$script:results['xUnit'] = 'Skipped'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
$dotnetArgs = @(
|
|
||||||
'test'
|
|
||||||
$coreTestProject
|
|
||||||
'--logger'
|
|
||||||
'console;verbosity=normal'
|
|
||||||
'--nologo'
|
|
||||||
)
|
|
||||||
|
|
||||||
if ($Framework) {
|
|
||||||
$dotnetArgs += '--framework'
|
|
||||||
$dotnetArgs += $Framework
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "Running: dotnet $($dotnetArgs -join ' ')" -ForegroundColor DarkGray
|
|
||||||
|
|
||||||
& dotnet @dotnetArgs
|
|
||||||
$exitCode = $LASTEXITCODE
|
|
||||||
|
|
||||||
if ($exitCode -eq 0) {
|
|
||||||
Write-Host 'xUnit tests PASSED.' -ForegroundColor Green
|
|
||||||
$script:results['xUnit'] = 'Passed'
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Host "xUnit tests FAILED (exit code $exitCode)." -ForegroundColor Red
|
|
||||||
$script:results['xUnit'] = "Failed (exit $exitCode)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Pester unit tests
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function Invoke-PesterUnitTests {
|
|
||||||
Write-Header 'Pester Unit Tests (PSProxmoxVE.Tests)'
|
|
||||||
|
|
||||||
if (-not (Test-Path $pesterTestDir)) {
|
|
||||||
Write-Warning "Pester test directory not found at: $pesterTestDir -- skipping."
|
|
||||||
$script:results['Pester-Unit'] = 'Skipped'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$pesterModule = Get-Module -Name Pester -ListAvailable |
|
|
||||||
Sort-Object Version -Descending |
|
|
||||||
Select-Object -First 1
|
|
||||||
|
|
||||||
if (-not $pesterModule) {
|
|
||||||
throw 'Pester module is not installed. Run: Install-Module Pester -Force'
|
|
||||||
}
|
|
||||||
|
|
||||||
Import-Module -Name Pester -RequiredVersion $pesterModule.Version -Force
|
|
||||||
|
|
||||||
$config = New-PesterConfiguration
|
|
||||||
$config.Run.Path = $pesterTestDir
|
|
||||||
$config.Filter.ExcludeTag = @('Integration')
|
|
||||||
$config.Output.Verbosity = 'Detailed'
|
|
||||||
$config.Run.PassThru = $true
|
|
||||||
|
|
||||||
$pesterResult = Invoke-Pester -Configuration $config
|
|
||||||
|
|
||||||
if ($pesterResult.FailedCount -gt 0) {
|
|
||||||
Write-Host "Pester unit tests FAILED ($($pesterResult.FailedCount) failed, $($pesterResult.PassedCount) passed)." -ForegroundColor Red
|
|
||||||
$script:results['Pester-Unit'] = "Failed ($($pesterResult.FailedCount) failed)"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Host "Pester unit tests PASSED ($($pesterResult.PassedCount) passed)." -ForegroundColor Green
|
|
||||||
$script:results['Pester-Unit'] = "Passed ($($pesterResult.PassedCount) passed)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
Write-Host "Pester unit tests ERROR: $_" -ForegroundColor Red
|
|
||||||
$script:results['Pester-Unit'] = "Error: $_"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Pester integration tests
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function Invoke-PesterIntegrationTests {
|
|
||||||
Write-Header 'Pester Integration Tests (PSProxmoxVE.Tests/Integration)'
|
|
||||||
|
|
||||||
if (-not (Test-Path $integrationDir)) {
|
|
||||||
Write-Warning "Integration test directory not found at: $integrationDir -- skipping."
|
|
||||||
$script:results['Pester-Integration'] = 'Skipped'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$pesterModule = Get-Module -Name Pester -ListAvailable |
|
|
||||||
Sort-Object Version -Descending |
|
|
||||||
Select-Object -First 1
|
|
||||||
|
|
||||||
if (-not $pesterModule) {
|
|
||||||
throw 'Pester module is not installed. Run: Install-Module Pester -Force'
|
|
||||||
}
|
|
||||||
|
|
||||||
Import-Module -Name Pester -RequiredVersion $pesterModule.Version -Force
|
|
||||||
|
|
||||||
$config = New-PesterConfiguration
|
|
||||||
$config.Run.Path = $integrationDir
|
|
||||||
$config.Filter.Tag = @('Integration')
|
|
||||||
$config.Output.Verbosity = 'Detailed'
|
|
||||||
$config.Run.PassThru = $true
|
|
||||||
|
|
||||||
$pesterResult = Invoke-Pester -Configuration $config
|
|
||||||
|
|
||||||
if ($pesterResult.FailedCount -gt 0) {
|
|
||||||
Write-Host "Integration tests FAILED ($($pesterResult.FailedCount) failed, $($pesterResult.PassedCount) passed)." -ForegroundColor Red
|
|
||||||
$script:results['Pester-Integration'] = "Failed ($($pesterResult.FailedCount) failed)"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Host "Integration tests PASSED ($($pesterResult.PassedCount) passed)." -ForegroundColor Green
|
|
||||||
$script:results['Pester-Integration'] = "Passed ($($pesterResult.PassedCount) passed)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
Write-Host "Integration tests ERROR: $_" -ForegroundColor Red
|
|
||||||
$script:results['Pester-Integration'] = "Error: $_"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Dispatch
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
switch ($Tier) {
|
|
||||||
'Unit' {
|
|
||||||
Invoke-XUnitTests
|
|
||||||
Invoke-PesterUnitTests
|
|
||||||
}
|
|
||||||
'Integration' {
|
|
||||||
Invoke-PesterIntegrationTests
|
|
||||||
}
|
|
||||||
'All' {
|
|
||||||
Invoke-XUnitTests
|
|
||||||
Invoke-PesterUnitTests
|
|
||||||
Invoke-PesterIntegrationTests
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Summary
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
Write-Header 'Test Run Summary'
|
|
||||||
|
|
||||||
$anyFailure = $false
|
|
||||||
|
|
||||||
foreach ($suite in $results.Keys) {
|
|
||||||
$status = $results[$suite]
|
|
||||||
$color = switch -Wildcard ($status) {
|
|
||||||
'Passed*' { 'Green' }
|
|
||||||
'Skipped' { 'Yellow' }
|
|
||||||
default { 'Red' }
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($color -eq 'Red') { $anyFailure = $true }
|
|
||||||
|
|
||||||
Write-Host (' {0,-30} {1}' -f "${suite}:", $status) -ForegroundColor $color
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host ''
|
|
||||||
|
|
||||||
if ($anyFailure) {
|
|
||||||
Write-Host 'One or more test suites FAILED.' -ForegroundColor Red
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Host 'All test suites completed successfully.' -ForegroundColor Green
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user