microsoft/hve-core
Publicmirrored from https://github.com/microsoft/hve-coreAvailable
scripts/tests/linting/DotSourceGuard.Tests.ps1
108lines · modecode
| 1 | #Requires -Modules Pester |
| 2 | # Copyright (c) Microsoft Corporation. |
| 3 | # SPDX-License-Identifier: MIT |
| 4 | |
| 5 | <# |
| 6 | .SYNOPSIS |
| 7 | Regression test enforcing the dot-source guard convention. |
| 8 | |
| 9 | .DESCRIPTION |
| 10 | Every production script that a Pester test dot-sources must wrap its main |
| 11 | logic in the guard `if ($MyInvocation.InvocationName -ne '.')`. Without it, |
| 12 | dot-sourcing the script in a `BeforeAll` block executes the script's main |
| 13 | logic (and any top-level `exit`) during test setup, which hangs or corrupts |
| 14 | the run. This test discovers every dot-sourced target across the test suite |
| 15 | and asserts the guard is present. |
| 16 | #> |
| 17 | |
| 18 | BeforeDiscovery { |
| 19 | $testsRoot = Join-Path $PSScriptRoot '..' |
| 20 | |
| 21 | function Get-StringLiteralPath { |
| 22 | param([System.Management.Automation.Language.Ast]$Node) |
| 23 | |
| 24 | $literals = $Node.FindAll({ |
| 25 | param($n) |
| 26 | $n -is [System.Management.Automation.Language.StringConstantExpressionAst] -or |
| 27 | $n -is [System.Management.Automation.Language.ExpandableStringExpressionAst] |
| 28 | }, $true) |
| 29 | |
| 30 | ($literals | Where-Object { $_.Value -match '\.ps1' } | Select-Object -First 1).Value |
| 31 | } |
| 32 | |
| 33 | $discovered = [System.Collections.Generic.List[object]]::new() |
| 34 | $seen = [System.Collections.Generic.HashSet[string]]::new() |
| 35 | |
| 36 | $testFiles = Get-ChildItem -Path $testsRoot -Recurse -Filter '*.Tests.ps1' |
| 37 | |
| 38 | foreach ($testFile in $testFiles) { |
| 39 | $tokens = $null |
| 40 | $errors = $null |
| 41 | $ast = [System.Management.Automation.Language.Parser]::ParseFile( |
| 42 | $testFile.FullName, [ref]$tokens, [ref]$errors) |
| 43 | |
| 44 | $dotSources = $ast.FindAll({ |
| 45 | param($n) |
| 46 | $n -is [System.Management.Automation.Language.CommandAst] -and |
| 47 | $n.InvocationOperator -eq 'Dot' |
| 48 | }, $true) |
| 49 | |
| 50 | foreach ($dotSource in $dotSources) { |
| 51 | $literal = Get-StringLiteralPath -Node $dotSource |
| 52 | |
| 53 | if (-not $literal) { |
| 54 | # Variable indirection: resolve `$path = Join-Path ...; . $path`. |
| 55 | $variable = $dotSource.CommandElements[0] -as [System.Management.Automation.Language.VariableExpressionAst] |
| 56 | if ($variable) { |
| 57 | $name = $variable.VariablePath.UserPath -replace '^script:', '' |
| 58 | $assignment = $ast.FindAll({ |
| 59 | param($n) |
| 60 | $n -is [System.Management.Automation.Language.AssignmentStatementAst] |
| 61 | }, $true) | Where-Object { |
| 62 | $_.Left.Extent.Text -match ('\$(script:)?' + [regex]::Escape($name) + '\b') |
| 63 | } | Select-Object -First 1 |
| 64 | |
| 65 | if ($assignment) { |
| 66 | $literal = Get-StringLiteralPath -Node $assignment.Right |
| 67 | } |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | if (-not $literal) { continue } |
| 72 | |
| 73 | $relative = $literal -replace '\$PSScriptRoot', '' -replace '^[\\/]+', '' |
| 74 | $candidate = Join-Path $testFile.DirectoryName $relative |
| 75 | $resolved = $null |
| 76 | try { |
| 77 | $resolved = (Resolve-Path -LiteralPath $candidate -ErrorAction Stop).Path |
| 78 | } |
| 79 | catch { |
| 80 | continue |
| 81 | } |
| 82 | |
| 83 | if ($resolved -and $resolved -notmatch '\.Tests\.ps1$' -and $seen.Add($resolved)) { |
| 84 | $discovered.Add(@{ |
| 85 | TargetPath = $resolved |
| 86 | TargetName = Split-Path $resolved -Leaf |
| 87 | TestName = $testFile.Name |
| 88 | }) |
| 89 | } |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | $script:DotSourcedScripts = $discovered |
| 94 | } |
| 95 | |
| 96 | Describe 'Dot-source guard convention' -Tag 'Unit' { |
| 97 | It 'discovers at least one dot-sourced script' { |
| 98 | $DotSourcedScripts.Count | Should -BeGreaterThan 0 |
| 99 | } |
| 100 | |
| 101 | It '<TargetName> guards main logic from dot-sourcing (referenced by <TestName>)' -ForEach $DotSourcedScripts { |
| 102 | $content = Get-Content -LiteralPath $TargetPath -Raw |
| 103 | $content | Should -Match "InvocationName\s+-ne\s+'\." -Because ( |
| 104 | "$TargetName is dot-sourced by $TestName and must wrap its main logic in " + |
| 105 | "if (`$MyInvocation.InvocationName -ne '.') so test setup does not execute it" |
| 106 | ) |
| 107 | } |
| 108 | } |