Skip to content

Commit 8060742

Browse files
authored
Feature splathash & build refactor (#63)
* [WIP] Add function to sanitize splat hash passed to Pester on remote nodes * [WIP] Adds unit tests for the Sanitize-PesterSplathash func * [WIP] Adds parameter -PesterSplatHash - work done for JSON parameterset - TODO the bootstrap parameterset * [WIP] Fixes typo in the tests, replace $reservedKey with $key * Refactors the build steps * Sets the project root in the Build task
1 parent 2e5964d commit 8060742

10 files changed

+191
-62
lines changed

InvokeBuild.ps1 Build/InvokeBuild.ps1

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ task Test Init, {
8080
task Build Test, {
8181
$lines
8282

83+
Set-Location $ProjectRoot
8384
# Load the module, read the exported functions, update the psd1 FunctionsToExport
8485
Set-ModuleFunctions
8586

Build/Start-Build.ps1

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
param(
2+
$Task = 'Build' # build is the default task, add support to deploy later
3+
)
4+
5+
# dependencies
6+
Get-PackageProvider -Name NuGet -ForceBootstrap | Out-Null
7+
if(-not (Get-Module -ListAvailable PSDepend))
8+
{
9+
& (Resolve-Path "$PSScriptRoot\helpers\Install-PSDepend.ps1")
10+
}
11+
Import-Module PSDepend
12+
$null = Invoke-PSDepend -Path "$PSScriptRoot\build.requirements.psd1" -Install -Import -Force
13+
14+
Set-BuildEnvironment -Force
15+
16+
Invoke-Build -File $PSScriptRoot\InvokeBuild.ps1 -Task $Task

Build/build.requirements.psd1

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
@{
2+
# Some defaults for all dependencies
3+
PSDependOptions = @{
4+
Target = '$ENV:USERPROFILE\Documents\WindowsPowerShell\Modules'
5+
AddToPath = $True
6+
}
7+
8+
# Grab some modules without depending on PowerShellGet
9+
'InvokeBuild' = @{ DependencyType = 'PSGalleryNuget' }
10+
'PSDeploy' = @{ DependencyType = 'PSGalleryNuget' }
11+
'BuildHelpers' = @{ DependencyType = 'PSGalleryNuget' }
12+
'Pester' = @{ DependencyType = 'PSGalleryNuget' }
13+
'PSScriptAnalyzer' = @{ DependencyType = 'PSGalleryNuget' }
14+
}

Build/helpers/Install-PSDepend.ps1

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<#
2+
.SYNOPSIS
3+
Bootstrap PSDepend
4+
5+
.DESCRIPTION
6+
Bootstrap PSDepend
7+
8+
Why? No reliance on PowerShellGallery
9+
10+
* Downloads nuget to your ~\ home directory
11+
* Creates $Path (and full path to it)
12+
* Downloads module to $Path\PSDepend
13+
* Moves nuget.exe to $Path\PSDepend (skips nuget bootstrap on initial PSDepend import)
14+
15+
.PARAMETER Path
16+
Module path to install PSDepend
17+
18+
Defaults to Profile\Documents\WindowsPowerShell\Modules
19+
20+
.EXAMPLE
21+
.\Install-PSDepend.ps1 -Path C:\Modules
22+
23+
# Installs to C:\Modules\PSDepend
24+
#>
25+
[cmdletbinding()]
26+
param(
27+
[string]$Path = $( Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'WindowsPowerShell\Modules')
28+
)
29+
$ExistingProgressPreference = "$ProgressPreference"
30+
$ProgressPreference = 'SilentlyContinue'
31+
try {
32+
# Bootstrap nuget if we don't have it
33+
if(-not ($NugetPath = (Get-Command 'nuget.exe' -ErrorAction SilentlyContinue).Path)) {
34+
$NugetPath = Join-Path $ENV:USERPROFILE nuget.exe
35+
if(-not (Test-Path $NugetPath)) {
36+
Invoke-WebRequest -uri 'https://dist.nuget.org/win-x86-commandline/latest/nuget.exe' -OutFile $NugetPath
37+
}
38+
}
39+
40+
# Bootstrap PSDepend, re-use nuget.exe for the module
41+
if($path) { $null = mkdir $path -Force }
42+
$NugetParams = 'install', 'PSDepend', '-Source', 'https://www.powershellgallery.com/api/v2/',
43+
'-ExcludeVersion', '-NonInteractive', '-OutputDirectory', $Path
44+
& $NugetPath @NugetParams
45+
Move-Item -Path $NugetPath -Destination "$(Join-Path $Path PSDepend)\nuget.exe" -Force
46+
}
47+
finally {
48+
$ProgressPreference = $ExistingProgressPreference
49+
}
50+
File renamed without changes.

PSRemotely/private/ConfigurationData.ps1

+24
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,27 @@ function ConvertPSObjectToHashtable
129129
}
130130
}
131131
}
132+
133+
134+
Function Sanitize-PesterSplatHash {
135+
[CmdletBinding()]
136+
param(
137+
# Pass the Pester splat hash to be modified
138+
[Parameter(Mandatory=$true)]
139+
[ValidateNotNullOrEmpty()]
140+
[HashTable]$SplatHash
141+
)
142+
143+
# Filter our the conflicting keys from the PesterSplathash
144+
# Keys to remove - Script, PassThru, Quiet, OutputFormat, OutPutFile
145+
$ExcludeKeys = @('Script','PassThru','Quiet','OutputFormat','OutputFile')
146+
$CloneHash = $SplatHash.Clone()
147+
Foreach ($Key in $CloneHash.Keys) {
148+
# process each key present in the splat hash
149+
if ($ExcludeKeys -contains $Key) {
150+
# remove this key and value
151+
Write-Warning -Message "Key $Key found in the SplatHash. Removing it"
152+
$SplatHash.Remove($Key)
153+
}
154+
}
155+
}

