Files
PSProxmoxVE/.github/workflows/unit-tests.yml
T
goodolclint-claude[bot] fc073e7e2a ci: pin Pester by exact version everywhere it is installed or imported
Pester was installed with -MinimumVersion 5.0 and no ceiling in the CI job
image, both install steps in unit-tests.yml, and both Import-Module calls, plus
the suite's own import inside the container. The image is rebuilt on every CI
run and Pester is installed fresh on every unit-test run, so PSGallery chose the
version — a new major could reach the required PR checks with no commit here,
surfacing as unexplained test breakage on whichever PR ran next.

It had already happened. Steps named "Install Pester 5" were resolving 6.1.0 on
both legs, because Pester 6 declares PowerShellVersion 5.1 and so installs on
Windows PowerShell too. Nothing broke — the suite uses only constructs common to
5 and 6, and runs 1566/0 under 6.1.0 with no deprecation warnings — but nobody
chose it. The step names are corrected; they had been describing an install that
stopped happening some time ago.

Pinning the install alone is not enough, in two ways review found:

An unset variable does not fail. -RequiredVersion accepts an empty value and
degrades to "latest" for Install-Module and to "any" for Import-Module, both
exiting 0, so a renamed or dropped env key would silently restore the float this
commit removes. A guard step now fails the job instead.

The point of use was still floored. run-integration.sh imported the suite's
Pester with -MinimumVersion 5.0, so a second Pester reaching PSModulePath would
win regardless of what was installed. The Dockerfile now promotes the ARG to ENV
so the version is discoverable at runtime, and that import is pinned to it.

The pin lives in two files, so shell-selfchecks asserts they agree — split-brain
between the workflow and the image is precisely the unexplained breakage this is
meant to prevent. CONTRIBUTING.md and CLAUDE.md are updated too; the contributor
instructions were a third floating install site.

Recorded as an amendment to D017 — the same principle as the nested PVE package
pin, applied to the lane's own tooling.
2026-09-01 18:08:02 -05:00

205 lines
7.7 KiB
YAML

