Skip to content

Commit f592f3b

Browse files
committed
Validating examples
1 parent 6354195 commit f592f3b

File tree

2 files changed

+271
-5
lines changed

2 files changed

+271
-5
lines changed

resources/Help/Microsoft.VSCode.Dsc/VSCodeExtension.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ The `VSCodeExtension` DSC Resource allows you to install, update, and remove Vis
3535
$params = @{
3636
Name = 'ms-python.python'
3737
}
38-
Invoke-DscResource -Name VSCodeExtension -Method Set -Property $params -ModuleName Microsoft.VSCode.Dsc
38+
Invoke-DscResource -ModuleName Microsoft.VSCode.Dsc -Name VSCodeExtension -Method Set -Property $params
3939
```
4040

4141
### EXAMPLE 2
@@ -46,7 +46,7 @@ $params = @{
4646
Name = 'ms-python.python'
4747
Version = '2021.5.842923320'
4848
}
49-
Invoke-DscResource -Name VSCodeExtension -Method Set -Property $params -ModuleName Microsoft.VSCode.Dsc
49+
Invoke-DscResource -ModuleName Microsoft.VSCode.Dsc -Name VSCodeExtension -Method Set -Property $params
5050
```
5151

5252
### EXAMPLE 3
@@ -57,7 +57,7 @@ $params = @{
5757
Name = 'ms-python.python'
5858
Exist = $false
5959
}
60-
Invoke-DscResource -Name VSCodeExtension -Method Set -Property $params -ModuleName Microsoft.VSCode.Dsc
60+
Invoke-DscResource -ModuleName Microsoft.VSCode.Dsc -Name VSCodeExtension -Method Set -Property $params
6161
```
6262

6363
### EXAMPLE 4
@@ -68,5 +68,5 @@ $params = @{
6868
Name = 'ms-python.python'
6969
Insiders = $true
7070
}
71-
Invoke-DscResource -Name VSCodeExtension -Method Set -Property $params -ModuleName Microsoft.VSCode.Dsc
71+
Invoke-DscResource -ModuleName Microsoft.VSCode.Dsc -Name VSCodeExtension -Method Set -Property $params
7272
```

tests/QA/module.tests.ps1

