diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c11318..4647ef8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,5 @@ # Changelog for DscResource.Test -# Changelog - All notable changes to this project will be documented in this file. The format is based on and uses the types of changes according to [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), @@ -9,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- `Restore-TestEnvironment` + - A new parameter `KeepNewMachinePSModulePath` was added and only works + if the test type is `Integration` or `All`. The new parameter will + keep any new paths that was added to the machine environment variable + `PSModulePath` after the command `Initialize-TestEnvironment` was called. + This is helpful if a a path is added by an integration test and is needed + by a second integration test and there is a need to run `Restore-TestEnvironment` + between tests. +- Added private function `Join-PSModulePath` that will concatenate two + strings with semi-colon separated paths. + +### Fixed + +- `Initialize-TestEnvironment` + - Now `$script:machineOldPSModulePath` is always set when called with the + test type `Integration` or `All`. Before it reverted to the paths on the + event `OnRemove` that were the current paths when `Initialize-TestEnvironment` + was first called. On subsequent calls any new paths were ignored. + - If there are a subsequent call to `Initialize-TestEnvironment` without the + command `Restore-TestEnvironment` was called prior the command will now + fail with a non-terminating exception asking the user to run `Restore-TestEnvironment` + to avoid the previously saved paths (`$script:machineOldPSModulePath`) + to be overwritten. + ## [0.16.1] - 2022-04-20 ### Changed diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5a5ccec..822e01d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -56,8 +56,6 @@ stages: - stage: Test dependsOn: Build jobs: - - - job: test_windows_wps displayName: 'Test Windows (WPS)' pool: diff --git a/source/Private/Join-PSModulePath.ps1 b/source/Private/Join-PSModulePath.ps1 new file mode 100644 index 0000000..f2a3fcb --- /dev/null +++ b/source/Private/Join-PSModulePath.ps1 @@ -0,0 +1,41 @@ + +<# + .SYNOPSIS + Concatenates two string that contain semi-colon separated strings. + + .PARAMETER Path + A string with all the paths separated by semi-colons. + + .PARAMETER NewPath + A string with all the paths separated by semi-colons. + + .EXAMPLE + Join-PSModulePath -Path ';' -NewPath 'Path3;Path4' +#> +function Join-PSModulePath +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Path, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $NewPath + ) + + foreach ($currentNewPath in ($NewPath -split ';')) + { + if ($Path -cnotmatch [System.Text.RegularExpressions.Regex]::Escape($currentNewPath)) + { + $Path = @($Path, $currentNewPath) -join ';' + } + } + + return $Path +} diff --git a/source/Public/Initialize-TestEnvironment.ps1 b/source/Public/Initialize-TestEnvironment.ps1 index 3801801..c4f5a9a 100644 --- a/source/Public/Initialize-TestEnvironment.ps1 +++ b/source/Public/Initialize-TestEnvironment.ps1 @@ -219,10 +219,9 @@ function Initialize-TestEnvironment $Principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) ) { - if (!$script:MachineOldPSModulePath) + if ($script:MachineOldPSModulePath) { - Write-Warning "This will change your Machine Environment Variable" - $script:MachineOldPSModulePath = [System.Environment]::GetEnvironmentVariable('PSModulePath', 'Machine') + Write-Error -Message 'There were already saved paths of the machine environment variable PSModulePath from a previous call to the command. The previous saved paths will be overwritten if ErrorAction is not set to Stop. To avoid this error run the command Restore-TestEnvironment before subsequent calls of the command Initialize-TestEnvironment' -Category 'InvalidData' -ErrorId 'IT0001' -TargetObject 'PSModulePath' } # Preserve and set the execution policy so that the DSC MOF can be created @@ -255,6 +254,11 @@ function Initialize-TestEnvironment Write-Verbose -Message ('The machine execution policy is set to ''{0}''' -f $currentMachineExecutionPolicy) + Write-Warning -Message 'This will change your machine environment variable PSModulePath but can be restored by running the command Restore-TestEnvironment.' + + # The variable $script:machineOldPSModulePath is also used in suffix.ps1. + $script:machineOldPSModulePath = [System.Environment]::GetEnvironmentVariable('PSModulePath', 'Machine') + <# For integration tests we have to set the machine's PSModulePath because otherwise the DSC LCM won't be able to find the resource module being tested or may use the wrong one. diff --git a/source/Public/Restore-TestEnvironment.ps1 b/source/Public/Restore-TestEnvironment.ps1 index d3fb94d..0317cd9 100644 --- a/source/Public/Restore-TestEnvironment.ps1 +++ b/source/Public/Restore-TestEnvironment.ps1 @@ -22,8 +22,12 @@ function Restore-TestEnvironment ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [Hashtable] - $TestEnvironment + [System.Collections.Hashtable] + $TestEnvironment, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $KeepNewMachinePSModulePath ) Write-Verbose -Message "Cleaning up Test Environment after $($TestEnvironment.TestType) testing of $($TestEnvironment.DSCResourceName) in module $($TestEnvironment.DSCModuleName)." @@ -32,18 +36,33 @@ function Restore-TestEnvironment { # Clear the DSC LCM & Configurations Clear-DscLcmConfiguration + + if ($script:machineOldPSModulePath) + { + if ($KeepNewMachinePSModulePath.IsPresent) + { + $currentMachinePSModulePath = [System.Environment]::GetEnvironmentVariable('PSModulePath', 'Machine') + + if ($currentMachinePSModulePath) + { + $script:machineOldPSModulePath = Join-PSModulePath -Path $script:machineOldPSModulePath -NewPath $currentMachinePSModulePath + } + } + + <# + Restore the machine PSModulePath. The variable $script:machineOldPSModulePath + is also used in suffix.ps1. + #> + Set-PSModulePath -Path $script:machineOldPSModulePath -Machine -ErrorAction 'Stop' + + $script:machineOldPSModulePath = $null + } } # Restore PSModulePath if ($TestEnvironment.OldPSModulePath -ne $env:PSModulePath) { Set-PSModulePath -Path $TestEnvironment.OldPSModulePath - - if ($TestEnvironment.TestType -in ('Integration','All')) - { - # Restore the machine PSModulePath for integration tests. - Set-PSModulePath -Path $TestEnvironment.OldPSModulePath -Machine - } } # Restore the Execution Policy @@ -52,11 +71,6 @@ function Restore-TestEnvironment Set-ExecutionPolicy -ExecutionPolicy $TestEnvironment.OldExecutionPolicy -Scope 'Process' -Force } - if ($script:MachineOldPSModulePath) - { - [System.Environment]::SetEnvironmentVariable('PSModulePath', $script:MachineOldPSModulePath, 'Machine') - } - if ($script:MachineOldExecutionPolicy) { Set-ExecutionPolicy -ExecutionPolicy $script:MachineOldExecutionPolicy -Scope LocalMachine -Force -ErrorAction Stop diff --git a/source/suffix.ps1 b/source/suffix.ps1 index 127227c..a0a4b6a 100644 --- a/source/suffix.ps1 +++ b/source/suffix.ps1 @@ -1,7 +1,9 @@ $MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = { - if ($script:MachineOldPSModulePath) + if ($script:machineOldPSModulePath) { - [System.Environment]::SetEnvironmentVariable('PSModulePath', $script:MachineOldPSModulePath, 'Machine') + Set-PSModulePath -Path $script:machineOldPSModulePath -Machine -ErrorAction 'Stop' + + $script:machineOldPSModulePath = $null } if ($script:MachineOldExecutionPolicy) diff --git a/tests/Unit/Public/Initialize-TestEnvironment.Tests.ps1 b/tests/Unit/Public/Initialize-TestEnvironment.Tests.ps1 index 35ec925..c6e1443 100644 --- a/tests/Unit/Public/Initialize-TestEnvironment.Tests.ps1 +++ b/tests/Unit/Public/Initialize-TestEnvironment.Tests.ps1 @@ -9,6 +9,20 @@ Import-Module $ProjectName -Force InModuleScope $ProjectName { Describe 'Initialize-TestEnvironment' { + BeforeAll { + if ($script:machineOldPSModulePath) + { + throw 'The script variable $script:machineOldPSModulePath was already set, cannot run unit test. This should not happen unless the test is run in the context of an integration test.' + } + } + + AfterEach { + <# + Make sure to set this to $null so that the unit tests won't fail. + #> + $script:machineOldPSModulePath = $null + } + Context 'When initializing the test environment' { BeforeAll { $mockDscModuleName = 'TestModule' diff --git a/tests/Unit/Public/Restore-TestEnvironment.Tests.ps1 b/tests/Unit/Public/Restore-TestEnvironment.Tests.ps1 index 384247c..85bf3b9 100644 --- a/tests/Unit/Public/Restore-TestEnvironment.Tests.ps1 +++ b/tests/Unit/Public/Restore-TestEnvironment.Tests.ps1 @@ -86,9 +86,44 @@ InModuleScope $ProjectName { $Path -eq $testEnvironmentParameter.OldPSModulePath ` -and $PSBoundParameters.ContainsKey('Machine') -eq $false } -Exactly -Times 1 -Scope It + } + } + + Context 'When restoring the test environment from an integration test that changed the machine PSModulePath' { + BeforeAll { + if ($script:machineOldPSModulePath) + { + throw 'The script variable $script:machineOldPSModulePath was already set, cannot run unit test. This should not happen unless the test is run in the context of an integration test.' + } + + $script:machineOldPSModulePath = 'SavedPath' + } + + AfterAll { + $script:machineOldPSModulePath = $null + } + + It 'Should restore without throwing and call the correct mocks' { + $testEnvironmentParameter = @{ + DSCModuleName = 'TestModule' + DSCResourceName = 'TestResource' + TestType = 'Integration' + ImportedModulePath = $moduleToImportFilePath + OldPSModulePath = 'Wrong paths' + OldExecutionPolicy = Get-ExecutionPolicy + } + + { Restore-TestEnvironment -TestEnvironment $testEnvironmentParameter -KeepNewMachinePSModulePath } | Should -Not -Throw + + Assert-MockCalled -CommandName 'Clear-DscLcmConfiguration' -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName 'Set-PSModulePath' -ParameterFilter { $Path -eq $testEnvironmentParameter.OldPSModulePath ` + -and $PSBoundParameters.ContainsKey('Machine') -eq $false + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName 'Set-PSModulePath' -ParameterFilter { + $Path -match 'SavedPath' ` -and $PSBoundParameters.ContainsKey('Machine') -eq $true } -Exactly -Times 1 -Scope It } @@ -126,5 +161,4 @@ InModuleScope $ProjectName { } } } - }