lib/kitchen/verifier/pester.rb in kitchen-pester-0.5.0 vs lib/kitchen/verifier/pester.rb in kitchen-pester-0.7.0

- old
+ new

@@ -1,197 +1,310 @@ -# -*- encoding: utf-8 -*- -# -# Author:: Steven Murawski (<steven.murawski@gmail.com>) -# -# Copyright (C) 2015, Steven Murawski -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -require 'pathname' -require 'kitchen/verifier/base' -require 'kitchen/verifier/pester_version' - -module Kitchen - - module Verifier - - class Pester < Kitchen::Verifier::Base - - kitchen_verifier_api_version 1 - - plugin_version Kitchen::Verifier::PESTER_VERSION - - default_config :restart_winrm, false - default_config :test_folder - - # Creates a new Verifier object using the provided configuration data - # which will be merged with any default configuration. - # - # @param config [Hash] provided verifier configuration - def initialize(config = {}) - init_config(config) - end - - # Creates a temporary directory on the local workstation into which - # verifier related files and directories can be copied or created. The - # contents of this directory will be copied over to the instance before - # invoking the verifier's run command. After this method completes, it - # is expected that the contents of the sandbox is complete and ready for - # copy to the remote instance. - # - # **Note:** any subclasses would be well advised to call super first when - # overriding this method, for example: - # - # @example overriding `#create_sandbox` - # - # class MyVerifier < Kitchen::Verifier::Base - # def create_sandbox - # super - # # any further file copies, preparations, etc. - # end - # end - def create_sandbox - super - prepare_pester_tests - end - - # Generates a command string which will install and configure the - # verifier software on an instance. If no work is required, then `nil` - # will be returned. - # - # @return [String] a command string - def install_command - return if local_suite_files.empty? - - cmd = <<-CMD - set-executionpolicy unrestricted -force - if (-not (get-module -list pester)) { - if (get-module -list PowerShellGet){ - import-module PowerShellGet -force - import-module PackageManagement -force - get-packageprovider -name NuGet -force | out-null - install-module Pester -force - } - else { - if (-not (get-module -list PsGet)){ - iex (new-object Net.WebClient).DownloadString('http://bit.ly/GetPsGet') - } - import-module psget -force - Install-Module Pester - } - } - CMD - wrap_shell_code(Util.outdent!(cmd)) - end - - # Generates a command string which will perform any data initialization - # or configuration required after the verifier software is installed - # but before the sandbox has been transferred to the instance. If no work - # is required, then `nil` will be returned. - # - # @return [String] a command string - def init_command - restart_winrm_service if config[:restart_winrm] - end - - # Generates a command string which will perform any commands or - # configuration required just before the main verifier run command but - # after the sandbox has been transferred to the instance. If no work is - # required, then `nil` will be returned. - # - # @return [String] a command string - def prepare_command - end - - # Generates a command string which will invoke the main verifier - # command on the prepared instance. If no work is required, then `nil` - # will be returned. - # - # @return [String] a command string - def run_command - return if local_suite_files.empty? - wrap_shell_code(Util.outdent!(<<-CMD - $global:ProgressPreference = 'SilentlyContinue' - $TestPath = "#{File.join(config[:root_path], 'suites')}" - import-module Pester -force; invoke-pester -path $testpath -enableexit - CMD - )) - end - - #private - - def restart_winrm_service - - cmd = 'schtasks /Create /TN restart_winrm /TR ' \ - '"powershell -command restart-service winrm" ' \ - '/SC ONCE /ST 00:00 ' - wrap_shell_code(Util.outdent!(<<-CMD - #{cmd} - schtasks /RUN /TN restart_winrm - CMD - )) - end - - # Returns an Array of test suite filenames for the related suite currently - # residing on the local workstation. Any special provisioner-specific - # directories (such as a Chef roles/ directory) are excluded. - # - # @return [Array<String>] array of suite files - # @api private - - def local_suite_files - base = File.join(test_folder, config[:suite_name]) - top_level_glob = File.join(base, "*") - folder_glob = File.join(base, "*/**/*") - top = Dir.glob(top_level_glob) - nested = Dir.glob(folder_glob) - (top << nested).flatten!.reject do |f| - File.directory?(f) - end - end - - # Copies all test suite files into the suites directory in the sandbox. - # - # @api private - def prepare_pester_tests - base = File.join(test_folder, config[:suite_name]) - info("Preparing to copy files from #{base} to the SUT.") - - local_suite_files.each do |src| - dest = File.join(sandbox_suites_dir, src.sub("#{base}/", "")) - debug("Copying #{src} to #{dest}") - FileUtils.mkdir_p(File.dirname(dest)) - FileUtils.cp(src, dest, preserve: true) - end - end - - # @return [String] path to suites directory under sandbox path - # @api private - def sandbox_suites_dir - File.join(sandbox_path, "suites") - end - - def test_folder - return config[:test_base_path] if config[:test_folder].nil? - absolute_test_folder - end - - def absolute_test_folder - path = (Pathname.new config[:test_folder]).realpath - integration_path = File.join(path, 'integration') - return path unless Dir.exist?(integration_path) - integration_path - end - - end - end -end +# -*- encoding: utf-8 -*- +# +# Author:: Steven Murawski (<steven.murawski@gmail.com>) +# +# Copyright (C) 2015, Steven Murawski +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'pathname' +require 'kitchen/verifier/base' +require 'kitchen/verifier/pester_version' + +module Kitchen + + module Verifier + + class Pester < Kitchen::Verifier::Base + + kitchen_verifier_api_version 1 + + plugin_version Kitchen::Verifier::PESTER_VERSION + + default_config :restart_winrm, false + default_config :test_folder + default_config :run_as_scheduled_task, false + default_config :use_local_pester_module, false + + # Creates a new Verifier object using the provided configuration data + # which will be merged with any default configuration. + # + # @param config [Hash] provided verifier configuration + def initialize(config = {}) + init_config(config) + end + + # Creates a temporary directory on the local workstation into which + # verifier related files and directories can be copied or created. The + # contents of this directory will be copied over to the instance before + # invoking the verifier's run command. After this method completes, it + # is expected that the contents of the sandbox is complete and ready for + # copy to the remote instance. + # + # **Note:** any subclasses would be well advised to call super first when + # overriding this method, for example: + # + # @example overriding `#create_sandbox` + # + # class MyVerifier < Kitchen::Verifier::Base + # def create_sandbox + # super + # # any further file copies, preparations, etc. + # end + # end + def create_sandbox + super + prepare_powershell_modules + prepare_pester_tests + end + + # Generates a command string which will install and configure the + # verifier software on an instance. If no work is required, then `nil` + # will be returned. + # + # @return [String] a command string + def install_command + return if local_suite_files.empty? + return if config[:use_local_pester_module] + + really_wrap_shell_code(install_command_script) + end + + # Generates a command string which will perform any data initialization + # or configuration required after the verifier software is installed + # but before the sandbox has been transferred to the instance. If no work + # is required, then `nil` will be returned. + # + # @return [String] a command string + def init_command + restart_winrm_service if config[:restart_winrm] + end + + # Generates a command string which will perform any commands or + # configuration required just before the main verifier run command but + # after the sandbox has been transferred to the instance. If no work is + # required, then `nil` will be returned. + # + # @return [String] a command string + def prepare_command + end + + # Generates a command string which will invoke the main verifier + # command on the prepared instance. If no work is required, then `nil` + # will be returned. + # + # @return [String] a command string + def run_command + return if local_suite_files.empty? + + cmd = if config[:run_as_scheduled_task] + wrap_scheduled_task('verify-run', run_command_script) + else + run_command_script + end + + really_wrap_shell_code(cmd) + end + + #private + def run_command_script + <<-CMD + $TestPath = "#{File.join(config[:root_path], 'pester')}"; + import-module Pester -force; + $result = invoke-pester -path $testpath -passthru ; + $result | + export-clixml (join-path $testpath 'result.xml'); + $host.setshouldexit($result.failedcount) + CMD + end + + def really_wrap_shell_code(code) + wrap_shell_code(Util.outdent!(use_local_powershell_modules(code))) + end + + def use_local_powershell_modules(script) + <<-EOH + set-executionpolicy unrestricted -force; + $global:ProgressPreference = 'SilentlyContinue' + #{"$VerbosePreference = 'Continue'" if instance.logger.logdev.level == 0} + $env:psmodulepath += ";$(join-path (resolve-path $env:temp).path 'verifier/modules')"; + # $env:psmodulepath -split ';' | % {write-output "PSModulePath contains:"} {write-output "`t$_"} + #{script} + EOH + end + + def random_string + (0...8).map { (65 + rand(26)).chr }.join + end + + def wrap_scheduled_task (name, script) + randomized_name = "#{name}-#{random_string}" + <<-EOH + import-module NamedPipes, ScheduledTaskRunner, PesterUtil + $Action = @' +#{script} +'@ + $ScriptBlock = [scriptblock]::Create($action) + Add-ScheduledTaskCommand -name #{randomized_name} -Action $ScriptBlock + Invoke-ScheduledTaskCommand -name #{randomized_name} + $ExitCode = Get-ScheduledTaskExitCode -name #{randomized_name} + Remove-ScheduledTaskCommand -name #{randomized_name} + $TestResultPath = "#{File.join(config[:root_path], 'pester/result.xml')}" + $TestResults = import-clixml $TestResultPath + Write-Host + ConvertFrom-PesterOutputObject $TestResults + Write-Host + $host.SetShouldExit($ExitCode) + EOH + end + + def install_command_script + <<-EOH + function directory($path){ + if (test-path $path) {(resolve-path $path).providerpath} + else {(resolve-path (mkdir $path)).providerpath} + } + $VerifierModulePath = directory $env:temp/verifier/modules + $VerifierTestsPath = directory $env:temp/verifier/pester + + function test-module($module){ + (get-module $module -list) -ne $null + } + if (-not (test-module pester)) { + if (test-module PowerShellGet){ + import-module PowerShellGet -force + import-module PackageManagement -force + get-packageprovider -name NuGet -force | out-null + install-module Pester -force + } + else { + if (-not (test-module PsGet)){ + iex (new-object Net.WebClient).DownloadString('http://bit.ly/GetPsGet') + } + try { + import-module psget -force -erroraction stop + Install-Module Pester + } + catch { + Write-Output "Installing from Github" + $zipfile = join-path(resolve-path "$env:temp/verifier") "pester.zip" + if (-not (test-path $zipfile)){ + $source = 'https://github.com/pester/Pester/archive/3.3.14.zip' + [byte[]]$bytes = (new-object System.net.WebClient).DownloadData($source) + [IO.File]::WriteAllBytes($zipfile, $bytes) + $bytes = $null + [gc]::collect() + write-output "Downloaded Pester.zip" + } + write-output "Creating Shell.Application COM object" + $shellcom = new-object -com shell.application + Write-Output "Creating COM object for zip file." + $zipcomobject = $shellcom.namespace($zipfile) + Write-Output "Creating COM object for module destination." + $destination = $shellcom.namespace($VerifierModulePath) + Write-Output "Unpacking zip file." + $destination.CopyHere($zipcomobject.Items(), 0x610) + rename-item (join-path $VerifierModulePath "Pester-3.3.14") -newname 'Pester' -force + } + } + } + if (-not (test-module Pester)) { + throw "Unable to install Pester. Please include Pester in your base image or install during your converge." + } + EOH + end + + def restart_winrm_service + + cmd = 'schtasks /Create /TN restart_winrm /TR ' \ + '"powershell -command restart-service winrm" ' \ + '/SC ONCE /ST 00:00 ' + wrap_shell_code(Util.outdent!(<<-CMD + #{cmd} + schtasks /RUN /TN restart_winrm + CMD + )) + end + + # Returns an Array of test suite filenames for the related suite currently + # residing on the local workstation. Any special provisioner-specific + # directories (such as a Chef roles/ directory) are excluded. + # + # @return [Array<String>] array of suite files + # @api private + + def suite_test_folder + @suite_test_folder ||= File.join(test_folder, config[:suite_name]) + end + + def suite_level_glob + Dir.glob(File.join(suite_test_folder, "*")) + end + + def suite_verifier_level_glob + Dir.glob(File.join(suite_test_folder, "*/**/*")) + end + + def local_suite_files + suite = suite_level_glob + suite_verifier = suite_verifier_level_glob + (suite << suite_verifier).flatten!.reject do |f| + File.directory?(f) + end + end + + def sandboxify_path(path) + File.join(sandbox_path, path.sub("#{suite_test_folder}/", "")) + end + + # Copies all test suite files into the suites directory in the sandbox. + # + # @api private + def prepare_pester_tests + info("Preparing to copy files from #{suite_test_folder} to the SUT.") + + local_suite_files.each do |src| + dest = sandboxify_path(src) + debug("Copying #{src} to #{dest}") + FileUtils.mkdir_p(File.dirname(dest)) + FileUtils.cp(src, dest, preserve: true) + end + end + + def prepare_powershell_module(name) + FileUtils.mkdir_p(File.join(sandbox_path, "modules/#{name}")) + FileUtils.cp(File.join(File.dirname(__FILE__), "../../support/powershell/#{name}/#{name}.psm1"), File.join(sandbox_path, "modules/#{name}/#{name}.psm1"), preserve: true) + end + + def prepare_powershell_modules + info("Preparing to copy supporting powershell modules.") + %w[NamedPipes ScheduledTaskRunner PesterUtil].each do |module_name| + prepare_powershell_module module_name + end + + end + + def test_folder + return config[:test_base_path] if config[:test_folder].nil? + absolute_test_folder + end + + def absolute_test_folder + path = (Pathname.new config[:test_folder]).realpath + integration_path = File.join(path, 'integration') + return path unless Dir.exist?(integration_path) + integration_path + end + + end + end +end