kitchen-pester 0.11.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09484ef2e5522d84210a1a61d4b073e69cf0c16b9c1c04675fcb9ef0d8c734e2'
4
- data.tar.gz: 4d503946de43116dd54786eba30913c6f095e1b4bbc9d5bbce72417e0a7cac2d
3
+ metadata.gz: 5f323e411287a34d2008dc58aba57ced9995e39511e77207031606c01a5b258d
4
+ data.tar.gz: 45031fdce5ecef091be46aa44e3934afe21e4233d3d1ba469ba2c026105be145
5
5
  SHA512:
6
- metadata.gz: ed143b476765bb34ee3897284f377e28e9fdd6f60072035d59007db91f7a774e5c0d0c0e975aafb856d8042796abbce77cb954ca3c53a43ba25b05bb7af5cdbd
7
- data.tar.gz: 53e54218891590a598aee9718cb5d9137f569784bf3864b8d81a7a9d860a83f8dd033d542c99e51404fe4a75de66a991bce734d4628f8823f52d0b477519c5bc
6
+ metadata.gz: f1bfc59bc045bd3fe2c3313d6f0fb202f7a8f5887017251610546df15b0b1a644fbd75b4222bc7b9fdad80cd9a97faf4c0abd65eee36c8b54c3aeccc14ed43fd
7
+ data.tar.gz: 64f70c980a546c5d930f26bbea48e449f4da064feac617bffddd3fbef77649324e7204754af8fdacb41d79594e03bfcbcc779cf90f12b0766f519758070f36f1
data/Gemfile CHANGED
@@ -11,12 +11,12 @@ group :integration do
11
11
  end
12
12
 
13
13
  group :changelog do
14
- gem "github_changelog_generator", "1.15.0"
14
+ gem "github_changelog_generator", "1.16.4"
15
15
  end
16
16
 
17
17
  group :debug do
18
- gem "pry"
19
- gem "pry-byebug"
18
+ gem "pry", "~>0.13.1"
19
+ gem "pry-byebug", "~>3.9.0"
20
20
  gem "pry-stack_explorer"
21
21
  end
22
22
 
data/Rakefile CHANGED
@@ -1,5 +1,3 @@
1
- # -*- encoding: utf-8 -*-
2
-
3
1
  require "bundler/gem_tasks"
4
2
 
5
3
  require "rake/testtask"
@@ -25,7 +23,7 @@ desc "Run all quality tasks"
25
23
  task quality: :style
26
24
 
27
25
  begin
28
- require "yard"
26
+ require "yard" unless defined?(YARD)
29
27
  YARD::Rake::YardocTask.new
30
28
  rescue LoadError
31
29
  puts "yard is not available. (sudo) gem install yard to generate yard documentation."
