Tests/QA/PSSAResource.common.v5.Tests.ps1
<# .NOTES To run manually: $dscResourceModuleName = 'FileSystemDsc' $pathToHQRMTests = Join-Path -Path (Get-Module DscResource.Test).ModuleBase -ChildPath 'Tests\QA' $container = New-PesterContainer -Path "$pathToHQRMTests/PSSAResource.common.*.Tests.ps1" -Data @{ ProjectPath = '.' ModuleBase = "./output/$dscResourceModuleName/*" # SourcePath = './source' # ExcludeModuleFile = @('Modules/DscResource.Common') # ExcludeSourceFile = @('Examples') } Invoke-Pester -Container $container -Output Detailed #> # Suppressing this rule because Script Analyzer does not understand Pester's syntax. [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] param ( [Parameter()] [System.String] $ProjectPath, [Parameter()] [System.String] $ModuleBase, [Parameter(Mandatory = $true)] [System.String] $SourcePath, [Parameter()] [System.String[]] $ExcludeSourceFile, [Parameter(ValueFromRemainingArguments = $true)] $Args ) # This test _must_ be outside the BeforeDiscovery-block since Pester 4 does not recognizes it. $isPester5 = (Get-Module -Name Pester).Version -ge '5.1.0' # Only run if Pester 5.1. if (-not $isPester5) { Write-Verbose -Message 'Repository is using old Pester version, new HQRM tests for Pester 5 are skipped.' -Verbose return } BeforeDiscovery { if ($PSVersionTable.PSVersion.Major -lt 5) { Write-Warning -Message 'PS Script Analyzer can not run on this platform. Please run tests on a machine with WMF 5.0+.' return } $skipCustomRules = $false if (-not (Get-Module -Name 'DscResource.AnalyzerRules' -ListAvailable)) { Write-Warning -Message 'Required module DscResource.AnalyzerRules not found. Please add to RequiredModules.psd1. Skipping tests that uses custom PSSA rules.' # Skipping test must be done during discovery. $skipCustomRules = $true } # Re-imports the private (and public) functions. Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '../../DscResource.Test.psm1') -Force $moduleFiles = @(Get-ChildItem -Path $SourcePath -Filter '*.psm1' -Recurse | WhereSourceFileNotExcluded -ExcludeSourceFile $ExcludeSourceFile) if ($ProjectPath) { # Expand the project folder if it is a relative path. $resolvedProjectPath = (Resolve-Path -Path $ProjectPath).Path } else { $resolvedProjectPath = $ModuleBase } $moduleFileToTest = @() foreach ($file in $moduleFiles) { # Use the project folder to extrapolate relative path. $descriptiveName = Get-RelativePathFromModuleRoot -FilePath $file.FullName -ModuleRootFilePath $resolvedProjectPath $moduleFileToTest += @( @{ File = $file DescriptiveName = $descriptiveName } ) } # Get the required rules to build the test cases $PSSA_rule_config = Get-StructuredObjectFromFile -Path (Join-Path -Path (Get-CurrentModuleBase) -ChildPath 'Config/PSSA_rules_config.json') $requiredRuleToTest = $PSSA_rule_config.required_rules } BeforeAll { # Re-imports the private (and public) functions. Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '../../DscResource.Test.psm1') -Force } AfterAll { # Re-import just the public functions. Import-Module -Name 'DscResource.Test' -Force } Describe 'Common Tests - PS Script Analyzer on Resource Files' -Tag @('DscPSSA', 'Common Tests - PS Script Analyzer on Resource Files') { BeforeAll { $PSSA_rule_config = Get-StructuredObjectFromFile -Path (Join-Path -Path (Get-CurrentModuleBase) -ChildPath 'Config/PSSA_rules_config.json') $invokeScriptAnalyzerParameters = @{ Path = $dscResourcesPsm1File.FullName IncludeDefaultRules = $true IncludeRule = @($PSSA_rule_config.required_rules + $PSSA_rule_config.flagged_rules + $PSSA_rule_config.ignore_rules) ErrorVariable = 'MyErrors' } # If the module is not available then the tests was skipped in Discovery. if ((Get-Module -Name 'DscResource.AnalyzerRules' -ListAvailable)) { $dscResourceAnalyzerRulesModule = Import-Module 'DscResource.AnalyzerRules' -PassThru -ErrorAction 'Stop' $invokeScriptAnalyzerParameters.CustomRulePath = Join-Path -Path $dscResourceAnalyzerRulesModule.ModuleBase -ChildPath $dscResourceAnalyzerRulesModule.RootModule $invokeScriptAnalyzerParameters.IncludeRule += 'Measure-*' } } Context 'When module file ''<DescriptiveName>'' exist' -ForEach $moduleFileToTest { BeforeAll { $invokeScriptAnalyzerParameters.Path = $File.FullName $PSSAErrors = Invoke-ScriptAnalyzer @invokeScriptAnalyzerParameters $errorPssaRulesOutput = $PSSAErrors.Where{ $_.Severity -eq 'Error' } $requiredPssaRulesOutput = $PSSAErrors.Where{ $_.RuleName -in $PSSA_rule_config.required_rules } $flaggedPssaRulesOutput = $PSSAErrors.Where{ $_.RuleName -in $PSSA_rule_config.flagged_rules } $DSCCustomRulesOutput = $PSSAErrors.Where{ $_.RuleName -like "DscResource.AnalyzerRules*" } $ignoredPssaRulesOutput = $PSSAErrors.Where{ $_.RuleName -in $PSSA_rule_config.ignore_rules } $NewErrorRulesOutput = @($ignoredPssaRulesOutput + $flaggedPssaRulesOutput + $requiredPssaRulesOutput) $suppressedRuleNames = @( Get-SuppressedPSSARuleNameList -FilePath $File.FullName | ForEach-Object -Process { # Remove any starting or trailing ' and ". $newItem = $_ -replace '^["'']|["'']$', '' # Only return non-empty strings. if ($newItem) { $newItem } } ) } It 'Should not suppress the required rule ''<_>''' -ForEach $requiredRuleToTest -Tag @('Common Tests - Required Script Analyzer Rules', 'RequiredPSSA') { $_ | Should -Not -BeIn $suppressedRuleNames -Because 'no module script file should suppress a required Script Analyzer rule' } It 'Should pass all error-level PS Script Analyzer rules' -Tag @('Common Tests - Error-Level Script Analyzer Rules', 'ErrorPSSA') { $report = $errorPssaRulesOutput | Format-Table -AutoSize | Out-String -Width 110 $errorPssaRulesOutput | Should -HaveCount 0 -Because "Error-level Rule(s) triggered.`r`n`r`n $report`r`n" } It 'Should pass all required PS Script Analyzer rules' -Tag @('Common Tests - Required Script Analyzer Rules', 'RequiredPSSA') { $report = $requiredPssaRulesOutput | Format-Table -AutoSize | Out-String -Width 110 $requiredPssaRulesOutput | Should -HaveCount 0 -Because "Required Rule(s) triggered.`r`n`r`n $report`r`n" } It 'Should pass all flagged PS Script Analyzer rules' -Tag @('Common Tests - Flagged Script Analyzer Rules', 'FlaggedPSSA') { $report = $flaggedPssaRulesOutput | Format-Table -AutoSize | Out-String -Width 110 $flaggedPssaRulesOutput | Should -HaveCount 0 -Because "Flagged Rule(s) triggered.`r`n`r`n $report`r`n" } It 'Should pass any recently-added, error-level PS Script Analyzer rules' -Tag @('Common Tests - New Error-Level Script Analyzer Rules', 'NewErrorPSSA') { $report = $NewErrorRulesOutput | Format-Table -AutoSize | Out-String -Width 110 $NewErrorRulesOutput | Should -HaveCount 0 -Because "New Rules flagged `r`n`r`n $report `r`n" } It 'Should pass all custom DSC Resource Kit PSSA rules' -Skip:$skipCustomRules -Tag @('Common Tests - Custom Script Analyzer Rules', 'CustomPSSA', 'DscResource.AnalyzerRules') { $report = $DSCCustomRulesOutput | Select-Object @{ Name = 'RuleName' Expression = { $_.RuleName -replace 'DscResource.AnalyzerRules\\' } }, Severity, ScriptName, Line, Message | Format-Table -AutoSize -Wrap | Out-String -Width 110 $DSCCustomRulesOutput | Should -HaveCount 0 -Because "Custom Error-level Rule(s) triggered.`r`n`r`n $report`r`n" } } } |