kitchen-pester 0.10.0 → 1.0.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: '059571878e2fa384cd5938bde47baa245fae436e03122122ecd86cfd4c624f2b'
4
- data.tar.gz: 39caed26086298d573bee7cbee9b8d0196ecd5cccdaf9cec61402c465294b118
3
+ metadata.gz: 3d308bdccbb4f9795482dcdac1abbe373935c4cb164e0bb1bca3d16c8d7185fe
4
+ data.tar.gz: 798da4d69f83306722efd7311104e8797611cec975af00d88168c535a2056cc7
5
5
  SHA512:
6
- metadata.gz: '09ba88c3476bf660f88f72cd219e2f9ac0faec1a76338ac228ad32535f8cd5eddd5a9db197347f9f046ad82e56e7e8718c4db9c27cb44e83242f8e9a603b9617'
7
- data.tar.gz: d04e63a8238454275bb00f7b4fbe5c1e8ea78334825306742f9b3a8e8d6ac6d92354a90b6a9093e7da5d4f79d0ae00e79eedec1a53be11012ca8bf32c4f7a9e9
6
+ metadata.gz: 5bebf6a71183b7c02b3dcce3c8f1769b039dd30266a4da75da95f5c9fefd7da00f42421950e543fa529cfdf3cdbf2eb41756569d13e0a91bce68af881aed3cc3
7
+ data.tar.gz: 408ac94d07a5b4a544892f325b7c70b85dbf4e9dcc9193d4505ca3307fcbda5b5e0f5a49c1a3f80cda8b8f084490cf52d7ac5169b372419c6a2075abbb21467a
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.15.2"
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"
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  lib = File.expand_path("../lib", __FILE__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
 
@@ -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,8 +14,12 @@
16
14
  # See the License for the specific language governing permissions and
17
15
  # limitations under the License.
18
16
 
17
+ require "fileutils"
19
18
  require "pathname"
19
+ require "kitchen/util"
20
20
  require "kitchen/verifier/base"
21
+ require "kitchen/version"
22
+ require "base64"
21
23
  require_relative "pester_version"
22
24
 
23
25
  module Kitchen
@@ -31,9 +33,24 @@ module Kitchen
31
33
  plugin_version Kitchen::Verifier::PESTER_VERSION
32
34
 
33
35
  default_config :restart_winrm, false
34
- default_config :test_folder
35
- default_config :use_local_pester_module, false
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 :install_modules, []
36
51
  default_config :downloads, ["./PesterTestResults.xml"] => "./testresults"
52
+ default_config :copy_folders, []
53
+ default_config :sudo, false
37
54
 
38
55
  # Creates a new Verifier object using the provided configuration data
39
56
  # which will be merged with any default configuration.
@@ -63,21 +80,56 @@ module Kitchen
63
80
  # end
64
81
  def create_sandbox
65
82
  super
66
- prepare_powershell_modules
83
+ prepare_supporting_psmodules
84
+ prepare_copy_folders
67
85
  prepare_pester_tests
68
86
  prepare_helpers
87
+
88
+ debug("\n\n")
89
+ debug("Sandbox content:\n")
90
+ list_files(sandbox_path).each do |f|
91
+ debug(" #{f}")
92
+ end
69
93
  end
70
94
 
71
95
  # Generates a command string which will install and configure the
72
96
  # verifier software on an instance. If no work is required, then `nil`
73
97
  # will be returned.
98
+ # PowerShellGet & Pester Bootstrap are done in prepare_command (after sandbox is transferred)
99
+ # so that we can use the PesterUtil.psm1
74
100
  #
75
101
  # @return [String] a command string
76
102
  def install_command
77
- return if local_suite_files.empty?
78
- return if config[:use_local_pester_module]
103
+ # the sandbox has not yet been copied to the SUT.
104
+ install_command_string = <<-PS1
105
+ Write-Verbose 'Running Install Command...'
106
+ $modulesToRemove = @(
107
+ if ($#{config[:remove_builtin_powershellget]}) {
108
+ Get-module -ListAvailable -FullyQualifiedName @{ModuleName = 'PackageManagement'; RequiredVersion = '1.0.0.1'}
109
+ Get-module -ListAvailable -FullyQualifiedName @{ModuleName = 'PowerShellGet'; RequiredVersion = '1.0.0.1'}
110
+ }
79
111
 
80
- really_wrap_shell_code(install_command_script)
112
+ if ($#{config[:remove_builtin_pester]}) {
113
+ Get-module -ListAvailable -FullyQualifiedName @{ModuleName = 'Pester'; RequiredVersion = '3.4.0'}
114
+ }
115
+ )
116
+
117
+ if ($modulesToRemove.ModuleBase.Count -eq 0) {
118
+ # for PS7 on linux
119
+ return
120
+ }
121
+
122
+ $modulesToRemove.ModuleBase | Foreach-Object {
123
+ $ModuleBaseLeaf = Split-Path -Path $_ -Leaf
124
+ if ($ModuleBaseLeaf -as [System.version]) {
125
+ Remove-Item -force -Recurse (Split-Path -Parent -Path $_) -ErrorAction SilentlyContinue
126
+ }
127
+ else {
128
+ Remove-Item -force -Recurse $_ -ErrorAction SilentlyContinue
129
+ }
130
+ }
131
+ PS1
132
+ really_wrap_shell_code(Util.outdent!(install_command_string))
81
133
  end
82
134
 
83
135
  # Generates a command string which will perform any data initialization
@@ -96,7 +148,10 @@ module Kitchen
96
148
  # required, then `nil` will be returned.
97
149
  #
98
150
  # @return [String] a command string
99
- def prepare_command; end
151
+ def prepare_command
152
+ info("Preparing the SUT and Pester dependencies...")
153
+ really_wrap_shell_code(install_command_script)
154
+ end
100
155
 
101
156
  # Generates a command string which will invoke the main verifier
102
157
  # command on the prepared instance. If no work is required, then `nil`
@@ -104,8 +159,6 @@ module Kitchen
104
159
  #
105
160
  # @return [String] a command string
106
161
  def run_command
107
- return if local_suite_files.empty?
108
-
109
162
  really_wrap_shell_code(run_command_script)
110
163
  end
111
164
 
@@ -114,126 +167,219 @@ module Kitchen
114
167
  if Gem::Version.new(Kitchen::VERSION) <= Gem::Version.new("2.3.4")
115
168
  def call(state)
116
169
  super
170
+ ensure
171
+ download_test_files(state) unless config[:download].nil?
172
+ end
173
+ else
174
+ def call(state)
175
+ super
176
+ rescue
177
+ # If the verifier reports failure, we need to download the files ourselves.
178
+ # Test Kitchen's base verifier doesn't have the download in an `ensure` block.
179
+ download_test_files(state) unless config[:download].nil?
117
180
 
118
- download_test_files(state)
181
+ # Rethrow original exception, we still want to register the failure.
182
+ raise
119
183
  end
120
184
  end
121
185
 
122
186
  # private
123
187
  def run_command_script
124
- <<-CMD
125
- Import-Module Pester -Force
126
- $TestPath = "#{config[:root_path]}"
127
- $OutputFilePath = $TestPath | Join-Path -ChildPath 'PesterTestResults.xml'
188
+ <<-PS1
189
+ Import-Module -Name Pester -Force -ErrorAction Stop
190
+
191
+ $TestPath = Join-Path "#{config[:root_path]}" -ChildPath "suites"
192
+ $OutputFilePath = Join-Path "#{config[:root_path]}" -ChildPath 'PesterTestResults.xml'
193
+
194
+ $options = New-PesterOption -TestSuiteName "Pester - #{instance.to_str}"
128
195
 
129
- $result = Invoke-Pester -OutputFile $OutputFilePath -OutputFormat NUnitXml -Path $TestPath -Passthru
196
+ $result = Invoke-Pester -Script $TestPath -OutputFile $OutputFilePath -OutputFormat NUnitXml -PesterOption $options -PassThru
130
197
  $result | Export-CliXml -Path (Join-Path -Path $TestPath -ChildPath 'result.xml')
131
- $host.SetShouldExit($result.FailedCount)
132
- CMD
198
+
199
+ $LASTEXITCODE = $result.FailedCount
200
+ $host.SetShouldExit($LASTEXITCODE)
201
+
202
+ exit $LASTEXITCODE
203
+ PS1
204
+ end
205
+
206
+ def get_powershell_modules_from_nugetapi
207
+ # don't return anything is the modules subkey or bootstrap is null
208
+ return if config.dig(:bootstrap, :modules).nil?
209
+
210
+ bootstrap = config[:bootstrap]
211
+ # if the repository url is set, use that as parameter to Install-ModuleFromNuget. Default is the PSGallery url
212
+ gallery_url_param = bootstrap[:repository_url] ? "-GalleryUrl '#{bootstrap[:repository_url]}'" : ""
213
+
214
+ info("Bootstrapping environment without PowerShellGet Provider...")
215
+ Array(bootstrap[:modules]).map do |powershell_module|
216
+ if powershell_module.is_a? Hash
217
+ <<-PS1
218
+ ${#{powershell_module[:Name]}} = #{ps_hash(powershell_module)}
219
+ Install-ModuleFromNuget -Module ${#{powershell_module[:Name]}} #{gallery_url_param}
220
+ PS1
221
+ else
222
+ <<-PS1
223
+ Install-ModuleFromNuget -Module @{Name = '#{powershell_module}'} #{gallery_url_param}
224
+ PS1
225
+ end
226
+ end
227
+ end
228
+
229
+ # Returns the string command to set a PS Repository
230
+ # for each PSRepo configured.
231
+ #
232
+ # @return [Array<String>] array of suite files
233
+ # @api private
234
+ def register_psrepository
235
+ return if config[:register_repository].nil?
236
+
237
+ info("Registering a new PowerShellGet Repository")
238
+ Array(config[:register_repository]).map do |psrepo|
239
+ # Using Set-PSRepo from ../../*/*/*/PesterUtil.psm1
240
+ debug("Command to set PSRepo #{psrepo[:Name]}.")
241
+ <<-PS1
242
+ Write-Host 'Registering psrepo #{psrepo[:Name]}...'
243
+ ${#{psrepo[:Name]}} = #{ps_hash(psrepo)}
244
+ Set-PSRepo -Repository ${#{psrepo[:Name]}}
245
+ PS1
246
+ end
247
+ end
248
+
249
+ # Returns the string command set the PSGallery as trusted, and
250
+ # Install Pester from gallery based on the params from Pester_install_params config
251
+ #
252
+ # @return <String> command to install Pester Module
253
+ # @api private
254
+ def install_pester
255
+ return if config[:skip_pester_install]
256
+
257
+ pester_install_params = config[:pester_install] || {}
258
+ <<-PS1
259
+ if ((Get-PSRepository -Name PSGallery).InstallationPolicy -ne 'Trusted') {
260
+ Write-Host -Object "Trusting the PSGallery to install Pester without -Force"
261
+ Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -ErrorAction SilentlyContinue
262
+ }
263
+
264
+ Write-Host "Installing Pester..."
265
+ $installPesterParams = #{ps_hash(pester_install_params)}
266
+ $installPesterParams['Name'] = 'Pester'
267
+ Install-module @installPesterParams
268
+ Write-Host 'Pester Installed.'
269
+ PS1
270
+ end
271
+
272
+ # returns a piece of PS scriptblock for each Module to install
273
+ # from gallery that has been sepcified in install_modules config.
274
+ #
275
+ # @return [Array<String>] array of PS commands.
276
+ # @api private
277
+ def install_modules_from_gallery
278
+ return if config[:install_modules].nil?
279
+
280
+ Array(config[:install_modules]).map do |powershell_module|
281
+ if powershell_module.is_a? Hash
282
+ # Sanitize variable name so that $powershell-yaml becomes $powershell_yaml
283
+ module_name = powershell_module[:Name].gsub(/[\W]/, "_")
284
+ # so we can splat that variable to install module
285
+ <<-PS1
286
+ $#{module_name} = #{ps_hash(powershell_module)}
287
+ Write-Host -NoNewline 'Installing #{module_name}'
288
+ Install-Module @#{module_name}
289
+ Write-host '... done.'
290
+ PS1
291
+ else
292
+ <<-PS1
293
+ Write-host -NoNewline 'Installing #{powershell_module} ...'
294
+ Install-Module -Name '#{powershell_module}'
295
+ Write-host '... done.'
296
+ PS1
297
+ end
298
+ end
133
299
  end
134
300
 
135
301
  def really_wrap_shell_code(code)
302
+ windows_os? ? really_wrap_windows_shell_code(code) : really_wrap_posix_shell_code(code)
303
+ end
304
+
305
+ def really_wrap_windows_shell_code(code)
136
306
  wrap_shell_code(Util.outdent!(use_local_powershell_modules(code)))
137
307
  end
138
308
 
309
+ # Writing the command to a ps1 file, adding the pwsh shebang
310
+ # invoke the file
311
+ def really_wrap_posix_shell_code(code)
312
+ if config[:sudo]
313
+ pwsh_cmd = "sudo pwsh"
314
+ else
315
+ pwsh_cmd = "pwsh"
316
+ end
317
+
318
+ my_command = <<-BASH
319
+ echo "Running as '$(whoami)'"
320
+ # Send the bash heredoc 'EOF' to the file current.ps1 using the tool cat
321
+ cat << 'EOF' > current.ps1
322
+ #!/usr/bin/env pwsh
323
+ #{Util.outdent!(use_local_powershell_modules(code))}
324
+ EOF
325
+ # create the modules folder, making sure it's done as current user (not root)
326
+ mkdir -p foo #{config[:root_path]}/modules
327
+ # Invoke the created current.ps1 file using pwsh
328
+ #{pwsh_cmd} -f current.ps1
329
+ BASH
330
+
331
+ debug(Util.outdent!(my_command))
332
+ Util.outdent!(my_command)
333
+ end
334
+
139
335
  def use_local_powershell_modules(script)
140
- <<-EOH
141
- set-executionpolicy unrestricted -force;
336
+ <<-PS1
337
+ try {
338
+ if (!$IsLinux -and !$IsMacOs) {
339
+ Set-ExecutionPolicy Unrestricted -force
340
+ }
341
+ }
342
+ catch {
343
+ $_ | Out-String | Write-Warning
344
+ }
345
+
142
346
  $global:ProgressPreference = 'SilentlyContinue'
143
- $env:psmodulepath += ";$(join-path (resolve-path $env:temp).path 'verifier/modules')"
347
+ $PSModPathToPrepend = Join-Path "#{config[:root_path]}" -ChildPath 'modules'
348
+ Write-Verbose "Adding '$PSModPathToPrepend' to `$Env:PSModulePath."
349
+ if (!$isLinux -and -not (Test-Path -Path $PSModPathToPrepend)) {
350
+ # if you create this folder now un Linux, it will run as root (via sudo).
351
+ $null = New-Item -Path $PSModPathToPrepend -Force -ItemType Directory
352
+ }
353
+
354
+ if ($Env:PSModulePath.Split([io.path]::PathSeparator) -notcontains $PSModPathToPrepend) {
355
+ $env:PSModulePath = @($PSModPathToPrepend, $env:PSModulePath) -Join [io.path]::PathSeparator
356
+ }
357
+
144
358
  #{script}
145
- EOH
359
+ PS1
146
360
  end
147
361
 
148
362
  def install_command_script
149
- <<-EOH
150
- [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
151
- function directory($path){
152
- if (test-path $path) {(resolve-path $path).providerpath}
153
- else {(resolve-path (mkdir $path)).providerpath}
154
- }
155
- $VerifierModulePath = directory $env:temp/verifier/modules
156
- $VerifierTestsPath = directory $env:temp/verifier/pester
363
+ <<-PS1
364
+ $PSModPathToPrepend = "#{config[:root_path]}"
157
365
 
158
- $env:psmodulepath += ";$VerifierModulePath"
159
- function test-module($module){
160
- (get-module $module -list) -ne $null
161
- }
162
- if (-not (test-module pester)) {
163
- if (test-module PowerShellGet){
164
- import-module PowerShellGet -force
165
- import-module PackageManagement -force
166
- get-packageprovider -name NuGet -force | out-null
167
- install-module Pester -force
168
- }
169
- else {
170
- if (-not (test-module PsGet)){
171
- $wc = New-Object -TypeName Net.WebClient
172
-
173
- if($env:http_Proxy){
174
- if($env:no_proxy){
175
- Write-Output "Creating WebProxy with 'http_proxy' and 'no_proxy' environment variables.
176
- $webproxy = New-Object System.Net.WebProxy($env:http_Proxy,$true,$env:no_proxy)
177
- }else{
178
- Write-Output "Creating WebProxy with 'http_proxy' environment variable.
179
- $webproxy = New-Object -TypeName System.Net.WebProxy -ArgumentList ($env:http_Proxy)
180
- }
181
-
182
- $wc.proxy = $webproxy
183
- }
366
+ Import-Module -ErrorAction Stop PesterUtil
184
367
 
185
- Invoke-Expression -Command $wc.DownloadString('http://bit.ly/GetPsGet')
186
- }
187
- try {
188
- import-module psget -force -erroraction stop
189
- Install-Module Pester
190
- }
191
- catch {
192
- Write-Output "Installing from Github"
193
- $zipfile = join-path(resolve-path "$env:temp/verifier") "pester.zip"
194
- if (-not (test-path $zipfile)){
195
- $source = 'https://github.com/pester/Pester/archive/3.3.14.zip'
196
- $wc = New-Object -TypeName Net.WebClient
197
-
198
- if($env:http_Proxy){
199
- if($env:no_proxy){
200
- Write-Output "Creating WebProxy with 'http_proxy' and 'no_proxy' environment variables."
201
- $webproxy = New-Object System.Net.WebProxy($env:http_Proxy,$true,$env:no_proxy)
202
- }else{
203
- Write-Output "Creating WebProxy with 'http_proxy' environment variable."
204
- $webproxy = New-Object -TypeName System.Net.WebProxy -ArgumentList ($env:http_Proxy)
205
- }
206
-
207
- $wc.proxy = $webproxy
208
- }
209
-
210
- [byte[]]$bytes = $wc.DownloadData($source)
211
- [IO.File]::WriteAllBytes($zipfile, $bytes)
212
- $bytes = $null
213
- [gc]::collect()
214
- write-output "Downloaded Pester.zip"
215
- }
216
- write-output "Creating Shell.Application COM object"
217
- $shellcom = new-object -com shell.application
218
- Write-Output "Creating COM object for zip file."
219
- $zipcomobject = $shellcom.namespace($zipfile)
220
- Write-Output "Creating COM object for module destination."
221
- $destination = $shellcom.namespace($VerifierModulePath)
222
- Write-Output "Unpacking zip file."
223
- $destination.CopyHere($zipcomobject.Items(), 0x610)
224
- rename-item (join-path $VerifierModulePath "Pester-3.3.14") -newname 'Pester' -force
225
- }
226
- }
227
- }
228
- if (-not (test-module Pester)) {
229
- throw "Unable to install Pester. Please include Pester in your base image or install during your converge."
230
- }
231
- EOH
368
+ #{get_powershell_modules_from_nugetapi.join("\n") unless config.dig(:bootstrap, :modules).nil?}
369
+
370
+ #{register_psrepository.join("\n") unless config[:register_repository].nil?}
371
+
372
+ #{install_pester}
373
+
374
+ #{install_modules_from_gallery.join("\n") unless config[:install_modules].nil?}
375
+ PS1
232
376
  end
233
377
 
234
378
  def restart_winrm_service
379
+ return unless verifier.windows_os?
380
+
235
381
  cmd = "schtasks /Create /TN restart_winrm /TR " \
236
- '"powershell -command restart-service winrm" ' \
382
+ '"powershell -Command Restart-Service winrm" ' \
237
383
  "/SC ONCE /ST 00:00 "
238
384
  wrap_shell_code(Util.outdent!(<<-CMD
239
385
  #{cmd}
@@ -243,19 +389,13 @@ module Kitchen
243
389
  end
244
390
 
245
391
  def download_test_files(state)
246
- info("Downloading test result files from #{instance.to_str}")
392
+ return if config[:downloads].nil?
247
393
 
394
+ info("Downloading test result files from #{instance.to_str}")
248
395
  instance.transport.connection(state) do |conn|
249
396
  config[:downloads].to_h.each do |remotes, local|
250
397
  debug("Downloading #{Array(remotes).join(", ")} to #{local}")
251
-
252
- remotes.each do |file|
253
- safe_name = instance.name.gsub(/[^0-9A-Z-]/i, "_")
254
- local_path = File.join(local, safe_name, file)
255
- remote_path = File.join(config[:root_path], file)
256
-
257
- conn.download(remote_path, local_path)
258
- end
398
+ conn.download(remotes, local)
259
399
  end
260
400
  end
261
401
 
@@ -268,29 +408,25 @@ module Kitchen
268
408
  #
269
409
  # @return [Array<String>] array of suite files
270
410
  # @api private
271
-
272
411
  def suite_test_folder
273
412
  @suite_test_folder ||= File.join(test_folder, config[:suite_name])
274
413
  end
275
414
 
276
- def suite_level_glob
277
- Dir.glob(File.join(suite_test_folder, "*"))
278
- end
279
-
280
- def suite_verifier_level_glob
281
- Dir.glob(File.join(suite_test_folder, "*/**/*"))
282
- end
283
-
284
- def local_suite_files
285
- suite = suite_level_glob
286
- suite_verifier = suite_verifier_level_glob
287
- (suite << suite_verifier).flatten!.reject do |f|
288
- File.directory?(f)
289
- end
415
+ # Returns the current file's parent folder's full path.
416
+ #
417
+ # @return [string]
418
+ # @api private
419
+ def script_root
420
+ @script_root ||= File.dirname(__FILE__)
290
421
  end
291
422
 
292
- def sandboxify_path(path)
293
- File.join(sandbox_path, path.sub(%r{#{suite_test_folder}/}i, ""))
423
+ # Returns the absolute path of the Supporting PS module to
424
+ # be copied to the SUT via the Sandbox.
425
+ #
426
+ # @return [string]
427
+ # @api private
428
+ def support_psmodule_folder
429
+ @support_psmodule_folder ||= Pathname.new(File.join(script_root, "../../support/modules/PesterUtil")).cleanpath
294
430
  end
295
431
 
296
432
  # Returns an Array of common helper filenames currently residing on the
@@ -318,50 +454,128 @@ module Kitchen
318
454
  end
319
455
  end
320
456
 
321
- # Copies all test suite files into the suites directory in the sandbox.
457
+ # Creates a PowerShell hashtable from a ruby map.
458
+ # The only types supported for now are hash, array, string and Boolean.
322
459
  #
323
460
  # @api private
324
- def prepare_pester_tests
325
- info("Preparing to copy files from #{suite_test_folder} to the SUT.")
461
+ def ps_hash(obj, depth = 0)
462
+ if [true, false].include? obj
463
+ %{$#{obj}} # Return $true or $false when value is a bool
464
+ elsif obj.is_a?(Hash)
465
+ obj.map do |k, v|
466
+ # Format "Key = Value" enabling recursion
467
+ %{#{pad(depth + 2)}#{ps_hash(k)} = #{ps_hash(v, depth + 2)}}
468
+ end
469
+ .join("\n") # append \n to the key/value definitions
470
+ .insert(0, "@{\n") # prepend @{\n
471
+ .insert(-1, "\n#{pad(depth)}}\n") # append \n}\n
472
+
473
+ elsif obj.is_a?(Array)
474
+ array_string = obj.map { |v| ps_hash(v, depth + 4) }.join(",")
475
+ "#{pad(depth)}@(\n#{array_string}\n)"
476
+ else
477
+ # When the object is not a string nor a hash or array, it will be quoted as a string.
478
+ # In most cases, PS is smart enough to convert back to the type it needs.
479
+ "'" + obj.to_s + "'"
480
+ end
481
+ end
326
482
 
327
- local_suite_files.each do |src|
328
- dest = sandboxify_path(src)
329
- debug("Copying #{src} to #{dest}")
330
- FileUtils.mkdir_p(File.dirname(dest))
331
- FileUtils.cp(src, dest, preserve: true)
483
+ # returns the path of the modules subfolder
484
+ # in the sandbox, where PS Modules and folders will be copied to.
485
+ #
486
+ # @api private
487
+ def sandbox_module_path
488
+ File.join(sandbox_path, "modules")
489
+ end
490
+
491
+ # copy files into the 'modules' folder of the sandbox,
492
+ # so that copied folders can be discovered with the updated $Env:PSModulePath.
493
+ #
494
+ # @api private
495
+ def prepare_copy_folders
496
+ return if config[:copy_folders].nil?
497
+
498
+ info("Preparing to copy specified folders to #{sandbox_module_path}.")
499
+ kitchen_root_path = config[:kitchen_root]
500
+ config[:copy_folders].each do |folder|
501
+ debug("copying #{folder}")
502
+ folder_to_copy = File.join(kitchen_root_path, folder)
503
+ copy_if_src_exists(folder_to_copy, sandbox_module_path)
332
504
  end
333
505
  end
334
506
 
335
- def prepare_powershell_module(name)
336
- FileUtils.mkdir_p(File.join(sandbox_path, "modules/#{name}"))
337
- FileUtils.cp(
338
- File.join(File.dirname(__FILE__), "../../support/powershell/#{name}/#{name}.psm1"),
339
- File.join(sandbox_path, "modules/#{name}/#{name}.psm1"),
340
- preserve: true
341
- )
507
+ # returns an array of string
508
+ # Creates a flat list of files contained in a folder.
509
+ # This is useful when trying to debug what has been copied to
510
+ # the sandbox.
511
+ #
512
+ # @return [Array<String>] array of files in a folder
513
+ # @api private
514
+ def list_files(path)
515
+ base_directory_content = Dir.glob(File.join(path, "*"))
516
+ nested_directory_content = Dir.glob(File.join(path, "*/**/*"))
517
+ [base_directory_content, nested_directory_content].flatten
518
+ end
519
+
520
+ # Copies all test suite files into the suites directory in the sandbox.
521
+ #
522
+ # @api private
523
+ def prepare_pester_tests
524
+ info("Preparing to copy files from '#{suite_test_folder}' to the SUT.")
525
+ sandboxed_suites_path = File.join(sandbox_path, "suites")
526
+ copy_if_src_exists(suite_test_folder, sandboxed_suites_path)
342
527
  end
343
528
 
344
- def prepare_powershell_modules
345
- info("Preparing to copy supporting powershell modules.")
346
- %w{PesterUtil}.each do |module_name|
347
- prepare_powershell_module module_name
529
+ def prepare_supporting_psmodules
530
+ debug("Preparing to copy files from '#{support_psmodule_folder}' to the SUT.")
531
+ sandbox_module_path = File.join(sandbox_path, "modules")
532
+ copy_if_src_exists(support_psmodule_folder, sandbox_module_path)
533
+ end
534
+
535
+ # Copies a folder recursively preserving its layers,
536
+ # mostly used to copy to the sandbox.
537
+ #
538
+ # @api private
539
+ def copy_if_src_exists(src_to_validate, destination)
540
+ unless Dir.exist?(src_to_validate)
541
+ info("The path #{src_to_validate} was not found. Not copying to #{destination}.")
542
+ return
348
543
  end
544
+
545
+ debug("Moving #{src_to_validate} to #{destination}")
546
+ unless Dir.exist?(destination)
547
+ FileUtils.mkdir_p(destination)
548
+ debug("Folder '#{destination}' created.")
549
+ end
550
+ FileUtils.mkdir_p(File.join(destination, "__bugfix"))
551
+ FileUtils.cp_r(src_to_validate, destination, preserve: true)
349
552
  end
350
553
 
554
+ # returns the absolute path of the folders containing the
555
+ # test suites, use default if not set.
556
+ #
557
+ # @api private
351
558
  def test_folder
352
- return config[:test_base_path] if config[:test_folder].nil?
353
-
354
- absolute_test_folder
559
+ config[:test_folder].nil? ? config[:test_base_path] : absolute_test_folder
355
560
  end
356
561
 
562
+ # returns the absolute path of the relative folders containing the
563
+ # test suites, use default i not set.
564
+ #
565
+ # @api private
357
566
  def absolute_test_folder
358
567
  path = (Pathname.new config[:test_folder]).realpath
359
568
  integration_path = File.join(path, "integration")
360
- return path unless Dir.exist?(integration_path)
361
-
362
- integration_path
569
+ Dir.exist?(integration_path) ? integration_path : path
363
570
  end
364
571
 
572
+ # returns a string of space of the specified depth.
573
+ # This is used to pad messages or when building PS hashtables.
574
+ #
575
+ # @api private
576
+ def pad(depth = 0)
577
+ " " * depth
578
+ end
365
579
  end
366
580
  end
367
581
  end
@@ -1,5 +1,5 @@
1
1
  module Kitchen
2
2
  module Verifier
3
- PESTER_VERSION = "0.10.0".freeze
3
+ PESTER_VERSION = "1.0.0".freeze
4
4
  end
5
5
  end
@@ -0,0 +1,157 @@
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
+ if (-not (Get-Command Get-PSRepository) -and (Get-Command Get-PackageSource)) {
110
+ # Old version of PSGet do not have a *-PSrepository but have *-PackageSource instead.
111
+ if (Get-PackageSource -Name $Repository.Name) {
112
+ Set-PackageSource @Repository
113
+ }
114
+ else {
115
+ Register-PackageSource @Repository
116
+ }
117
+ }
118
+ elseif (Get-Command Get-PSRepository) {
119
+ if (Get-PSRepository -Name $Repository.Name -ErrorAction SilentlyContinue) {
120
+ # The repo exists, we should use Set-PSRepository and splat parameters
121
+ Set-PSRepository @Repository
122
+ }
123
+ else {
124
+ # The repo does not exist, use Register-PSRepository and splat
125
+ Register-PSRepository @Repository
126
+ }
127
+ }
128
+ else {
129
+ throw "Cannot Set PS Repository, command Set or Register for PSRepository or PackageSource not found."
130
+ }
131
+ }
132
+
133
+ function ConvertFrom-PesterOutputObject {
134
+ param (
135
+ [parameter(ValueFromPipeline=$true)]
136
+ [object]
137
+ $InputObject
138
+ )
139
+ begin {
140
+ $PesterModule = Import-Module Pester -Passthru
141
+ }
142
+ process {
143
+ $DescribeGroup = $InputObject.testresult | Group-Object Describe
144
+ foreach ($DescribeBlock in $DescribeGroup) {
145
+ $PesterModule.Invoke({Write-Screen $args[0]}, "Describing $($DescribeBlock.Name)")
146
+ $ContextGroup = $DescribeBlock.group | Group-Object Context
147
+ foreach ($ContextBlock in $ContextGroup) {
148
+ $PesterModule.Invoke({Write-Screen $args[0]}, "`tContext $($subheader.name)")
149
+ foreach ($TestResult in $ContextBlock.group) {
150
+ $PesterModule.Invoke({Write-PesterResult $args[0]}, $TestResult)
151
+ }
152
+ }
153
+ }
154
+
155
+ $PesterModule.Invoke({Write-PesterReport $args[0]}, $InputObject)
156
+ }
157
+ }
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.10.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steven Murawski
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-15 00:00:00.000000000 Z
11
+ date: 2020-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -105,7 +105,7 @@ 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
@@ -125,7 +125,7 @@ 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
128
+ rubygems_version: 3.0.6
129
129
  signing_key:
130
130
  specification_version: 4
131
131
  summary: Test-Kitchen verifier for Pester.
@@ -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
- }