-
-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathPesterInterface.ps1
167 lines (144 loc) · 5.39 KB
/
PesterInterface.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
using namespace System.Collections
using namespace System.Collections.Generic
using namespace Pester
[CmdletBinding(PositionalBinding = $false)]
param(
#Path(s) to search for tests. Paths can also contain line numbers (e.g. /path/to/file:25)
[Parameter(ValueFromRemainingArguments)][String[]]$Path = $PWD,
#Only return the test information, don't actually run them. Also returns minimal output
[Switch]$Discovery,
#Only load the functions but don't execute anything. Used for testing.
[Parameter(DontShow)][Switch]$LoadFunctionsOnly,
#If specified, emit the output objects as a flattened json to the specified named pipe handle. Used for IPC to the extension host.
#If this value is the special value 'stdout' or undefined, then the output object is written to stdout.
[String]$PipeName,
#The verbosity to pass to the system
[String]$Verbosity,
#If specified, the shim will write to a temporary file at Pipename path and this script will output what would have been written to the stream. Useful for testing.
[Switch]$DryRun,
#An optional custom path to the Pester module.
[String]$CustomModulePath,
#Include ANSI characters in output
[String]$IncludeAnsi
)
$VerbosePreference = 'SilentlyContinue'
$WarningPreference = 'SilentlyContinue'
$DebugPreference = 'SilentlyContinue'
if ($psversiontable.psversion -ge '7.2.0') {
if ($IncludeAnsi) {
$PSStyle.OutputRendering = 'ANSI'
} else {
$PSStyle.OutputRendering = 'PlainText'
}
}
filter Import-PrivateModule ([Parameter(ValueFromPipeline)][string]$Path) {
<#
.SYNOPSIS
This function imports a module from a file into a private variable and does not expose it via Get-Module.
.NOTES
Thanks to @SeeminglyScience for the inspiration
#>
#We dont use namespaces here to keep things portable
$absolutePath = Resolve-Path $Path -ErrorAction Stop
[Management.Automation.Language.Token[]]$tokens = $null
[Management.Automation.Language.ParseError[]]$errors = $null
[Management.Automation.Language.ScriptBlockAst]$scriptBlockAST = [Management.Automation.Language.Parser]::ParseFile($absolutePath, [ref]$tokens, [ref]$errors)
if ($errors) {
$errors | ForEach-Object { Write-Error $_.Message }
return
}
return [psmoduleinfo]::new($scriptBlockAst.GetScriptBlock())
}
function Register-PesterPlugin ([hashtable]$PluginConfiguration) {
<#
.SYNOPSIS
Utilizes a private Pester API to register the plugin.
#>
$Pester = (Get-Command Invoke-Pester -ErrorAction Stop).Module
& $Pester {
param($PluginConfiguration)
if ($null -ne $SCRIPT:additionalPlugins -and $testAdapterPlugin.Name -in $SCRIPT:additionalPlugins.Name) {
Write-Debug "PesterInterface: $($testAdapterPlugin.Name) is already registered. Skipping..."
return
}
if ($null -eq $SCRIPT:additionalPlugins) {
$SCRIPT:additionalPlugins = @()
}
$testAdapterPlugin = New-PluginObject @PluginConfiguration
$SCRIPT:additionalPlugins += $testAdapterPlugin
} $PluginConfiguration
}
function Unregister-PesterPlugin ([hashtable]$PluginConfiguration) {
<#
.SYNOPSIS
Utilizes a private Pester API to unregister the plugin.
#>
$Pester = (Get-Command Invoke-Pester -ErrorAction Stop).Module
& $Pester {
param($PluginConfiguration)
if (-not $SCRIPT:additionalPlugins) {
Write-Debug 'PesterInterface: No plugins are registered. Skipping...'
return
}
$plugin = $SCRIPT:additionalPlugins | Where-Object Name -EQ $PluginConfiguration.Name
if (-not $plugin) {
Write-Debug "PesterInterface: $($PluginConfiguration.Name) is not registered. Skipping..."
return
}
$SCRIPT:additionalPlugins = $SCRIPT:additionalPlugins | Where-Object Name -NE $PluginConfiguration.Name
} $PluginConfiguration
}
#endregion Functions
#Main Function
function Invoke-Main {
$modulePath = if ($CustomModulePath) { Resolve-Path $CustomModulePath -ErrorAction Stop } else { 'Pester' }
Import-Module -MinimumVersion '5.2.0' -Name $modulePath -ErrorAction Stop
$pluginModule = Import-PrivateModule $PSScriptRoot/PesterTestPlugin.psm1
$configArgs = @{
Discovery = $Discovery
PipeName = $PipeName
DryRun = $DryRun
}
#This syntax may seem strange but it allows us to inject state into the plugin.
$plugin = & $pluginModule {
param($externalConfigArgs) New-PesterTestAdapterPluginConfiguration @externalConfigArgs
} $configArgs
try {
Register-PesterPlugin $plugin
# These should be unique which is why we use a hashset
[HashSet[string]]$paths = @()
[HashSet[string]]$lines = @()
# Including both the path and the line speeds up the script by limiting the discovery surface
# Specifying just the line will still scan all files
$Path.foreach{
if ($PSItem -match '(?<Path>.+?):(?<Line>\d+)$') {
[void]$paths.Add($matches['Path'])
[void]$lines.Add($PSItem)
} else {
[void]$paths.Add($PSItem)
}
}
$config = New-PesterConfiguration @{
Run = @{
SkipRun = [bool]$Discovery
PassThru = $true
}
}
#If Verbosity is $null it will use PesterPreference
if ($Discovery) { $config.Output.Verbosity = 'None' }
elseif ($Verbosity) { $config.Output.Verbosity = $Verbosity }
if ($paths.Count) {
$config.Run.Path = [string[]]$paths #Cast to string array is required or it will error
}
if ($lines.Count) {
$config.Filter.Line = [string[]]$lines #Cast to string array is required or it will error
}
Invoke-Pester -Configuration $config | Out-Null
} catch {
throw
} finally {
Unregister-PesterPlugin $plugin
}
}
#Run Main function
if (-not $LoadFunctionsOnly) { Invoke-Main }