1
+ function Invoke-ManualPester {
2
+ <#
3
+ . SYNOPSIS
4
+ Runs dbatools tests.
5
+
6
+ . DESCRIPTION
7
+ This is an helper to automate running tests locally
8
+
9
+ . PARAMETER Path
10
+ The Path to the test files to run. It accepts multiple test file paths passed in (e.g. .\Find-DbaOrphanedFile.Tests.ps1) as well
11
+ as simple strings (e.g. "orphaned" will run all files matching .\*orphaned*.Tests.ps1)
12
+
13
+ . PARAMETER Show
14
+ Gets passed down to Pester's -Show parameter (useful if you want to reduce verbosity)
15
+
16
+ . PARAMETER PassThru
17
+ Gets passed down to Pester's -PassThru parameter (useful if you want to return an object to analyze)
18
+
19
+ . PARAMETER TestIntegration
20
+ dbatools's suite has unittests and integrationtests. This switch enables IntegrationTests, which need live instances
21
+ see constants.ps1 for customizations
22
+
23
+ . PARAMETER Coverage
24
+ Enables measuring code coverage on the tested function
25
+
26
+ . PARAMETER DependencyCoverage
27
+ Enables measuring code coverage also of "lower level" (i.e. called) functions
28
+
29
+ . PARAMETER ScriptAnalyzer
30
+ Enables checking the called function's code with Invoke-ScriptAnalyzer, with dbatools's profile
31
+
32
+ . EXAMPLE
33
+ Invoke-ManualPester -Path Find-DbaOrphanedFile.Tests.ps1 -TestIntegration -Coverage -DependencyCoverage -ScriptAnalyzer
34
+
35
+ The most complete number of checks:
36
+ - Runs both unittests and integrationtests
37
+ - Gathers and shows code coverage measurement for Find-DbaOrphanedFile and all its dependencies
38
+ - Checks Find-DbaOrphanedFile with Invoke-ScriptAnalyzer
39
+
40
+ . EXAMPLE
41
+ Invoke-ManualPester -Path Find-DbaOrphanedFile.Tests.ps1
42
+
43
+ Runs unittests stored in Find-DbaOrphanedFile.Tests.ps1
44
+
45
+ . EXAMPLE
46
+ Invoke-ManualPester -Path Find-DbaOrphanedFile.Tests.ps1 -PassThru
47
+
48
+ Runs unittests stored in Find-DbaOrphanedFile.Tests.ps1 and returns an object that can be analyzed
49
+
50
+ . EXAMPLE
51
+ Invoke-ManualPester -Path orphan
52
+
53
+ Runs unittests for all tests matching in `*orphan*.Tests.ps1
54
+
55
+ . EXAMPLE
56
+ Invoke-ManualPester -Path Find-DbaOrphanedFile.Tests.ps1 -Show Default
57
+
58
+ Runs unittests stored in Find-DbaOrphanedFile.Tests.ps1, with reduced verbosity
59
+
60
+ . EXAMPLE
61
+ Invoke-ManualPester -Path Find-DbaOrphanedFile.Tests.ps1 -TestIntegration
62
+
63
+ Runs both unittests and integrationtests stored in Find-DbaOrphanedFile.Tests.ps1
64
+
65
+ . EXAMPLE
66
+ Invoke-ManualPester -Path Find-DbaOrphanedFile.Tests.ps1 -TestIntegration -Coverage
67
+
68
+ Gathers and shows code coverage measurement for Find-DbaOrphanedFile
69
+
70
+ . EXAMPLE
71
+ Invoke-ManualPester -Path Find-DbaOrphanedFile.Tests.ps1 -TestIntegration -Coverage -DependencyCoverage
72
+
73
+ Gathers and shows code coverage measurement for Find-DbaOrphanedFile and all its dependencies
74
+
75
+ #>
76
+
77
+ [CmdletBinding ()]
78
+ param (
79
+ [Parameter (Position = 0 , ValueFromPipeline , ValueFromPipelineByPropertyName )]
80
+ [Alias (' FullName' )]
81
+ [string []]$Path ,
82
+ [ValidateSet (' None' , ' Default' , ' Passed' , ' Failed' , ' Pending' , ' Skipped' , ' Inconclusive' , ' Describe' , ' Context' , ' Summary' , ' Header' , ' All' , ' Fails' )]
83
+ [string ]$Show = " All" ,
84
+ [switch ]$PassThru ,
85
+ [switch ]$TestIntegration ,
86
+ [switch ]$Coverage ,
87
+ [switch ]$DependencyCoverage ,
88
+ [switch ]$ScriptAnalyzer
89
+ )
90
+
91
+ <#
92
+ Remove-Module -Name Pester
93
+ Import-Module -name Pester -MaximumVersion 4.*
94
+ #>
95
+
96
+ $invokeFormatterVersion = (Get-Command Invoke-Formatter - ErrorAction SilentlyContinue).Version
97
+ $HasScriptAnalyzer = $null -ne $invokeFormatterVersion
98
+ $MinimumPesterVersion = [Version ] ' 3.4.5.0' # Because this is when -Show was introduced
99
+ $MaximumPesterVersion = [Version ] ' 5.0.0.0' # Because our tests (and runners) are only compatible with 4.*
100
+ $PesterVersion = (Get-Command Invoke-Pester - ErrorAction SilentlyContinue).Version
101
+ $HasPester = $null -ne $PesterVersion
102
+ $ScriptAnalyzerCorrectVersion = ' 1.18.2'
103
+
104
+ if (! ($HasScriptAnalyzer )) {
105
+ Write-Warning " Please install PSScriptAnalyzer"
106
+ Write-Warning " Install-Module -Name PSScriptAnalyzer -RequiredVersion '$ScriptAnalyzerCorrectVersion '"
107
+ Write-Warning " or go to https://github.com/PowerShell/PSScriptAnalyzer"
108
+ } else {
109
+ if ($invokeFormatterVersion -ne $ScriptAnalyzerCorrectVersion ) {
110
+ Remove-Module PSScriptAnalyzer
111
+ try {
112
+ Import-Module PSScriptAnalyzer - RequiredVersion $ScriptAnalyzerCorrectVersion - ErrorAction Stop
113
+ } catch {
114
+ Write-Warning " Please install PSScriptAnalyzer $ScriptAnalyzerCorrectVersion "
115
+ Write-Warning " Install-Module -Name PSScriptAnalyzer -RequiredVersion '$ScriptAnalyzerCorrectVersion '"
116
+ }
117
+ }
118
+ }
119
+ if (! ($HasPester )) {
120
+ Write-Warning " Please install Pester"
121
+ Write-Warning " Install-Module -Name Pester -Force -SkipPublisherCheck"
122
+ Write-Warning " or go to https://github.com/pester/Pester"
123
+ }
124
+ if ($PesterVersion -lt $MinimumPesterVersion ) {
125
+ Write-Warning " Please update Pester to at least 3.4.5"
126
+ Write-Warning " Install-Module -Name Pester -MaximumVersion '4.10' -Force -SkipPublisherCheck"
127
+ Write-Warning " or go to https://github.com/pester/Pester"
128
+ }
129
+ if ($PesterVersion -gt $MaximumPesterVersion ) {
130
+ Write-Warning " Please get Pester to the 4.* release"
131
+ Write-Warning " Install-Module -Name Pester -MaximumVersion '4.10' -Force -SkipPublisherCheck"
132
+ Write-Warning " or go to https://github.com/pester/Pester"
133
+ }
134
+
135
+ if (($HasPester -and $HasScriptAnalyzer -and ($PesterVersion -ge $MinimumPesterVersion ) -and ($PesterVersion -lt $MaximumPesterVersion ) -and ($invokeFormatterVersion -eq $ScriptAnalyzerCorrectVersion )) -eq $false ) {
136
+ Write-Warning " Exiting..."
137
+ return
138
+ }
139
+
140
+ $ModuleBase = Split-Path - Path $PSScriptRoot - Parent
141
+
142
+ if (-not (Test-Path " $ModuleBase \.git" - Type Container)) {
143
+ New-Item - Type Container - Path " $ModuleBase \.git" - Force
144
+ }
145
+
146
+ # removes previously imported dbatools, if any
147
+ # No need the force will do it
148
+ # Remove-Module dbatools -ErrorAction Ignore
149
+ # imports the module making sure DLL is loaded ok
150
+ Import-Module " $ModuleBase \dbatools.psd1" - DisableNameChecking - Force
151
+ # imports the psm1 to be able to use internal functions in tests
152
+ Import-Module " $ModuleBase \dbatools.psm1" - DisableNameChecking - Force
153
+
154
+ $ScriptAnalyzerRulesExclude = @ (' PSUseOutputTypeCorrectly' , ' PSAvoidUsingPlainTextForPassword' , ' PSUseBOMForUnicodeEncodedFile' )
155
+
156
+ $testInt = $false
157
+ if ($config_TestIntegration ) {
158
+ $testInt = $true
159
+ }
160
+ if ($TestIntegration ) {
161
+ $testInt = $true
162
+ }
163
+
164
+ function Get-CoverageIndications ($Path , $ModuleBase ) {
165
+ # takes a test file path and figures out what to analyze for coverage (i.e. dependencies)
166
+ $CBHRex = [regex ]' (?smi)<#(.*)#>'
167
+ $everything = (Get-Module dbatools).ExportedCommands.Values
168
+ $everyfunction = $everything.Name
169
+ $funcs = @ ()
170
+ $leaf = Split-Path $path - Leaf
171
+ # assuming Get-DbaFoo.Tests.ps1 wants coverage for "Get-DbaFoo"
172
+ # but allowing also Get-DbaFoo.one.Tests.ps1 and Get-DbaFoo.two.Tests.ps1
173
+ $func_name += ($leaf -replace ' ^([^.]+)(.+)?.Tests.ps1' , ' $1' )
174
+ if ($func_name -in $everyfunction ) {
175
+ $funcs += $func_name
176
+ $f = $everything | Where-Object Name -eq $func_name
177
+ $source = $f.Definition
178
+ $CBH = $CBHRex.match ($source ).Value
179
+ $cmdonly = $source.Replace ($CBH , ' ' )
180
+ foreach ($e in $everyfunction ) {
181
+ # hacky, I know, but every occurrence of any function plus a space kinda denotes usage !?
182
+ $searchme = " $e "
183
+ if ($cmdonly.contains ($searchme )) {
184
+ $funcs += $e
185
+ }
186
+ }
187
+ }
188
+ $testpaths = @ ()
189
+ $allfiles = Get-ChildItem - File - Path " $ModuleBase \private\functions" , " $ModuleBase \public" - Filter ' *.ps1'
190
+ foreach ($f in $funcs ) {
191
+ # exclude always used functions ?!
192
+ if ($f -in (' Connect-DbaInstance' , ' Select-DefaultView' , ' Stop-Function' , ' Write-Message' )) { continue }
193
+ # can I find a correspondence to a physical file (again, on the convenience of having Get-DbaFoo.ps1 actually defining Get-DbaFoo)?
194
+ $res = $allfiles | Where-Object { $_.Name.Replace (' .ps1' , ' ' ) -eq $f }
195
+ if ($res.count -gt 0 ) {
196
+ $testpaths += $res.FullName
197
+ }
198
+ }
199
+ return @ () + ($testpaths | Select-Object - Unique)
200
+ }
201
+
202
+ $files = @ ()
203
+
204
+ if ($Path ) {
205
+ foreach ($item in $path ) {
206
+ if (Test-Path $item ) {
207
+ $files += Get-ChildItem - Path $item
208
+ } else {
209
+ $files += Get-ChildItem - Path " $ModuleBase \tests\*$item *.Tests.ps1"
210
+ }
211
+ }
212
+ }
213
+
214
+ if ($files.Length -eq 0 ) {
215
+ Write-Warning " No tests to be run"
216
+ }
217
+
218
+ $AllTestsWithinScenario = $files
219
+
220
+ foreach ($f in $AllTestsWithinScenario ) {
221
+ $PesterSplat = @ {
222
+ ' Script' = $f.FullName
223
+ ' Show' = $show
224
+ ' PassThru' = $passThru
225
+ }
226
+ # opt-in
227
+ $HeadFunctionPath = $f.FullName
228
+
229
+ if ($Coverage -or $ScriptAnalyzer ) {
230
+ $CoverFiles = Get-CoverageIndications - Path $f - ModuleBase $ModuleBase
231
+ $HeadFunctionPath = $CoverFiles | Select-Object - First 1
232
+ }
233
+ if ($Coverage ) {
234
+ if ($DependencyCoverage ) {
235
+ $CoverFilesPester = $CoverFiles
236
+ } else {
237
+ $CoverFilesPester = $HeadFunctionPath
238
+ }
239
+ $PesterSplat [' CodeCoverage' ] = $CoverFilesPester
240
+ }
241
+ if (! ($testInt )) {
242
+ $PesterSplat [' ExcludeTag' ] = " IntegrationTests"
243
+ }
244
+ Invoke-Pester @PesterSplat
245
+ if ($ScriptAnalyzer ) {
246
+ if ($Show -ne " None" ) {
247
+ Write-Host - ForegroundColor green - Object " ScriptAnalyzer check for $HeadFunctionPath "
248
+ }
249
+ Invoke-ScriptAnalyzer - Path $HeadFunctionPath - ExcludeRule $ScriptAnalyzerRulesExclude
250
+ }
251
+ }
252
+ }
0 commit comments