Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 82 additions & 10 deletions .github/workflows/Action-Test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
Result: ${{ steps.action-test.outputs.Result }}

steps:
# Need to check out as part of the test, as its a local action
# Need to check out as part of the test, as it's a local action
- name: Checkout repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
Expand Down Expand Up @@ -58,7 +58,7 @@ jobs:
Result: ${{ steps.action-test.outputs.Result }}

steps:
# Need to check out as part of the test, as its a local action
# Need to check out as part of the test, as it's a local action
- name: Checkout repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
Expand Down Expand Up @@ -89,7 +89,7 @@ jobs:
Result: ${{ steps.action-test.outputs.Result }}

steps:
# Need to check out as part of the test, as its a local action
# Need to check out as part of the test, as it's a local action
- name: Checkout repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
Expand Down Expand Up @@ -121,7 +121,7 @@ jobs:
Result: ${{ steps.action-test.outputs.Result }}

steps:
# Need to check out as part of the test, as its a local action
# Need to check out as part of the test, as it's a local action
- name: Checkout repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
Expand Down Expand Up @@ -153,7 +153,7 @@ jobs:
Result: ${{ steps.action-test.outputs.Result }}

steps:
# Need to check out as part of the test, as its a local action
# Need to check out as part of the test, as it's a local action
- name: Checkout repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
Expand Down Expand Up @@ -183,7 +183,7 @@ jobs:
Result: ${{ steps.action-test.outputs.Result }}

steps:
# Need to check out as part of the test, as its a local action
# Need to check out as part of the test, as it's a local action
- name: Checkout repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
Expand Down Expand Up @@ -218,7 +218,7 @@ jobs:
Result: ${{ steps.action-test.outputs.Result }}

steps:
# Need to check out as part of the test, as its a local action
# Need to check out as part of the test, as it's a local action
- name: Checkout repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
Expand Down Expand Up @@ -250,7 +250,7 @@ jobs:
Result: ${{ steps.action-test.outputs.Result }}

steps:
# Need to check out as part of the test, as its a local action
# Need to check out as part of the test, as it's a local action
- name: Checkout repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
Expand Down Expand Up @@ -281,7 +281,7 @@ jobs:
Result: ${{ steps.action-test.outputs.Result }}

steps:
# Need to check out as part of the test, as its a local action
# Need to check out as part of the test, as it's a local action
- name: Checkout repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
Expand All @@ -302,6 +302,68 @@ jobs:
CONCLUSION: ${{ steps.action-test.conclusion }}
run: tests/Show-Status.ps1

ActionTestPester5RangeConstraint:
name: Action-Test - [Pester 5.x Range Constraint]
runs-on: ubuntu-latest
outputs:
Outcome: ${{ steps.action-test.outcome }}
Conclusion: ${{ steps.action-test.conclusion }}
Executed: ${{ steps.action-test.outputs.Executed }}
Result: ${{ steps.action-test.outputs.Result }}

steps:
# Need to check out as part of the test, as it's a local action
- name: Checkout repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false

- name: Action-Test [Pester 5.x Range Constraint]
uses: ./
id: action-test
with:
WorkingDirectory: tests/4-PesterVersionConstraints/Pester5Range
Version: '[5.0.0,6.0.0)'
TestResult_TestSuiteName: Pester5RangeConstraint

- name: Status
shell: pwsh
env:
OUTCOME: ${{ steps.action-test.outcome }}
CONCLUSION: ${{ steps.action-test.conclusion }}
run: tests/Show-Status.ps1

ActionTestPester6ExactConstraint:
name: Action-Test - [Pester 6.0.0 Exact Constraint]
runs-on: ubuntu-latest
outputs:
Outcome: ${{ steps.action-test.outcome }}
Conclusion: ${{ steps.action-test.conclusion }}
Executed: ${{ steps.action-test.outputs.Executed }}
Result: ${{ steps.action-test.outputs.Result }}

steps:
# Need to check out as part of the test, as it's a local action
- name: Checkout repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false

- name: Action-Test [Pester 6.0.0 Exact Constraint]
uses: ./
id: action-test
with:
WorkingDirectory: tests/4-PesterVersionConstraints/Pester6Exact
Version: '6.0.0'
TestResult_TestSuiteName: Pester6ExactConstraint

