From 220874068f37bd30cd5e3ccf1116f54d22e2c21b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 31 Dec 2023 23:45:58 +0100 Subject: [PATCH] Fix QA tests --- CHANGELOG.md | 4 ++ tests/QA/module.tests.ps1 | 130 +++++++++++++++++--------------------- 2 files changed, 63 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75dd858824..e57885e7d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - SqlServerDsc - Added build tasks to generate Wiki documentation for public commands. + - QA test to verify that each public command's script file starts with a + new line (needed until PR https://github.com/PoshCode/ModuleBuilder/pull/127 + is merged and released). ### Fixed @@ -41,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 It is run with the meta task `pack` which is run by the pipeline. To run the meta task `docs` the SMO assemblies must be loaded into the session, either by importing SqlServer module or loading SMO stubs. + - QA test improved to speed up quality testing. ## [16.5.0] - 2023-10-05 diff --git a/tests/QA/module.tests.ps1 b/tests/QA/module.tests.ps1 index 21c019ccae..e42f8eb4d4 100644 --- a/tests/QA/module.tests.ps1 +++ b/tests/QA/module.tests.ps1 @@ -102,7 +102,7 @@ Describe 'Changelog Management' -Tag 'Changelog' { } It 'Changelog should have an Unreleased header' -Skip:$skipTest { - (Get-ChangelogData -Path (Join-Path -Path $ProjectPath -ChildPath 'CHANGELOG.md') -ErrorAction Stop).Unreleased | Should -Not -BeNullOrEmpty + (Get-ChangelogData -Path (Join-Path -Path $ProjectPath -ChildPath 'CHANGELOG.md') -ErrorAction Stop).Unreleased | Should -Not -BeNullOrEmpty } } @@ -125,14 +125,25 @@ BeforeDiscovery { $allModuleFunctions = & $mut { Get-Command -Module $args[0] -CommandType Function } $script:moduleName # Build test cases. - $testCases = @() + $testCasesAllModuleFunction = @() foreach ($function in $allModuleFunctions) { - $testCases += @{ + $testCasesAllModuleFunction += @{ Name = $function.Name } } + + $allPublicCommand = (Get-Command -Module $script:moduleName).Name + + $testCasesPublicCommand = @() + + foreach ($command in $allPublicCommand) + { + $testCasesPublicCommand += @{ + Name = $command + } + } } Describe 'Quality for module' -Tags 'TestQuality' { @@ -154,11 +165,11 @@ Describe 'Quality for module' -Tags 'TestQuality' { } } - It 'Should have a unit test for ' -ForEach $testCases { + It 'Should have a unit test for ' -ForEach $testCasesAllModuleFunction { Get-ChildItem -Path 'tests\' -Recurse -Include "$Name.Tests.ps1" | Should -Not -BeNullOrEmpty } - It 'Should pass Script Analyzer for ' -ForEach $testCases -Skip:(-not $scriptAnalyzerRules) { + It 'Should pass Script Analyzer for ' -ForEach $testCasesAllModuleFunction -Skip:(-not $scriptAnalyzerRules) { $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" $pssaResult = (Invoke-ScriptAnalyzer -Path $functionFile.FullName) @@ -169,88 +180,65 @@ Describe 'Quality for module' -Tags 'TestQuality' { } Describe 'Help for module' -Tags 'helpQuality' { - It 'Should have .SYNOPSIS for ' -ForEach $testCases { - $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" - - $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName - - $abstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput($scriptFileRawContent, [ref] $null, [ref] $null) + Context 'Validating help for ' -ForEach $testCasesAllModuleFunction -Tag 'helpQuality' { + BeforeAll { + $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" - $astSearchDelegate = { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] } + $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName - $parsedFunction = $abstractSyntaxTree.FindAll( $astSearchDelegate, $true ) | - Where-Object -FilterScript { - $_.Name -eq $Name - } + $abstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput($scriptFileRawContent, [ref] $null, [ref] $null) - $functionHelp = $parsedFunction.GetHelpContent() - - $functionHelp.Synopsis | Should -Not -BeNullOrEmpty - } - - It 'Should have a .DESCRIPTION with length greater than 40 characters for ' -ForEach $testCases { - $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" - - $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName - - $abstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput($scriptFileRawContent, [ref] $null, [ref] $null) - - $astSearchDelegate = { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] } - - $parsedFunction = $abstractSyntaxTree.FindAll($astSearchDelegate, $true) | - Where-Object -FilterScript { - $_.Name -eq $Name - } + $astSearchDelegate = { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] } - $functionHelp = $parsedFunction.GetHelpContent() + $parsedFunction = $abstractSyntaxTree.FindAll( $astSearchDelegate, $true ) | + Where-Object -FilterScript { + $_.Name -eq $Name + } - $functionHelp.Description.Length | Should -BeGreaterThan 40 - } + $script:functionHelp = $parsedFunction.GetHelpContent() + } - It 'Should have at least one (1) example for ' -ForEach $testCases { - $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" + It 'Should have .SYNOPSIS' { + $functionHelp.Synopsis | Should -Not -BeNullOrEmpty + } - $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName + It 'Should have a .DESCRIPTION with length greater than 40 characters for ' { + $functionHelp.Description.Length | Should -BeGreaterThan 40 + } - $abstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput($scriptFileRawContent, [ref] $null, [ref] $null) + It 'Should have at least one (1) example for ' { + $functionHelp.Examples.Count | Should -BeGreaterThan 0 + $functionHelp.Examples[0] | Should -Match ([regex]::Escape($function.Name)) + $functionHelp.Examples[0].Length | Should -BeGreaterThan ($function.Name.Length + 10) + } - $astSearchDelegate = { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] } + It 'Should have described all parameters for ' { + $parameters = $parsedFunction.Body.ParamBlock.Parameters.Name.VariablePath.ForEach({ $_.ToString() }) - $parsedFunction = $abstractSyntaxTree.FindAll( $astSearchDelegate, $true ) | - Where-Object -FilterScript { - $_.Name -eq $Name + foreach ($parameter in $parameters) + { + $functionHelp.Parameters.($parameter.ToUpper()) | Should -Not -BeNullOrEmpty -Because ('the parameter {0} must have a description' -f $parameter) + $functionHelp.Parameters.($parameter.ToUpper()).Length | Should -BeGreaterThan 25 -Because ('the parameter {0} must have descriptive description' -f $parameter) } - - $functionHelp = $parsedFunction.GetHelpContent() - - $functionHelp.Examples.Count | Should -BeGreaterThan 0 - $functionHelp.Examples[0] | Should -Match ([regex]::Escape($function.Name)) - $functionHelp.Examples[0].Length | Should -BeGreaterThan ($function.Name.Length + 10) - + } } - It 'Should have described all parameters for ' -ForEach $testCases { - $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" - - $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName - - $abstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput($scriptFileRawContent, [ref] $null, [ref] $null) - - $astSearchDelegate = { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] } + It 'Should have a new line at the start of the script file for command ' -ForEach $testCasesPublicCommand { + $publicFunctionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" - $parsedFunction = $abstractSyntaxTree.FindAll( $astSearchDelegate, $true ) | - Where-Object -FilterScript { - $_.Name -eq $Name - } - - $functionHelp = $parsedFunction.GetHelpContent() + $content = Get-Content -Raw -Path $publicFunctionFile.FullName - $parameters = $parsedFunction.Body.ParamBlock.Parameters.Name.VariablePath.ForEach({ $_.ToString() }) + $firstLine = $null - foreach ($parameter in $parameters) - { - $functionHelp.Parameters.($parameter.ToUpper()) | Should -Not -BeNullOrEmpty -Because ('the parameter {0} must have a description' -f $parameter) - $functionHelp.Parameters.($parameter.ToUpper()).Length | Should -BeGreaterThan 25 -Because ('the parameter {0} must have descriptive description' -f $parameter) + <# + Singles out the first line of the file. This is so that the test does not + output the entire file but just the first line if the file does not start + with a new line. + #> + if ($content -match '^.*\r?\n') { + $firstLine = $matches[0] } + + $firstLine | Should -Match '^\r?\n' -Because 'due to a bug with ModuleBuilder (see issue https://github.com/PoshCode/ModuleBuilder/issues/126) the first line of the script file must be a new line' } }