ScriptValidationTests/Generic/PSScriptAnalyzer.Tests.ps1

<#
Copyright 2016 ASOS.com Limited

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.
#>


<#
.NAME
    PSScriptAnalyzer.Tests
    
.SYNOPSIS
    Pester tests for PSScriptAnalyzer.
#>

param ([System.String] $sut, [System.String] $TestResultsFile, [System.Collections.Hashtable]$Settings)
Set-StrictMode -Version Latest

if ($Settings.Item("PSScriptAnalyzer-Enabled") -eq "True") {
    New-Variable -Name PSScriptAnalyzerSut -Value $sut -Scope Global # TODO: Pass sut into the PSScriptAnalyzer without using a global
    New-Variable -Name PSScriptAnalyzerSettings -Value $Settings -Scope Global # TODO: Pass sut into the PSScriptAnalyzer without using a global

    InModuleScope Pester { # This is how Pester tests itself. To add each failed rule as it's own test result it allows runtime access to $Pester.AddTestResult
        Describe 'PSScriptAnalyzer' {  
            if (-not (Get-Module PSScriptAnalyzer)) {
                Import-Module -Name PSScriptAnalyzer
            }

            $excludeRules = @()            
            if ($PSScriptAnalyzerSettings.ContainsKey('PSScriptAnalyzer-ExcludeRules')) {
                $excludeRules = @($PSScriptAnalyzerSettings.Item('PSScriptAnalyzer-ExcludeRules'))
            }
            $excludeRules += "PSAvoidUsingCmdletAliases"
            $excludeRules += "PSUseShouldProcessForStateChangingFunctions"
            
            $scriptAnalyzerResults = Invoke-ScriptAnalyzer -Path $PSScriptAnalyzerSut -ExcludeRule $excludeRules

            Get-ScriptAnalyzerRule | % {
                $Pester.EnterTest($_.RuleName)
                $ruleViolations = $scriptAnalyzerResults | ? RuleName -eq $_.RuleName | ? { $_.RuleSuppressionID -notin @(
                                                                                        # Step Template Metadata Variables
                                                                                        "StepTemplateName",
                                                                                        "StepTemplateDescription",
                                                                                        "StepTemplateParameters",
                                                                                        # Script Module Metadata Variables
                                                                                        "ScriptModuleName",
                                                                                        "ScriptModuleDescription"
                                                                                    ) }
                if ($ruleViolations) {
                    $pesterResult = switch ($_.Severity) {
                        # Pester result options: "Failed", "Passed", "Skipped", "Pending", "Inconclusive"
                        ([Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticSeverity]::Error) { "Failed" }
                        ([Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticSeverity]::Warning) { "Failed" }
                        ([Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticSeverity]::Information) { "Inconclusive" }
                    }
                    # TODO: Aggregate all ruleViolations so multiple violations of the same rule are reported in the same run rather than requiring multiple builds
                    $reportedRule = $ruleViolations | Select-Object -First 1
                    
                    $pesterFailureMessage = $reportedRule.Message
                    $pesterStackTrace =  "at line: $($reportedRule.Line) in $($reportedRule.ScriptName)`n$($reportedRule.Line): $($reportedRule.Extent.Text)" # Formatted according to Pester's standard formatting
                    $lineText = Get-Content -Path ($reportedRule | % Extent | % File) -TotalCount $reportedRule.Line | Select-Object -First 1 -Skip ($reportedRule.Line-1) | % Trim
                    $pesterErrorRecord = New-ShouldErrorRecord -Message $reportedRule.Message -File $reportedRule.ScriptName -Line $reportedRule.Line -LineText $lineText
                    # ScriptStackTrace is read by pester and is normally set when the ErrorRecord is thrown, however this would report the error being in this script
                    # so we set the _scriptStackTrace field manually with a stacktrace that points to where the rule violation occured
                    [System.Management.Automation.ErrorRecord].GetField('_scriptStackTrace', [System.Reflection.BindingFlags]'Instance,NonPublic').SetValue($pesterErrorRecord, $pesterStackTrace)
                } else {
                    $pesterResult = "Passed"
                    $pesterFailureMessage = ""
                    $pesterStackTrace = ""
                    $pesterErrorRecord = $null
                }
                
                # Name Result Time FailureMessage StackTrace ParameterizedSuiteName Parameters ErrorRecord
                $Pester.AddTestResult($_.RuleName, $pesterResult, $null, $pesterFailureMessage, $pesterStackTrace, "",                    $null,     $pesterErrorRecord)
                
                Write-PesterResult -TestResult $Pester.testresult[-1]
                $Pester.LeaveTest()
            }

            It "Should pass the PSScriptAnalyzer ruleset" {
                $scriptAnalyzerResult.Count | Should Be 0
            }
        } 
    }
    
    Remove-Variable -Name PSScriptAnalyzerSut -Scope Global
    Remove-Variable -Name PSScriptAnalyzerSettings -Scope Global
}