Skip to content

Commit

Permalink
SqlServerDsc: Add markdown creation for public commands (#1986)
Browse files Browse the repository at this point in the history
- SqlServerDsc
  - Added build tasks to generate Wiki documentation for public commands.
  - Documentation is now built using a separate meta task `docs`.
    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.
  • Loading branch information
johlju authored Jan 18, 2024
1 parent 575f809 commit a11aee3
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 90 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- SqlServerDsc
- Added build tasks to generate Wiki documentation for public commands.

### Fixed

- SqlServerDsc
Expand Down Expand Up @@ -35,6 +40,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Update markdown highlights with newly supported keywords.
- Bump GitHub Action _Stale_ to v9.
- Bump GitHub Action _CodeQL-Action_ to v3.
- Documentation is now built using a separate meta task `docs`.
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.
- SqlSetup
- Updated integration tests to use PSResourceGet to download required modules.

Expand Down
13 changes: 11 additions & 2 deletions RequiredModules.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
MarkdownLinkCheck = 'latest'
'DscResource.Test' = 'latest'
xDscResourceDesigner = 'latest'
'DscResource.DocGenerator' = 'latest'

# Build dependencies needed for using the module
'DscResource.Base' = 'latest'
Expand All @@ -46,9 +45,19 @@
# Need to pin this to 3.7.2 because 4.0.0 made the integration tests fail.
PSPKI = '3.7.2'

# Prerequisites modules needed for examples or integration tests
# Prerequisite modules needed for examples or integration tests
xPSDesiredStateConfiguration = '9.1.0'
StorageDsc = '5.1.0'
NetworkingDsc = '9.0.0'
WSManDsc = '3.1.1'

# Prerequisite modules for documentation.
#'DscResource.DocGenerator' = 'latest'
'DscResource.DocGenerator' = @{
Version = 'latest'
Parameters = @{
AllowPrerelease = $true
}
}
PlatyPS = 'latest'
}
15 changes: 15 additions & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ stages:
}
Write-Host -Object "##vso[build.updatebuildnumber]$($gitVersionObject.FullSemVer)"
displayName: Calculate ModuleVersion (GitVersion)
- pwsh: |
<#
This is intentionally not saved to RequiredModules. It is only
used for creating documentation and should not be part of the
build artifact.
#>
Write-Information -MessageData 'Removing SqlServer and SQLPS from the CI worker.' -InformationAction Continue
Import-Module -Name ./tests/TestHelpers/CommonTestHelper.psm1
Remove-PowerShellModuleFromCI -Name @('SqlServer', 'SQLPS')
Remove-Module -Name CommonTestHelper
Write-Information -MessageData 'Installing latest SqlServer on the CI worker' -InformationAction Continue
Install-Module -Name SqlServer -Force -Confirm:$false -Verbose
Get-Module -Name SqlServer -ListAvailable |
Select-Object -Property Name,Version,@{Name='Prerelease'; Expression={$_.PrivateData.PSData.Prerelease}}, Path
displayName: Install SqlServer module
- task: PowerShell@2
name: package
displayName: 'Build & Package Module'
Expand Down
50 changes: 38 additions & 12 deletions build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,21 @@ BuildWorkflow:
- Build_Module_ModuleBuilder
- Build_NestedModules_ModuleBuilder
- Create_Changelog_Release_Output

docs:
- Check_SqlServer_Availability
- Generate_Conceptual_Help
- Generate_Wiki_Content
- Generate_Markdown_For_Public_Commands
- Generate_External_Help_File_For_Public_Commands
- Clean_Markdown_Of_Public_Commands
- Generate_Wiki_Sidebar
- Clean_Markdown_Metadata
- Package_WikContent

fastbuild:
- Clean
- Build_Module_ModuleBuilder
- Build_NestedModules_ModuleBuilder

pack:
- build
- docs
- package_module_nupkg # cSpell: disable-line

hqrmtest: # cSpell: disable-line
Expand All @@ -38,15 +42,22 @@ BuildWorkflow:
- Publish_Module_To_gallery
- Publish_GitHub_Wiki_Content

Check_SqlServer_Availability: |
{
Import-Module -Name SqlServer -ErrorAction 'SilentlyContinue'
try
{
[Microsoft.SqlServer.Management.Smo.Server] -as [Type] | Out-Null
}
catch
{
throw 'The SqlServer types are not available. Please install the SqlServer module or load SMO stubs types and try again.'
}
}
Package_WikContent: |
{
<#
This is quick fix for issue https://github.com/PoshCode/ModuleBuilder/issues/103.
Aliases need to be added to module manifest, and we can only use
New-Alias or Set-Alias together with build tasks. This module
already automatically exports aliases using [Alias()] so this quick
fix is meant as a workaround until the above issue is resolved.
#>
param
(
$OutputDirectory = (property OutputDirectory (Join-Path $BuildRoot 'output')),
Expand Down Expand Up @@ -207,3 +218,18 @@ DscResource.DocGenerator:
- '_(.+?)_' # Match Italic (underscore)
- '\*\*(.+?)\*\*' # Match bold
- '\*(.+?)\*' # Match Italic (asterisk)
Publish_GitHub_Wiki_Content:
Debug: false
Generate_Wiki_Content:
MofResourceMetadata:
Type: MofResource
Category: Resources
ClassResourceMetadata:
Type: ClassResource
Category: Resources
CompositeResourceMetadata:
Type: CompositeResource
Category: Resources
Generate_Wiki_Sidebar:
Debug: false
AlwaysOverwrite: true
121 changes: 45 additions & 76 deletions tests/QA/module.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand All @@ -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' {
Expand All @@ -154,11 +165,11 @@ Describe 'Quality for module' -Tags 'TestQuality' {
}
}

It 'Should have a unit test for <Name>' -ForEach $testCases {
It 'Should have a unit test for <Name>' -ForEach $testCasesAllModuleFunction {
Get-ChildItem -Path 'tests\' -Recurse -Include "$Name.Tests.ps1" | Should -Not -BeNullOrEmpty
}

It 'Should pass Script Analyzer for <Name>' -ForEach $testCases -Skip:(-not $scriptAnalyzerRules) {
It 'Should pass Script Analyzer for <Name>' -ForEach $testCasesAllModuleFunction -Skip:(-not $scriptAnalyzerRules) {
$functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1"

$pssaResult = (Invoke-ScriptAnalyzer -Path $functionFile.FullName)
Expand All @@ -169,88 +180,46 @@ Describe 'Quality for module' -Tags 'TestQuality' {
}

Describe 'Help for module' -Tags 'helpQuality' {
It 'Should have .SYNOPSIS for <Name>' -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 <Name>' -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()
$astSearchDelegate = { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }

$functionHelp.Synopsis | Should -Not -BeNullOrEmpty
}

It 'Should have a .DESCRIPTION with length greater than 40 characters for <Name>' -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
}

$functionHelp = $parsedFunction.GetHelpContent()

$functionHelp.Description.Length | Should -BeGreaterThan 40
}
$parsedFunction = $abstractSyntaxTree.FindAll( $astSearchDelegate, $true ) |
Where-Object -FilterScript {
$_.Name -eq $Name
}

It 'Should have at least one (1) example for <Name>' -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
}

$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)

}
$script:functionHelp = $parsedFunction.GetHelpContent()
}

It 'Should have described all parameters for <Name>' -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 <Name>' {
$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 <Name>' {
$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 <Name>' {
$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()

$parameters = $parsedFunction.Body.ParamBlock.Parameters.Name.VariablePath.ForEach({ $_.ToString() })

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)
}
}
}

0 comments on commit a11aee3

Please sign in to comment.