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
9 changes: 4 additions & 5 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -540,18 +540,17 @@ try {

### Pester Test Template

Targets Pester 6.1+. See [Testing Framework](./instructions/pester.instructions.md) for the
Targets Pester 6.2+. See [Testing Framework](./instructions/pester.instructions.md) for the
full standards and [Assertion Guide](./instructions/pester-supporting-docs/assertion-guide.md)
for the `Should-*` reference.

```powershell
#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '6.1.0' }
#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '6.2.0' }

Describe "Get-ExampleData" -Tag "Unit" {
BeforeAll {
# Pester 6 discovers and runs one file at a time, so each file
# imports its own dependencies. Only one BeforeAll per block -
# duplicates throw.
# imports its own dependencies.
Import-Module $PSScriptRoot\..\ModuleName.psd1 -Force

# Mock external dependencies following community patterns
Expand Down Expand Up @@ -688,7 +687,7 @@ Use these prompts for quality assurance:
- **Community Standards**: Reference `.github/instructions/community-standards.instructions.md`
- **Style Guide**: Reference `.github/instructions/style-enforcement.instructions.md`
- **Troubleshooting**: Always organized in `./Troubleshooting/` folder structure
- **Testing**: Use Pester 6.1+ with comprehensive coverage requirements
- **Testing**: Use Pester 6.2+ with comprehensive coverage requirements
- **Help Docs**: Use Microsoft.PowerShell.PlatyPS 1.0.3+ — Markdown in `docs/`, MAML in `en-US/`
- **Security**: Implement defense-in-depth with community-approved patterns
- **Performance**: Optimize using community-identified best practices and expert feedback
Expand Down
2 changes: 1 addition & 1 deletion .github/instructions/cicd.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ not the audit configuration.

```powershell
# Pester 6 test execution with coverage
Import-Module Pester -MinimumVersion 6.1.0 -Force
Import-Module Pester -MinimumVersion 6.2.0 -Force

$pesterConfig = New-PesterConfiguration
$pesterConfig.Run.Path = './Tests'
Expand Down
9 changes: 4 additions & 5 deletions .github/instructions/module.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -381,11 +381,11 @@ function Get-ModuleCredential {

### Pester Test Structure

Generate comprehensive test suites targeting **Pester 6.1+**:
Generate comprehensive test suites targeting **Pester 6.2+**:

```powershell
# Tests/Unit/Public/Verb-Noun.Tests.ps1
#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '6.1.0' }
#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '6.2.0' }

BeforeAll {
# Pester 6 discovers and runs one file at a time - each file must be self-contained
Expand All @@ -411,7 +411,6 @@ Describe "Verb-Noun" -Tag "Unit", "Public" {
}

Context "Functionality" {
# Only one BeforeEach per block - Pester 6 throws on duplicates
BeforeEach {
Mock External-Dependency { "MockedResult" }
}
Expand All @@ -437,8 +436,8 @@ Describe "Verb-Noun" -Tag "Unit", "Public" {
```

Key Pester 6 points: `Should-*` assertions replace `Should -Be` for new tests,
`Assert-MockCalled` was removed in favour of `Should-Invoke`, duplicate setup blocks throw, and
every file must import its own dependencies. See
`Assert-MockCalled` was removed in favour of `Should-Invoke`, and every file must import its own
dependencies. See
[Pester instructions](./pester.instructions.md) and the
[migration guide](./pester-supporting-docs/v6-migration.md).

Expand Down
47 changes: 44 additions & 3 deletions .github/instructions/pester-supporting-docs/assertion-guide.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Pester 6 Assertion Guide

Targets **Pester 6.1+**.
Targets **Pester 6.2+**.

Pester 6 ships a new family of `Should-*` assertions (dash, no space) alongside the classic
`Should -Be` operator. Both work. This guide covers which to use and how they differ.
Expand All @@ -25,8 +25,9 @@ The classic `Should` routes `-Be`, `-BeExactly`, `-Contain` and friends through
left side is always unwrapped by the pipeline and failure messages have to guess intent. The
`Should-*` assertions are specialized and type-aware:

- Failure messages are precise (string diffs point a caret at the first differing character;
collection comparisons point at the first differing index).
- Failure messages are precise (a short string diff points a caret at the first differing character
and a long one prints every differing region with line numbers; collection comparisons point at
the first differing index).
- `$Expected` drives the comparison type, so `1 | Should-Be $true` compares as booleans.
- Type-specific switches live where they belong (`Should-BeString -IgnoreWhitespace`).
- `$null`, empty collections, and single-item arrays behave consistently.
Expand Down Expand Up @@ -138,6 +139,34 @@ Get-Content $path -Raw | Should-BeString $expected -NormalizeLineEnding
`Should-NotBeString` takes both `-TrimWhitespace` and `-NormalizeLineEnding`, and its `-Expected` is
mandatory.

#### Long strings get a real diff

From 6.2, when either string is longer than 10 lines or 120 characters, a failing `Should-BeString`
prints every region that differs with context, instead of a caret under the first difference.
Expected line numbers are on the left and actual on the right, so the output stays readable when
lines were added or removed and the two sides stop lining up:

```text
Expected strings to be the same, but they were different.
Expected length: 860
Actual length: 834
Expected 30 line(s), actual 30 line(s).
2 regions differ.
3 3 | line 3 of the expected text
4 4 | line 4 of the expected text
5 | - line 5 of the expected text
5 | + line 5 CHANGED
6 6 | line 6 of the expected text
...
17 | - line 17 of the expected text
17 | + line 17 CHANGED
```

Only the differing lines are expanded, so a tab or trailing space shows up without the unchanged
context turning into escape codes. This is the assertion to use for snapshot tests, rendered
templates, and generated configuration files - pair it with `-NormalizeLineEnding`. Short strings
keep the caret.

### Booleans and null

```powershell
Expand Down Expand Up @@ -267,6 +296,18 @@ $record.Created | Should-BeBefore ([datetime]::Now)
the TimeSpan-vs-double comparison mistakes that pattern invites. Its `-Expected` is mandatory, as is
`Should-BeSlowerThan`'s - a bare `Should-BeFasterThan` no longer binds to a silent default.

Both take a `[scriptblock]` to measure or a `[timespan]` to compare, and nothing else. From 6.2
anything else **throws**:

```powershell
{ Invoke-Thing } | Should-BeFasterThan 1s # measures the scriptblock
(Invoke-Thing) | Should-BeFasterThan 1s # 6.1: passed without measuring anything. 6.2: throws
# Expected a [scriptblock] to measure or a [timespan] to compare, but got [string] 'result'.
```

On 6.1 a string, a number, or `$null` fell through the assertion and the test passed having asserted
nothing. A performance test that starts failing on 6.2 with that message was never measuring.

### Deep object comparison

`Should-BeEquivalent` walks nested properties, hashtables, dictionaries, and collections and emits
Expand Down
81 changes: 52 additions & 29 deletions .github/instructions/pester-supporting-docs/cicd-integration.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# CI/CD Integration Guide

Targets **Pester 6.1+**. Pester 6 supports **Windows PowerShell 5.1** and **PowerShell 7.4+** only -
Targets **Pester 6.2+**. Pester 6 supports **Windows PowerShell 5.1** and **PowerShell 7.4+** only -
support for PowerShell 3, 4, 6, and early/unsupported 7.x was removed, so drop `7.2` and `7.3` from
existing test matrices.

Expand All @@ -14,7 +14,7 @@ text descriptions and standard ASCII characters only.

## Job Design for Pester 6

Two facts shape the pipeline:
Three facts shape the pipeline:

1. **Coverage and parallel pull against each other.** Pester 6.0 refused to combine them at all;
6.1 merges coverage across workers but forces slower breakpoint-based collection to do it. Either
Expand All @@ -23,13 +23,18 @@ Two facts shape the pipeline:
2. **Discovery failures do not appear in `FailedCount`.** A file that fails discovery contributes
zero failed tests. Every gate must also check `FailedContainersCount`, and a cheap discovery-only
job should run first.
3. **Pester 6.0 and 6.1 do not import on PowerShell 7.4.0 to 7.4.5.** Their `net8.0` assembly
referenced a newer `System.Management.Automation` than those releases carry, so `Import-Module`
failed with `Cannot convert "PesterConfigurationDeserializer" from String to Type`. 6.2 fixed it.
Hosted runners ship the latest 7.4.x, so this bites self-hosted agents and pinned container
images - pin Pester at 6.2.0 or newer wherever a 7.4 leg remains.

```text
validate (discovery-only, fast)
|
+--> test-parallel (no coverage, PS 7.4+, fast feedback)
+--> test-parallel (no coverage, fast feedback)
+--> test-coverage (sequential, coverage gate)
+--> test-ps51 (Windows PowerShell 5.1, sequential - no parallel support)
+--> test-ps51 (Windows PowerShell 5.1; parallel works here too from 6.2)
```

## GitHub Actions Integration
Expand All @@ -49,12 +54,12 @@ on:

env:
POWERSHELL_TELEMETRY_OPTOUT: 1
PESTER_VERSION: '6.1.0'
PESTER_VERSION: '6.2.0'

jobs:
# Fast structural check. Catches the Pester 6 breakages - duplicate setup blocks,
# empty -ForEach, files that cannot be discovered independently - without running
# a single test.
# Fast structural check. Catches the Pester 6 breakages - empty -ForEach, files
# that cannot be discovered independently, a wrong-typed configuration value -
# without running a single test.
validate:
runs-on: ubuntu-latest
steps:
Expand Down Expand Up @@ -119,7 +124,7 @@ jobs:
os: [windows-latest, ubuntu-latest, macos-latest]
shell: [pwsh]
include:
# Windows PowerShell 5.1 - sequential only, no parallel support
# Windows PowerShell 5.1 - Run.Parallel works here from Pester 6.2
- os: windows-latest
shell: powershell

Expand Down Expand Up @@ -160,17 +165,17 @@ jobs:
throw "PSScriptAnalyzer found $($analysisResults.Count) issues"
}

# Fast feedback: parallel, no coverage. Parallel is silently ignored on
# Windows PowerShell 5.1, which falls back to sequential with a warning.
# Fast feedback: parallel, no coverage. From Pester 6.2 the parallel runner
# works on Windows PowerShell 5.1 too; 6.0 and 6.1 fell back to sequential there.
- name: Run Unit Tests
shell: ${{ matrix.shell }}
run: |
Import-Module Pester -MinimumVersion 6.1.0 -Force
Import-Module Pester -MinimumVersion 6.2.0 -Force

$config = New-PesterConfiguration
$config.Run.Path = './Tests/Unit'
$config.Run.PassThru = $true
$config.Run.Parallel = $PSVersionTable.PSVersion.Major -ge 7
$config.Run.Parallel = $true
$config.TestResult.Enabled = $true
$config.TestResult.OutputFormat = 'NUnitXml'
$config.TestResult.OutputPath = './TestResults.xml'
Expand All @@ -187,7 +192,7 @@ jobs:
- name: Run Integration Tests
shell: ${{ matrix.shell }}
run: |
Import-Module Pester -MinimumVersion 6.1.0 -Force
Import-Module Pester -MinimumVersion 6.2.0 -Force

$config = New-PesterConfiguration
$config.Run.Path = './Tests/Integration'
Expand Down Expand Up @@ -233,7 +238,7 @@ jobs:
- name: Run Tests with Coverage
shell: pwsh
run: |
Import-Module Pester -MinimumVersion 6.1.0 -Force
Import-Module Pester -MinimumVersion 6.2.0 -Force

$config = New-PesterConfiguration
$config.Run.Path = './Tests/Unit'
Expand Down Expand Up @@ -308,7 +313,7 @@ jobs:
- name: Run Security Tests
shell: pwsh
run: |
Import-Module Pester -MinimumVersion 6.1.0 -Force
Import-Module Pester -MinimumVersion 6.2.0 -Force

$config = New-PesterConfiguration
$config.Run.Path = './Tests/Security'
Expand Down Expand Up @@ -372,7 +377,7 @@ jobs:
- name: Run Performance Tests
shell: pwsh
run: |
Import-Module Pester -MinimumVersion 6.1.0 -Force
Import-Module Pester -MinimumVersion 6.2.0 -Force

$config = New-PesterConfiguration
$config.Run.Path = './Tests/Performance'
Expand Down Expand Up @@ -485,7 +490,7 @@ stages:
targetType: 'inline'
script: |
Set-PSRepository PSGallery -InstallationPolicy Trusted
Install-Module Pester -MinimumVersion 6.1.0 -Force -Scope CurrentUser
Install-Module Pester -MinimumVersion 6.2.0 -Force -Scope CurrentUser
Install-Module PSScriptAnalyzer -Force -Scope CurrentUser
pwsh: $(powershellVersion -eq '7.x')

Expand All @@ -507,7 +512,7 @@ stages:
inputs:
targetType: 'inline'
script: |
Import-Module Pester -MinimumVersion 6.1.0 -Force
Import-Module Pester -MinimumVersion 6.2.0 -Force

$config = New-PesterConfiguration
$config.Run.Path = './Tests'
Expand Down Expand Up @@ -575,7 +580,7 @@ stages:
targetType: 'inline'
script: |
Set-PSRepository PSGallery -InstallationPolicy Trusted
Install-Module Pester -MinimumVersion 6.1.0 -Force -Scope CurrentUser
Install-Module Pester -MinimumVersion 6.2.0 -Force -Scope CurrentUser
pwsh: true

- task: PowerShell@2
Expand Down Expand Up @@ -670,7 +675,7 @@ pipeline {
steps {
powershell '''
Set-PSRepository PSGallery -InstallationPolicy Trusted
Install-Module Pester -MinimumVersion 6.1.0 -Force -Scope CurrentUser
Install-Module Pester -MinimumVersion 6.2.0 -Force -Scope CurrentUser

$config = New-PesterConfiguration
$config.Run.Path = './Tests'
Expand All @@ -697,7 +702,7 @@ pipeline {
steps {
pwsh '''
Set-PSRepository PSGallery -InstallationPolicy Trusted
Install-Module Pester -MinimumVersion 6.1.0 -Force -Scope CurrentUser
Install-Module Pester -MinimumVersion 6.2.0 -Force -Scope CurrentUser

./Invoke-Tests.ps1 -TestType All -Environment CI -CodeCoverage
'''
Expand Down Expand Up @@ -775,7 +780,7 @@ variables:
.powershell_template: &powershell_template
before_script:
- Set-PSRepository PSGallery -InstallationPolicy Trusted
- Install-Module Pester -MinimumVersion 6.1.0 -Force -Scope CurrentUser
- Install-Module Pester -MinimumVersion 6.2.0 -Force -Scope CurrentUser

# GitLab needs JUnit for test results and Cobertura for coverage. Pester 6
# supports both natively - set TestResult.OutputFormat = 'JUnitXml' and
Expand Down Expand Up @@ -849,7 +854,7 @@ Pester 6 has a built-in parallel runner. `Run.Container` is for _parametrizing_
parallelism - the old snippet using multiple containers ran sequentially.

```powershell
# Actual parallel execution: one file per runspace, PowerShell 7+ only
# Actual parallel execution: one file per runspace, on 5.1 and 7 alike from 6.2
$config = New-PesterConfiguration
$config.Run.Path = './Tests/Unit'
$config.Run.Parallel = $true
Expand All @@ -871,22 +876,30 @@ jobs is still the better default - a parallel job without coverage for feedback,
job with the profiler for the gate - but measure before assuming either is faster.

Each worker starts from a **clean runspace**, so every test file must be self-contained. Provide
shared bootstrap through a `Pester.BeforeContainer.ps1` at the repository root, which Pester
dot-sources before every container:
shared bootstrap through `Pester.BeforeContainer.ps1` files, which Pester dot-sources before every
container - from 6.2, every one from `Run.RepoRoot` down to the test file's folder, outermost first.
Put run-time setup in a `BeforeAll`; top-level code in the file runs at discovery only:

```powershell
# Pester.BeforeContainer.ps1, at the repository root
. "$PSScriptRoot/Tests/TestHelpers/Bootstrap.ps1"
BeforeAll {
. "$PSScriptRoot/Tests/TestHelpers/Bootstrap.ps1"
}
```

The `Run.BeforeContainer` option was removed in 6.1 - a CI job that still sets it now throws. In CI
also set `Run.RepoRoot` explicitly, because the default is resolved from the .NET process working
directory and a job that runs Pester from a subdirectory will not find the bootstrap file:
also set `Run.RepoRoot` explicitly. From 6.2 an unset root is resolved from the session's current
location (6.1 used the .NET process working directory), but a checkout without a `.git` directory,
or a run launched from outside the repository, still lands on the wrong root and finds no setup file:

```powershell
$config.Run.RepoRoot = $env:GITHUB_WORKSPACE # or the equivalent for your CI system
```

A parallel worker that died used to drop its file from the results without a trace. From 6.2 the run
throws naming the missing files, and a worker that throws no longer aborts the remaining files when
the job runs with `$ErrorActionPreference = 'Stop'`.

Verify isolation before enabling parallel in CI - if a file only passes as part of a full run, it is
not self-contained:

Expand Down Expand Up @@ -964,3 +977,13 @@ if ($result.CodeCoverage) {

Items 2 and 3 are the ones a Pester 5 pipeline will not have, and they are exactly how a v6 upgrade
turns green while running fewer tests than before.

From 6.2 the configuration is part of the gate too. A wrong-typed value - `'true'` as a string in a
`psd1`, say - throws when the configuration is built, and a key that matches no option is reported
once by `Invoke-Pester` as `WARNING: Ignoring configuration keys ...`. A warning does not fail a job,
so check the list before the run:

```powershell
$unknown = @($config.GetUnknownKeys())
if ($unknown.Count -gt 0) { throw "Configuration keys that match no option: $($unknown -join ', ')" }
```
Loading