name: Unit Tests
# Pinned, not floored. Pester is installed fresh on every run, so a version range
# lets a new major reach the required checks with no commit to this repo (#112).
# Keep in step with PESTER_VERSION in tests/Dockerfile.test.
env:
PESTER_VERSION: 6.1.0
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
# ── Build module artifacts (only job that needs .NET SDK) ──────────
build:
runs-on: ${{ matrix.os }}
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
framework: netstandard2.0
- os: ubuntu-latest
framework: netstandard2.0
steps:
- uses: actions/checkout@v7
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: '10.0.x'
- name: Build module
run: dotnet publish src/PSProxmoxVE/PSProxmoxVE.csproj --configuration Release --framework ${{ matrix.framework }} --output ./publish/${{ matrix.framework }}
- name: Clean publish output for PS module loading
shell: bash
run: rm -f ./publish/${{ matrix.framework }}/*.deps.json ./publish/${{ matrix.framework }}/*.runtimeconfig.json
- name: Upload module artifact
uses: actions/upload-artifact@v7
with:
name: module-${{ matrix.framework }}-${{ matrix.os }}
path: ./publish/${{ matrix.framework }}/
# ── Pester tests (no .NET SDK — just PowerShell + Pester) ─────────
pester-tests:
needs: build
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
ps_version: '5.1'
framework: netstandard2.0
artifact_os: windows-latest
shell: powershell
- os: windows-latest
ps_version: '7.5'
framework: netstandard2.0
artifact_os: windows-latest
shell: pwsh
- os: ubuntu-latest
ps_version: '7.5'
framework: netstandard2.0
artifact_os: ubuntu-latest
shell: pwsh
- os: macos-latest
ps_version: '7.5'
framework: netstandard2.0
artifact_os: ubuntu-latest
shell: pwsh
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- name: Download module artifact
uses: actions/download-artifact@v8
with:
name: module-${{ matrix.framework }}-${{ matrix.artifact_os }}
path: ./publish/${{ matrix.framework }}/
# -RequiredVersion accepts an empty value and degrades to "latest" with
# exit 0, so an unset variable would silently restore the float this pin
# removes. Fail the job instead.
- name: Verify PESTER_VERSION is set
shell: pwsh
run: |
if ([string]::IsNullOrWhiteSpace($env:PESTER_VERSION)) { throw 'PESTER_VERSION is not set' }
"Pinning Pester to $env:PESTER_VERSION"
- name: Install Pester (PS 5.1)
if: matrix.ps_version == '5.1'
shell: powershell
run: |
Install-Module -Name Pester -RequiredVersion $env:PESTER_VERSION -Force -Scope CurrentUser -SkipPublisherCheck
- name: Install Pester (PS 7.x)
if: matrix.ps_version != '5.1'
shell: pwsh
run: |
Install-Module -Name Pester -RequiredVersion $env:PESTER_VERSION -Force -Scope CurrentUser
- name: Copy module to module path (PS 5.1)
if: matrix.ps_version == '5.1'
shell: powershell
run: |
$modulePath = "$env:USERPROFILE\Documents\WindowsPowerShell\Modules\PSProxmoxVE"
New-Item -ItemType Directory -Path $modulePath -Force | Out-Null
Copy-Item -Path .\publish\${{ matrix.framework }}\* -Destination $modulePath -Recurse -Force
- name: Copy module to module path (PS 7.x, Windows)
if: matrix.ps_version != '5.1' && matrix.os == 'windows-latest'
shell: pwsh
run: |
$modulePath = "$env:USERPROFILE\Documents\PowerShell\Modules\PSProxmoxVE"
New-Item -ItemType Directory -Path $modulePath -Force | Out-Null
Copy-Item -Path .\publish\${{ matrix.framework }}\* -Destination $modulePath -Recurse -Force
- name: Copy module to module path (PS 7.x, non-Windows)
if: matrix.ps_version != '5.1' && matrix.os != 'windows-latest'
shell: pwsh
run: |
$modulePath = "$HOME/.local/share/powershell/Modules/PSProxmoxVE"
New-Item -ItemType Directory -Path $modulePath -Force | Out-Null
Copy-Item -Path ./publish/${{ matrix.framework }}/* -Destination $modulePath -Recurse -Force
- name: Run Pester tests (PS 5.1)
if: matrix.ps_version == '5.1'
shell: powershell
run: |
Import-Module Pester -RequiredVersion $env:PESTER_VERSION
$config = New-PesterConfiguration
$config.Run.Path = "tests/PSProxmoxVE.Tests"
$config.Run.Exit = $true
$config.Filter.ExcludeTag = @("Integration")
$config.Output.Verbosity = "Detailed"
$config.TestResult.Enabled = $true
$config.TestResult.OutputFormat = "NUnitXml"
$config.TestResult.OutputPath = "TestResults/pester-results.xml"
Invoke-Pester -Configuration $config
- name: Run Pester tests (PS 7.x)
if: matrix.ps_version != '5.1'
shell: pwsh
run: |
Import-Module Pester -RequiredVersion $env:PESTER_VERSION
$config = New-PesterConfiguration
$config.Run.Path = "tests/PSProxmoxVE.Tests"
$config.Run.Exit = $true
$config.Filter.ExcludeTag = @("Integration")
$config.Output.Verbosity = "Detailed"
$config.TestResult.Enabled = $true
$config.TestResult.OutputFormat = "NUnitXml"
$config.TestResult.OutputPath = "TestResults/pester-results.xml"
Invoke-Pester -Configuration $config
- name: Upload test results
if: always()
uses: actions/upload-artifact@v7
with:
name: pester-results-${{ matrix.os }}-ps${{ matrix.ps_version }}
path: TestResults/
# ── CI infrastructure shell self-checks ─────────────────────────────
# These guard the provisioning and reporting scripts, whose real exercise is
# a ~45-minute nested-PVE run. They stub ssh/gh/git and finish in seconds, so
# a regression in them is caught on the PR rather than a week later.
shell-selfchecks:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: prepare-test-environment self-check
run: bash tests/infrastructure/scripts/prepare-test-environment.test.sh
- name: report-package-currency self-check
run: bash tests/infrastructure/scripts/report-package-currency.test.sh
# The pin lives in two files; split-brain is exactly the unexplained
# breakage D017 exists to prevent, so assert they agree.
- name: Pester pin agrees across workflow and image
run: |
wf=$(sed -n 's/^ PESTER_VERSION: *//p' .github/workflows/unit-tests.yml)
img=$(sed -n 's/^ARG PESTER_VERSION=//p' tests/Dockerfile.test)
echo "workflow=$wf image=$img"
[ -n "$wf" ] && [ -n "$img" ] || { echo "could not read one of the pins" >&2; exit 1; }
[ "$wf" = "$img" ] || { echo "PESTER_VERSION differs: workflow=$wf image=$img" >&2; exit 1; }
- name: Shell syntax check
run: |
for f in tests/infrastructure/scripts/*.sh; do
bash -n "$f" || exit 1
done