- name: Status
shell: pwsh
env:
OUTCOME: ${{ steps.action-test.outcome }}
CONCLUSION: ${{ steps.action-test.conclusion }}
run: tests/Show-Status.ps1

CatchJob:
name: Catch Job - Aggregate Status
needs:
Expand All @@ -314,6 +376,8 @@ jobs:
- ActionTest2StandardPrescriptFile
- ActionTest2StandardNoSummary
- ActionTest3Advanced
- ActionTestPester5RangeConstraint
- ActionTestPester6ExactConstraint
if: always()
runs-on: ubuntu-latest
steps:
Expand All @@ -323,7 +387,7 @@ jobs:
persist-credentials: false

- name: Display Aggregated Results as a Table
uses: PSModule/GitHub-Script@1ee97bbc652d19c38ae12f6e1e47e9d9fbd12d0a # v1.8.0
uses: PSModule/GitHub-Script@8083ec1f733f00357ee4d0db0c6056686e483bc0 # v1.9.0
env:
ACTIONTEST1SIMPLE_OUTCOME: ${{ needs.ActionTest1Simple.outputs.Outcome }}
ACTIONTEST1SIMPLE_CONCLUSION: ${{ needs.ActionTest1Simple.outputs.Conclusion }}
Expand Down Expand Up @@ -361,5 +425,13 @@ jobs:
ACTIONTEST3ADVANCED_CONCLUSION: ${{ needs.ActionTest3Advanced.outputs.Conclusion }}
ACTIONTEST3ADVANCED_EXECUTED: ${{ needs.ActionTest3Advanced.outputs.Executed }}
ACTIONTEST3ADVANCED_RESULT: ${{ needs.ActionTest3Advanced.outputs.Result }}
ACTIONTESTPESTER5RANGECONSTRAINT_OUTCOME: ${{ needs.ActionTestPester5RangeConstraint.outputs.Outcome }}
ACTIONTESTPESTER5RANGECONSTRAINT_CONCLUSION: ${{ needs.ActionTestPester5RangeConstraint.outputs.Conclusion }}
ACTIONTESTPESTER5RANGECONSTRAINT_EXECUTED: ${{ needs.ActionTestPester5RangeConstraint.outputs.Executed }}
ACTIONTESTPESTER5RANGECONSTRAINT_RESULT: ${{ needs.ActionTestPester5RangeConstraint.outputs.Result }}
ACTIONTESTPESTER6EXACTCONSTRAINT_OUTCOME: ${{ needs.ActionTestPester6ExactConstraint.outputs.Outcome }}
ACTIONTESTPESTER6EXACTCONSTRAINT_CONCLUSION: ${{ needs.ActionTestPester6ExactConstraint.outputs.Conclusion }}
ACTIONTESTPESTER6EXACTCONSTRAINT_EXECUTED: ${{ needs.ActionTestPester6ExactConstraint.outputs.Executed }}
ACTIONTESTPESTER6EXACTCONSTRAINT_RESULT: ${{ needs.ActionTestPester6ExactConstraint.outputs.Result }}
with:
Script: tests/Test-ActionResults.ps1
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ If you specify `CodeCoverage_Enabled: true` here, it will enable coverage even i

1. **Prerequisite Setup**
- Installs required PowerShell modules if they're not present.
- Installs Pester at the latest version by default, or the version matching the `Version` constraint when set.
- Imports the exact installed Pester version so the version that runs is deterministic, even when the runner ships a different preinstalled Pester.
- Imports the modules so the testing framework is ready to use.
2. **Loading Inputs and Configuration**
- Loads a default Pester configuration.
Expand Down Expand Up @@ -262,6 +264,8 @@ jobs:
| **Input** | **Description** | **Default** |
|--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------|
| `Path` | Path to where tests are located or a configuration file. | *(none)* |
| `Version` | Version of Pester to install (NuGet range, e.g. `[6.0.0,7.0.0)` for any 6.x). Empty installs the latest version. | *(none)* |
| `Prerelease` | Allow installing prerelease versions of Pester. | `false` |
| `ReportAsJson` | Output generated reports in JSON format in addition to the configured format through Pester. | `true` |
| `Prescript` | Script to be executed before the test run. This script is executed in the same context as the test run. | *(none)* |
| `Notice_Mode` | Controls when to show notices for test completion.<br><ul><li>`Full` - show on success and failure</li><li>`Failed` - show only on failure</li><li>`None` - disable notices</li></ul> | `Failed` |
Expand Down Expand Up @@ -312,8 +316,8 @@ jobs:
| `TestRegistry_Enabled` | Enable `TestRegistry`. | *(none)* |
| `Debug` | Enable debug output. | `false` |
| `Verbose` | Enable verbose output. | `false` |
| `Version` | Specifies the exact version of the GitHub module to install. | *(none)* |
| `Prerelease` | Allow prerelease versions if available. | `false` |
| `GitHubVersion` | Version of the GitHub module to install during init bootstrap (NuGet range, e.g. `[1.8.0,2.0.0)`). Empty installs latest. | *(none)* |
| `GitHubPrerelease` | Allow installing prerelease versions of the GitHub module. | `false` |
| `WorkingDirectory` | The working directory where the script runs. | `.` |