@@ -1,5 +1,4 @@
1
- # encoding: utf-8
2
- lib = File.expand_path("../lib", __FILE__)
1
+ lib = File.expand_path("lib", __dir__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
 
5
4
  require "kitchen/verifier/pester_version"
@@ -1,5 +1,3 @@
1
- # -*- encoding: utf-8 -*-
2
- #
3
1
  # Author:: Steven Murawski (<steven.murawski@gmail.com>)
4
2
  #
5
3
  # Copyright (C) 2015, Steven Murawski
@@ -16,9 +14,12 @@
16
14
  # See the License for the specific language governing permissions and
17
15
  # limitations under the License.
18
16
 
19
- require "pathname"
17
+ require "fileutils" unless defined?(FileUtils)
18
+ require "pathname" unless defined?(Pathname)
19
+ require "kitchen/util"
20
20
  require "kitchen/verifier/base"
21
21
  require "kitchen/version"
22
+ require "base64" unless defined?(Base64)
22
23
  require_relative "pester_version"
23
24
 
24
25
  module Kitchen
@@ -32,9 +33,39 @@ module Kitchen
32
33
  plugin_version Kitchen::Verifier::PESTER_VERSION
33
34
 
34
35
  default_config :restart_winrm, false
35
- default_config :test_folder
36
- default_config :use_local_pester_module, false
37
- default_config :downloads, ["./PesterTestResults.xml"] => "./testresults"
36
+ default_config :test_folder, "tests"
37
+ default_config :remove_builtin_powershellget, true
38
+ default_config :remove_builtin_pester, true
39
+ default_config :skip_pester_install, false
40
+ default_config :bootstrap, {
41
+ repository_url: "https://www.powershellgallery.com/api/v2",
42
+ modules: [],
43
+ }
44
+ default_config :register_repository, []
45
+ default_config :pester_install, {
46
+ SkipPublisherCheck: true,
47
+ Force: true,
48
+ ErrorAction: "Stop",
49
+ }
50
+ default_config :pester_configuration, {
51
+ run: {
52
+ path: ".",
53
+ PassThru: true,
54
+ },
55
+ TestResult: {
56
+ Enabled: true,
57
+ OutputPath: "PesterTestResults.xml",
58
+ TestSuiteName: "",
59
+ },
60
+ Output: {
61
+ Verbosity: "Detailed",
62
+ },
63
+ }
64
+ default_config :install_modules, []
65
+ default_config :downloads, { "./PesterTestResults.xml" => "./testresults/" }
66
+ default_config :copy_folders, []
67
+ default_config :sudo, false
68
+ default_config :shell, nil
38
69
 
39
70
  # Creates a new Verifier object using the provided configuration data
40
71
  # which will be merged with any default configuration.
@@ -64,21 +95,56 @@ module Kitchen
64
95
  # end
65
96
  def create_sandbox
66
97
  super
67
- prepare_powershell_modules
98
+ prepare_supporting_psmodules
99
+ prepare_copy_folders
68
100
  prepare_pester_tests
69
101
  prepare_helpers
102
+
103
+ debug("\n\n")
104
+ debug("Sandbox content:\n")
105
+ list_files(sandbox_path).each do |f|
106
+ debug(" #{f}")
107
+ end
70
108
  end
71
109
 
72
110
  # Generates a command string which will install and configure the
73
111
  # verifier software on an instance. If no work is required, then `nil`
74
112
  # will be returned.
113
+ # PowerShellGet & Pester Bootstrap are done in prepare_command (after sandbox is transferred)
114
+ # so that we can use the PesterUtil.psm1
75
115
  #
76
116
  # @return [String] a command string
77
117
  def install_command
78
- return if local_suite_files.empty?
79
- return if config[:use_local_pester_module]
118
+ # the sandbox has not yet been copied to the SUT.
119
+ install_command_string = <<-PS1
120
+ Write-Verbose 'Running Install Command...'
121
+ $modulesToRemove = @(
122
+ if ($#{config[:remove_builtin_powershellget]}) {
123
+ Get-module -ListAvailable -FullyQualifiedName @{ModuleName = 'PackageManagement'; RequiredVersion = '1.0.0.1'}
124
+ Get-module -ListAvailable -FullyQualifiedName @{ModuleName = 'PowerShellGet'; RequiredVersion = '1.0.0.1'}
125
+ }
80
126
 
81
- really_wrap_shell_code(install_command_script)
127
+ if ($#{config[:remove_builtin_pester]}) {
128
+ Get-module -ListAvailable -FullyQualifiedName @{ModuleName = 'Pester'; RequiredVersion = '3.4.0'}
129
+ }
130
+ )
131
+
132
+ if ($modulesToRemove.ModuleBase.Count -eq 0) {
133
+ # for PS7 on linux
134
+ return
135
+ }
136
+
137
+ $modulesToRemove.ModuleBase | Foreach-Object {
138
+ $ModuleBaseLeaf = Split-Path -Path $_ -Leaf
139
+ if ($ModuleBaseLeaf -as [System.version]) {
140
+ Remove-Item -force -Recurse (Split-Path -Parent -Path $_) -ErrorAction SilentlyContinue
141
+ }
142
+ else {
143
+ Remove-Item -force -Recurse $_ -ErrorAction SilentlyContinue
144
+ }
145
+ }
146
+ PS1
147
+ really_wrap_shell_code(Util.outdent!(install_command_string))
82
148
  end
83
149
 
84
150
  # Generates a command string which will perform any data initialization
@@ -97,7 +163,11 @@ module Kitchen
97
163
  # required, then `nil` will be returned.
98
164
  #
99
165
  # @return [String] a command string
100
- def prepare_command; end
166
+ def prepare_command
167
+ info("Preparing the SUT and Pester dependencies...")
168
+ resolve_downloads_paths!
169
+ really_wrap_shell_code(install_command_script)
170
+ end
101
171
 
102
172
  # Generates a command string which will invoke the main verifier
103
173
  # command on the prepared instance. If no work is required, then `nil`
@@ -105,9 +175,38 @@ module Kitchen
105
175
  #
106
176
  # @return [String] a command string
107
177
  def run_command
108
- return if local_suite_files.empty?
178
+ really_wrap_shell_code(invoke_pester_scriptblock)
179
+ end
109
180
 
110
- really_wrap_shell_code(run_command_script)
181
+ # Resolves the remote Downloads path from the verifier root path,
182
+ # unless they're absolute path (starts with / or C:\)
183
+ # This updates the config[:downloads], nothing (nil) is returned.
184
+ #
185
+ # @return [nil] updates config downloads
186
+ def resolve_downloads_paths!
187
+ info("Resolving Downloads path from config.")
188
+ config[:downloads] = config[:downloads]
189
+ .map do |source, destination|
190
+ source = source.to_s
191
+ info(" resolving remote source's absolute path.")
192
+ unless source.match?('^/|^[a-zA-Z]:[\\/]') # is Absolute?
193
+ info(" '#{source}' is a relative path, resolving to: #{File.join(config[:root_path], source)}")
194
+ source = File.join(config[:root_path], source.to_s).to_s
195
+ end
196
+
197
+ if destination.match?('\\$|/$') # is Folder (ends with / or \)
198
+ destination = File.join(destination, File.basename(source)).to_s
199
+ end
200
+ info(" Destination: #{destination}")
201
+ if !File.directory?(File.dirname(destination))
202
+ FileUtils.mkdir_p(File.dirname(destination))
203
+ else
204
+ info(" Directory #{File.dirname(destination)} seem to exist.")
205
+ end
206
+
207
+ [ source, destination ]
208
+ end
209
+ nil # make sure we do not return anything
111
210
  end
112
211
 
113
212
  # Download functionality was added to the base verifier behavior after
@@ -115,128 +214,304 @@ module Kitchen
115
214
  if Gem::Version.new(Kitchen::VERSION) <= Gem::Version.new("2.3.4")
116
215
  def call(state)
117
216
  super
118
-
119
- download_test_files(state)
217
+ ensure
218
+ info("Ensure download test files.")
219
+ download_test_files(state) unless config[:downloads].nil?
220
+ info("Download complete.")
221
+ end
222
+ else
223
+ def call(state)
224
+ super
225
+ rescue
226
+ # If the verifier reports failure, we need to download the files ourselves.
227
+ # Test Kitchen's base verifier doesn't have the download in an `ensure` block.
228
+ info("Rescue to download test files.")
229
+ download_test_files(state) unless config[:downloads].nil?
230
+ # Rethrow original exception, we still want to register the failure.
231
+ raise
120
232
  end
121
233
  end
122
234
 
123
235
  # private
124
- def run_command_script
125
- <<-CMD
126
- Import-Module Pester -Force
127
- $TestPath = "#{config[:root_path]}"
128
- $OutputFilePath = $TestPath | Join-Path -ChildPath 'PesterTestResults.xml'
129
-
130
- $result = Invoke-Pester -OutputFile $OutputFilePath -OutputFormat NUnitXml -Path $TestPath -Passthru
131
- $result | Export-CliXml -Path (Join-Path -Path $TestPath -ChildPath 'result.xml')
236
+ def invoke_pester_scriptblock
237
+ <<-PS1
238
+ $PesterModule = Import-Module -Name Pester -Force -ErrorAction Stop -PassThru
239
+
240
+ $TestPath = Join-Path "#{config[:root_path]}" -ChildPath "suites"
241
+ $OutputFilePath = Join-Path "#{config[:root_path]}" -ChildPath 'PesterTestResults.xml'
242
+
243
+ if ($PesterModule.Version.Major -le 4)
244
+ {
245
+ Write-Host -Object "Invoke Pester with v$($PesterModule.Version) Options"
246
+ $options = New-PesterOption -TestSuiteName "Pester - #{instance.to_str}"
247
+ $defaultPesterParameters = @{
248
+ Script = $TestPath
249
+ OutputFile = $OutputFilePath
250
+ OutputFormat = 'NUnitXml'
251
+ PassThru = $true
252
+ PesterOption = $options
253
+ }
254
+
255
+ $pesterCmd = Get-Command -Name 'Invoke-Pester'
256
+ $pesterConfig = #{ps_hash(config[:pester_configuration])}
257
+ $invokePesterParams = @{}
258
+
259
+ foreach ($paramName in $pesterCmd.Parameters.Keys)
260
+ {
261
+ $paramValue = $pesterConfig.($paramName)
262
+
263
+ if ($paramValue) {
264
+ Write-Host -Object "Using $paramName from Yaml config."
265
+ $invokePesterParams[$paramName] = $paramValue
266
+ }
267
+ elseif ($defaultPesterParameters.ContainsKey($paramName))
268
+ {
269
+ Write-Host -Object "Using $paramName from Defaults: $($defaultPesterParameters[$paramName])."
270
+ $invokePesterParams[$paramName] = $defaultPesterParameters[$paramName]
271
+ }
272
+ }
273
+
274
+ $result = Invoke-Pester @invokePesterParams
275
+ }
276
+ else
277
+ {
278
+ Write-Host -Object "Invoke Pester with v$($PesterModule.Version) Configuration."
279
+ $pesterConfigHash = #{ps_hash(config[:pester_configuration])}
280
+
281
+ if (-not $pesterConfigHash.ContainsKey('run')) {
282
+ $pesterConfigHash['run'] = @{}
283
+ }
284
+
285
+ if (-not $pesterConfigHash.ContainsKey('TestResult')) {
286
+ $pesterConfigHash['TestResult'] = @{}
287
+ }
288
+
289
+ if (-not $pesterConfigHash.run.path) {
290
+ $pesterConfigHash['run']['path'] = $TestPath
291
+ }
292
+
293
+ if (-not $pesterConfigHash.TestResult.TestSuiteName) {
294
+ $pesterConfigHash['TestResult']['TestSuiteName'] = 'Pester - #{instance.to_str}'
295
+ }
296
+
297
+ if (-not $pesterConfigHash.TestResult.OutputPath) {
298
+ $pesterConfigHash['TestResult']['OutputPath'] = $OutputFilePath
299
+ }
300
+
301
+ $PesterConfig = New-PesterConfiguration -Hashtable $pesterConfigHash
302
+ $result = Invoke-Pester -Configuration $PesterConfig
303
+ }
304
+
305
+ $resultXmlPath = (Join-Path -Path $TestPath -ChildPath 'result.xml')
306
+ if (Test-Path -Path $resultXmlPath) {
307
+ $result | Export-CliXml -Path
308
+ }
309
+
132
310
  $LASTEXITCODE = $result.FailedCount
133
311
  $host.SetShouldExit($LASTEXITCODE)
312
+
134
313
  exit $LASTEXITCODE
135
- CMD
314
+ PS1
315
+ end
316
+
317
+ def get_powershell_modules_from_nugetapi
318
+ # don't return anything is the modules subkey or bootstrap is null
319
+ return if config.dig(:bootstrap, :modules).nil?
320
+
321
+ bootstrap = config[:bootstrap]
322
+ # if the repository url is set, use that as parameter to Install-ModuleFromNuget. Default is the PSGallery url
323
+ gallery_url_param = bootstrap[:repository_url] ? "-GalleryUrl '#{bootstrap[:repository_url]}'" : ""
324
+
325
+ info("Bootstrapping environment without PowerShellGet Provider...")
326
+ Array(bootstrap[:modules]).map do |powershell_module|
327
+ if powershell_module.is_a? Hash
328
+ <<-PS1
329
+ ${#{powershell_module[:Name]}} = #{ps_hash(powershell_module)}
330
+
331
+ Install-ModuleFromNuget -Module ${#{powershell_module[:Name]}} #{gallery_url_param}
332
+ PS1
333
+ else
334
+ <<-PS1
335
+ Install-ModuleFromNuget -Module @{Name = '#{powershell_module}'} #{gallery_url_param}
336
+ PS1
337
+ end
338
+ end
339
+ end
340
+
341
+ # Returns the string command to set a PS Repository
342
+ # for each PSRepo configured.
343
+ #
344
+ # @return [Array<String>] array of suite files
345
+ # @api private
346
+ def register_psrepository_scriptblock
347
+ return if config[:register_repository].nil?
348
+
349
+ info("Registering a new PowerShellGet Repository")
350
+ Array(config[:register_repository]).map do |psrepo|
351
+ # Using Set-PSRepo from ../../*/*/*/PesterUtil.psm1
352
+ debug("Command to set PSRepo #{psrepo[:Name]}.")
353
+ <<-PS1
354
+ Write-Host 'Registering psrepo #{psrepo[:Name]}...'
355
+ ${#{psrepo[:Name]}} = #{ps_hash(psrepo)}
356
+ Set-PSRepo -Repository ${#{psrepo[:Name]}}
357
+ PS1
358
+ end
359
+ end
360
+
361
+ # Returns the string command set the PSGallery as trusted, and
362
+ # Install Pester from gallery based on the params from Pester_install_params config
363
+ #
364
+ # @return <String> command to install Pester Module
365
+ # @api private
366
+ def install_pester
367
+ return if config[:skip_pester_install]
368
+
369
+ pester_install_params = config[:pester_install] || {}
370
+ <<-PS1
371
+ if ((Get-PSRepository -Name PSGallery).InstallationPolicy -ne 'Trusted') {
372
+ Write-Host -Object "Trusting the PSGallery to install Pester without -Force"
373
+ Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -ErrorAction SilentlyContinue
374
+ }
375
+
376
+ Write-Host "Installing Pester..."
377
+ $installPesterParams = #{ps_hash(pester_install_params)}
378
+ $installPesterParams['Name'] = 'Pester'
379
+ Install-module @installPesterParams
380
+ Write-Host 'Pester Installed.'
381
+ PS1
382
+ end
383
+
384
+ # returns a piece of PS scriptblock for each Module to install
385
+ # from gallery that has been sepcified in install_modules config.
386
+ #
387
+ # @return [Array<String>] array of PS commands.
388
+ # @api private
389
+ def install_modules_from_gallery
390
+ return if config[:install_modules].nil?
391
+
392
+ Array(config[:install_modules]).map do |powershell_module|
393
+ if powershell_module.is_a? Hash
394
+ # Sanitize variable name so that $powershell-yaml becomes $powershell_yaml
395
+ module_name = powershell_module[:Name].gsub(/[\W]/, "_")
396
+ # so we can splat that variable to install module
397
+ <<-PS1
398
+ $#{module_name} = #{ps_hash(powershell_module)}
399
+ Write-Host -NoNewline 'Installing #{module_name}'
400
+ Install-Module @#{module_name}
401
+ Write-host '... done.'
402
+ PS1
403
+ else
404
+ <<-PS1
405
+ Write-host -NoNewline 'Installing #{powershell_module} ...'
406
+ Install-Module -Name '#{powershell_module}'
407
+ Write-host '... done.'
408
+ PS1
409
+ end
410
+ end
136
411
  end
137
412
 
138
413
  def really_wrap_shell_code(code)
139
- wrap_shell_code(Util.outdent!(use_local_powershell_modules(code)))
414
+ windows_os? ? really_wrap_windows_shell_code(code) : really_wrap_posix_shell_code(code)
415
+ end
416
+
417
+ # Get the defined shell or fall back to pwsh, unless we're on windows where we use powershell
418
+ # call via sudo if sudo is true.
419
+ # This allows to use pwsh-preview instead of pwsh, or a full path to a specific binary.
420
+ def shell_cmd
421
+ if !config[:shell].nil?
422
+ config[:sudo] ? "sudo #{config[:shell]}" : "#{config[:shell]}"
423
+ elsif windows_os?
424
+ "powershell"
425
+ else
426
+ config[:sudo] ? "sudo pwsh" : "pwsh"
427
+ end
428
+ end
429
+
430
+ def really_wrap_windows_shell_code(code)
431
+ my_command = <<-PWSH
432
+ echo "Running as '$(whoami)'..."
433
+ New-Item -ItemType Directory -Path '#{config[:root_path]}/modules' -Force -ErrorAction SilentlyContinue
434
+ Set-Location -Path "#{config[:root_path]}"
435
+ # Send the pwsh here string to the file kitchen_cmd.ps1
436
+ @'
437
+ try {
438
+ Set-ExecutionPolicy Unrestricted -force
439
+ }
440
+ catch {
441
+ $_ | Out-String | Write-Warning
442
+ }
443
+ #{Util.outdent!(use_local_powershell_modules(code))}
444
+ '@ | Set-Content -Path kitchen_cmd.ps1 -Encoding utf8 -Force -ErrorAction 'Stop'
445
+ # create the modules folder, making sure it's done as current user (not root)
446
+ #
447
+ # Invoke the created kitchen_cmd.ps1 file using pwsh
448
+ #{shell_cmd} ./kitchen_cmd.ps1
449
+ PWSH
450
+ wrap_shell_code(Util.outdent!(my_command))
451
+ end
452
+
453
+ # Writing the command to a ps1 file, adding the pwsh shebang
454
+ # invoke the file
455
+ def really_wrap_posix_shell_code(code)
456
+ my_command = <<-BASH
457
+ echo "Running as '$(whoami)'"
458
+ # create the modules folder, making sure it's done as current user (not root)
459
+ mkdir -p #{config[:root_path]}/modules
460
+ cd #{config[:root_path]}
461
+ # Send the bash heredoc 'EOF' to the file kitchen_cmd.ps1 using the tool cat
462
+ cat << 'EOF' > kitchen_cmd.ps1
463
+ #!/usr/bin/env pwsh
464
+ #{Util.outdent!(use_local_powershell_modules(code))}
465
+ EOF
466
+ chmod +x kitchen_cmd.ps1
467
+ # Invoke the created kitchen_cmd.ps1 file using pwsh
468
+ #{shell_cmd} ./kitchen_cmd.ps1
469
+ BASH
470
+
471
+ debug(Util.outdent!(my_command))
472
+ Util.outdent!(my_command)
140
473
  end
141
474
 
142
475
  def use_local_powershell_modules(script)
143
- <<-EOH
144
- set-executionpolicy unrestricted -force;
476
+ <<-PS1
477
+ Write-Host -Object ("{0} - PowerShell {1}" -f $PSVersionTable.OS,$PSVersionTable.PSVersion)
145
478
  $global:ProgressPreference = 'SilentlyContinue'
146
- $env:psmodulepath += ";$(join-path (resolve-path $env:temp).path 'verifier/modules')"
479
+ $PSModPathToPrepend = Join-Path "#{config[:root_path]}" -ChildPath 'modules'
480
+ Write-Verbose "Adding '$PSModPathToPrepend' to `$Env:PSModulePath."
481
+ if (!$isLinux -and -not (Test-Path -Path $PSModPathToPrepend)) {
482
+ # if you create this folder now in Linux, it may run as root (via sudo).
483
+ $null = New-Item -Path $PSModPathToPrepend -Force -ItemType Directory
484
+ }
485
+
486
+ if ($Env:PSModulePath.Split([io.path]::PathSeparator) -notcontains $PSModPathToPrepend) {
487
+ $env:PSModulePath = @($PSModPathToPrepend, $env:PSModulePath) -Join [io.path]::PathSeparator
488
+ }
489
+
147
490
  #{script}
148
- EOH
491
+ PS1
149
492
  end
150
493
 
151
494
  def install_command_script
152
- <<-EOH
153
- [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
154
- function directory($path){
155
- if (test-path $path) {(resolve-path $path).providerpath}
156
- else {(resolve-path (mkdir $path)).providerpath}
157
- }
158
- $VerifierModulePath = directory $env:temp/verifier/modules
159
- $VerifierTestsPath = directory $env:temp/verifier/pester
495
+ <<-PS1
496
+ $PSModPathToPrepend = "#{config[:root_path]}"
160
497
 
161
- $env:psmodulepath += ";$VerifierModulePath"
162
- function test-module($module){
163
- (get-module $module -list) -ne $null
164
- }
165
- if (-not (test-module pester)) {
166
- if (test-module PowerShellGet){
167
- import-module PowerShellGet -force
168
- import-module PackageManagement -force
169
- get-packageprovider -name NuGet -force | out-null
170
- install-module Pester -force
171
- }
172
- else {
173
- if (-not (test-module PsGet)){
174
- $wc = New-Object -TypeName Net.WebClient
175
-
176
- if($env:http_Proxy){
177
- if($env:no_proxy){
178
- Write-Output "Creating WebProxy with 'http_proxy' and 'no_proxy' environment variables.
179
- $webproxy = New-Object System.Net.WebProxy($env:http_Proxy,$true,$env:no_proxy)
180
- }else{
181
- Write-Output "Creating WebProxy with 'http_proxy' environment variable.
182
- $webproxy = New-Object -TypeName System.Net.WebProxy -ArgumentList ($env:http_Proxy)
183
- }
498
+ Import-Module -ErrorAction Stop PesterUtil
184
499
 
185
- $wc.proxy = $webproxy
186
- }
500
+ #{get_powershell_modules_from_nugetapi.join("\n") unless config.dig(:bootstrap, :modules).nil?}
187
501
 
188
- Invoke-Expression -Command $wc.DownloadString('http://bit.ly/GetPsGet')
189
- }
190
- try {
191
- import-module psget -force -erroraction stop
192
- Install-Module Pester
193
- }
194
- catch {
195
- Write-Output "Installing from Github"
196
- $zipfile = join-path(resolve-path "$env:temp/verifier") "pester.zip"
197
- if (-not (test-path $zipfile)){
198
- $source = 'https://github.com/pester/Pester/archive/3.3.14.zip'
199
- $wc = New-Object -TypeName Net.WebClient
200
-
201
- if($env:http_Proxy){
202
- if($env:no_proxy){
203
- Write-Output "Creating WebProxy with 'http_proxy' and 'no_proxy' environment variables."
204
- $webproxy = New-Object System.Net.WebProxy($env:http_Proxy,$true,$env:no_proxy)
205
- }else{
206
- Write-Output "Creating WebProxy with 'http_proxy' environment variable."
207
- $webproxy = New-Object -TypeName System.Net.WebProxy -ArgumentList ($env:http_Proxy)
208
- }
209
-
210
- $wc.proxy = $webproxy
211
- }
212
-
213
- [byte[]]$bytes = $wc.DownloadData($source)
214
- [IO.File]::WriteAllBytes($zipfile, $bytes)
215
- $bytes = $null
216
- [gc]::collect()
217
- write-output "Downloaded Pester.zip"
218
- }
219
- write-output "Creating Shell.Application COM object"
220
- $shellcom = new-object -com shell.application
221
- Write-Output "Creating COM object for zip file."
222
- $zipcomobject = $shellcom.namespace($zipfile)
223
- Write-Output "Creating COM object for module destination."
224
- $destination = $shellcom.namespace($VerifierModulePath)
225
- Write-Output "Unpacking zip file."
226
- $destination.CopyHere($zipcomobject.Items(), 0x610)
227
- rename-item (join-path $VerifierModulePath "Pester-3.3.14") -newname 'Pester' -force
228
- }
229
- }
230
- }
231
- if (-not (test-module Pester)) {
232
- throw "Unable to install Pester. Please include Pester in your base image or install during your converge."
233
- }
234
- EOH
502
+ #{register_psrepository_scriptblock.join("\n") unless config[:register_repository].nil?}
503
+
504
+ #{install_pester}
505
+
506
+ #{install_modules_from_gallery.join("\n") unless config[:install_modules].nil?}
507
+ PS1
235
508
  end
236
509
 
237
510
  def restart_winrm_service
511
+ return unless verifier.windows_os?
512
+
238
513
  cmd = "schtasks /Create /TN restart_winrm /TR " \
239
- '"powershell -command restart-service winrm" ' \
514
+ '"powershell -Command Restart-Service winrm" ' \
240
515
  "/SC ONCE /ST 00:00 "
241
516
  wrap_shell_code(Util.outdent!(<<-CMD
242
517
  #{cmd}
@@ -246,19 +521,16 @@ module Kitchen
246
521
  end
247
522
 
248
523
  def download_test_files(state)
249
- info("Downloading test result files from #{instance.to_str}")
524
+ if config[:downloads].nil?
525
+ info("Skipped downloading test result file from #{instance.to_str}; 'downloads' hash is empty.")
526
+ return
527
+ end
250
528
 
529
+ info("Downloading test result files from #{instance.to_str}")
251
530
  instance.transport.connection(state) do |conn|
252
- config[:downloads].to_h.each do |remotes, local|
253
- debug("Downloading #{Array(remotes).join(", ")} to #{local}")
254
-
255
- remotes.each do |file|
256
- safe_name = instance.name.gsub(/[^0-9A-Z-]/i, "_")
257
- local_path = File.join(local, safe_name, file)
258
- remote_path = File.join(config[:root_path], file)
259
-
260
- conn.download(remote_path, local_path)
261
- end
531
+ config[:downloads].each do |remotes, local|
532
+ debug("downloading #{Array(remotes).join(", ")} to #{local}")
533
+ conn.download(remotes, local)
262
534
  end
263
535
  end
264
536
 
@@ -271,29 +543,25 @@ module Kitchen
271
543
  #
272
544
  # @return [Array<String>] array of suite files
273
545
  # @api private
274
-
275
546
  def suite_test_folder
276
547
  @suite_test_folder ||= File.join(test_folder, config[:suite_name])
277
548
  end
278
549
 
279
- def suite_level_glob
280
- Dir.glob(File.join(suite_test_folder, "*"))
281
- end
282
-
283
- def suite_verifier_level_glob
284
- Dir.glob(File.join(suite_test_folder, "*/**/*"))
285
- end
286
-
287
- def local_suite_files
288
- suite = suite_level_glob
289
- suite_verifier = suite_verifier_level_glob
290
- (suite << suite_verifier).flatten!.reject do |f|
291
- File.directory?(f)
292
- end
550
+ # Returns the current file's parent folder's full path.
551
+ #
552
+ # @return [string]
553
+ # @api private
554
+ def script_root
555
+ @script_root ||= File.dirname(__FILE__)
293
556
  end
294
557
 
295
- def sandboxify_path(path)
296
- File.join(sandbox_path, path.sub(%r{#{suite_test_folder}/}i, ""))
558
+ # Returns the absolute path of the Supporting PS module to
559
+ # be copied to the SUT via the Sandbox.
560
+ #
561
+ # @return [string]
562
+ # @api private
563
+ def support_psmodule_folder
564
+ @support_psmodule_folder ||= Pathname.new(File.join(script_root, "../../support/modules/PesterUtil")).cleanpath
297
565
  end
298
566
 
299
567
  # Returns an Array of common helper filenames currently residing on the
@@ -321,50 +589,128 @@ module Kitchen
321
589
  end
322
590
  end
323
591
 
324
- # Copies all test suite files into the suites directory in the sandbox.
592
+ # Creates a PowerShell hashtable from a ruby map.
593
+ # The only types supported for now are hash, array, string and Boolean.
325
594
  #
326
595
  # @api private
327
- def prepare_pester_tests
328
- info("Preparing to copy files from #{suite_test_folder} to the SUT.")
596
+ def ps_hash(obj, depth = 0)
597
+ if [true, false].include? obj
598
+ %{$#{obj}} # Return $true or $false when value is a bool
599
+ elsif obj.is_a?(Hash)
600
+ obj.map do |k, v|
601
+ # Format "Key = Value" enabling recursion
602
+ %{#{pad(depth + 2)}#{ps_hash(k)} = #{ps_hash(v, depth + 2)}}
603
+ end
604
+ .join("\n") # append \n to the key/value definitions
605
+ .insert(0, "@{\n") # prepend @{\n
606
+ .insert(-1, "\n#{pad(depth)}}\n") # append \n}\n
607
+
608
+ elsif obj.is_a?(Array)
609
+ array_string = obj.map { |v| ps_hash(v, depth + 4) }.join(",")
610
+ "#{pad(depth)}@(\n#{array_string}\n)"
611
+ else
612
+ # When the object is not a string nor a hash or array, it will be quoted as a string.
613
+ # In most cases, PS is smart enough to convert back to the type it needs.
614
+ "'" + obj.to_s + "'"
615
+ end
616
+ end
329
617
 
330
- local_suite_files.each do |src|
331
- dest = sandboxify_path(src)
332
- debug("Copying #{src} to #{dest}")
333
- FileUtils.mkdir_p(File.dirname(dest))
334
- FileUtils.cp(src, dest, preserve: true)
618
+ # returns the path of the modules subfolder
619
+ # in the sandbox, where PS Modules and folders will be copied to.
620
+ #
621
+ # @api private
622
+ def sandbox_module_path
623
+ File.join(sandbox_path, "modules")
624
+ end
625
+
626
+ # copy files into the 'modules' folder of the sandbox,
627
+ # so that copied folders can be discovered with the updated $Env:PSModulePath.
628
+ #
629
+ # @api private
630
+ def prepare_copy_folders
631
+ return if config[:copy_folders].nil?
632
+
633
+ info("Preparing to copy specified folders to #{sandbox_module_path}.")
634
+ kitchen_root_path = config[:kitchen_root]
635
+ config[:copy_folders].each do |folder|
636
+ debug("copying #{folder}")
637
+ folder_to_copy = File.join(kitchen_root_path, folder)
638
+ copy_if_src_exists(folder_to_copy, sandbox_module_path)
335
639
  end
336
640
  end
337
641
 
338
- def prepare_powershell_module(name)
339
- FileUtils.mkdir_p(File.join(sandbox_path, "modules/#{name}"))
340
- FileUtils.cp(
341
- File.join(File.dirname(__FILE__), "../../support/powershell/#{name}/#{name}.psm1"),
342
- File.join(sandbox_path, "modules/#{name}/#{name}.psm1"),
343
- preserve: true
344
- )
642
+ # returns an array of string
643
+ # Creates a flat list of files contained in a folder.
644
+ # This is useful when trying to debug what has been copied to
645
+ # the sandbox.
646
+ #
647
+ # @return [Array<String>] array of files in a folder
648
+ # @api private
649
+ def list_files(path)
650
+ base_directory_content = Dir.glob(File.join(path, "*"))
651
+ nested_directory_content = Dir.glob(File.join(path, "*/**/*"))
652
+ [base_directory_content, nested_directory_content].flatten
345
653
  end
346
654
 
347
- def prepare_powershell_modules
348
- info("Preparing to copy supporting powershell modules.")
349
- %w{PesterUtil}.each do |module_name|
350
- prepare_powershell_module module_name
655
+ # Copies all test suite files into the suites directory in the sandbox.
656
+ #
657
+ # @api private
658
+ def prepare_pester_tests
659
+ info("Preparing to copy files from '#{suite_test_folder}' to the SUT.")
660
+ sandboxed_suites_path = File.join(sandbox_path, "suites")
661
+ copy_if_src_exists(suite_test_folder, sandboxed_suites_path)
662
+ end
663
+
664
+ def prepare_supporting_psmodules
665
+ info("Preparing to copy files from '#{support_psmodule_folder}' to the SUT.")
666
+ sandbox_module_path = File.join(sandbox_path, "modules")
667
+ copy_if_src_exists(support_psmodule_folder, sandbox_module_path)
668
+ end
669
+
670
+ # Copies a folder recursively preserving its layers,
671
+ # mostly used to copy to the sandbox.
672
+ #
673
+ # @api private
674
+ def copy_if_src_exists(src_to_validate, destination)
675
+ unless Dir.exist?(src_to_validate)
676
+ info("The path #{src_to_validate} was not found. Not copying to #{destination}.")
677
+ return
678
+ end
679
+
680
+ info("Moving #{src_to_validate} to #{destination}")
681
+ unless Dir.exist?(destination)
682
+ FileUtils.mkdir_p(destination)
683
+ debug("Folder '#{destination}' created.")
351
684
  end
685
+ FileUtils.mkdir_p(File.join(destination, "__bugfix"))
686
+ FileUtils.cp_r(src_to_validate, destination, preserve: true)
352
687
  end
353
688
 
689
+ # returns the absolute path of the folders containing the
690
+ # test suites, use default if not set.
691
+ #
692
+ # @api private
354
693
  def test_folder
355
- return config[:test_base_path] if config[:test_folder].nil?
356
-
357
- absolute_test_folder
694
+ config[:test_folder].nil? ? config[:test_base_path] : absolute_test_folder
358
695
  end
359
696
 
697
+ # returns the absolute path of the relative folders containing the
698
+ # test suites, use default i not set.
699
+ #
700
+ # @api private
360
701
  def absolute_test_folder
361
702
  path = (Pathname.new config[:test_folder]).realpath
362
703
  integration_path = File.join(path, "integration")
363
- return path unless Dir.exist?(integration_path)
364
-
365
- integration_path
704
+ Dir.exist?(integration_path) ? integration_path : path
366
705
  end
367
706
 
707
+ # returns a string of space of the specified depth.
708
+ # This is used to pad messages or when building PS hashtables.
709
+ #
710
+ # @api private
711
+ def pad(depth = 0)
712
+ " " * depth
713
+ end
368
714
  end
369
715
  end
370
716
  end
@@ -1,5 +1,5 @@
1
1
  module Kitchen
2
2
  module Verifier
3
- PESTER_VERSION = "0.11.0".freeze
3
+ PESTER_VERSION = "1.1.0".freeze
4
4
  end
5
5
  end
@@ -0,0 +1,158 @@
1
+ [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
2
+
3
+ function Install-ModuleFromNuget {
4
+ [CmdletBinding()]
5
+ param (
6
+ [hashtable]
7
+ $Module,
8
+
9
+ [string]
10
+ $GalleryUrl = 'https://www.powershellgallery.com/api/v2'
11
+ )
12
+
13
+ $tempPath = [System.IO.Path]::GetTempPath()
14
+ $zipFileName = "{0}.{1}.{2}" -f $Module.Name, $Module.Version, 'zip'
15
+ $downloadedZip = Join-Path -Path $tempPath $zipFileName
16
+ $ModulePath = Join-Path -Path $PSHome -ChildPath 'Modules'
17
+ $ModuleFolder = Join-Path -Path $ModulePath -ChildPath $Module.Name
18
+
19
+ if ((Test-Path $ModuleFolder) -and ($PSVersionTable.PSVersion.Major -lt 5 -or $module.Force)) {
20
+ # Check if available version is correct
21
+ $ModuleManifest = Join-Path -Path $ModuleFolder -ChildPath "$($Module.Name).psd1"
22
+ if ((Test-Path -Path $ModuleManifest) -and -not $Module.Force) {
23
+ # Import-PowerShellDataFile only exists since 5.1
24
+ $ManifestInfo = Import-LocalizedData -BaseDirectory (Split-Path -Parent -Path $ModuleManifest) -FileName $Module.Name
25
+ $ModuleVersionNoPreRelease = $Module.Version -replace '-.*$'
26
+ # Compare the version in manifest with version required without Pre-release
27
+ if ($ManifestInfo.ModuleVersion -eq $ModuleVersionNoPreRelease) {
28
+ Write-Host "Module $($Module.Name) already installed, skipping."
29
+ return
30
+ }
31
+ else {
32
+ Write-Host "Module $($Module.Name) found with version '$($ManifestInfo.ModuleVersion)', expecting '$ModuleVersionNoPreRelease'."
33
+ }
34
+ }
35
+ else {
36
+ # if incorrect, remove it before install
37
+ Remove-Item -Recurse -Force -Path $ModuleFolder
38
+ }
39
+ }
40
+ elseif ($PSVersionTable.PSVersion.Major -gt 5) {
41
+ # skip if the version already exists or if force is enabled
42
+ $ModuleVersionNoPreRelease = $Module.Version -replace '-.*$'
43
+ $ModuleFolder = Join-Path -Path $ModuleFolder -ChildPath $ModuleVersionNoPreRelease
44
+ if (-not $Module.Force -and (Test-Path -Path $ModuleFolder)) {
45
+ Write-Verbose -Message "Module already installed."
46
+ return
47
+ }
48
+ }
49
+
50
+ if (-not (Test-Path $ModuleFolder)) {
51
+ $null = New-Item -Path $ModuleFolder -force -ItemType Directory
52
+ }
53
+
54
+ $urlSuffix = "/package/$($Module.Name)/$($Module.Version)".TrimEnd('/')
55
+ $nupkgUrl = $GalleryUrl.TrimEnd('/') + '/' + $urlSuffix.Trim('/')
56
+ $webclient = New-Object 'system.net.webclient'
57
+
58
+ if ($env:HTTP_PROXY){
59
+ if ($env:NO_PROXY){
60
+ Write-Host "Creating WebProxy with 'HTTP_PROXY' and 'NO_PROXY' environment variables."
61
+ $webproxy = New-Object -TypeName System.Net.WebProxy -ArgumentList $env:HTTP_PROXY, $true, $env:NO_PROXY
62
+ }
63
+ else {
64
+ Write-Host "Creating WebProxy with 'HTTP_PROXY' environment variable."
65
+ $webproxy = New-Object -TypeName System.Net.WebProxy -ArgumentList $env:HTTP_PROXY
66
+ }
67
+
68
+ $webclient.Proxy = $webproxy
69
+ }
70
+
71
+ Write-Verbose -Message "Downloading Package from $nupkgUrl"
72
+ try {
73
+ if (Test-Path $downloadedZip) {
74
+ Remove-Item -Force -ErrorAction SilentlyContinue -Path $downloadedZip
75
+ }
76
+ $webclient.DownloadFile($nupkgUrl, $downloadedZip)
77
+ }
78
+ catch {
79
+ Write-Error "Error trying to download nupkg '$nupkgUrl' to '$downloadedZip'."
80
+ throw $_
81
+ }
82
+
83
+ if (-not (Test-Path -Path $downloadedZip)) {
84
+ throw "Error trying to download nupkg '$nupkgUrl' to '$downloadedZip'."
85
+ }
86
+
87
+ # Test to see if Expand-Archive is available first
88
+ if (Get-Command Expand-Archive) {
89
+ Expand-Archive -Path $downloadedZip -DestinationPath $ModuleFolder -Force
90
+ }
91
+ else {
92
+ # Fall back to COM object for Shell.Application zip extraction
93
+ Write-Host "Creating COM object for zip file '$downloadedZip'."
94
+ $shellcom = New-Object -ComObject Shell.Application
95
+ $zipcomobject = $shellcom.Namespace($downloadedZip)
96
+ $destination = $shellcom.Namespace($ModuleFolder)
97
+ $destination.CopyHere($zipcomobject.Items(), 0x610)
98
+ Write-Host "Nupkg installed at $ModuleFolder"
99
+ }
100
+
101
+ [GC]::Collect()
102
+ }
103
+
104
+ function Set-PSRepo {
105
+ param(
106
+ [Parameter(Mandatory)]
107
+ $Repository
108
+ )
109
+
110
+ if (-not (Get-Command Get-PSRepository) -and (Get-Command Get-PackageSource)) {
111
+ # Old versions of PSGet do not have a *-PSrepository but have *-PackageSource instead.
112
+ if (Get-PackageSource -Name $Repository.Name) {
113
+ Set-PackageSource @Repository
114
+ }
115
+ else {
116
+ Register-PackageSource @Repository
117
+ }
118
+ }
119
+ elseif (Get-Command Get-PSRepository) {
120
+ if (Get-PSRepository -Name $Repository.Name -ErrorAction SilentlyContinue) {
121
+ # The repo exists, we should use Set-PSRepository and splat parameters
122
+ Set-PSRepository @Repository
123
+ }
124
+ else {
125
+ # The repo does not exist, use Register-PSRepository and splat
126
+ Register-PSRepository @Repository
127
+ }
128
+ }
129
+ else {
130
+ throw "Cannot Set PS Repository, command Set or Register for PSRepository or PackageSource not found."
131
+ }
132
+ }
133
+
134
+ function ConvertFrom-PesterOutputObject {
135
+ param (
136
+ [parameter(ValueFromPipeline=$true)]
137
+ [object]
138
+ $InputObject
139
+ )
140
+ begin {
141
+ $PesterModule = Import-Module Pester -Passthru
142
+ }
143
+ process {
144
+ $DescribeGroup = $InputObject.testresult | Group-Object Describe
145
+ foreach ($DescribeBlock in $DescribeGroup) {
146
+ $PesterModule.Invoke({Write-Screen $args[0]}, "Describing $($DescribeBlock.Name)")
147
+ $ContextGroup = $DescribeBlock.group | Group-Object Context
148
+ foreach ($ContextBlock in $ContextGroup) {
149
+ $PesterModule.Invoke({Write-Screen $args[0]}, "`tContext $($subheader.name)")
150
+ foreach ($TestResult in $ContextBlock.group) {
151
+ $PesterModule.Invoke({Write-PesterResult $args[0]}, $TestResult)
152
+ }
153
+ }
154
+ }
155
+
156
+ $PesterModule.Invoke({Write-PesterReport $args[0]}, $InputObject)
157
+ }
158
+ }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kitchen-pester
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steven Murawski
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-25 00:00:00.000000000 Z
11
+ date: 2021-07-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -105,12 +105,12 @@ files:
105
105
  - kitchen-pester.gemspec
106
106
  - lib/kitchen/verifier/pester.rb
107
107
  - lib/kitchen/verifier/pester_version.rb
108
- - lib/support/powershell/PesterUtil/PesterUtil.psm1
108
+ - lib/support/modules/PesterUtil/PesterUtil.psm1
109
109
  homepage: https://github.com/test-kitchen/kitchen-pester
110
110
  licenses:
111
111
  - Apache-2.0
112
112
  metadata: {}
113
- post_install_message:
113
+ post_install_message:
114
114
  rdoc_options: []
115
115
  require_paths:
116
116
  - lib
@@ -125,8 +125,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
125
  - !ruby/object:Gem::Version
126
126
  version: '0'
127
127
  requirements: []
128
- rubygems_version: 3.0.3
129
- signing_key:
128
+ rubygems_version: 3.0.6
129
+ signing_key:
130
130
  specification_version: 4
131
131
  summary: Test-Kitchen verifier for Pester.
132
132
  test_files: []
@@ -1,24 +0,0 @@
1
- function ConvertFrom-PesterOutputObject {
2
- param (
3
- [parameter(ValueFromPipeline=$true)]
4
- [object]
5
- $InputObject
6
- )
7
- begin {
8
- $PesterModule = Import-Module Pester -Passthru
9
- }
10
- process {
11
- $DescribeGroup = $InputObject.testresult | Group-Object Describe
12
- foreach ($DescribeBlock in $DescribeGroup) {
13
- $PesterModule.Invoke({Write-Screen $args[0]}, "Describing $($DescribeBlock.Name)")
14
- $ContextGroup = $DescribeBlock.group | Group-Object Context
15
- foreach ($ContextBlock in $ContextGroup) {
16
- $PesterModule.Invoke({Write-Screen $args[0]}, "`tContext $($subheader.name)")
17
- foreach ($TestResult in $ContextBlock.group) {
18
- $PesterModule.Invoke({Write-PesterResult $args[0]}, $TestResult)
19
- }
20
- }
21
- }
22
- $PesterModule.Invoke({Write-PesterReport $args[0]}, $InputObject)
23
- }
24
- }