kitchen-pester 0.11.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
- }