### Outputs
Expand Down
30 changes: 23 additions & 7 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ inputs:
description: |
Path to where tests are located or a configuration file.
required: false
Version:
description: |
Version of the Pester module to install, using NuGet version-range syntax, e.g. '[6.0.0,7.0.0)' to allow any 6.x.
A bare version such as '6.4.0' installs that exact version. When empty, the latest available version is installed.
required: false
Prerelease:
description: |
Allow installing prerelease versions of Pester.
required: false
default: 'false'
ReportAsJson:
description: |
Output generated reports in JSON format in addition to the configured format through Pester.
Expand Down Expand Up @@ -219,11 +229,13 @@ inputs:
description: Enable verbose output.
required: false
default: 'false'
Version:
description: Specifies the version of the GitHub module to be installed. The value must be an exact version.
GitHubVersion:
description: |
Version of the GitHub module to install during the init bootstrap step, using NuGet version-range syntax.
A bare version such as '1.8.0' installs that exact version. When empty, the latest available version is installed.
required: false
Prerelease:
description: Allow prerelease versions if available.
GitHubPrerelease:
description: Allow installing prerelease versions of the GitHub module.
required: false
default: 'false'
WorkingDirectory:
Expand Down Expand Up @@ -285,9 +297,11 @@ runs:
using: composite
steps:
- name: Invoke-Pester (init)
uses: PSModule/GitHub-Script@1ee97bbc652d19c38ae12f6e1e47e9d9fbd12d0a # v1.8.0
uses: PSModule/GitHub-Script@8083ec1f733f00357ee4d0db0c6056686e483bc0 # v1.9.0
env:
PSMODULE_INVOKE_PESTER_INPUT_Path: ${{ inputs.Path }}
PSMODULE_INVOKE_PESTER_INPUT_Version: ${{ inputs.Version }}
PSMODULE_INVOKE_PESTER_INPUT_Prerelease: ${{ inputs.Prerelease }}
PSMODULE_INVOKE_PESTER_INPUT_Run_Path: ${{ inputs.Run_Path }}
PSMODULE_INVOKE_PESTER_INPUT_Run_ExcludePath: ${{ inputs.Run_ExcludePath }}
PSMODULE_INVOKE_PESTER_INPUT_Run_ScriptBlock: ${{ inputs.Run_ScriptBlock }}
Expand Down Expand Up @@ -334,9 +348,9 @@ runs:
ShowInfo: false
ShowOutput: true
Debug: ${{ inputs.Debug }}
Prerelease: ${{ inputs.Prerelease }}
Prerelease: ${{ inputs.GitHubPrerelease }}
Verbose: ${{ inputs.Verbose }}
Version: ${{ inputs.Version }}
Version: ${{ inputs.GitHubVersion }}
WorkingDirectory: ${{ inputs.WorkingDirectory }}
Name: Invoke-Pester
Script: |
Expand All @@ -349,6 +363,8 @@ runs:
working-directory: ${{ inputs.WorkingDirectory }}
env:
PSMODULE_INVOKE_PESTER_INPUT_ReportAsJson: ${{ inputs.ReportAsJson }}
PSMODULE_INVOKE_PESTER_INPUT_Version: ${{ inputs.Version }}
PSMODULE_INVOKE_PESTER_INPUT_Prerelease: ${{ inputs.Prerelease }}
PSMODULE_INVOKE_PESTER_INPUT_Notice_Mode: ${{ inputs.Notice_Mode }}
PSMODULE_INVOKE_PESTER_INPUT_StepSummary_Mode: ${{ inputs.StepSummary_Mode }}
PSMODULE_INVOKE_PESTER_INPUT_StepSummary_ShowTestOverview: ${{ inputs.StepSummary_ShowTestOverview }}
Expand Down
70 changes: 65 additions & 5 deletions src/Helpers.psm1 → src/Invoke-Pester.Helpers.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -1283,10 +1283,14 @@ function Invoke-ProcessTestDirectory {
function Install-PSResourceWithRetry {
<#
.SYNOPSIS
Installs a PowerShell module with retry mechanism
Installs a PowerShell module with a retry mechanism, then imports the installed version.

.DESCRIPTION
Attempts to install a PowerShell module multiple times in case of failure
Attempts to install a PowerShell module multiple times in case of failure. When a version
constraint is supplied, the newest version satisfying it is installed; that exact version is
then imported so the loaded module is deterministic even when other versions (for example the
runner's preinstalled copy) are present on the machine. When no version is supplied, the latest
available version is installed.
#>
[CmdletBinding()]
param (
Expand All @@ -1298,6 +1302,15 @@ function Install-PSResourceWithRetry {
)]
[string] $Name,

# Version of the module to install. Accepts an exact version or a NuGet version range,
# e.g. '[6.0.0,7.0.0)' to allow any 6.x. When empty, the latest available version is installed.
[Parameter()]
[string] $Version,

# Allow installing prerelease versions.
[Parameter()]
[switch] $Prerelease,

# Number of times to retry installation, default is 5
[Parameter()]
[int] $RetryCount = 5,
Expand All @@ -1308,10 +1321,30 @@ function Install-PSResourceWithRetry {
)

process {
Write-Output "Installing module: $Name"
$installParams = @{
Name = $Name
Repository = 'PSGallery'
TrustRepository = $true
PassThru = $true
WarningAction = 'SilentlyContinue'
}
if (-not [string]::IsNullOrWhiteSpace($Version)) {
$installParams['Version'] = $Version
}
if ($Prerelease) {
$installParams['Prerelease'] = $true
}

$label = $Name
if (-not [string]::IsNullOrWhiteSpace($Version)) {
$label = "$Name $Version"
}
Write-Output "Installing module: $label"

$installed = $null
for ($i = 0; $i -lt $RetryCount; $i++) {
try {
Install-PSResource -Name $Name -WarningAction SilentlyContinue -TrustRepository -Repository PSGallery
$installed = Install-PSResource @installParams
break
} catch {
Write-Warning "Installation of $Name failed with error: $_"
Expand All @@ -1322,6 +1355,33 @@ function Install-PSResourceWithRetry {
Start-Sleep -Seconds $RetryDelay
}
}
Import-Module -Name $Name

# Resolve the exact version to import. Prefer what was just installed; if the resource was
# already present, Install-PSResource returns nothing, so fall back to the newest installed
# version that satisfies the requested constraint.
$resolved = $installed | Where-Object { $_.Name -eq $Name } | Sort-Object Version -Descending | Select-Object -First 1
if (-not $resolved) {
$getParams = @{ Name = $Name; Verbose = $false; ErrorAction = 'SilentlyContinue' }
if (-not [string]::IsNullOrWhiteSpace($Version)) {
$getParams['Version'] = $Version
}
$resolved = Get-PSResource @getParams | Sort-Object Version -Descending | Select-Object -First 1
}

# Import into the global session state so the resolved version is the one every subsequent
# command (for example Invoke-Pester in exec.ps1) uses, instead of PowerShell auto-loading the
# highest version available on PSModulePath.
if ($resolved) {
Write-Output "Importing module: $Name $($resolved.Version)"
Import-Module -Name $Name -RequiredVersion $resolved.Version -Force -Global -ErrorAction Stop
} elseif (-not [string]::IsNullOrWhiteSpace($Version)) {
# A version constraint was requested but no satisfying installed version could be resolved. Importing the
# module unconstrained here would silently load an arbitrary copy from PSModulePath (for example the
# runner's preinstalled module), defeating the deterministic, constraint-driven selection this function
# exists to guarantee. Fail fast instead.
throw "No installed '$Name' version satisfies constraint '$Version'; refusing to import an unconstrained version."
} else {
Import-Module -Name $Name -Force -Global -ErrorAction Stop
}
}
}
Loading
Loading