Skip to content

Commit 133a964

Browse files
committed
Add PesterV10 task which supports only Pester v5+
1 parent e39a686 commit 133a964

File tree

8 files changed

+397
-30
lines changed

8 files changed

+397
-30
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
function Get-HashtableFromString
2+
{
3+
param
4+
(
5+
[string]$lineInput
6+
)
7+
8+
# check for empty first
9+
If ([String]::IsNullOrEmpty($lineInput.trim(';'))) {
10+
return @{}
11+
}
12+
13+
Foreach ($line in ($lineInput -split '(?<=}\s*),')) {
14+
$Hashtable = [System.Management.Automation.Language.Parser]::ParseInput($line, [Ref]$null, [Ref]$null).Find({
15+
$args[0] -is [System.Management.Automation.Language.HashtableAst]
16+
}, $false)
17+
18+
if ($PSVersionTable.PSVersion.Major -ge 5) {
19+
# Use the language parser to safely parse the hashtable string into a real hashtable.
20+
$Hashtable.SafeGetValue()
21+
}
22+
else {
23+
Write-Warning -Message "PowerShell Version lower than 5 detected. Performing Unsafe hashtable conversion. Please update to version 5 or above when possible for safe conversion of hashtable."
24+
Invoke-Expression -Command $Hashtable.Extent
25+
}
26+
}
27+
}
28+
29+
function Import-Pester {
30+
[cmdletbinding()]
31+
param (
32+
[string]$Version
33+
)
34+
35+
36+
if ((Get-Module -Name PowerShellGet -ListAvailable) -and
37+
(Get-Command Install-Module).Parameters.ContainsKey('SkipPublisherCheck') -and
38+
(Get-PSRepository)) {
39+
40+
try {
41+
$null = Get-PackageProvider -Name NuGet -ErrorAction Stop
42+
}
43+
catch {
44+
try {
45+
Install-PackageProvider -Name Nuget -RequiredVersion 2.8.5.201 -Scope CurrentUser -Force -Confirm:$false -ErrorAction Stop
46+
}
47+
catch {
48+
Write-Host "##vso[task.logissue type=warning]Falling back to version of Pester shipped with extension. To use a newer version please update the version of PowerShellGet available on this machine."
49+
Import-Module -Name (Join-Path $PSScriptRoot "4.10.1\Pester.psd1") -Force -Verbose:$false -Global
50+
}
51+
}
52+
53+
if ($Version -eq "latest") {
54+
$NewestPester = Find-Module -Name Pester -MinimumVersion 5.0.0 | Sort-Object Version -Descending | Select-Object -First 1
55+
56+
If ((Get-Module Pester -ListAvailable | Sort-Object Version -Descending| Select-Object -First 1).Version -lt $NewestPester.Version) {
57+
Install-Module -Name Pester -RequiredVersion $NewestPester.Version -Scope CurrentUser -Force -Repository $NewestPester.Repository -SkipPublisherCheck
58+
}
59+
}
60+
else {
61+
$NewestPester = Find-Module -Name Pester -RequiredVersion $Version | Select-Object -First 1
62+
63+
Install-Module -Name Pester -RequiredVersion $NewestPester.Version -Scope CurrentUser -Force -Repository $NewestPester.Repository -SkipPublisherCheck
64+
}
65+
66+
Import-Module -Name Pester -RequiredVersion $NewestPester.Version -Verbose:$false -Global
67+
}
68+
else {
69+
Write-Host "##vso[task.logissue type=warning]Falling back to version of Pester shipped with extension. To use a newer version please update the version of PowerShellGet available on this machine."
70+
Import-Module -Name (Join-Path $PSScriptRoot "4.10.1\Pester.psd1") -Force -Verbose:$false -Global
71+
}
72+
73+
}
+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
[CmdletBinding()]
2+
param
3+
(
4+
[Parameter(Mandatory)]
5+
$TestFolder,
6+
7+
[Parameter(Mandatory)]
8+
[ValidateScript( {
9+
if ((Test-Path (Split-Path $_ -Parent)) -and ($_.split('.')[-1] -eq 'xml')) {
10+
$true
11+
}
12+
else {
13+
Throw "Path is invalid or results file does not end in .xml ($_)"
14+
}
15+
})]
16+
[string]$resultsFile,
17+
18+
[string]$run32Bit,
19+
20+
[string]$additionalModulePath,
21+
22+
[string[]]$Tag,
23+
24+
[String[]]$ExcludeTag,
25+
26+
[validateScript( {
27+
if ([string]::IsNullOrWhiteSpace($_)) {
28+
$true
29+
}
30+
else {
31+
if (-not($_.Split('.')[-1] -eq 'xml')) {
32+
throw "Extension must be XML"
33+
}
34+
$true
35+
}
36+
})]
37+
[string]$CodeCoverageOutputFile,
38+
39+
[string]$CodeCoverageFolder,
40+
41+
[string]$ScriptBlock,
42+
43+
[string]$TargetPesterVersion = "latest"
44+
)
45+
46+
if ($TargetPesterVersion -match '^4') {
47+
Write-Host "##vso[task.logissue type=error]This version of the task does not support Pester V4, please use task version 9."
48+
}
49+
50+
Write-Host "TestFolder $TestFolder"
51+
Write-Host "resultsFile $resultsFile"
52+
Write-Host "run32Bit $run32Bit"
53+
Write-Host "additionalModulePath $additionalModulePath"
54+
Write-Host "tag $Tag"
55+
Write-Host "ExcludeTag $ExcludeTag"
56+
Write-Host "CodeCoverageOutputFile $CodeCoverageOutputFile"
57+
Write-Host "CodeCoverageFolder $CodeCoverageFolder"
58+
Write-Host "ScriptBlock $ScriptBlock"
59+
60+
Import-Module -Name (Join-Path $PSScriptRoot "HelperModule.psm1") -Force
61+
Import-Pester -Version $TargetPesterVersion
62+
63+
if ($run32Bit -eq $true -and $env:Processor_Architecture -ne "x86") {
64+
Write-Warning "32bit support is considered deprecated in this version of the task and will be removed in a future major version."
65+
# Get the command parameters
66+
$args = $myinvocation.BoundParameters.GetEnumerator() | ForEach-Object {
67+
if (-not([string]::IsNullOrWhiteSpace($_.Value))) {
68+
If ($_.Value -eq 'True' -and $_.Key -ne 'run32Bit' -and $_.Key -ne 'ForceUseOfPesterInTasks') {
69+
"-$($_.Key)"
70+
}
71+
else {
72+
"-$($_.Key)"
73+
"$($_.Value)"
74+
}
75+
}
76+
77+
}
78+
write-warning "Re-launching in x86 PowerShell with $($args -join ' ')"
79+
&"$env:windir\syswow64\windowspowershell\v1.0\powershell.exe" -noprofile -executionpolicy bypass -file $myinvocation.Mycommand.path $args
80+
exit
81+
}
82+
Write-Host "Running in $($env:Processor_Architecture) PowerShell"
83+
84+
if ($PSBoundParameters.ContainsKey('additionalModulePath')) {
85+
Write-Host "Adding additional module path [$additionalModulePath] to `$env:PSModulePath"
86+
$env:PSModulePath = $additionalModulePath + ';' + $env:PSModulePath
87+
}
88+
89+
$PesterConfig = @{
90+
91+
Run = @{
92+
Path = $TestFolder
93+
Exit = $true
94+
}
95+
TestResult = @{
96+
Enabled = $true
97+
OutputFormat = 'NUnit2.5'
98+
OutputPath = $resultsFile
99+
}
100+
}
101+
102+
$Filter = @{}
103+
if ($Tag) {
104+
$Tag = $Tag.Split(',').Replace('"', '').Replace("'", "")
105+
$Filter.Add('Tag', $Tag)
106+
}
107+
if ($ExcludeTag) {
108+
$ExcludeTag = $ExcludeTag.Split(',').Replace('"', '').Replace("'", "")
109+
$Filter.Add('ExcludeTag', $ExcludeTag)
110+
}
111+
112+
$PesterConfig['Filter'] = $Filter
113+
114+
$CodeCoverage = @{}
115+
if ($CodeCoverageOutputFile) {
116+
$CodeCoverage['Enabled'] = $True
117+
$CodeCoverage['OutputFormat'] = "JaCoCo"
118+
119+
if (-not $PSBoundParameters.ContainsKey('CodeCoverageFolder')) {
120+
$CodeCoverageFolder = $TestFolder
121+
}
122+
$Files = Get-ChildItem -Path $CodeCoverageFolder -include *.ps1, *.psm1 -Exclude *.Tests.ps1 -Recurse |
123+
Select-object -ExpandProperty Fullname
124+
125+
if ($Files) {
126+
$CodeCoverage.Add('Path', $Files)
127+
$CodeCoverage.Add('OutputPath', $CodeCoverageOutputFile)
128+
}
129+
else {
130+
Write-Warning -Message "No PowerShell files found under [$CodeCoverageFolder] to analyse for code coverage."
131+
}
132+
}
133+
134+
$PesterConfig['CodeCoverage'] = $CodeCoverage
135+
136+
if (-not([String]::IsNullOrWhiteSpace($ScriptBlock))) {
137+
$ScriptBlockObject = [ScriptBlock]::Create($ScriptBlock)
138+
139+
$ScriptBlockObject.Invoke()
140+
}
141+
142+
$result = Invoke-Pester -Configuration ([PesterConfiguration]$PesterConfig)
143+
144+
if ($result.failedCount -ne 0) {
145+
Write-Error "Pester returned errors"
146+
}
2.18 KB
Loading
+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
{
2+
"id": "cca5462b-887d-4617-bf3f-dcf0d3c622e9",
3+
"name": "Pester",
4+
"friendlyName": "Pester Test Runner",
5+
"description": "Run Pester tests by either installing the latest version of Pester at run time (if possible) or using the version shipped with the task (5.0.0)",
6+
"helpMarkDown": "Version: #{Build.BuildNumber}#. [More Information](https://github.com/pester/AzureDevOpsExtension)",
7+
"category": "Test",
8+
"visibility": [
9+
"Build",
10+
"Release"
11+
],
12+
"author": "Pester",
13+
"version": {
14+
"Major": 1,
15+
"Minor": 1,
16+
"Patch": 0
17+
},
18+
"demands": [],
19+
"minimumAgentVersion": "1.82.0",
20+
"groups": [
21+
{
22+
"name": "tags",
23+
"displayName": "Tags",
24+
"isExpanded": false
25+
},
26+
{
27+
"name": "advanced",
28+
"displayName": "Advanced",
29+
"isExpanded": false
30+
}
31+
],
32+
"inputs": [
33+
{
34+
"name": "TestFolder",
35+
"aliases": [
36+
"scriptFolder"
37+
],
38+
"type": "string",
39+
"label": "Tests Folder",
40+
"defaultValue": "$(System.DefaultWorkingDirectory)\\*",
41+
"required": true,
42+
"helpMarkDown": "Folder containing tests to run from e.g $(System.DefaultWorkingDirectory)\\*"
43+
},
44+
{
45+
"name": "resultsFile",
46+
"type": "string",
47+
"label": "Results File",
48+
"defaultValue": "$(System.DefaultWorkingDirectory)\\Test-Pester.XML",
49+
"required": true,
50+
"helpMarkDown": "Results File (nUnit format)"
51+
},
52+
{
53+
"name": "CodeCoverageOutputFile",
54+
"type": "string",
55+
"label": "Code Coverage Output File",
56+
"defaultValue": "",
57+
"required": false,
58+
"helpMarkDown": "Code coverage output file (JaCoCo format) - Requires Pester version 4.0.4+"
59+
},
60+
{
61+
"name": "tag",
62+
"type": "string",
63+
"label": "Tags",
64+
"defaultValue": "",
65+
"required": false,
66+
"helpMarkDown": "Tags used to filter which tests to run.",
67+
"groupName": "tags"
68+
},
69+
{
70+
"name": "excludeTag",
71+
"type": "string",
72+
"label": "Exclude Tag",
73+
"defaultValue": "",
74+
"required": false,
75+
"helpMarkDown": "Tags to exclude when running tests.",
76+
"groupName": "tags"
77+
},
78+
{
79+
"name": "usePSCore",
80+
"type": "boolean",
81+
"label": "Use PowerShell Core (Windows Only)",
82+
"defaultValue": "False",
83+
"required": true,
84+
"helpMarkDown": "On a Windows agent, if set will run Pester tests with 'pwsh', if false will use 'PowerShell.exe'. On Non-Windows agents 'pwsh' always used",
85+
"groupName": "advanced"
86+
},
87+
{
88+
"name": "additionalModulePath",
89+
"type": "string",
90+
"label": "Path to additional PowerShell modules",
91+
"defaultValue": "",
92+
"required": false,
93+
"helpMarkDown": "Adds a path to PSModulePath before running the tests, for dependencies or generated modules",
94+
"groupName": "advanced"
95+
},
96+
{
97+
"name": "CodeCoverageFolder",
98+
"type": "string",
99+
"label": "Code Coverage Folder",
100+
"defaultValue": "",
101+
"required": false,
102+
"helpMarkDown": "Path to the folder or file to run coverage against. Requires CodeCoverageOutputFile.",
103+
"groupName": "advanced"
104+
},
105+
{
106+
"name": "run32Bit",
107+
"type": "boolean",
108+
"label": "Run in 32bit",
109+
"defaultValue": "False",
110+
"required": false,
111+
"helpMarkDown": "Run in 32bit, only applicable on Windows agents",
112+
"groupName": "advanced",
113+
"visibleRule": "usePSCore = False"
114+
},
115+
{
116+
"name": "PesterVersion",
117+
"type": "radio",
118+
"label": "Pester Version",
119+
"defaultValue": "LatestVersion",
120+
"required": false,
121+
"options": {
122+
"LatestVersion": "Latest Available version",
123+
"OtherVersion": "Specify other version"
124+
},
125+
"groupName": "advanced",
126+
"helpMarkDown": "Choose which version of Pester to use for your tests, the version specified will be downloaded from an available PS Gallery. If no gallery is available then it will fall back to the version shipped with the task, currently 4.10.1"
127+
},
128+
{
129+
"name": "TargetPesterVersion",
130+
"aliases": [
131+
"preferredPesterVersion"
132+
],
133+
"type": "string",
134+
"label": "Preferred Pester PowerShell Version",
135+
"defaultValue": "",
136+
"required": true,
137+
"visibleRule": "PesterVersion = OtherVersion",
138+
"groupName": "advanced",
139+
"helpMarkDown": "Speicify version of Pester to install if not latest. This will download it from whichever PS Gallery has it available. If it's not available then the task will fail."
140+
},
141+
{
142+
"name": "ScriptBlock",
143+
"type": "string",
144+
"label": "Pre-Test Scriptblock",
145+
"required": false,
146+
"helpMarkDown": "A scriptblock to run before tests are executed. This will be run in the scope of the task where Invoke-Pester is run.",
147+
"groupName": "advanced"
148+
}
149+
],
150+
"instanceNameFormat": "Pester Test Runner",
151+
"execution": {
152+
"Node": {
153+
"target": "pesterV10.js",
154+
"argumentFormat": ""
155+
}
156+
}
157+
}

Extension/PesterTask/src/pesterv10.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
export async function run() {
1111
try {
1212

13-
let scriptFolder = tl.getInput("scriptFolder");
13+
let TestFolder = tl.getInput("TestFolder");
1414
let resultsFile = tl.getInput("resultsFile");
1515
let run32Bit = tl.getInput("run32Bit");
1616
let additionalModulePath = tl.getInput("additionalModulePath");
@@ -45,7 +45,7 @@ export async function run() {
4545

4646
// we need to not pass the null param
4747
var args = [__dirname + "\\Pester.ps1",
48-
"-scriptFolder", scriptFolder,
48+
"-TestFolder", TestFolder,
4949
"-resultsFile", resultsFile,
5050
"-run32Bit", run32Bit,
5151
];

0 commit comments

Comments
 (0)