diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..0fab71f --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] + FriedrichWeinmann +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/PSModuleDevelopment/PSModuleDevelopment.psd1 b/PSModuleDevelopment/PSModuleDevelopment.psd1 index 7186d76..cb1b050 100644 --- a/PSModuleDevelopment/PSModuleDevelopment.psd1 +++ b/PSModuleDevelopment/PSModuleDevelopment.psd1 @@ -4,7 +4,7 @@ RootModule = 'PSModuleDevelopment.psm1' # Version number of this module. - ModuleVersion = '2.2.7.90' + ModuleVersion = '2.2.7.98' # ID used to uniquely identify this module GUID = '37dd5fce-e7b5-4d57-ac37-832055ce49d6' diff --git a/PSModuleDevelopment/changelog.md b/PSModuleDevelopment/changelog.md index db630d7..fc95bdd 100644 --- a/PSModuleDevelopment/changelog.md +++ b/PSModuleDevelopment/changelog.md @@ -1,4 +1,15 @@ # Changelog +## 2.2.7.98 (May 30th, 2020) + +- Upd: Template PSFTest - Pester v5 compatibility +- Upd: Template PSFModule - Pester v5 compatibility +- Upd: Template PSFProject - Pester v5 compatibility +- Upd: Template PSFProject - Simplified module import workflow +- Upd: Template PSFProject - Improved build process cross-agent convenience +- Upd: Template PSFProject - Prerequisites task automatically detects module dependencies +- Upd: Template PSFProject - Prerequisites task can be configured to work with any registered repository +- Upd: Export-PSMDString - Now also detects splatted localization strings (thanks @StevePlp ; #117) + ## 2.2.7.90 (September 1st, 2019) - New: Export-PSMDString - Parses strings from modules using the PSFramework localization feature. - Upd: Measure-PSMDCommand - Renamed from Measure-PSMDCommandEx, performance upgrades, adding option for comparing multiple test sets. diff --git a/PSModuleDevelopment/functions/moduledebug/Get-PSMDModuleDebug.ps1 b/PSModuleDevelopment/functions/moduledebug/Get-PSMDModuleDebug.ps1 index 7bc5056..8b8df97 100644 --- a/PSModuleDevelopment/functions/moduledebug/Get-PSMDModuleDebug.ps1 +++ b/PSModuleDevelopment/functions/moduledebug/Get-PSMDModuleDebug.ps1 @@ -16,6 +16,7 @@ Returns the module debugging configuration for all modules with a name that contains "net" #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [CmdletBinding()] Param ( [string] diff --git a/PSModuleDevelopment/functions/moduledebug/Import-PSMDModuleDebug.ps1 b/PSModuleDevelopment/functions/moduledebug/Import-PSMDModuleDebug.ps1 index 3181a9c..7eca92d 100644 --- a/PSModuleDevelopment/functions/moduledebug/Import-PSMDModuleDebug.ps1 +++ b/PSModuleDevelopment/functions/moduledebug/Import-PSMDModuleDebug.ps1 @@ -25,7 +25,7 @@ { # Get original module configuration $____module = $null - $____module = Import-Clixml -Path (Get-PSFConfigValue -FullName 'PSModuleDevelopment.Debug.ConfigPath') | Where-Object { $_.Name -eq $Name } + $____module = Import-Clixml -Path (Get-PSFConfigValue -FullName 'PSModuleDevelopment.Debug.ConfigPath') | Where-Object Name -eq $Name if (-not $____module) { throw "No matching module configuration found" } # Process entry diff --git a/PSModuleDevelopment/functions/refactor/Export-PSMDString.ps1 b/PSModuleDevelopment/functions/refactor/Export-PSMDString.ps1 index 6808e7e..3559764 100644 --- a/PSModuleDevelopment/functions/refactor/Export-PSMDString.ps1 +++ b/PSModuleDevelopment/functions/refactor/Export-PSMDString.ps1 @@ -72,7 +72,67 @@ StringValues = $stringValueParamValue } } - + + # Additional checks for splatted commands + # find all splatted commands + $splattedVariables = $ast.FindAll( { + if ($args[0] -isnot [System.Management.Automation.Language.VariableExpressionAst ]) { return $false } + if (-not ($args[0].Splatted -eq $true)) { return $false } + $true + }, $true) + + foreach ($splattedVariable in $splattedVariables) + { + #get the variable name + $splatParamName = $splattedVariable.VariablePath.UserPath + if ($splatParamName) + { + # match the $param = @{ + $splatParamNameRegex = "^\s?\`$$($splatParamName)\s?=\s?\@\{" + # get all variable assignments where the + # left side matches our param + # operator is = + # matches our assignment regex + $splatAssignmentAsts = $ast.FindAll( { + if ($args[0] -isnot [System.Management.Automation.Language.AssignmentStatementAst ]) { return $false } + if (-not ($args[0].Left -match $splatParamName)) { return $false } + if (-not ($args[0].Operator -eq 'Equals')) { return $false } + if (-not ($args[0].Extent -match $splatParamNameRegex)) { return $false } + $true + }, $true) + foreach ($splatAssignmentAst in $splatAssignmentAsts) + { + # get the hashtable + $splatHashTable = $splatAssignmentAst.Right.Expression + # see if its an empty assignment or null + if ($splatHashTable -and $splatHashTable.KeyValuePairs.Count -gt 0) + { + # find any String or ActionString + $splatParam = $splatAssignmentAst.Right.Expression.KeyValuePairs | Where-Object Item1 -match '^String$|^ActionString$' + # The kvp.item.extent.text returns nested quotes where as the commandast.extent.text doesn't so strip them off + $splatParamValue = $splatParam.Item2.Extent.Text.Trim('"').Trim("'") + # find any StringValue or ActionStringValue + $splatValueParam = $splatAssignmentAst.Right.Expression.KeyValuePairs | Where-Object Item1 -match '^StringValues$|^ActionStringValues$' + if ($splatValueParam) + { + # The kvp.item.extent.text returns nested quotes whereas the commandast.extent.text doesn't so strip them off + $splatValueParamValue = $splatValueParam.Item2.Extent.Text.Trim('"').Trim("'") + } + else { $splatValueParamValue = '' } + + [PSCustomObject]@{ + PSTypeName = 'PSModuleDevelopment.String.ParsedItem' + File = $file.FullName + Line = $splatHashTable.Extent.StartLineNumber + CommandName = $splattedVariable.Parent.CommandElements[0].Value + String = $splatParamValue + StringValues = $splatValueParamValue + } + } + } + } + } + $validateAsts = $ast.FindAll({ if ($args[0] -isnot [System.Management.Automation.Language.AttributeAst]) { return $false } if ($args[0].TypeName -notmatch '^PsfValidateScript$|^PsfValidatePattern$') { return $false } diff --git a/PSModuleDevelopment/functions/refactor/Format-PSMDParameter.ps1 b/PSModuleDevelopment/functions/refactor/Format-PSMDParameter.ps1 index d5082a1..5076cc6 100644 --- a/PSModuleDevelopment/functions/refactor/Format-PSMDParameter.ps1 +++ b/PSModuleDevelopment/functions/refactor/Format-PSMDParameter.ps1 @@ -30,6 +30,7 @@ Updates all commands in the module to have a cmdletbinding attribute. #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] @@ -45,6 +46,7 @@ #region Utility functions function Invoke-AstWalk { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [CmdletBinding()] param ( $Ast, diff --git a/PSModuleDevelopment/functions/refactor/Set-PSMDCmdletBinding.ps1 b/PSModuleDevelopment/functions/refactor/Set-PSMDCmdletBinding.ps1 index f5e9adb..0b959db 100644 --- a/PSModuleDevelopment/functions/refactor/Set-PSMDCmdletBinding.ps1 +++ b/PSModuleDevelopment/functions/refactor/Set-PSMDCmdletBinding.ps1 @@ -46,6 +46,7 @@ #region Utility functions function Invoke-AstWalk { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [CmdletBinding()] Param ( $Ast, diff --git a/PSModuleDevelopment/functions/refactor/Split-PSMDScriptFile.ps1 b/PSModuleDevelopment/functions/refactor/Split-PSMDScriptFile.ps1 index 8757246..1c6dabb 100644 --- a/PSModuleDevelopment/functions/refactor/Split-PSMDScriptFile.ps1 +++ b/PSModuleDevelopment/functions/refactor/Split-PSMDScriptFile.ps1 @@ -47,7 +47,7 @@ foreach ($functionAst in ($ast.EndBlock.Statements | Where-Object { $_.GetType().FullName -eq "System.Management.Automation.Language.FunctionDefinitionAst" })) { - $ast.Extent.Text.Substring($functionAst.Extent.StartOffset, ($functionAst.Extent.EndOffset - $functionAst.Extent.StartOffset)) | Set-Content "$Path\$($functionAst.Name).ps1" -Encoding UTF8 + $ast.Extent.Text.Substring($functionAst.Extent.StartOffset, ($functionAst.Extent.EndOffset - $functionAst.Extent.StartOffset)) | Set-Content "$Path\$($functionAst.Name).ps1" -Encoding $Encoding } } } diff --git a/PSModuleDevelopment/functions/templating/Invoke-PSMDTemplate.ps1 b/PSModuleDevelopment/functions/templating/Invoke-PSMDTemplate.ps1 index dc7997e..fea81e3 100644 --- a/PSModuleDevelopment/functions/templating/Invoke-PSMDTemplate.ps1 +++ b/PSModuleDevelopment/functions/templating/Invoke-PSMDTemplate.ps1 @@ -78,6 +78,7 @@ Creates a project based on the module template with the name "MyModule" #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSPossibleIncorrectUsageOfAssignmentOperator", "")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'NameStore')] diff --git a/PSModuleDevelopment/functions/templating/New-PSMDDotNetProject.ps1 b/PSModuleDevelopment/functions/templating/New-PSMDDotNetProject.ps1 index bbe8562..8e2fdd8 100644 --- a/PSModuleDevelopment/functions/templating/New-PSMDDotNetProject.ps1 +++ b/PSModuleDevelopment/functions/templating/New-PSMDDotNetProject.ps1 @@ -58,6 +58,7 @@ - It will set authentication to windows - It will skip the automatic restore of the project on create #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = 'Create')] Param ( [Parameter(Position = 0, Mandatory = $true, ParameterSetName = 'Create')] diff --git a/PSModuleDevelopment/functions/utility/Measure-PSMDLinesOfCode.ps1 b/PSModuleDevelopment/functions/utility/Measure-PSMDLinesOfCode.ps1 index e34429a..d708041 100644 --- a/PSModuleDevelopment/functions/utility/Measure-PSMDLinesOfCode.ps1 +++ b/PSModuleDevelopment/functions/utility/Measure-PSMDLinesOfCode.ps1 @@ -35,6 +35,7 @@ #region Utility Functions function Invoke-AstWalk { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [CmdletBinding()] param ( $Ast, diff --git a/PSModuleDevelopment/tests/general/FileIntegrity.Tests.ps1 b/PSModuleDevelopment/tests/general/FileIntegrity.Tests.ps1 index fa85f8b..89e6c9c 100644 --- a/PSModuleDevelopment/tests/general/FileIntegrity.Tests.ps1 +++ b/PSModuleDevelopment/tests/general/FileIntegrity.Tests.ps1 @@ -1,89 +1,94 @@ -$moduleRoot = (Resolve-Path "$PSScriptRoot\..\..").Path +$moduleRoot = (Resolve-Path "$global:testroot\..").Path -. "$PSScriptRoot\FileIntegrity.Exceptions.ps1" - -function Get-FileEncoding -{ -<# - .SYNOPSIS - Tests a file for encoding. - - .DESCRIPTION - Tests a file for encoding. - - .PARAMETER Path - The file to test -#> - [CmdletBinding()] - Param ( - [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] - [Alias('FullName')] - [string] - $Path - ) - - [byte[]]$byte = get-content -Encoding byte -ReadCount 4 -TotalCount 4 -Path $Path - - if ($byte[0] -eq 0xef -and $byte[1] -eq 0xbb -and $byte[2] -eq 0xbf) { 'UTF8' } - elseif ($byte[0] -eq 0xfe -and $byte[1] -eq 0xff) { 'Unicode' } - elseif ($byte[0] -eq 0 -and $byte[1] -eq 0 -and $byte[2] -eq 0xfe -and $byte[3] -eq 0xff) { 'UTF32' } - elseif ($byte[0] -eq 0x2b -and $byte[1] -eq 0x2f -and $byte[2] -eq 0x76) { 'UTF7' } - else { 'Unknown, possible ASCII' } -} +. "$global:testroot\general\FileIntegrity.Exceptions.ps1" Describe "Verifying integrity of module files" { + BeforeAll { + function Get-FileEncoding + { + <# + .SYNOPSIS + Tests a file for encoding. + + .DESCRIPTION + Tests a file for encoding. + + .PARAMETER Path + The file to test + #> + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] + [Alias('FullName')] + [string] + $Path + ) + + if ($PSVersionTable.PSVersion.Major -lt 6) + { + [byte[]]$byte = get-content -Encoding byte -ReadCount 4 -TotalCount 4 -Path $Path + } + else + { + [byte[]]$byte = Get-Content -AsByteStream -ReadCount 4 -TotalCount 4 -Path $Path + } + + if ($byte[0] -eq 0xef -and $byte[1] -eq 0xbb -and $byte[2] -eq 0xbf) { 'UTF8 BOM' } + elseif ($byte[0] -eq 0xfe -and $byte[1] -eq 0xff) { 'Unicode' } + elseif ($byte[0] -eq 0 -and $byte[1] -eq 0 -and $byte[2] -eq 0xfe -and $byte[3] -eq 0xff) { 'UTF32' } + elseif ($byte[0] -eq 0x2b -and $byte[1] -eq 0x2f -and $byte[2] -eq 0x76) { 'UTF7' } + else { 'Unknown' } + } + } + Context "Validating PS1 Script files" { - $allFiles = Get-ChildItem -Path $moduleRoot -Recurse -Filter "*.ps1" | Where-Object FullName -NotLike "$moduleRoot\tests\*" + $allFiles = Get-ChildItem -Path $moduleRoot -Recurse | Where-Object Name -like "*.ps1" | Where-Object FullName -NotLike "$moduleRoot\tests\*" foreach ($file in $allFiles) { $name = $file.FullName.Replace("$moduleRoot\", '') - It "[$name] Should have UTF8 encoding" { - Get-FileEncoding -Path $file.FullName | Should Be 'UTF8' + It "[$name] Should have UTF8 encoding with Byte Order Mark" -TestCases @{ file = $file } { + Get-FileEncoding -Path $file.FullName | Should -Be 'UTF8 BOM' } - It "[$name] Should have no trailing space" { - ($file | Select-String "\s$" | Where-Object { $_.Line.Trim().Length -gt 0} | Measure-Object).Count | Should Be 0 + It "[$name] Should have no trailing space" -TestCases @{ file = $file } { + ($file | Select-String "\s$" | Where-Object { $_.Line.Trim().Length -gt 0}).LineNumber | Should -BeNullOrEmpty } $tokens = $null $parseErrors = $null $ast = [System.Management.Automation.Language.Parser]::ParseFile($file.FullName, [ref]$tokens, [ref]$parseErrors) - It "[$name] Should have no syntax errors" { - $parseErrors | Should Be $Null + It "[$name] Should have no syntax errors" -TestCases @{ parseErrors = $parseErrors } { + $parseErrors | Should -BeNullOrEmpty } foreach ($command in $global:BannedCommands) { if ($global:MayContainCommand["$command"] -notcontains $file.Name) { - It "[$name] Should not use $command" { - $tokens | Where-Object Text -EQ $command | Should Be $null + It "[$name] Should not use $command" -TestCases @{ tokens = $tokens; command = $command } { + $tokens | Where-Object Text -EQ $command | Should -BeNullOrEmpty } } } - - It "[$name] Should not contain aliases" { - $tokens | Where-Object TokenFlags -eq CommandName | Where-Object { Test-Path "alias:\$($_.Text)" } | Measure-Object | Select-Object -ExpandProperty Count | Should Be 0 - } } } Context "Validating help.txt help files" { - $allFiles = Get-ChildItem -Path $moduleRoot -Recurse -Filter "*.help.txt" | Where-Object FullName -NotLike "$moduleRoot\tests\*" + $allFiles = Get-ChildItem -Path $moduleRoot -Recurse | Where-Object Name -like "*.help.txt" | Where-Object FullName -NotLike "$moduleRoot\tests\*" foreach ($file in $allFiles) { $name = $file.FullName.Replace("$moduleRoot\", '') - It "[$name] Should have UTF8 encoding" { - Get-FileEncoding -Path $file.FullName | Should Be 'UTF8' + It "[$name] Should have UTF8 encoding" -TestCases @{ file = $file } { + Get-FileEncoding -Path $file.FullName | Should -Be 'UTF8 BOM' } - It "[$name] Should have no trailing space" { - ($file | Select-String "\s$" | Where-Object { $_.Line.Trim().Length -gt 0 } | Measure-Object).Count | Should Be 0 + It "[$name] Should have no trailing space" -TestCases @{ file = $file } { + ($file | Select-String "\s$" | Where-Object { $_.Line.Trim().Length -gt 0 } | Measure-Object).Count | Should -Be 0 } } } diff --git a/PSModuleDevelopment/tests/general/Help.Tests.ps1 b/PSModuleDevelopment/tests/general/Help.Tests.ps1 index 27893b2..1369fe5 100644 --- a/PSModuleDevelopment/tests/general/Help.Tests.ps1 +++ b/PSModuleDevelopment/tests/general/Help.Tests.ps1 @@ -36,13 +36,13 @@ Param ( $SkipTest, [string[]] - $CommandPath = @("$PSScriptRoot\..\..\functions", "$PSScriptRoot\..\..\internal\functions"), + $CommandPath = @("$global:testroot\..\functions", "$global:testroot\..\internal\functions"), [string] $ModuleName = "PSModuleDevelopment", [string] - $ExceptionsFile = "$PSScriptRoot\Help.Exceptions.ps1" + $ExceptionsFile = "$global:testroot\general\Help.Exceptions.ps1" ) if ($SkipTest) { return } . $ExceptionsFile @@ -62,58 +62,32 @@ foreach ($command in $commands) { # The module-qualified command fails on Microsoft.PowerShell.Archive cmdlets $Help = Get-Help $commandName -ErrorAction SilentlyContinue - $testhelperrors = 0 - $testhelpall = 0 - Describe "Test help for $commandName" { - - $testhelpall += 1 - if ($Help.Synopsis -like '*`[``]*') { - # If help is not found, synopsis in auto-generated help is the syntax diagram - It "should not be auto-generated" { - $Help.Synopsis | Should Not BeLike '*`[``]*' - } - $testhelperrors += 1 - } - - $testhelpall += 1 - if ([String]::IsNullOrEmpty($Help.Description.Text)) { - # Should be a description for every function - It "gets description for $commandName" { - $Help.Description | Should Not BeNullOrEmpty - } - $testhelperrors += 1 - } + + Describe "Test help for $commandName" { - $testhelpall += 1 - if ([String]::IsNullOrEmpty(($Help.Examples.Example | Select-Object -First 1).Code)) { - # Should be at least one example - It "gets example code from $commandName" { - ($Help.Examples.Example | Select-Object -First 1).Code | Should Not BeNullOrEmpty - } - $testhelperrors += 1 - } + # If help is not found, synopsis in auto-generated help is the syntax diagram + It "should not be auto-generated" -TestCases @{ Help = $Help } { + $Help.Synopsis | Should -Not -BeLike '*`[``]*' + } - $testhelpall += 1 - if ([String]::IsNullOrEmpty(($Help.Examples.Example.Remarks | Select-Object -First 1).Text)) { - # Should be at least one example description - It "gets example help from $commandName" { - ($Help.Examples.Example.Remarks | Select-Object -First 1).Text | Should Not BeNullOrEmpty - } - $testhelperrors += 1 - } + # Should be a description for every function + It "gets description for $commandName" -TestCases @{ Help = $Help } { + $Help.Description | Should -Not -BeNullOrEmpty + } - if ($testhelperrors -eq 0) { - It "Ran silently $testhelpall tests" { - $testhelperrors | Should be 0 - } - } + # Should be at least one example + It "gets example code from $commandName" -TestCases @{ Help = $Help } { + ($Help.Examples.Example | Select-Object -First 1).Code | Should -Not -BeNullOrEmpty + } + + # Should be at least one example description + It "gets example help from $commandName" -TestCases @{ Help = $Help } { + ($Help.Examples.Example.Remarks | Select-Object -First 1).Text | Should -Not -BeNullOrEmpty + } - $testparamsall = 0 - $testparamserrors = 0 Context "Test parameter help for $commandName" { - $Common = 'Debug', 'ErrorAction', 'ErrorVariable', 'InformationAction', 'InformationVariable', 'OutBuffer', 'OutVariable', - 'PipelineVariable', 'Verbose', 'WarningAction', 'WarningVariable' + $common = 'Debug', 'ErrorAction', 'ErrorVariable', 'InformationAction', 'InformationVariable', 'OutBuffer', 'OutVariable', 'PipelineVariable', 'Verbose', 'WarningAction', 'WarningVariable' $parameters = $command.ParameterSets.Parameters | Sort-Object -Property Name -Unique | Where-Object Name -notin $common $parameterNames = $parameters.Name @@ -121,79 +95,50 @@ foreach ($command in $commands) { foreach ($parameter in $parameters) { $parameterName = $parameter.Name $parameterHelp = $Help.parameters.parameter | Where-Object Name -EQ $parameterName + + # Should be a description for every parameter + It "gets help for parameter: $parameterName : in $commandName" -TestCases @{ parameterHelp = $parameterHelp } { + $parameterHelp.Description.Text | Should -Not -BeNullOrEmpty + } - $testparamsall += 1 - if ([String]::IsNullOrEmpty($parameterHelp.Description.Text)) { - # Should be a description for every parameter - It "gets help for parameter: $parameterName : in $commandName" { - $parameterHelp.Description.Text | Should Not BeNullOrEmpty - } - $testparamserrors += 1 - } - - $testparamsall += 1 $codeMandatory = $parameter.IsMandatory.toString() - if ($parameterHelp.Required -ne $codeMandatory) { - # Required value in Help should match IsMandatory property of parameter - It "help for $parameterName parameter in $commandName has correct Mandatory value" { - $parameterHelp.Required | Should Be $codeMandatory - } - $testparamserrors += 1 - } + It "help for $parameterName parameter in $commandName has correct Mandatory value" -TestCases @{ parameterHelp = $parameterHelp; codeMandatory = $codeMandatory } { + $parameterHelp.Required | Should -Be $codeMandatory + } if ($HelpTestSkipParameterType[$commandName] -contains $parameterName) { continue } $codeType = $parameter.ParameterType.Name - $testparamsall += 1 if ($parameter.ParameterType.IsEnum) { # Enumerations often have issues with the typename not being reliably available $names = $parameter.ParameterType::GetNames($parameter.ParameterType) - if ($parameterHelp.parameterValueGroup.parameterValue -ne $names) { - # Parameter type in Help should match code - It "help for $commandName has correct parameter type for $parameterName" { - $parameterHelp.parameterValueGroup.parameterValue | Should be $names - } - $testparamserrors += 1 - } + # Parameter type in Help should match code + It "help for $commandName has correct parameter type for $parameterName" -TestCases @{ parameterHelp = $parameterHelp; names = $names } { + $parameterHelp.parameterValueGroup.parameterValue | Should -be $names + } } elseif ($parameter.ParameterType.FullName -in $HelpTestEnumeratedArrays) { # Enumerations often have issues with the typename not being reliably available $names = [Enum]::GetNames($parameter.ParameterType.DeclaredMembers[0].ReturnType) - if ($parameterHelp.parameterValueGroup.parameterValue -ne $names) { - # Parameter type in Help should match code - It "help for $commandName has correct parameter type for $parameterName" { - $parameterHelp.parameterValueGroup.parameterValue | Should be $names - } - $testparamserrors += 1 - } + It "help for $commandName has correct parameter type for $parameterName" -TestCases @{ parameterHelp = $parameterHelp; names = $names } { + $parameterHelp.parameterValueGroup.parameterValue | Should -be $names + } } else { # To avoid calling Trim method on a null object. $helpType = if ($parameterHelp.parameterValue) { $parameterHelp.parameterValue.Trim() } - if ($helpType -ne $codeType) { - # Parameter type in Help should match code - It "help for $commandName has correct parameter type for $parameterName" { - $helpType | Should be $codeType - } - $testparamserrors += 1 - } + # Parameter type in Help should match code + It "help for $commandName has correct parameter type for $parameterName" -TestCases @{ helpType = $helpType; codeType = $codeType } { + $helpType | Should -be $codeType + } } } foreach ($helpParm in $HelpParameterNames) { - $testparamsall += 1 - if ($helpParm -notin $parameterNames) { - # Shouldn't find extra parameters in help. - It "finds help parameter in code: $helpParm" { - $helpParm -in $parameterNames | Should Be $true - } - $testparamserrors += 1 - } - } - if ($testparamserrors -eq 0) { - It "Ran silently $testparamsall tests" { - $testparamserrors | Should be 0 - } + # Shouldn't find extra parameters in help. + It "finds help parameter in code: $helpParm" -TestCases @{ helpParm = $helpParm; parameterNames = $parameterNames } { + $helpParm -in $parameterNames | Should -Be $true + } } } } diff --git a/PSModuleDevelopment/tests/general/PSScriptAnalyzer.Tests.ps1 b/PSModuleDevelopment/tests/general/PSScriptAnalyzer.Tests.ps1 index 1ccd2de..74e5a65 100644 --- a/PSModuleDevelopment/tests/general/PSScriptAnalyzer.Tests.ps1 +++ b/PSModuleDevelopment/tests/general/PSScriptAnalyzer.Tests.ps1 @@ -4,17 +4,15 @@ Param ( $SkipTest, [string[]] - $CommandPath = @("$PSScriptRoot\..\..\functions", "$PSScriptRoot\..\..\internal\functions") + $CommandPath = @("$global:testroot\..\functions", "$global:testroot\..\internal\functions") ) if ($SkipTest) { return } -if ($env:BUILD_BUILDURI -like "vstfs*") { Install-Module PSScriptAnalyzer -Force -SkipPublisherCheck } - -$list = New-Object System.Collections.ArrayList +$global:__pester_data.ScriptAnalyzer = New-Object System.Collections.ArrayList Describe 'Invoking PSScriptAnalyzer against commandbase' { - $commandFiles = Get-ChildItem -Path $CommandPath -Recurse -Filter "*.ps1" + $commandFiles = Get-ChildItem -Path $CommandPath -Recurse | Where-Object Name -like "*.ps1" $scriptAnalyzerRules = Get-ScriptAnalyzerRule foreach ($file in $commandFiles) @@ -24,10 +22,10 @@ Describe 'Invoking PSScriptAnalyzer against commandbase' { forEach ($rule in $scriptAnalyzerRules) { - It "Should pass $rule" { + It "Should pass $rule" -TestCases @{ analysis = $analysis; rule = $rule } { If ($analysis.RuleName -contains $rule) { - $analysis | Where-Object RuleName -EQ $rule -outvariable failures | ForEach-Object { $list.Add($_) } + $analysis | Where-Object RuleName -EQ $rule -outvariable failures | ForEach-Object { $null = $global:__pester_data.ScriptAnalyzer.Add($_) } 1 | Should -Be 0 } @@ -39,6 +37,4 @@ Describe 'Invoking PSScriptAnalyzer against commandbase' { } } } -} - -$list | Out-Default \ No newline at end of file +} \ No newline at end of file diff --git a/PSModuleDevelopment/tests/general/manifest.Tests.ps1 b/PSModuleDevelopment/tests/general/manifest.Tests.ps1 index 05d10ac..8890e32 100644 --- a/PSModuleDevelopment/tests/general/manifest.Tests.ps1 +++ b/PSModuleDevelopment/tests/general/manifest.Tests.ps1 @@ -1,49 +1,62 @@ Describe "Validating the module manifest" { - $moduleRoot = (Resolve-Path "$PSScriptRoot\..\..").Path + $moduleRoot = (Resolve-Path "$global:testroot\..").Path $manifest = ((Get-Content "$moduleRoot\PSModuleDevelopment.psd1") -join "`n") | Invoke-Expression Context "Basic resources validation" { - It "Exports all functions in the public folder" { - $files = Get-ChildItem "$moduleRoot\functions" -Recurse -File -Filter "*.ps1" - $count = (Compare-Object -ReferenceObject $files.BaseName -DifferenceObject $manifest.FunctionsToExport).Count - $count | Should be 0 + $files = Get-ChildItem "$moduleRoot\functions" -Recurse -File | Where-Object Name -like "*.ps1" + It "Exports all functions in the public folder" -TestCases @{ files = $files; manifest = $manifest } { + + $functions = (Compare-Object -ReferenceObject $files.BaseName -DifferenceObject $manifest.FunctionsToExport | Where-Object SideIndicator -Like '<=').InputObject + $functions | Should -BeNullOrEmpty } - - It "Exports none of its internal functions" { + It "Exports no function that isn't also present in the public folder" -TestCases @{ files = $files; manifest = $manifest } { + $functions = (Compare-Object -ReferenceObject $files.BaseName -DifferenceObject $manifest.FunctionsToExport | Where-Object SideIndicator -Like '=>').InputObject + $functions | Should -BeNullOrEmpty + } + + It "Exports none of its internal functions" -TestCases @{ moduleRoot = $moduleRoot; manifest = $manifest } { $files = Get-ChildItem "$moduleRoot\internal\functions" -Recurse -File -Filter "*.ps1" - $files | Where-Object BaseName -In $manifest.FunctionsToExport | Should Be $null + $files | Where-Object BaseName -In $manifest.FunctionsToExport | Should -BeNullOrEmpty } } - + Context "Individual file validation" { - It "The root module file exists" { - Test-Path "$moduleRoot\$($manifest.RootModule)" | Should Be $true + It "The root module file exists" -TestCases @{ moduleRoot = $moduleRoot; manifest = $manifest } { + Test-Path "$moduleRoot\$($manifest.RootModule)" | Should -Be $true } - + foreach ($format in $manifest.FormatsToProcess) { - It "The file $format should exist" { - Test-Path "$moduleRoot\$format" | Should Be $true + It "The file $format should exist" -TestCases @{ moduleRoot = $moduleRoot; format = $format } { + Test-Path "$moduleRoot\$format" | Should -Be $true } } - + foreach ($type in $manifest.TypesToProcess) { - It "The file $type should exist" { - Test-Path "$moduleRoot\$type" | Should Be $true + It "The file $type should exist" -TestCases @{ moduleRoot = $moduleRoot; type = $type } { + Test-Path "$moduleRoot\$type" | Should -Be $true } } - + foreach ($assembly in $manifest.RequiredAssemblies) { - It "The file $assembly should exist" { - Test-Path "$moduleRoot\$assembly" | Should Be $true - } - } - - foreach ($tag in $manifest.PrivateData.PSData.Tags) { - It "Tags should have no spaces in name" { + if ($assembly -like "*.dll") { + It "The file $assembly should exist" -TestCases @{ moduleRoot = $moduleRoot; assembly = $assembly } { + Test-Path "$moduleRoot\$assembly" | Should -Be $true + } + } + else { + It "The file $assembly should load from the GAC" -TestCases @{ moduleRoot = $moduleRoot; assembly = $assembly } { + { Add-Type -AssemblyName $assembly } | Should -Not -Throw + } + } + } + + foreach ($tag in $manifest.PrivateData.PSData.Tags) + { + It "Tags should have no spaces in name" -TestCases @{ tag = $tag } { $tag -match " " | Should -Be $false } } } -} +} \ No newline at end of file diff --git a/PSModuleDevelopment/tests/pester.ps1 b/PSModuleDevelopment/tests/pester.ps1 index 0ade6e8..ba0cd46 100644 --- a/PSModuleDevelopment/tests/pester.ps1 +++ b/PSModuleDevelopment/tests/pester.ps1 @@ -1,82 +1,113 @@ param ( - [ValidateSet('None', 'Default', 'Passed', 'Failed', 'Pending', 'Skipped', 'Inconclusive', 'Describe', 'Context', 'Summary', 'Header', 'Fails', 'All')] - [string] - $Show = "None", - - [ValidateSet('Everything', 'Functions', 'General')] - [string] - $Run = "Everything", - - [string] - $Filter = "*.Tests.ps1" + $TestGeneral = $true, + + $TestFunctions = $true, + + [ValidateSet('None', 'Normal', 'Detailed', 'Diagnostic')] + [Alias('Show')] + $Output = "None", + + $Include = "*", + + $Exclude = "" ) -Write-PSFMessage -Level Host -Message "Starting Tests" +Write-PSFMessage -Level Important -Message "Starting Tests" -Write-PSFMessage -Level Host -Message "Importing Module" +Write-PSFMessage -Level Important -Message "Importing Module" + +$global:testroot = $PSScriptRoot +$global:__pester_data = @{ } Remove-Module PSModuleDevelopment -ErrorAction Ignore Import-Module "$PSScriptRoot\..\PSModuleDevelopment.psd1" Import-Module "$PSScriptRoot\..\PSModuleDevelopment.psm1" -Force -Write-PSFMessage -Level Host -Message "Creating test result folder" +# Need to import explicitly so we can use the configuration class +Import-Module Pester + +Write-PSFMessage -Level Important -Message "Creating test result folder" $null = New-Item -Path "$PSScriptRoot\..\.." -Name TestResults -ItemType Directory -Force $totalFailed = 0 $totalRun = 0 $testresults = @() +$config = [PesterConfiguration]::Default +$config.TestResult.Enabled = $true -if ($Run -match "Everything|General") { - Write-PSFMessage -Level Important -Message "Modules imported, proceeding with general tests" - foreach ($file in (Get-ChildItem "$PSScriptRoot\general" -Filter $Filter)) { - Write-PSFMessage -Level Significant -Message " Executing $($file.Name)" - $TestOuputFile = Join-Path "$PSScriptRoot\..\..\TestResults" "TEST-$($file.BaseName).xml" - $results = Invoke-Pester -Script $file.FullName -Show $Show -PassThru -OutputFile $TestOuputFile -OutputFormat NUnitXml - foreach ($result in $results) { - $totalRun += $result.TotalCount - $totalFailed += $result.FailedCount - $result.TestResult | Where-Object { -not $_.Passed } | ForEach-Object { - $name = $_.Name - $testresults += [pscustomobject]@{ - Describe = $_.Describe - Context = $_.Context - Name = "It $name" - Result = $_.Result - Message = $_.FailureMessage - } - } - } - } +#region Run General Tests +if ($TestGeneral) +{ + Write-PSFMessage -Level Important -Message "Modules imported, proceeding with general tests" + foreach ($file in (Get-ChildItem "$PSScriptRoot\general" | Where-Object Name -like "*.Tests.ps1")) + { + if ($file.Name -notlike $Include) { continue } + if ($file.Name -like $Exclude) { continue } + + Write-PSFMessage -Level Significant -Message " Executing $($file.Name)" + $config.TestResult.OutputPath = Join-Path "$PSScriptRoot\..\..\TestResults" "TEST-$($file.BaseName).xml" + $config.Run.Path = $file.FullName + $config.Run.PassThru = $true + $config.Output.Verbosity = $Output + $results = Invoke-Pester -Configuration $config + foreach ($result in $results) + { + $totalRun += $result.TotalCount + $totalFailed += $result.FailedCount + $result.Tests | Where-Object Result -ne 'Passed' | ForEach-Object { + $testresults += [pscustomobject]@{ + Block = $_.Block + Name = "It $($_.Name)" + Result = $_.Result + Message = $_.ErrorRecord.DisplayErrorMessage + } + } + } + } } +#endregion Run General Tests + +$global:__pester_data.ScriptAnalyzer | Out-Host -if ($Run -match "Everything|Functions") { - Write-PSFMessage -Level Important -Message "Proceeding with individual tests" - foreach ($file in (Get-ChildItem "$PSScriptRoot\functions" -Recurse -File -Filter $Filter)) { - Write-PSFMessage -Level Significant -Message " Executing $($file.Name)" - $results = Invoke-Pester -Script $file.FullName -Show $Show -PassThru - foreach ($result in $results) { - $totalRun += $result.TotalCount - $totalFailed += $result.FailedCount - $result.TestResult | Where-Object { -not $_.Passed } | ForEach-Object { - $name = $_.Name - $testresults += [pscustomobject]@{ - Describe = $_.Describe - Context = $_.Context - Name = "It $name" - Result = $_.Result - Message = $_.FailureMessage - } - } - } - } +#region Test Commands +if ($TestFunctions) +{ + Write-PSFMessage -Level Important -Message "Proceeding with individual tests" + foreach ($file in (Get-ChildItem "$PSScriptRoot\functions" -Recurse -File | Where-Object Name -like "*Tests.ps1")) + { + if ($file.Name -notlike $Include) { continue } + if ($file.Name -like $Exclude) { continue } + + Write-PSFMessage -Level Significant -Message " Executing $($file.Name)" + $config.TestResult.OutputPath = Join-Path "$PSScriptRoot\..\..\TestResults" "TEST-$($file.BaseName).xml" + $config.Run.Path = $file.FullName + $config.Run.PassThru = $true + $config.Output.Verbosity = $Output + $results = Invoke-Pester -Configuration $config + foreach ($result in $results) + { + $totalRun += $result.TotalCount + $totalFailed += $result.FailedCount + $result.Tests | Where-Object Result -ne 'Passed' | ForEach-Object { + $testresults += [pscustomobject]@{ + Block = $_.Block + Name = "It $($_.Name)" + Result = $_.Result + Message = $_.ErrorRecord.DisplayErrorMessage + } + } + } + } } +#endregion Test Commands $testresults | Sort-Object Describe, Context, Name, Result, Message | Format-List if ($totalFailed -eq 0) { Write-PSFMessage -Level Critical -Message "All $totalRun tests executed without a single failure!" } else { Write-PSFMessage -Level Critical -Message "$totalFailed tests out of $totalRun tests failed!" } -if ($totalFailed -gt 0) { - throw "$totalFailed / $totalRun tests failed!" -} +if ($totalFailed -gt 0) +{ + throw "$totalFailed / $totalRun tests failed!" +} \ No newline at end of file diff --git a/templates/PSFModule/PSMDTemplate.ps1 b/templates/PSFModule/PSMDTemplate.ps1 index c3adc4e..953b412 100644 --- a/templates/PSFModule/PSMDTemplate.ps1 +++ b/templates/PSFModule/PSMDTemplate.ps1 @@ -1,6 +1,6 @@ @{ TemplateName = 'PSFModule' - Version = "1.1.1.0" + Version = "1.1.2.0" AutoIncrementVersion = $true Tags = 'module','psframework' Author = 'Friedrich Weinmann' @@ -22,10 +22,8 @@ testfolder = { } - testresults = { - @' -$results = Invoke-Pester -Script $file.FullName -Show $Show -PassThru -'@ + pesterconfig = { + } } } \ No newline at end of file diff --git a/templates/PSFModule/internal/scripts/postimport.ps1 b/templates/PSFModule/internal/scripts/postimport.ps1 index 0569132..81583ec 100644 --- a/templates/PSFModule/internal/scripts/postimport.ps1 +++ b/templates/PSFModule/internal/scripts/postimport.ps1 @@ -1,22 +1,26 @@ -# Add all things you want to run after importing the main code +<# +Add all things you want to run after importing the main function code + +WARNING: ONLY provide paths to files! + +After building the module, this file will be completely ignored, adding anything but paths to files ... +- Will not work after publishing +- Could break the build process +#> + +$moduleRoot = Split-Path (Split-Path $PSScriptRoot) # Load Configurations -foreach ($file in (Get-ChildItem "$($script:ModuleRoot)\internal\configurations\*.ps1" -ErrorAction Ignore)) { - . Import-ModuleFile -Path $file.FullName -} +(Get-ChildItem "$moduleRoot\internal\configurations\*.ps1" -ErrorAction Ignore).FullName # Load Scriptblocks -foreach ($file in (Get-ChildItem "$($script:ModuleRoot)\internal\scriptblocks\*.ps1" -ErrorAction Ignore)) { - . Import-ModuleFile -Path $file.FullName -} +(Get-ChildItem "$moduleRoot\internal\scriptblocks\*.ps1" -ErrorAction Ignore).FullName # Load Tab Expansion -foreach ($file in (Get-ChildItem "$($script:ModuleRoot)\internal\tepp\*.tepp.ps1" -ErrorAction Ignore)) { - . Import-ModuleFile -Path $file.FullName -} +(Get-ChildItem "$moduleRoot\internal\tepp\*.tepp.ps1" -ErrorAction Ignore).FullName # Load Tab Expansion Assignment -. Import-ModuleFile -Path "$($script:ModuleRoot)\internal\tepp\assignment.ps1" +"$moduleRoot\internal\tepp\assignment.ps1" # Load License -. Import-ModuleFile -Path "$($script:ModuleRoot)\internal\scripts\license.ps1" \ No newline at end of file +"$moduleRoot\internal\scripts\license.ps1" \ No newline at end of file diff --git a/templates/PSFModule/internal/scripts/preimport.ps1 b/templates/PSFModule/internal/scripts/preimport.ps1 index 288ef8a..475843b 100644 --- a/templates/PSFModule/internal/scripts/preimport.ps1 +++ b/templates/PSFModule/internal/scripts/preimport.ps1 @@ -1,4 +1,14 @@ -# Add all things you want to run before importing the main code +<# +Add all things you want to run before importing the main function code. + +WARNING: ONLY provide paths to files! + +After building the module, this file will be completely ignored, adding anything but paths to files ... +- Will not work after publishing +- Could break the build process +#> + +$moduleRoot = Split-Path (Split-Path $PSScriptRoot) # Load the strings used in messages -. Import-ModuleFile -Path "$($script:ModuleRoot)\internal\scripts\strings.ps1" \ No newline at end of file +"$moduleRoot\internal\scripts\strings.ps1" \ No newline at end of file diff --git "a/templates/PSFModule/\303\276name\303\276.psm1" "b/templates/PSFModule/\303\276name\303\276.psm1" index 282c740..54e62a3 100644 --- "a/templates/PSFModule/\303\276name\303\276.psm1" +++ "b/templates/PSFModule/\303\276name\303\276.psm1" @@ -54,7 +54,9 @@ function Import-ModuleFile if ($importIndividualFiles) { # Execute Preimport actions - . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\preimport.ps1" + foreach ($path in (& "$ModuleRoot\internal\scripts\preimport.ps1")) { + . Import-ModuleFile -Path $path + } # Import all internal functions foreach ($function in (Get-ChildItem "$ModuleRoot\internal\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) @@ -69,7 +71,9 @@ if ($importIndividualFiles) } # Execute Postimport actions - . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\postimport.ps1" + foreach ($path in (& "$ModuleRoot\internal\scripts\postimport.ps1")) { + . Import-ModuleFile -Path $path + } # End it here, do not load compiled code below return diff --git a/templates/PSFProject/PSMDTemplate.ps1 b/templates/PSFProject/PSMDTemplate.ps1 index 45bddc4..05aa08e 100644 --- a/templates/PSFProject/PSMDTemplate.ps1 +++ b/templates/PSFProject/PSMDTemplate.ps1 @@ -1,6 +1,6 @@ @{ TemplateName = 'PSFProject' - Version = "1.3.1.0" + Version = "1.3.2.0" AutoIncrementVersion = $true Tags = 'module','psframework' Author = 'Friedrich Weinmann' @@ -34,11 +34,8 @@ Write-PSFMessage -Level Important -Message "Creating test result folder" $null = New-Item -Path "$PSScriptRoot\..\.." -Name TestResults -ItemType Directory -Force '@ } - testresults = { - @' -$TestOuputFile = Join-Path "$PSScriptRoot\..\..\TestResults" "TEST-$($file.BaseName).xml" - $results = Invoke-Pester -Script $file.FullName -Show $Show -PassThru -OutputFile $TestOuputFile -OutputFormat NUnitXml -'@ + pesterconfig = { +'$config.TestResult.Enabled = $true' } } } \ No newline at end of file diff --git a/templates/PSFProject/README.md b/templates/PSFProject/README.md index dc6f97a..bf15d49 100644 --- a/templates/PSFProject/README.md +++ b/templates/PSFProject/README.md @@ -7,27 +7,25 @@ Remember, it's the first thing a visitor will see. # Project Setup Instructions ## Working with the layout - - Don't touch the psm1 file - - Place functions you export in `functions/` (can have subfolders) - - Place private/internal functions invisible to the user in `internal/functions` (can have subfolders) - - Don't add code directly to the `postimport.ps1` or `preimport.ps1`. - Those files are designed to import other files only. - - When adding files you load during `preimport.ps1`, be sure to add corresponding entries to `filesBefore.txt`. - The text files are used as reference when compiling the module during the build script. - - When adding files you load during `postimport.ps1`, be sure to add corresponding entries to `filesAfter.txt`. - The text files are used as reference when compiling the module during the build script. +- Don't touch the psm1 file +- Place functions you export in `functions/` (can have subfolders) +- Place private/internal functions invisible to the user in `internal/functions` (can have subfolders) +- Don't add code directly to the `postimport.ps1` or `preimport.ps1`. + Those files are designed to import other files only. +- When adding files & folders, make sure they are covered by either `postimport.ps1` or `preimport.ps1`. + This adds them to both the import and the build sequence. ## Setting up CI/CD > To create a PR validation pipeline, set up tasks like this: - - Install Prerequisites (PowerShell Task; VSTS-Prerequisites.ps1) - - Validate (PowerShell Task; VSTS-Validate.ps1) - - Publish Test Results (Publish Test Results; NUnit format; Run no matter what) +- Install Prerequisites (PowerShell Task; VSTS-Prerequisites.ps1) +- Validate (PowerShell Task; VSTS-Validate.ps1) +- Publish Test Results (Publish Test Results; NUnit format; Run no matter what) > To create a build/publish pipeline, set up tasks like this: - - Install Prerequisites (PowerShell Task; VSTS-Prerequisites.ps1) - - Validate (PowerShell Task; VSTS-Validate.ps1) - - Build (PowerShell Task; VSTS-Build.ps1) - - Publish Test Results (Publish Test Results; NUnit format; Run no matter what) \ No newline at end of file +- Install Prerequisites (PowerShell Task; VSTS-Prerequisites.ps1) +- Validate (PowerShell Task; VSTS-Validate.ps1) +- Build (PowerShell Task; VSTS-Build.ps1) +- Publish Test Results (Publish Test Results; NUnit format; Run no matter what) diff --git a/templates/PSFProject/build/filesAfter.txt b/templates/PSFProject/build/filesAfter.txt deleted file mode 100644 index 41d9a5e..0000000 --- a/templates/PSFProject/build/filesAfter.txt +++ /dev/null @@ -1,8 +0,0 @@ -# List all files that are loaded in the postimport.ps1 -# In the order they are loaded during postimport - -internal\configurations\*.ps1 -internal\scriptblocks\*.ps1 -internal\tepp\*.tepp.ps1 -internal\tepp\assignment.ps1 -internal\scripts\license.ps1 \ No newline at end of file diff --git a/templates/PSFProject/build/filesBefore.txt b/templates/PSFProject/build/filesBefore.txt deleted file mode 100644 index 10ce89f..0000000 --- a/templates/PSFProject/build/filesBefore.txt +++ /dev/null @@ -1,4 +0,0 @@ -# List all files that are loaded in the preimport.ps1 -# In the order they are loaded during preimport - -internal\scripts\strings.ps1 \ No newline at end of file diff --git a/templates/PSFProject/build/vsts-build.ps1 b/templates/PSFProject/build/vsts-build.ps1 index ffd8db5..cb69b66 100644 --- a/templates/PSFProject/build/vsts-build.ps1 +++ b/templates/PSFProject/build/vsts-build.ps1 @@ -30,6 +30,7 @@ if (-not $WorkingDirectory) } else { $WorkingDirectory = $env:SYSTEM_DEFAULTWORKINGDIRECTORY } } +if (-not $WorkingDirectory) { $WorkingDirectory = Split-Path $PSScriptRoot } #endregion Handle Working Directory Defaults # Prepare publish folder @@ -42,19 +43,15 @@ $text = @() $processed = @() # Gather Stuff to run before -foreach ($line in (Get-Content "$($PSScriptRoot)\filesBefore.txt" | Where-Object { $_ -notlike "#*" })) +foreach ($filePath in (& "$($PSScriptRoot)\..\þnameþ\internal\scripts\preimport.ps1")) { - if ([string]::IsNullOrWhiteSpace($line)) { continue } + if ([string]::IsNullOrWhiteSpace($filePath)) { continue } - $basePath = Join-Path "$($publishDir.FullName)\þnameþ" $line - foreach ($entry in (Resolve-PSFPath -Path $basePath)) - { - $item = Get-Item $entry - if ($item.PSIsContainer) { continue } - if ($item.FullName -in $processed) { continue } - $text += [System.IO.File]::ReadAllText($item.FullName) - $processed += $item.FullName - } + $item = Get-Item $filePath + if ($item.PSIsContainer) { continue } + if ($item.FullName -in $processed) { continue } + $text += [System.IO.File]::ReadAllText($item.FullName) + $processed += $item.FullName } # Gather commands @@ -66,19 +63,15 @@ Get-ChildItem -Path "$($publishDir.FullName)\þnameþ\functions\" -Recurse -File } # Gather stuff to run afterwards -foreach ($line in (Get-Content "$($PSScriptRoot)\filesAfter.txt" | Where-Object { $_ -notlike "#*" })) +foreach ($filePath in (& "$($PSScriptRoot)\..\þnameþ\internal\scripts\postimport.ps1")) { - if ([string]::IsNullOrWhiteSpace($line)) { continue } + if ([string]::IsNullOrWhiteSpace($filePath)) { continue } - $basePath = Join-Path "$($publishDir.FullName)\þnameþ" $line - foreach ($entry in (Resolve-PSFPath -Path $basePath)) - { - $item = Get-Item $entry - if ($item.PSIsContainer) { continue } - if ($item.FullName -in $processed) { continue } - $text += [System.IO.File]::ReadAllText($item.FullName) - $processed += $item.FullName - } + $item = Get-Item $filePath + if ($item.PSIsContainer) { continue } + if ($item.FullName -in $processed) { continue } + $text += [System.IO.File]::ReadAllText($item.FullName) + $processed += $item.FullName } #endregion Gather text data to compile diff --git a/templates/PSFProject/build/vsts-prerequisites.ps1 b/templates/PSFProject/build/vsts-prerequisites.ps1 index 6a39c66..f0df108 100644 --- a/templates/PSFProject/build/vsts-prerequisites.ps1 +++ b/templates/PSFProject/build/vsts-prerequisites.ps1 @@ -1,7 +1,25 @@ -$modules = @("Pester", "PSFramework", "PSModuleDevelopment", "PSScriptAnalyzer") +param ( + [string] + $Repository = 'PSGallery' +) + +$modules = @("Pester", "PSFramework", "PSModuleDevelopment", "PSScriptAnalyzer") + +# Automatically add missing dependencies +$data = Import-PowerShellDataFile -Path "$PSScriptRoot\..\þnameþ\þnameþ.psd1" +foreach ($dependency in $data.RequiredModules) { + if ($dependency -is [string]) { + if ($modules -contains $dependency) { continue } + $modules += $dependency + } + else { + if ($modules -contains $dependency.ModuleName) { continue } + $modules += $dependency.ModuleName + } +} foreach ($module in $modules) { Write-Host "Installing $module" -ForegroundColor Cyan - Install-Module $module -Force -SkipPublisherCheck + Install-Module $module -Force -SkipPublisherCheck -Repository $Repository Import-Module $module -Force -PassThru } \ No newline at end of file diff --git a/templates/PSFTests/PSMDTemplate.ps1 b/templates/PSFTests/PSMDTemplate.ps1 index 7f0ffdb..770fb51 100644 --- a/templates/PSFTests/PSMDTemplate.ps1 +++ b/templates/PSFTests/PSMDTemplate.ps1 @@ -1,6 +1,6 @@ @{ TemplateName = 'PSFTests' # Insert name of template - Version = "1.0.0.0" # Version to build to + Version = "2.0.0.0" # Version to build to AutoIncrementVersion = $true # If a newer version than specified is present, instead of the specified version, make it one greater than the existing template Tags = @('Tests', 'PSFramework') # Insert Tags as desired Author = 'Friedrich Weinmann' # The author of the template, not the file / project created from it @@ -13,10 +13,8 @@ testfolder = { } - testresults = { - @' -$results = Invoke-Pester -Script $file.FullName -Show $Show -PassThru -'@ + pesterconfig = { + } } # Insert additional scriptblocks as needed. Each scriptblock will be executed once only on create, no matter how often it is referenced. } \ No newline at end of file diff --git a/templates/PSFTests/general/FileIntegrity.Exceptions.ps1 b/templates/PSFTests/general/FileIntegrity.Exceptions.ps1 index e9a46a0..e385e41 100644 --- a/templates/PSFTests/general/FileIntegrity.Exceptions.ps1 +++ b/templates/PSFTests/general/FileIntegrity.Exceptions.ps1 @@ -1,19 +1,22 @@ # List of forbidden commands $global:BannedCommands = @( - 'Write-Host', - 'Write-Verbose', - 'Write-Warning', - 'Write-Error', - 'Write-Output', - 'Write-Information', - 'Write-Debug', + 'Write-Host' + 'Write-Verbose' + 'Write-Warning' + 'Write-Error' + 'Write-Output' + 'Write-Information' + 'Write-Debug' # Use CIM instead where possible - 'Get-WmiObject', - 'Invoke-WmiMethod', - 'Register-WmiEvent', - 'Remove-WmiObject', + 'Get-WmiObject' + 'Invoke-WmiMethod' + 'Register-WmiEvent' + 'Remove-WmiObject' 'Set-WmiInstance' + + # Use Get-WinEvent instead + 'Get-EventLog' ) <# diff --git a/templates/PSFTests/general/FileIntegrity.Tests.ps1 b/templates/PSFTests/general/FileIntegrity.Tests.ps1 index 82fa344..89e6c9c 100644 --- a/templates/PSFTests/general/FileIntegrity.Tests.ps1 +++ b/templates/PSFTests/general/FileIntegrity.Tests.ps1 @@ -1,44 +1,46 @@ -$moduleRoot = (Resolve-Path "$PSScriptRoot\..\..").Path +$moduleRoot = (Resolve-Path "$global:testroot\..").Path -. "$PSScriptRoot\FileIntegrity.Exceptions.ps1" +. "$global:testroot\general\FileIntegrity.Exceptions.ps1" -function Get-FileEncoding -{ -<# - .SYNOPSIS - Tests a file for encoding. - - .DESCRIPTION - Tests a file for encoding. - - .PARAMETER Path - The file to test -#> - [CmdletBinding()] - Param ( - [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] - [Alias('FullName')] - [string] - $Path - ) - - if ($PSVersionTable.PSVersion.Major -lt 6) - { - [byte[]]$byte = get-content -Encoding byte -ReadCount 4 -TotalCount 4 -Path $Path - } - else - { - [byte[]]$byte = Get-Content -AsByteStream -ReadCount 4 -TotalCount 4 -Path $Path +Describe "Verifying integrity of module files" { + BeforeAll { + function Get-FileEncoding + { + <# + .SYNOPSIS + Tests a file for encoding. + + .DESCRIPTION + Tests a file for encoding. + + .PARAMETER Path + The file to test + #> + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] + [Alias('FullName')] + [string] + $Path + ) + + if ($PSVersionTable.PSVersion.Major -lt 6) + { + [byte[]]$byte = get-content -Encoding byte -ReadCount 4 -TotalCount 4 -Path $Path + } + else + { + [byte[]]$byte = Get-Content -AsByteStream -ReadCount 4 -TotalCount 4 -Path $Path + } + + if ($byte[0] -eq 0xef -and $byte[1] -eq 0xbb -and $byte[2] -eq 0xbf) { 'UTF8 BOM' } + elseif ($byte[0] -eq 0xfe -and $byte[1] -eq 0xff) { 'Unicode' } + elseif ($byte[0] -eq 0 -and $byte[1] -eq 0 -and $byte[2] -eq 0xfe -and $byte[3] -eq 0xff) { 'UTF32' } + elseif ($byte[0] -eq 0x2b -and $byte[1] -eq 0x2f -and $byte[2] -eq 0x76) { 'UTF7' } + else { 'Unknown' } + } } - - if ($byte[0] -eq 0xef -and $byte[1] -eq 0xbb -and $byte[2] -eq 0xbf) { 'UTF8 BOM' } - elseif ($byte[0] -eq 0xfe -and $byte[1] -eq 0xff) { 'Unicode' } - elseif ($byte[0] -eq 0 -and $byte[1] -eq 0 -and $byte[2] -eq 0xfe -and $byte[3] -eq 0xff) { 'UTF32' } - elseif ($byte[0] -eq 0x2b -and $byte[1] -eq 0x2f -and $byte[2] -eq 0x76) { 'UTF7' } - else { 'Unknown' } -} -Describe "Verifying integrity of module files" { Context "Validating PS1 Script files" { $allFiles = Get-ChildItem -Path $moduleRoot -Recurse | Where-Object Name -like "*.ps1" | Where-Object FullName -NotLike "$moduleRoot\tests\*" @@ -46,11 +48,11 @@ Describe "Verifying integrity of module files" { { $name = $file.FullName.Replace("$moduleRoot\", '') - It "[$name] Should have UTF8 encoding with Byte Order Mark" { + It "[$name] Should have UTF8 encoding with Byte Order Mark" -TestCases @{ file = $file } { Get-FileEncoding -Path $file.FullName | Should -Be 'UTF8 BOM' } - It "[$name] Should have no trailing space" { + It "[$name] Should have no trailing space" -TestCases @{ file = $file } { ($file | Select-String "\s$" | Where-Object { $_.Line.Trim().Length -gt 0}).LineNumber | Should -BeNullOrEmpty } @@ -58,15 +60,15 @@ Describe "Verifying integrity of module files" { $parseErrors = $null $ast = [System.Management.Automation.Language.Parser]::ParseFile($file.FullName, [ref]$tokens, [ref]$parseErrors) - It "[$name] Should have no syntax errors" { - $parseErrors | Should Be $Null + It "[$name] Should have no syntax errors" -TestCases @{ parseErrors = $parseErrors } { + $parseErrors | Should -BeNullOrEmpty } foreach ($command in $global:BannedCommands) { if ($global:MayContainCommand["$command"] -notcontains $file.Name) { - It "[$name] Should not use $command" { + It "[$name] Should not use $command" -TestCases @{ tokens = $tokens; command = $command } { $tokens | Where-Object Text -EQ $command | Should -BeNullOrEmpty } } @@ -81,11 +83,11 @@ Describe "Verifying integrity of module files" { { $name = $file.FullName.Replace("$moduleRoot\", '') - It "[$name] Should have UTF8 encoding" { + It "[$name] Should have UTF8 encoding" -TestCases @{ file = $file } { Get-FileEncoding -Path $file.FullName | Should -Be 'UTF8 BOM' } - It "[$name] Should have no trailing space" { + It "[$name] Should have no trailing space" -TestCases @{ file = $file } { ($file | Select-String "\s$" | Where-Object { $_.Line.Trim().Length -gt 0 } | Measure-Object).Count | Should -Be 0 } } diff --git a/templates/PSFTests/general/Help.Tests.ps1 b/templates/PSFTests/general/Help.Tests.ps1 index b65aeeb..f34879a 100644 --- a/templates/PSFTests/general/Help.Tests.ps1 +++ b/templates/PSFTests/general/Help.Tests.ps1 @@ -36,13 +36,13 @@ Param ( $SkipTest, [string[]] - $CommandPath = @("$PSScriptRoot\..\..\functions", "$PSScriptRoot\..\..\internal\functions"), + $CommandPath = @("$global:testroot\..\functions", "$global:testroot\..\internal\functions"), [string] $ModuleName = "þnameþ", [string] - $ExceptionsFile = "$PSScriptRoot\Help.Exceptions.ps1" + $ExceptionsFile = "$global:testroot\general\Help.Exceptions.ps1" ) if ($SkipTest) { return } . $ExceptionsFile @@ -62,58 +62,32 @@ foreach ($command in $commands) { # The module-qualified command fails on Microsoft.PowerShell.Archive cmdlets $Help = Get-Help $commandName -ErrorAction SilentlyContinue - $testhelperrors = 0 - $testhelpall = 0 - Describe "Test help for $commandName" { - - $testhelpall += 1 - if ($Help.Synopsis -like '*`[``]*') { - # If help is not found, synopsis in auto-generated help is the syntax diagram - It "should not be auto-generated" { - $Help.Synopsis | Should -Not -BeLike '*`[``]*' - } - $testhelperrors += 1 - } - - $testhelpall += 1 - if ([String]::IsNullOrEmpty($Help.Description.Text)) { - # Should be a description for every function - It "gets description for $commandName" { - $Help.Description | Should -Not -BeNullOrEmpty - } - $testhelperrors += 1 - } + + Describe "Test help for $commandName" { - $testhelpall += 1 - if ([String]::IsNullOrEmpty(($Help.Examples.Example | Select-Object -First 1).Code)) { - # Should be at least one example - It "gets example code from $commandName" { - ($Help.Examples.Example | Select-Object -First 1).Code | Should -Not -BeNullOrEmpty - } - $testhelperrors += 1 - } + # If help is not found, synopsis in auto-generated help is the syntax diagram + It "should not be auto-generated" -TestCases @{ Help = $Help } { + $Help.Synopsis | Should -Not -BeLike '*`[``]*' + } - $testhelpall += 1 - if ([String]::IsNullOrEmpty(($Help.Examples.Example.Remarks | Select-Object -First 1).Text)) { - # Should be at least one example description - It "gets example help from $commandName" { - ($Help.Examples.Example.Remarks | Select-Object -First 1).Text | Should -Not -BeNullOrEmpty - } - $testhelperrors += 1 - } + # Should be a description for every function + It "gets description for $commandName" -TestCases @{ Help = $Help } { + $Help.Description | Should -Not -BeNullOrEmpty + } - if ($testhelperrors -eq 0) { - It "Ran silently $testhelpall tests" { - $testhelperrors | Should -be 0 - } - } + # Should be at least one example + It "gets example code from $commandName" -TestCases @{ Help = $Help } { + ($Help.Examples.Example | Select-Object -First 1).Code | Should -Not -BeNullOrEmpty + } + + # Should be at least one example description + It "gets example help from $commandName" -TestCases @{ Help = $Help } { + ($Help.Examples.Example.Remarks | Select-Object -First 1).Text | Should -Not -BeNullOrEmpty + } - $testparamsall = 0 - $testparamserrors = 0 Context "Test parameter help for $commandName" { - $Common = 'Debug', 'ErrorAction', 'ErrorVariable', 'InformationAction', 'InformationVariable', 'OutBuffer', 'OutVariable', - 'PipelineVariable', 'Verbose', 'WarningAction', 'WarningVariable' + $common = 'Debug', 'ErrorAction', 'ErrorVariable', 'InformationAction', 'InformationVariable', 'OutBuffer', 'OutVariable', 'PipelineVariable', 'Verbose', 'WarningAction', 'WarningVariable' $parameters = $command.ParameterSets.Parameters | Sort-Object -Property Name -Unique | Where-Object Name -notin $common $parameterNames = $parameters.Name @@ -121,79 +95,50 @@ foreach ($command in $commands) { foreach ($parameter in $parameters) { $parameterName = $parameter.Name $parameterHelp = $Help.parameters.parameter | Where-Object Name -EQ $parameterName + + # Should be a description for every parameter + It "gets help for parameter: $parameterName : in $commandName" -TestCases @{ parameterHelp = $parameterHelp } { + $parameterHelp.Description.Text | Should -Not -BeNullOrEmpty + } - $testparamsall += 1 - if ([String]::IsNullOrEmpty($parameterHelp.Description.Text)) { - # Should be a description for every parameter - It "gets help for parameter: $parameterName : in $commandName" { - $parameterHelp.Description.Text | Should -Not -BeNullOrEmpty - } - $testparamserrors += 1 - } - - $testparamsall += 1 $codeMandatory = $parameter.IsMandatory.toString() - if ($parameterHelp.Required -ne $codeMandatory) { - # Required value in Help should match IsMandatory property of parameter - It "help for $parameterName parameter in $commandName has correct Mandatory value" { - $parameterHelp.Required | Should -Be $codeMandatory - } - $testparamserrors += 1 - } + It "help for $parameterName parameter in $commandName has correct Mandatory value" -TestCases @{ parameterHelp = $parameterHelp; codeMandatory = $codeMandatory } { + $parameterHelp.Required | Should -Be $codeMandatory + } if ($HelpTestSkipParameterType[$commandName] -contains $parameterName) { continue } $codeType = $parameter.ParameterType.Name - $testparamsall += 1 if ($parameter.ParameterType.IsEnum) { # Enumerations often have issues with the typename not being reliably available $names = $parameter.ParameterType::GetNames($parameter.ParameterType) - if ($parameterHelp.parameterValueGroup.parameterValue -ne $names) { - # Parameter type in Help should match code - It "help for $commandName has correct parameter type for $parameterName" { - $parameterHelp.parameterValueGroup.parameterValue | Should -be $names - } - $testparamserrors += 1 - } + # Parameter type in Help should match code + It "help for $commandName has correct parameter type for $parameterName" -TestCases @{ parameterHelp = $parameterHelp; names = $names } { + $parameterHelp.parameterValueGroup.parameterValue | Should -be $names + } } elseif ($parameter.ParameterType.FullName -in $HelpTestEnumeratedArrays) { # Enumerations often have issues with the typename not being reliably available $names = [Enum]::GetNames($parameter.ParameterType.DeclaredMembers[0].ReturnType) - if ($parameterHelp.parameterValueGroup.parameterValue -ne $names) { - # Parameter type in Help should match code - It "help for $commandName has correct parameter type for $parameterName" { - $parameterHelp.parameterValueGroup.parameterValue | Should -be $names - } - $testparamserrors += 1 - } + It "help for $commandName has correct parameter type for $parameterName" -TestCases @{ parameterHelp = $parameterHelp; names = $names } { + $parameterHelp.parameterValueGroup.parameterValue | Should -be $names + } } else { # To avoid calling Trim method on a null object. $helpType = if ($parameterHelp.parameterValue) { $parameterHelp.parameterValue.Trim() } - if ($helpType -ne $codeType) { - # Parameter type in Help should match code - It "help for $commandName has correct parameter type for $parameterName" { - $helpType | Should -be $codeType - } - $testparamserrors += 1 - } + # Parameter type in Help should match code + It "help for $commandName has correct parameter type for $parameterName" -TestCases @{ helpType = $helpType; codeType = $codeType } { + $helpType | Should -be $codeType + } } } foreach ($helpParm in $HelpParameterNames) { - $testparamsall += 1 - if ($helpParm -notin $parameterNames) { - # Shouldn't find extra parameters in help. - It "finds help parameter in code: $helpParm" { - $helpParm -in $parameterNames | Should -Be $true - } - $testparamserrors += 1 - } - } - if ($testparamserrors -eq 0) { - It "Ran silently $testparamsall tests" { - $testparamserrors | Should -be 0 - } + # Shouldn't find extra parameters in help. + It "finds help parameter in code: $helpParm" -TestCases @{ helpParm = $helpParm; parameterNames = $parameterNames } { + $helpParm -in $parameterNames | Should -Be $true + } } } } diff --git a/templates/PSFTests/general/Manifest.Tests.ps1 b/templates/PSFTests/general/Manifest.Tests.ps1 index f5b7cd5..b8b930d 100644 --- a/templates/PSFTests/general/Manifest.Tests.ps1 +++ b/templates/PSFTests/general/Manifest.Tests.ps1 @@ -1,39 +1,39 @@ Describe "Validating the module manifest" { - $moduleRoot = (Resolve-Path "$PSScriptRoot\..\..").Path + $moduleRoot = (Resolve-Path "$global:testroot\..").Path $manifest = ((Get-Content "$moduleRoot\þnameþ.psd1") -join "`n") | Invoke-Expression Context "Basic resources validation" { $files = Get-ChildItem "$moduleRoot\functions" -Recurse -File | Where-Object Name -like "*.ps1" - It "Exports all functions in the public folder" { + It "Exports all functions in the public folder" -TestCases @{ files = $files; manifest = $manifest } { $functions = (Compare-Object -ReferenceObject $files.BaseName -DifferenceObject $manifest.FunctionsToExport | Where-Object SideIndicator -Like '<=').InputObject $functions | Should -BeNullOrEmpty } - It "Exports no function that isn't also present in the public folder" { + It "Exports no function that isn't also present in the public folder" -TestCases @{ files = $files; manifest = $manifest } { $functions = (Compare-Object -ReferenceObject $files.BaseName -DifferenceObject $manifest.FunctionsToExport | Where-Object SideIndicator -Like '=>').InputObject $functions | Should -BeNullOrEmpty } - It "Exports none of its internal functions" { + It "Exports none of its internal functions" -TestCases @{ moduleRoot = $moduleRoot; manifest = $manifest } { $files = Get-ChildItem "$moduleRoot\internal\functions" -Recurse -File -Filter "*.ps1" $files | Where-Object BaseName -In $manifest.FunctionsToExport | Should -BeNullOrEmpty } } Context "Individual file validation" { - It "The root module file exists" { + It "The root module file exists" -TestCases @{ moduleRoot = $moduleRoot; manifest = $manifest } { Test-Path "$moduleRoot\$($manifest.RootModule)" | Should -Be $true } foreach ($format in $manifest.FormatsToProcess) { - It "The file $format should exist" { + It "The file $format should exist" -TestCases @{ moduleRoot = $moduleRoot; format = $format } { Test-Path "$moduleRoot\$format" | Should -Be $true } } foreach ($type in $manifest.TypesToProcess) { - It "The file $type should exist" { + It "The file $type should exist" -TestCases @{ moduleRoot = $moduleRoot; type = $type } { Test-Path "$moduleRoot\$type" | Should -Be $true } } @@ -41,12 +41,12 @@ foreach ($assembly in $manifest.RequiredAssemblies) { if ($assembly -like "*.dll") { - It "The file $assembly should exist" { + It "The file $assembly should exist" -TestCases @{ moduleRoot = $moduleRoot; assembly = $assembly } { Test-Path "$moduleRoot\$assembly" | Should -Be $true } } else { - It "The file $assembly should load from the GAC" { + It "The file $assembly should load from the GAC" -TestCases @{ moduleRoot = $moduleRoot; assembly = $assembly } { { Add-Type -AssemblyName $assembly } | Should -Not -Throw } } @@ -54,7 +54,7 @@ foreach ($tag in $manifest.PrivateData.PSData.Tags) { - It "Tags should have no spaces in name" { + It "Tags should have no spaces in name" -TestCases @{ tag = $tag } { $tag -match " " | Should -Be $false } } diff --git a/templates/PSFTests/general/PSScriptAnalyzer.Tests.ps1 b/templates/PSFTests/general/PSScriptAnalyzer.Tests.ps1 index 1569b14..74e5a65 100644 --- a/templates/PSFTests/general/PSScriptAnalyzer.Tests.ps1 +++ b/templates/PSFTests/general/PSScriptAnalyzer.Tests.ps1 @@ -4,12 +4,12 @@ Param ( $SkipTest, [string[]] - $CommandPath = @("$PSScriptRoot\..\..\functions", "$PSScriptRoot\..\..\internal\functions") + $CommandPath = @("$global:testroot\..\functions", "$global:testroot\..\internal\functions") ) if ($SkipTest) { return } -$list = New-Object System.Collections.ArrayList +$global:__pester_data.ScriptAnalyzer = New-Object System.Collections.ArrayList Describe 'Invoking PSScriptAnalyzer against commandbase' { $commandFiles = Get-ChildItem -Path $CommandPath -Recurse | Where-Object Name -like "*.ps1" @@ -22,21 +22,19 @@ Describe 'Invoking PSScriptAnalyzer against commandbase' { forEach ($rule in $scriptAnalyzerRules) { - It "Should pass $rule" { + It "Should pass $rule" -TestCases @{ analysis = $analysis; rule = $rule } { If ($analysis.RuleName -contains $rule) { - $analysis | Where-Object RuleName -EQ $rule -outvariable failures | ForEach-Object { $list.Add($_) } + $analysis | Where-Object RuleName -EQ $rule -outvariable failures | ForEach-Object { $null = $global:__pester_data.ScriptAnalyzer.Add($_) } - 1 | Should Be 0 + 1 | Should -Be 0 } else { - 0 | Should Be 0 + 0 | Should -Be 0 } } } } } -} - -$list | Out-Default \ No newline at end of file +} \ No newline at end of file diff --git a/templates/PSFTests/general/strings.Tests.ps1 b/templates/PSFTests/general/strings.Tests.ps1 index 861a215..5045dc1 100644 --- a/templates/PSFTests/general/strings.Tests.ps1 +++ b/templates/PSFTests/general/strings.Tests.ps1 @@ -6,14 +6,16 @@ It also checks, whether the language files have orphaned entries that need cleaning up. #> -$moduleRoot = (Get-Module þnameþ).ModuleBase -$stringsResults = Export-PSMDString -ModuleRoot $moduleRoot -$exceptions = & "$PSScriptRoot\strings.Exceptions.ps1" + Describe "Testing localization strings" { - foreach ($stringEntry in $stringsResults) { + $moduleRoot = (Get-Module þnameþ).ModuleBase + $stringsResults = Export-PSMDString -ModuleRoot $moduleRoot + $exceptions = & "$global:testroot\general\strings.Exceptions.ps1" + + foreach ($stringEntry in $stringsResults) { if ($stringEntry.String -eq "key") { continue } # Skipping the template default entry - It "Should be used & have text: $($stringEntry.String)" { + It "Should be used & have text: $($stringEntry.String)" -TestCases @{ stringEntry = $stringEntry } { if ($exceptions.LegalSurplus -notcontains $stringEntry.String) { $stringEntry.Surplus | Should -BeFalse } diff --git a/templates/PSFTests/pester.ps1 b/templates/PSFTests/pester.ps1 index fdd54bc..8e32bf1 100644 --- a/templates/PSFTests/pester.ps1 +++ b/templates/PSFTests/pester.ps1 @@ -3,8 +3,9 @@ $TestFunctions = $true, - [ValidateSet('None', 'Default', 'Passed', 'Failed', 'Pending', 'Skipped', 'Inconclusive', 'Describe', 'Context', 'Summary', 'Header', 'Fails', 'All')] - $Show = "None", + [ValidateSet('None', 'Normal', 'Detailed', 'Diagnostic')] + [Alias('Show')] + $Output = "None", $Include = "*", @@ -15,16 +16,24 @@ Write-PSFMessage -Level Important -Message "Starting Tests" Write-PSFMessage -Level Important -Message "Importing Module" +$global:testroot = $PSScriptRoot +$global:__pester_data = @{ } + Remove-Module þnameþ -ErrorAction Ignore Import-Module "$PSScriptRoot\..\þnameþ.psd1" Import-Module "$PSScriptRoot\..\þnameþ.psm1" -Force +# Need to import explicitly so we can use the configuration class +Import-Module Pester + þ!testfolder!þ $totalFailed = 0 $totalRun = 0 $testresults = @() +$config = [PesterConfiguration]::Default +þ!pesterconfig!þ #region Run General Tests if ($TestGeneral) @@ -32,20 +41,25 @@ if ($TestGeneral) Write-PSFMessage -Level Important -Message "Modules imported, proceeding with general tests" foreach ($file in (Get-ChildItem "$PSScriptRoot\general" | Where-Object Name -like "*.Tests.ps1")) { + if ($file.Name -notlike $Include) { continue } + if ($file.Name -like $Exclude) { continue } + Write-PSFMessage -Level Significant -Message " Executing $($file.Name)" - þ!testresults!þ + $config.TestResult.OutputPath = Join-Path "$PSScriptRoot\..\..\TestResults" "TEST-$($file.BaseName).xml" + $config.Run.Path = $file.FullName + $config.Run.PassThru = $true + $config.Output.Verbosity = $Output + $results = Invoke-Pester -Configuration $config foreach ($result in $results) { $totalRun += $result.TotalCount $totalFailed += $result.FailedCount - $result.TestResult | Where-Object { -not $_.Passed } | ForEach-Object { - $name = $_.Name + $result.Tests | Where-Object Result -ne 'Passed' | ForEach-Object { $testresults += [pscustomobject]@{ - Describe = $_.Describe - Context = $_.Context - Name = "It $name" + Block = $_.Block + Name = "It $($_.Name)" Result = $_.Result - Message = $_.FailureMessage + Message = $_.ErrorRecord.DisplayErrorMessage } } } @@ -53,29 +67,33 @@ if ($TestGeneral) } #endregion Run General Tests +$global:__pester_data.ScriptAnalyzer | Out-Host + #region Test Commands if ($TestFunctions) { -Write-PSFMessage -Level Important -Message "Proceeding with individual tests" + Write-PSFMessage -Level Important -Message "Proceeding with individual tests" foreach ($file in (Get-ChildItem "$PSScriptRoot\functions" -Recurse -File | Where-Object Name -like "*Tests.ps1")) { if ($file.Name -notlike $Include) { continue } if ($file.Name -like $Exclude) { continue } Write-PSFMessage -Level Significant -Message " Executing $($file.Name)" - þ!testresults!þ + $config.TestResult.OutputPath = Join-Path "$PSScriptRoot\..\..\TestResults" "TEST-$($file.BaseName).xml" + $config.Run.Path = $file.FullName + $config.Run.PassThru = $true + $config.Output.Verbosity = $Output + $results = Invoke-Pester -Configuration $config foreach ($result in $results) { $totalRun += $result.TotalCount $totalFailed += $result.FailedCount - $result.TestResult | Where-Object { -not $_.Passed } | ForEach-Object { - $name = $_.Name + $result.Tests | Where-Object Result -ne 'Passed' | ForEach-Object { $testresults += [pscustomobject]@{ - Describe = $_.Describe - Context = $_.Context - Name = "It $name" + Block = $_.Block + Name = "It $($_.Name)" Result = $_.Result - Message = $_.FailureMessage + Message = $_.ErrorRecord.DisplayErrorMessage } } } diff --git a/templates/module/internal/scripts/readme.md b/templates/module/internal/scripts/readme.md index 121788c..9023877 100644 --- a/templates/module/internal/scripts/readme.md +++ b/templates/module/internal/scripts/readme.md @@ -1,7 +1,7 @@ -# Scipts +# Scripts This is the folder where the internal scripts go. These are files that are only run during import and can be used to add content other than just functions. -For example you could use these to connect to a service, load configuration data or define tab completion. \ No newline at end of file +For example you could use these to connect to a service, load configuration data or define tab completion.