Lines changed: 267 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,157 @@ param (
1111
Write-Verbose ("repoRootPath: $repoRootPath") -Verbose
1212
Write-Verbose ("modules: $($modules.Count)") -Verbose
1313

14+
#region Functions
15+
function Get-MarkdownHeadings {
16+
[CmdletBinding()]
17+
param (
18+
[Parameter(Mandatory = $true)]
19+
[string]$FilePath
20+
)
21+
22+
$fileContent = Get-Content -Path $FilePath
23+
24+
$headings = @()
25+
26+
# Use pattern to capture all headings
27+
$headingPattern = '^(#+)\s+(.*)'
28+
29+
foreach ($line in $fileContent) {
30+
if ($line -match $headingPattern) {
31+
$level = $matches[1].Length
32+
$text = $matches[2]
33+
34+
$heading = [PSCustomObject]@{
35+
Level = $level
36+
Text = $text
37+
}
38+
39+
$headings += $heading
40+
}
41+
}
42+
43+
return $headings
44+
}
45+
46+
function Get-MdCodeBlock {
47+
[CmdletBinding()]
48+
[OutputType([CodeBlock])]
49+
param (
50+
[Parameter(Mandatory, ValueFromPipeline, Position = 0)]
51+
[string[]]
52+
[SupportsWildcards()]
53+
$Path,
54+
55+
[Parameter()]
56+
[string]
57+
$BasePath = '.',
58+
59+
[Parameter()]
60+
[string]
61+
$Language
62+
)
63+
64+
process {
65+
foreach ($unresolved in $Path) {
66+
foreach ($file in (Resolve-Path -Path $unresolved).Path) {
67+
$file = (Resolve-Path -Path $file).Path
68+
$BasePath = (Resolve-Path -Path $BasePath).Path
69+
$escapedRoot = [regex]::Escape($BasePath)
70+
$relativePath = $file -replace "$escapedRoot\\", ''
71+
72+
73+
# This section imports files referenced by PyMdown snippet syntax
74+
# Example: --8<-- "abbreviations.md"
75+
# Note: This function only supports very basic snippet syntax.
76+
# See https://facelessuser.github.io/pymdown-extensions/extensions/snippets/ for documentation on the Snippets PyMdown extension
77+
$lines = [System.IO.File]::ReadAllLines($file, [System.Text.Encoding]::UTF8) | ForEach-Object {
78+
if ($_ -match '--8<-- "(?<file>[^"]+)"') {
79+
$snippetPath = Join-Path -Path $BasePath -ChildPath $Matches.file
80+
if (Test-Path -Path $snippetPath) {
81+
Get-Content -Path $snippetPath
82+
} else {
83+
Write-Warning "Snippet not found: $snippetPath"
84+
}
85+
} else {
86+
$_
87+
}
88+
}
89+
90+
91+
$lineNumber = 0
92+
$code = $null
93+
$state = [MdState]::Undefined
94+
$content = [System.Text.StringBuilder]::new()
95+
96+
foreach ($line in $lines) {
97+
$lineNumber++
98+
switch ($state) {
99+
'Undefined' {
100+
if ($line -match '^\s*```(?<lang>\w+)?' -and ([string]::IsNullOrWhiteSpace($Language) -or $Matches.lang -eq $Language)) {
101+
$state = [MdState]::InCodeBlock
102+
$code = [CodeBlock]@{
103+
Source = $relativePath
104+
Language = $Matches.lang
105+
LineNumber = $lineNumber
106+
}
107+
} elseif (($inlineMatches = [regex]::Matches($line, '(?<!`)`(#!(?<lang>\w+) )?(?<code>[^`]+)`(?!`)'))) {
108+
if (-not [string]::IsNullOrWhiteSpace($Language) -and $inlineMatch.Groups.lang -ne $Language) {
109+
continue
110+
}
111+
foreach ($inlineMatch in $inlineMatches) {
112+
[CodeBlock]@{
113+
Source = $relativePath
114+
Language = $inlineMatch.Groups.lang
115+
Content = $inlineMatch.Groups.code
116+
LineNumber = $lineNumber
117+
Position = $inlineMatch.Index
118+
Inline = $true
119+
}
120+
}
121+
}
122+
}
123+
124+
'InCodeBlock' {
125+
if ($line -match '^\s*```') {
126+
$state = [MdState]::Undefined
127+
$code.Content = $content.ToString()
128+
$code
129+
$code = $null
130+
$null = $content.Clear()
131+
} else {
132+
$null = $content.AppendLine($line)
133+
}
134+
}
135+
}
136+
}
137+
}
138+
}
139+
}
140+
}
141+
#endRegion Functions
142+
143+
#region Enum
144+
enum MdState {
145+
Undefined
146+
InCodeBlock
147+
}
148+
#endRegion Enum
149+
class CodeBlock {
150+
[string] $Source
151+
[string] $Language
152+
[string] $Content
153+
[int] $LineNumber
154+
[int] $Position
155+
[bool] $Inline
156+
157+
[string] ToString() {
158+
return '{0}:{1}:{2}' -f $this.Source, $this.LineNumber, $this.Language
159+
}
160+
}
161+
#region Classes
162+
163+
#endRegion Classes
164+
14165
BeforeDiscovery {
15166
$moduleResources = [System.Collections.ArrayList]@()
16167

@@ -86,13 +237,39 @@ Describe 'Module tests' {
86237
$moduleResource = $_
87238
$moduleImport = Import-PowerShellDataFile -Path $moduleResource.ModulePath.Replace('.psm1', '.psd1')
88239

240+
# For the resources
89241
$resources = [System.Collections.ArrayList]@()
90242

243+
# For the code blocks to capture in the examples
244+
$codeBlocks = [System.Collections.ArrayList]@()
245+
91246
foreach ($resource in $moduleImport.DscResourcesToExport) {
247+
$helpFile = Join-Path $repoRootPath 'resources' 'Help' $moduleResource.ModuleName "$resource.md"
248+
92249
$resources += @{
93250
moduleName = $moduleResource.ModuleName
94251
resource = $resource
95-
HelpFile = Join-Path $repoRootPath 'resources' 'Help' $moduleResource.ModuleName "$resource.md"
252+
helpFile = $helpFile
253+
CodeBlock = Get-MdCodeBlock -Path $helpFile -Language 'powershell' -ErrorAction SilentlyContinue
254+
}
255+
256+
$blocks = Get-MdCodeBlock -Path $helpFile -Language 'powershell' -ErrorAction SilentlyContinue
257+
if (-not $blocks) {
258+
$codeBlocks += @{
259+
moduleName = $moduleResource.ModuleName
260+
resource = $resource
261+
content = 'No code block found'
262+
language = 'powershell'
263+
}
264+
}
265+
266+
foreach ($block in $blocks) {
267+
$codeBlocks += @{
268+
moduleName = $moduleResource.ModuleName
269+
resource = $resource
270+
content = $block.Content
271+
language = $block.Language
272+
}
96273
}
97274
}
98275

@@ -117,5 +294,94 @@ Describe 'Module tests' {
117294
$file = Get-Item -Path $helpFile -ErrorAction SilentlyContinue
118295
$file.Length | Should -BeGreaterThan 0
119296
}
297+
298+
It '[<moduleName>] Should have a help file for [<resource>] resource with heading 1' -TestCases $resources {
299+
param (
300+
[string] $moduleName,
301+
[string] $resource,
302+
[string] $helpFile
303+
)
304+
305+
$headings = Get-MarkdownHeadings -FilePath $helpFile -ErrorAction SilentlyContinue
306+
307+
$h1 = $headings | Where-Object { $_.Level -eq 1 -and $_.Text -eq $moduleName }
308+
$h1 | Should -Not -BeNullOrEmpty
309+
}
310+
311+
It '[<moduleName>] Should have a help file for [<resource>] resource with heading 2 matching SYNOPSIS' -TestCases $resources {
312+
param (
313+
[string] $moduleName,
314+
[string] $resource,
315+
[string] $helpFile
316+
)
317+
318+
$headings = Get-MarkdownHeadings -FilePath $helpFile -ErrorAction SilentlyContinue
319+
320+
$h2 = $headings | Where-Object { $_.Level -eq 2 -and $_.Text -eq 'SYNOPSIS' }
321+
$h2 | Should -Not -BeNullOrEmpty
322+
}
323+
324+
It '[<moduleName>] Should have a help file for [<resource>] resource with heading 2 matching DESCRIPTION' -TestCases $resources {
325+
param (
326+
[string] $moduleName,
327+
[string] $resource,
328+
[string] $helpFile
329+
)
330+
331+
$headings = Get-MarkdownHeadings -FilePath $helpFile -ErrorAction SilentlyContinue
332+
333+
$h2 = $headings | Where-Object { $_.Level -eq 2 -and $_.Text -eq 'DESCRIPTION' }
334+
$h2 | Should -Not -BeNullOrEmpty
335+
}
336+
337+
It '[<moduleName>] Should have a help file for [<resource>] resource with heading 2 matching PARAMETERS' -TestCases $resources {
338+
param (
339+
[string] $moduleName,
340+
[string] $resource,
341+
[string] $helpFile
342+
)
343+
344+
$headings = Get-MarkdownHeadings -FilePath $helpFile -ErrorAction SilentlyContinue
345+
346+
$h2 = $headings | Where-Object { $_.Level -eq 2 -and $_.Text -eq 'PARAMETERS' }
347+
$h2 | Should -Not -BeNullOrEmpty
348+
}
349+
350+
It '[<moduleName>] Should have a help file for [<resource>] resource with heading 2 matching EXAMPLES' -TestCases $resources {
351+
param (
352+
[string] $moduleName,
353+
[string] $resource,
354+
[string] $helpFile
355+
)
356+
357+
$headings = Get-MarkdownHeadings -FilePath $helpFile -ErrorAction SilentlyContinue
358+
359+
$h2 = $headings | Where-Object { $_.Level -eq 2 -and $_.Text -eq 'EXAMPLES' }
360+
$h2 | Should -Not -BeNullOrEmpty
361+
}
362+
363+
It '[<moduleName>] Should have a help file for [<resource>] with 1 example' -TestCases $resources {
364+
param (
365+
[string] $moduleName,
366+
[string] $resource,
367+
[string] $helpFile
368+
)
369+
370+
$headings = Get-MarkdownHeadings -FilePath $helpFile -ErrorAction SilentlyContinue
371+
372+
$h3 = $headings | Where-Object { $_.Level -eq 3 -and $_.Text -eq 'EXAMPLE 1' }
373+
$h3 | Should -Not -BeNullOrEmpty
374+
}
375+
376+
It '[<moduleName>] Should have at least a PowerShell coding example with Invoke-DscResource' -TestCases $codeBlocks {
377+
param (
378+
[string] $ModuleName,
379+
[string] $Content,
380+
[string] $Language
381+
)
382+
383+
$Content | Should -Match "Invoke-DscResource -ModuleName $ModuleName -Name $ResourceName"
384+
}
120385
}
121386
}
387+

0 commit comments

Comments
 (0)