PSRemotely/public/Invoke-PSRemotely.ps1

+41-7
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,19 @@ Function Invoke-PSRemotely {
5555
]
5656
}
5757
58-
58+
.PARAMETER PesterSplatHash
59+
Pass a hash table which is splatted to Invoke-Pester's execution on the PSRemotely node.
60+
This let's you pass arguments to Invoke-Pester such as -Tag, -ExcludeTag, -Strict etc.
61+
Note - PSRemotely automatically supplies the below arguments to Invoke-Pester. So if these are
62+
specified then it will be ignored.
63+
64+
Script = <Based on the input this gets passed to Pester>
65+
PassThru = $True;
66+
Quiet = $True;
67+
OutputFormat = 'NunitXML';
68+
OutputFile = <NodeName>.xml;
69+
70+
5971
.EXAMPLE
6072
PS> Invoke-PSRemotely
6173
@@ -101,7 +113,7 @@ Function Invoke-PSRemotely {
101113
#>
102114
[CmdletBinding(DefaultParameterSetName='BootStrap',SupportsShouldProcess=$True)]
103115
param(
104-
[Parameter(Position=-0,
116+
[Parameter(Position=0,
105117
Mandatory=$False,
106118
ParameterSetName='BootStrap',
107119
ValueFromPipeline=$true)]
@@ -110,7 +122,12 @@ Function Invoke-PSRemotely {
110122
[Parameter(Position=0,
111123
Mandatory=$true,
112124
ParameterSetName='JSON')]
113-
[String]$JSONInput
125+
[String]$JSONInput,
126+
127+
[Parameter(Position=1,
128+
Mandatory=$False)]
129+
[Alias('SplatHash')]
130+
[HashTable]$PesterSplatHash
114131

115132
)
116133
BEGIN {
@@ -137,14 +154,19 @@ Function Invoke-PSRemotely {
137154
Select-Object -ExpandProperty Value |
138155
Select-Object -ExpandProperty Session
139156

157+
# Check if the Pester splat hash was passed
158+
if ($PesterSplatHash) {
159+
Sanitize-PesterSplatHash -SplatHash $PesterSplatHash
160+
}
140161
# build the splat hashtable
141162
$invokeTestParams = @{
142163
Session = $session;
143-
ArgumentList = $JSONInput, $Object.NodeName #@(,$Object.Tests.Name);
164+
ArgumentList = $JSONInput, $Object.NodeName, $PesterSplatHash #@(,$Object.Tests.Name);
144165
ScriptBlock = {
145166
param(
146167
[String]$JSONString,
147-
[String]$NodeName
168+
[String]$NodeName,
169+
[HashTable]$PesterSplatHash
148170
)
149171
$Object = ConvertFrom-Json -InputObject $JSONString
150172
foreach ($test in @($Object.Tests.Name)) {
@@ -166,11 +188,22 @@ Function Invoke-PSRemotely {
166188

167189
$testFile = "$($Global:PSRemotely.PSRemotelyNodePath)\$testFileName"
168190
$outPutFile = "{0}\{1}.{2}.xml" -f $PSRemotely.PSRemotelyNodePath, $nodeName, $test
191+
$invokePesterParams = @{
192+
PassThru = $True;
193+
Quiet = $True;
194+
OutputFormat = 'NunitXML';
195+
OutputFile = $OutputFile
196+
}
197+
198+
if ($PesterSplatHash) {
199+
$invokePesterParams += $PesterSplatHash
200+
}
201+
169202
if ($Node) {
170-
Invoke-Pester -Script @{Path=$($TestFile); Parameters=@{Node=$Node}} -PassThru -Quiet -OutputFormat NUnitXML -OutputFile $outPutFile
203+
Invoke-Pester -Script @{Path=$($TestFile); Parameters=@{Node=$Node}} @invokePesterParams
171204
}
172205
else {
173-
Invoke-Pester -Script $testFile -PassThru -Quiet -OutputFormat NUnitXML -OutputFile $outPutFile
206+
Invoke-Pester -Script $testFile @invokePesterParams
174207
}
175208
}
176209
}; # end scriptBlock
@@ -209,6 +242,7 @@ Function Invoke-PSRemotely {
209242
try {
210243
do{
211244
Write-VerboseLog -Message "Invoking test script -> $($testscript.path)"
245+
# TODO pass the PesterSplatHash if specified in the command line
212246
& $invokeTestScript -Path $testScript.Path -Arguments $testScript.Arguments -Parameters $testScript.Parameters
213247
} until ($true)
214248
}

Tests/Unit/ConfigurationData.Tests.ps1

+43
Original file line numberDiff line numberDiff line change
@@ -188,4 +188,47 @@ InModuleScope -ModuleName $ENV:BHProjectName {
188188
}
189189
}
190190
}
191+
192+
Describe "Sanitize-PesterSplatHash" -Tag UnitTest {
193+
194+
Context "Sanitizing the Splat hash" {
195+
# Arrange
196+
$SplatHash = @{
197+
Script=".\test.ps1";
198+
Tag = @('Dev','WebServers');
199+
PassThru=$False;
200+
Quiet=$True;
201+
OutputFormat="XML";
202+
OutputFile="dummy.xml";
203+
TestName='Dummy test';
204+
}
205+
$OrigSplatHash = $SplatHash.Clone()
206+
$ExcludedKeys = @('Script','PassThru','Quiet','OutputFormat','OutputFile')
207+
208+
# Act
209+
$Result = Sanitize-PesterSplatHash -SplatHash $SplatHash
210+
211+
# Assert
212+
213+
foreach ($key in $OrigSplatHash.keys) {
214+
215+
if ($ExcludedKeys -contains $key) {
216+
It "Should NOT have the reserved key -> $key in the Splat hash" {
217+
$SplatHash.ContainsKey($key) | Should Be $False
218+
}
219+
}
220+
else {
221+
It "Should retain the non-reserved key -> $key in the Splat hash" {
222+
$SplatHash.ContainsKey($key) | Should Be $True
223+
}
224+
}
225+
226+
}
227+
228+
It "Should not return anything" {
229+
$Result | Should Be $Null
230+
}
231+
232+
}
233+
}
191234
}

appveyor.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ build: false
2323

2424
#Kick off the CI/CD pipeline
2525
test_script:
26-
- ps: . .\build.ps1
26+
- ps: . .\build\Start-Build.ps1 -Task Build
27+
2728

2829
# Block the RDP for debugging
2930
#on_finish:

build.ps1

-54
This file was deleted.

0 commit comments

Comments
 (0)