Functions/Output.ps1
$Script:ReportStrings = DATA { @{ HeaderMessage = 'Pester v{0}' StartMessage = "Executing all tests in '{0}'" FilterMessage = ' matching test name {0}' TagMessage = ' with Tags {0}' MessageOfs = "', '" CoverageTitle = 'Code coverage report:' CoverageMessage = 'Covered {2:P2} of {3:N0} analyzed {0} in {4:N0} {1}.' MissedSingular = 'Missed command:' MissedPlural = 'Missed commands:' CommandSingular = 'Command' CommandPlural = 'Commands' FileSingular = 'File' FilePlural = 'Files' Describe = 'Describing {0}' Script = 'Executing script {0}' Context = 'Context {0}' Margin = ' ' Timing = 'Tests completed in {0}' # If this is set to an empty string, the count won't be printed ContextsPassed = '' ContextsFailed = '' TestsPassed = 'Tests Passed: {0}, ' TestsFailed = 'Failed: {0}, ' TestsSkipped = 'Skipped: {0}, ' TestsPending = 'Pending: {0}, ' TestsInconclusive = 'Inconclusive: {0} ' } } $Script:ReportTheme = DATA { @{ Describe = 'Green' DescribeDetail = 'DarkYellow' Context = 'Cyan' ContextDetail = 'DarkCyan' Pass = 'DarkGreen' PassTime = 'DarkGray' Fail = 'Red' FailTime = 'DarkGray' Skipped = 'Yellow' SkippedTime = 'DarkGray' Pending = 'Gray' PendingTime = 'DarkGray' Inconclusive = 'Gray' InconclusiveTime = 'DarkGray' Incomplete = 'Yellow' IncompleteTime = 'DarkGray' Foreground = 'White' Information = 'DarkGray' Coverage = 'White' CoverageWarn = 'DarkRed' } } function Format-PesterPath ($Path, [String]$Delimiter) { # -is check is not enough for the arrays, the incoming value will likely be object[] # so we have to check if we can upcast to our required type if ($null -eq $Path) { $null } elseif ($Path -is [String]) { $Path } elseif ($Path -is [hashtable]) { # a well formed pester hashtable contains Path $Path.Path } elseif ($null -ne ($path -as [hashtable[]])) { ($path | ForEach-Object { $_.Path }) -join $Delimiter } # needs to stay at the bottom because almost everything can be upcast to array of string elseif ($Path -as [String[]]) { $Path -join $Delimiter } } function Write-PesterStart { param( [Parameter(mandatory = $true, valueFromPipeline = $true)] $PesterState, $Path = '.' ) process { if (-not ( $pester.Show | Has-Flag 'Header')) { return } $OFS = $ReportStrings.MessageOfs $moduleInfo = $MyInvocation.MyCommand.ScriptBlock.Module $moduleVersion = $moduleInfo.Version.ToString() if ($moduleInfo.PrivateData.PSData.Prerelease) { $moduleVersion += "-$($moduleInfo.PrivateData.PSData.Prerelease)" } $message = $ReportStrings.HeaderMessage -f $moduleVersion $message += [Environment]::NewLine $message += $ReportStrings.StartMessage -f (Format-PesterPath $Path -Delimiter $OFS) if ($PesterState.TestNameFilter) { $message += $ReportStrings.FilterMessage -f "$($PesterState.TestNameFilter)" } if ($PesterState.ScriptBlockFilter) { $m = $(foreach ($m in $PesterState.ScriptBlockFilter) { "$($m.Path):$($m.Line)" }) -join ", " $message += $ReportStrings.FilterMessage -f $m } if ($PesterState.TagFilter) { $message += $ReportStrings.TagMessage -f "$($PesterState.TagFilter)" } & $SafeCommands['Write-Host'] & $SafeCommands['Write-Host'] $message -Foreground $ReportTheme.Foreground } } function Write-Describe { param ( [Parameter(mandatory = $true, valueFromPipeline = $true)] $Describe, [string] $CommandUsed = 'Describe' ) process { if (-not ( $pester.Show | Has-Flag Describe)) { return } $margin = $ReportStrings.Margin * $pester.IndentLevel $Text = if ($Describe.PSObject.Properties['Name'] -and $Describe.Name) { $ReportStrings.$CommandUsed -f $Describe.Name } else { $ReportStrings.$CommandUsed -f $Describe } & $SafeCommands['Write-Host'] & $SafeCommands['Write-Host'] "${margin}${Text}" -ForegroundColor $ReportTheme.Describe # If the feature has a longer description, write that too if ($Describe.PSObject.Properties['Description'] -and $Describe.Description) { $Describe.Description -split "$([System.Environment]::NewLine)" | ForEach-Object { & $SafeCommands['Write-Host'] ($ReportStrings.Margin * ($pester.IndentLevel + 1)) $_ -ForegroundColor $ReportTheme.DescribeDetail } } } } function Write-Context { param ( [Parameter(mandatory = $true, valueFromPipeline = $true)] $Context ) process { if (-not ( $pester.Show | Has-Flag Context)) { return } $Text = if ($Context.PSObject.Properties['Name'] -and $Context.Name) { $ReportStrings.Context -f $Context.Name } else { $ReportStrings.Context -f $Context } & $SafeCommands['Write-Host'] & $SafeCommands['Write-Host'] ($ReportStrings.Margin + $Text) -ForegroundColor $ReportTheme.Context # If the scenario has a longer description, write that too if ($Context.PSObject.Properties['Description'] -and $Context.Description) { $Context.Description -split "$([System.Environment]::NewLine)" | ForEach-Object { & $SafeCommands['Write-Host'] (" " * $ReportStrings.Context.Length) $_ -ForegroundColor $ReportTheme.ContextDetail } } } } function ConvertTo-PesterResult { param( [String] $Name, [Nullable[TimeSpan]] $Time, [System.Management.Automation.ErrorRecord] $ErrorRecord ) $testResult = @{ Name = $Name Time = $time FailureMessage = "" StackTrace = "" ErrorRecord = $null Success = $false Result = "Failed" } if (-not $ErrorRecord) { $testResult.Result = "Passed" $testResult.Success = $true return $testResult } if (@('PesterAssertionFailed', 'PesterTestSkipped', 'PesterTestInconclusive', 'PesterTestPending') -contains $ErrorRecord.FullyQualifiedErrorID) { # we use TargetObject to pass structured information about the error. $details = $ErrorRecord.TargetObject $failureMessage = $details.Message $file = $details.File $line = $details.Line $Text = $details.LineText if (-not $Pester.Strict) { switch ($ErrorRecord.FullyQualifiedErrorID) { PesterTestInconclusive { $testResult.Result = "Inconclusive"; break; } PesterTestPending { $testResult.Result = "Pending"; break; } PesterTestSkipped { $testResult.Result = "Skipped"; break; } } } } else { $failureMessage = $ErrorRecord.ToString() $file = $ErrorRecord.InvocationInfo.ScriptName $line = $ErrorRecord.InvocationInfo.ScriptLineNumber $Text = $ErrorRecord.InvocationInfo.Line } $testResult.FailureMessage = $failureMessage $testResult.StackTrace = "at <ScriptBlock>, ${file}: line ${line}$([System.Environment]::NewLine)${line}: ${Text}" $testResult.ErrorRecord = $ErrorRecord return $testResult } function Remove-Comments ($Text) { $text -replace "(?s)(<#.*#>)" -replace "\#.*" } function Write-PesterResult { param ( [Parameter(mandatory = $true, valueFromPipeline = $true)] $TestResult ) process { $quiet = $pester.Show -eq [Pester.OutputTypes]::None $OutputType = [Pester.OutputTypes] $TestResult.Result $writeToScreen = $pester.Show | Has-Flag $OutputType $skipOutput = $quiet -or (-not $writeToScreen) if ($skipOutput) { return } $margin = $ReportStrings.Margin * ($pester.IndentLevel + 1) $error_margin = $margin + $ReportStrings.Margin $output = $TestResult.Name $humanTime = Get-HumanTime $TestResult.Time.TotalSeconds if (-not ($OutputType | Has-Flag 'Default, Summary')) { switch ($TestResult.Result) { Passed { & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Pass "$margin[+] $output" -NoNewLine & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.PassTime " $humanTime" break } Failed { & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Fail "$margin[-] $output" -NoNewLine & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.FailTime " $humanTime" if ($pester.IncludeVSCodeMarker) { & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Fail $($TestResult.StackTrace -replace '(?m)^', $error_margin) & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Fail $($TestResult.FailureMessage -replace '(?m)^', $error_margin) } else { $TestResult.ErrorRecord | ConvertTo-FailureLines | ForEach-Object {$_.Message + $_.Trace} | ForEach-Object { & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Fail $($_ -replace '(?m)^', $error_margin) } } break } Skipped { $targetObject = if ($null -ne $testresult.ErrorRecord -and ($o = $testresult.ErrorRecord.PSObject.Properties.Item("TargetObject"))) { $o.Value } $because = if ($targetObject -and $targetObject.Data.Because) { ", because $($testresult.ErrorRecord.TargetObject.Data.Because)" } else { $null } & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Skipped "$margin[!] $output" -NoNewLine & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Skipped ", is skipped$because" -NoNewLine & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.SkippedTime " $humanTime" break } Pending { $because = if ($testresult.ErrorRecord.TargetObject.Data.Because) { ", because $($testresult.ErrorRecord.TargetObject.Data.Because)" } else { $null } & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Pending "$margin[?] $output" -NoNewLine & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Pending ", is pending$because" -NoNewLine & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.PendingTime " $humanTime" break } Inconclusive { $because = if ($testresult.ErrorRecord.TargetObject.Data.Because) { ", because $($testresult.ErrorRecord.TargetObject.Data.Because)" } else { $null } & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Inconclusive "$margin[?] $output" -NoNewLine & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Inconclusive ", is inconclusive$because" -NoNewLine & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.InconclusiveTime " $humanTime" break } default { # TODO: Add actual Incomplete status as default rather than checking for null time. if ($null -eq $TestResult.Time) { & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Incomplete "$margin[?] $output" -NoNewLine & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.IncompleteTime " $humanTime" } } } } } } function Write-PesterReport { param ( [Parameter(mandatory = $true, valueFromPipeline = $true)] $PesterState ) if (-not ($PesterState.Show | Has-Flag Summary)) { return } & $SafeCommands['Write-Host'] ($ReportStrings.Timing -f (Get-HumanTime $PesterState.Time.TotalSeconds)) -Foreground $ReportTheme.Foreground $Success, $Failure = if ($PesterState.FailedCount -gt 0) { $ReportTheme.Foreground, $ReportTheme.Fail } else { $ReportTheme.Pass, $ReportTheme.Information } $Skipped = if ($PesterState.SkippedCount -gt 0) { $ReportTheme.Skipped } else { $ReportTheme.Information } $Pending = if ($PesterState.PendingCount -gt 0) { $ReportTheme.Pending } else { $ReportTheme.Information } $Inconclusive = if ($PesterState.InconclusiveCount -gt 0) { $ReportTheme.Inconclusive } else { $ReportTheme.Information } Try { $PesterStatePassedScenariosCount = $PesterState.PassedScenarios.Count } Catch { $PesterStatePassedScenariosCount = 0 } Try { $PesterStateFailedScenariosCount = $PesterState.FailedScenarios.Count } Catch { $PesterStateFailedScenariosCount = 0 } if ($ReportStrings.ContextsPassed) { & $SafeCommands['Write-Host'] ($ReportStrings.ContextsPassed -f $PesterStatePassedScenariosCount) -Foreground $Success -NoNewLine & $SafeCommands['Write-Host'] ($ReportStrings.ContextsFailed -f $PesterStateFailedScenariosCount) -Foreground $Failure } if ($ReportStrings.TestsPassed) { & $SafeCommands['Write-Host'] ($ReportStrings.TestsPassed -f $PesterState.PassedCount) -Foreground $Success -NoNewLine & $SafeCommands['Write-Host'] ($ReportStrings.TestsFailed -f $PesterState.FailedCount) -Foreground $Failure -NoNewLine & $SafeCommands['Write-Host'] ($ReportStrings.TestsSkipped -f $PesterState.SkippedCount) -Foreground $Skipped -NoNewLine & $SafeCommands['Write-Host'] ($ReportStrings.TestsPending -f $PesterState.PendingCount) -Foreground $Pending -NoNewLine & $SafeCommands['Write-Host'] ($ReportStrings.TestsInconclusive -f $PesterState.InconclusiveCount) -Foreground $Inconclusive } } function Write-CoverageReport { param ([object] $CoverageReport) if ($null -eq $CoverageReport -or ($pester.Show -eq [Pester.OutputTypes]::None) -or $CoverageReport.NumberOfCommandsAnalyzed -eq 0) { return } $totalCommandCount = $CoverageReport.NumberOfCommandsAnalyzed $fileCount = $CoverageReport.NumberOfFilesAnalyzed $executedPercent = ($CoverageReport.NumberOfCommandsExecuted / $CoverageReport.NumberOfCommandsAnalyzed).ToString("P2") $command = if ($totalCommandCount -gt 1) { $ReportStrings.CommandPlural } else { $ReportStrings.CommandSingular } $file = if ($fileCount -gt 1) { $ReportStrings.FilePlural } else { $ReportStrings.FileSingular } $commonParent = Get-CommonParentPath -Path $CoverageReport.AnalyzedFiles $report = $CoverageReport.MissedCommands | & $SafeCommands['Select-Object'] -Property @( @{ Name = 'File'; Expression = { Get-RelativePath -Path $_.File -RelativeTo $commonParent } } 'Class' 'Function' 'Line' 'Command' ) & $SafeCommands['Write-Host'] & $SafeCommands['Write-Host'] $ReportStrings.CoverageTitle -Foreground $ReportTheme.Coverage if ($CoverageReport.MissedCommands.Count -gt 0) { & $SafeCommands['Write-Host'] ($ReportStrings.CoverageMessage -f $command, $file, $executedPercent, $totalCommandCount, $fileCount) -Foreground $ReportTheme.CoverageWarn if ($CoverageReport.MissedCommands.Count -eq 1) { & $SafeCommands['Write-Host'] $ReportStrings.MissedSingular -Foreground $ReportTheme.CoverageWarn } else { & $SafeCommands['Write-Host'] $ReportStrings.MissedPlural -Foreground $ReportTheme.CoverageWarn } $report | & $SafeCommands['Format-Table'] -AutoSize | & $SafeCommands['Out-Host'] } else { & $SafeCommands['Write-Host'] ($ReportStrings.CoverageMessage -f $command, $file, $executedPercent, $totalCommandCount, $fileCount) -Foreground $ReportTheme.Coverage } } function ConvertTo-FailureLines { param ( [Parameter(mandatory = $true, valueFromPipeline = $true)] $ErrorRecord ) process { $lines = & $script:SafeCommands['New-Object'] psobject -Property @{ Message = @() Trace = @() } ## convert the exception messages $exception = $ErrorRecord.Exception $exceptionLines = @() while ($exception) { $exceptionName = $exception.GetType().Name $thisLines = $exception.Message.Split([string[]]($([System.Environment]::NewLine), "\n", "`n"), [System.StringSplitOptions]::RemoveEmptyEntries) if ($ErrorRecord.FullyQualifiedErrorId -ne 'PesterAssertionFailed' -and $thisLines.Length -gt 0) { if ($thisLines[0] -match '(?n)\(Parameter ''(?<param>[^'']+)''\)$') { $thisLines = @( "$exceptionName`: $($thisLines[0] -replace '\s*\(Parameter ''[^'']+''\)$')" "Parameter name: $($Matches.param)" for ($i = 1; $i -lt $thisLines.Length; $i++) { $thisLines[$i] } ) } else { $thisLines[0] = "$exceptionName`: $($thisLines[0])" } } [array]::Reverse($thisLines) $exceptionLines += $thisLines $exception = $exception.InnerException } [array]::Reverse($exceptionLines) $lines.Message += $exceptionLines if ($ErrorRecord.FullyQualifiedErrorId -eq 'PesterAssertionFailed') { $lines.Message += "$($ErrorRecord.TargetObject.Line)`: $($ErrorRecord.TargetObject.LineText)".Split([string[]]($([System.Environment]::NewLine), "\n", "`n"), [System.StringSplitOptions]::RemoveEmptyEntries) } if ( -not ($ErrorRecord | & $SafeCommands['Get-Member'] -Name ScriptStackTrace) ) { if ($ErrorRecord.FullyQualifiedErrorID -eq 'PesterAssertionFailed') { $lines.Trace += "at line: $($ErrorRecord.TargetObject.Line) in $($ErrorRecord.TargetObject.File)" } else { $lines.Trace += "at line: $($ErrorRecord.InvocationInfo.ScriptLineNumber) in $($ErrorRecord.InvocationInfo.ScriptName)" } return $lines } ## convert the stack trace if present (there might be none if we are raising the error ourselves) # todo: this is a workaround see https://github.com/pester/Pester/pull/886 if ($null -ne $ErrorRecord.ScriptStackTrace) { $traceLines = $ErrorRecord.ScriptStackTrace.Split([Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries) } $count = 0 # omit the lines internal to Pester If ((GetPesterOS) -ne 'Windows') { [String]$pattern1 = '^at (Invoke-Test|Context|Describe|InModuleScope|Invoke-Pester), .*/Functions/.*.ps1: line [0-9]*$' [String]$pattern2 = '^at Should<End>, .*/Functions/Assertions/Should.ps1: line [0-9]*$' [String]$pattern3 = '^at Assert-MockCalled, .*/Functions/Mock.ps1: line [0-9]*$' [String]$pattern4 = '^at Invoke-Assertion, .*/Functions/.*.ps1: line [0-9]*$' [String]$pattern5 = '^at (<ScriptBlock>|Invoke-Gherkin.*), (<No file>|.*/Functions/.*.ps1): line [0-9]*$' [String]$pattern6 = '^at Invoke-LegacyAssertion, .*/Functions/.*.ps1: line [0-9]*$' } Else { [String]$pattern1 = '^at (Invoke-Test|Context|Describe|InModuleScope|Invoke-Pester), .*\\Functions\\.*.ps1: line [0-9]*$' [String]$pattern2 = '^at Should<End>, .*\\Functions\\Assertions\\Should.ps1: line [0-9]*$' [String]$pattern3 = '^at Assert-MockCalled, .*\\Functions\\Mock.ps1: line [0-9]*$' [String]$pattern4 = '^at Invoke-Assertion, .*\\Functions\\.*.ps1: line [0-9]*$' [String]$pattern5 = '^at (<ScriptBlock>|Invoke-Gherkin.*), (<No file>|.*\\Functions\\.*.ps1): line [0-9]*$' [String]$pattern6 = '^at Invoke-LegacyAssertion, .*\\Functions\\.*.ps1: line [0-9]*$' } foreach ( $line in $traceLines ) { if ( $line -match $pattern1 ) { break } $count ++ } if ($ExecutionContext.SessionState.PSVariable.GetValue("PesterDebugPreference_ShowFullErrors")) { $lines.Trace += $traceLines } else { $lines.Trace += $traceLines | & $SafeCommands['Select-Object'] -First $count | & $SafeCommands['Where-Object'] { $_ -notmatch $pattern2 -and $_ -notmatch $pattern3 -and $_ -notmatch $pattern4 -and $_ -notmatch $pattern5 -and $_ -notmatch $pattern6 } } return $lines } } # SIG # Begin signature block # MIIZbgYJKoZIhvcNAQcCoIIZXzCCGVsCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUP/U3k//pi9SMFPcN67S8pwhk # rjCgghR8MIIE/jCCA+agAwIBAgIQDUJK4L46iP9gQCHOFADw3TANBgkqhkiG9w0B # AQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD # VQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFz # c3VyZWQgSUQgVGltZXN0YW1waW5nIENBMB4XDTIxMDEwMTAwMDAwMFoXDTMxMDEw # NjAwMDAwMFowSDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu # MSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMTCCASIwDQYJKoZIhvcN # AQEBBQADggEPADCCAQoCggEBAMLmYYRnxYr1DQikRcpja1HXOhFCvQp1dU2UtAxQ # tSYQ/h3Ib5FrDJbnGlxI70Tlv5thzRWRYlq4/2cLnGP9NmqB+in43Stwhd4CGPN4 # bbx9+cdtCT2+anaH6Yq9+IRdHnbJ5MZ2djpT0dHTWjaPxqPhLxs6t2HWc+xObTOK # fF1FLUuxUOZBOjdWhtyTI433UCXoZObd048vV7WHIOsOjizVI9r0TXhG4wODMSlK # XAwxikqMiMX3MFr5FK8VX2xDSQn9JiNT9o1j6BqrW7EdMMKbaYK02/xWVLwfoYer # vnpbCiAvSwnJlaeNsvrWY4tOpXIc7p96AXP4Gdb+DUmEvQECAwEAAaOCAbgwggG0 # MA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsG # AQUFBwMIMEEGA1UdIAQ6MDgwNgYJYIZIAYb9bAcBMCkwJwYIKwYBBQUHAgEWG2h0 # dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAfBgNVHSMEGDAWgBT0tuEgHf4prtLk # YaWyoiWyyBc1bjAdBgNVHQ4EFgQUNkSGjqS6sGa+vCgtHUQ23eNqerwwcQYDVR0f # BGowaDAyoDCgLoYsaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJl # ZC10cy5jcmwwMqAwoC6GLGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWFz # c3VyZWQtdHMuY3JsMIGFBggrBgEFBQcBAQR5MHcwJAYIKwYBBQUHMAGGGGh0dHA6 # Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBPBggrBgEFBQcwAoZDaHR0cDovL2NhY2VydHMu # ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkFzc3VyZWRJRFRpbWVzdGFtcGluZ0NB # LmNydDANBgkqhkiG9w0BAQsFAAOCAQEASBzctemaI7znGucgDo5nRv1CclF0CiNH # o6uS0iXEcFm+FKDlJ4GlTRQVGQd58NEEw4bZO73+RAJmTe1ppA/2uHDPYuj1UUp4 # eTZ6J7fz51Kfk6ftQ55757TdQSKJ+4eiRgNO/PT+t2R3Y18jUmmDgvoaU+2QzI2h # F3MN9PNlOXBL85zWenvaDLw9MtAby/Vh/HUIAHa8gQ74wOFcz8QRcucbZEnYIpp1 # FUL1LTI4gdr0YKK6tFL7XOBhJCVPst/JKahzQ1HavWPWH1ub9y4bTxMd90oNcX6X # t/Q/hOvB46NJofrOp79Wz7pZdmGJX36ntI5nePk2mOHLKNpbh6aKLzCCBQ0wggP1 # oAMCAQICEAPBBFtdmcD9x4iXgGyKU+cwDQYJKoZIhvcNAQELBQAwcjELMAkGA1UE # BhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2lj # ZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUg # U2lnbmluZyBDQTAeFw0yMTAxMjgwMDAwMDBaFw0yNDAxMzEyMzU5NTlaMEsxCzAJ # BgNVBAYTAkNaMQ4wDAYDVQQHEwVQcmFoYTEVMBMGA1UECgwMSmFrdWIgSmFyZcWh # MRUwEwYDVQQDDAxKYWt1YiBKYXJlxaEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw # ggEKAoIBAQC154nkzSQ95K1yCflVL3iFQhzw/25ht4o506hK7ATFgvP71ZI+YHce # gvVoMtaMhJp2/EzXsgxDF7EZBR4Hl9vNbIpLAFSVCK4GBD0DxNCDFrJTPtNohgsA # STNMcK6t0iunh7MEkaYt1yPgiISA1vcQUMKi51WSUxeWnsUNTkJDZkyM61fETbhy # CI66xLItaf3OWdyjiOFPq2n8yx+eg1w7GCC/eNYVAjzqtSmiE/xv6Qoj7z9qFyS1 # pAO4cxDRLAD9IcCiYmHOJVgsho3/u4QNNm72ghz7iiRAO5lDoBcZIiLS5RKxJwMG # nnYbIiAuISZmv4PtrkcSu81Lzmtu81idAgMBAAGjggHEMIIBwDAfBgNVHSMEGDAW # gBRaxLl7KgqjpepxA8Bg+S32ZXUOWDAdBgNVHQ4EFgQUF2nEZEX1uTrPSD3h5VSJ # 8g9ef20wDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHcGA1Ud # HwRwMG4wNaAzoDGGL2h0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3Vy # ZWQtY3MtZzEuY3JsMDWgM6Axhi9odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hh # Mi1hc3N1cmVkLWNzLWcxLmNybDBLBgNVHSAERDBCMDYGCWCGSAGG/WwDATApMCcG # CCsGAQUFBwIBFhtodHRwOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCAYGZ4EMAQQB # MIGEBggrBgEFBQcBAQR4MHYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj # ZXJ0LmNvbTBOBggrBgEFBQcwAoZCaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t # L0RpZ2lDZXJ0U0hBMkFzc3VyZWRJRENvZGVTaWduaW5nQ0EuY3J0MAwGA1UdEwEB # /wQCMAAwDQYJKoZIhvcNAQELBQADggEBANuuPZ7LhU/v1GDprUhYch3/fov3sIxp # wshFvufWbGxUYzxEVQSsZLdFVUzAdsCz3fKInY2ihCwqIoU5vbwKFvV1zvizat/r # aNn5aa8H34NjEXPHQiyNykOk9CdFgk+zZn+YpeyzBMAEvQR4uB4eDv1USWkwdXPB # VVZcjM0xEsx9H/ZZRSEGS0x3ue+shvZdPRzoWcuiK8hNcbFZr15hMGi4s0F9IxTZ # QzoSpNJsBA/vMmkbp2SWeENn49BNx8q760e+ELMfuSBltKs8S2hB9TLrpko3nIvp # l1323zyR6ZpWK1/FHbGkHRsSJKvOOBdlSL08+KM2kNzXez88eUae+1YwggUwMIIE # GKADAgECAhAECRgbX9W7ZnVTQ7VvlVAIMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNV # BAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdp # Y2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAe # Fw0xMzEwMjIxMjAwMDBaFw0yODEwMjIxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUw # EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x # MTAvBgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcg # Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD407Mcfw4Rr2d3B9ML # MUkZz9D7RZmxOttE9X/lqJ3bMtdx6nadBS63j/qSQ8Cl+YnUNxnXtqrwnIal2CWs # DnkoOn7p0WfTxvspJ8fTeyOU5JEjlpB3gvmhhCNmElQzUHSxKCa7JGnCwlLyFGeK # iUXULaGj6YgsIJWuHEqHCN8M9eJNYBi+qsSyrnAxZjNxPqxwoqvOf+l8y5Kh5Tsx # HM/q8grkV7tKtel05iv+bMt+dDk2DZDv5LVOpKnqagqrhPOsZ061xPeM0SAlI+sI # ZD5SlsHyDxL0xY4PwaLoLFH3c7y9hbFig3NBggfkOItqcyDQD2RzPJ6fpjOp/Rnf # JZPRAgMBAAGjggHNMIIByTASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQE # AwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzB5BggrBgEFBQcBAQRtMGswJAYIKwYB # BQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0 # cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB # LmNydDCBgQYDVR0fBHoweDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29t # L0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDA6oDigNoY0aHR0cDovL2NybDMu # ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDBPBgNVHSAE # SDBGMDgGCmCGSAGG/WwAAgQwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGln # aWNlcnQuY29tL0NQUzAKBghghkgBhv1sAzAdBgNVHQ4EFgQUWsS5eyoKo6XqcQPA # YPkt9mV1DlgwHwYDVR0jBBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8wDQYJKoZI # hvcNAQELBQADggEBAD7sDVoks/Mi0RXILHwlKXaoHV0cLToaxO8wYdd+C2D9wz0P # xK+L/e8q3yBVN7Dh9tGSdQ9RtG6ljlriXiSBThCk7j9xjmMOE0ut119EefM2FAaK # 95xGTlz/kLEbBw6RFfu6r7VRwo0kriTGxycqoSkoGjpxKAI8LpGjwCUR4pwUR6F6 # aGivm6dcIFzZcbEMj7uo+MUSaJ/PQMtARKUT8OZkDCUIQjKyNookAv4vcn4c10lF # luhZHen6dGRrsutmQ9qzsIzV6Q3d9gEgzpkxYz0IGhizgZtPxpMQBvwHgfqL2vmC # SfdibqFT+hKUGIUukpHqaGxEMrJmoecYpJpkUe8wggUxMIIEGaADAgECAhAKoSXW # 1jIbfkHkBdo2l8IVMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVTMRUwEwYD # VQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAi # BgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xNjAxMDcxMjAw # MDBaFw0zMTAxMDcxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdp # Q2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERp # Z2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBUaW1lc3RhbXBpbmcgQ0EwggEiMA0GCSqG # SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC90DLuS82Pf92puoKZxTlUKFe2I0rEDgdF # M1EQfdD5fU1ofue2oPSNs4jkl79jIZCYvxO8V9PD4X4I1moUADj3Lh477sym9jJZ # /l9lP+Cb6+NGRwYaVX4LJ37AovWg4N4iPw7/fpX786O6Ij4YrBHk8JkDbTuFfAnT # 7l3ImgtU46gJcWvgzyIQD3XPcXJOCq3fQDpct1HhoXkUxk0kIzBdvOw8YGqsLwfM # /fDqR9mIUF79Zm5WYScpiYRR5oLnRlD9lCosp+R1PrqYD4R/nzEU1q3V8mTLex4F # 0IQZchfxFwbvPc3WTe8GQv2iUypPhR3EHTyvz9qsEPXdrKzpVv+TAgMBAAGjggHO # MIIByjAdBgNVHQ4EFgQU9LbhIB3+Ka7S5GGlsqIlssgXNW4wHwYDVR0jBBgwFoAU # Reuir/SSy4IxLVGLp6chnfNtyA8wEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8B # Af8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgweQYIKwYBBQUHAQEEbTBrMCQG # CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKG # N2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJv # b3RDQS5jcnQwgYEGA1UdHwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0 # LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9j # cmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwUAYD # VR0gBEkwRzA4BgpghkgBhv1sAAIEMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3 # LmRpZ2ljZXJ0LmNvbS9DUFMwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4IB # AQBxlRLpUYdWac3v3dp8qmN6s3jPBjdAhO9LhL/KzwMC/cWnww4gQiyvd/MrHwwh # Wiq3BTQdaq6Z+CeiZr8JqmDfdqQ6kw/4stHYfBli6F6CJR7Euhx7LCHi1lssFDVD # BGiy23UC4HLHmNY8ZOUfSBAYX4k4YU1iRiSHY4yRUiyvKYnleB/WCxSlgNcSR3Cz # ddWThZN+tpJn+1Nhiaj1a5bA9FhpDXzIAbG5KHW3mWOFIoxhynmUfln8jA/jb7UB # JrZspe6HUSHkWGCbugwtK22ixH67xCUrRwIIfEmuE7bhfEJCKMYYVs9BNLZmXbZ0 # e/VWMyIvIjayS6JKldj1po5SMYIEXDCCBFgCAQEwgYYwcjELMAkGA1UEBhMCVVMx # FTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNv # bTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2lnbmlu # ZyBDQQIQA8EEW12ZwP3HiJeAbIpT5zAJBgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIB # DDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEE # AYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQU94onkb3KWMtH # qQwdM02F1VkYRzEwDQYJKoZIhvcNAQEBBQAEggEAs0y9ycf0Qfcnca8FKWwoNd4X # TwZeVeFvYYboL+R3yojXE2Z/NEk0ZO04PfSKdW/kaqvKIkGXUl0V+Gm3NGGsLjhi # kNEIca+CtrXXaQo+kta9se1vdAp58KCZYFy1JV6L9268xjwx4emtuujEdFoSCmBp # rTBJOsY8gXyixveQwA0U4djQaUZIATx6aK2WyUmNjv0gJWdge0WStJDjJ55zvsmj # F+OvqjHlXiB4yJN8X6x4frsvw+OW2+ebBdSGwomgG2SsqkHJ3O//B8RLGQhspqdo # eijnzb9kVl+fdwaYbvg+z/EZM33f5BGWiSSsyVw/cK/p6U+tYtf0649EZRzIiKGC # AjAwggIsBgkqhkiG9w0BCQYxggIdMIICGQIBATCBhjByMQswCQYDVQQGEwJVUzEV # MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t # MTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgVGltZXN0YW1waW5n # IENBAhANQkrgvjqI/2BAIc4UAPDdMA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcN # AQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjEwNTI5MTIyODA1WjAv # BgkqhkiG9w0BCQQxIgQggkQsuy5MXZRXsXs0nLFPXxa+xE4EHEcHHTFycx9dKu4w # DQYJKoZIhvcNAQEBBQAEggEAG3bJygRNp1Y/FQaCNzan5nrKVQtGzI8X+21XbsYF # qvcE+KxgqzVAVzfRnb7jt+AbBo3OI1x0RFz6uAhgyImOxBvU9EfA5HZhzLFEMNQn # 7RE5g//sMl7EkKnNNKNwwcqLwxmx0Y/7+xIvfx0AGd34o/9kxzceqen5Lz6dHypl # RN68WpIILBftaC3tgCGcCWjnKt30Ymv0Vgnz++EZRwyM6FI95hiWQQvjWloCqjuc # 3Uh0csOYTf+m/tliUGmEEIvYHu/Z5nvVWFGTPF9XtxP1v8EEOhheqjxJqBS3atVa # 0ZKe7djx5edEbUNCe+MqO7XExKE9tYFdSQGoJlY/bm8ReA== # SIG # End signature block |