Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Pester.Types.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ if ($null -ne $configurationType) {
}
}

if ($PSVersionTable.PSVersion.Major -ge 6) {
if ($PSVersionTable.PSVersion.Major -ge 7) {
$path = "$PSScriptRoot/bin/net8.0/Pester.dll"
# PESTER_BUILD
if ((Get-Variable -Name "PESTER_BUILD" -ValueOnly -ErrorAction Ignore)) {
Expand Down
65 changes: 22 additions & 43 deletions src/functions/Coverage.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,7 @@ function Test-CommandInScope {
$classResult = !$Class
$functionResult = !$Function
for ($ast = $Command; $null -ne $ast; $ast = $ast.Parent) {
if (!$classResult -and $PSVersionTable.PSVersion.Major -ge 5) {
# Classes have been introduced in PowerShell 5.0
if (!$classResult) {
$classAst = $ast -as [System.Management.Automation.Language.TypeDefinitionAst]
if ($null -ne $classAst -and $classAst.Name -like $Class) {
$classResult = $true
Expand Down Expand Up @@ -412,20 +411,18 @@ function IsIgnoredCommand {
return $true
}

if ($PSVersionTable.PSVersion.Major -ge 4) {
if ($Command.Extent.Text -eq 'Configuration') {
# More DSC voodoo. Calls to "configuration" generate breakpoints, but their HitCount
# stays zero (even though they are executed.) For now, ignore them, unless we can come
# up with a better solution.
return $true
}
if ($Command.Extent.Text -eq 'Configuration') {
# More DSC voodoo. Calls to "configuration" generate breakpoints, but their HitCount
# stays zero (even though they are executed.) For now, ignore them, unless we can come
# up with a better solution.
return $true
}

if (IsChildOfHashtableDynamicKeyword -Command $Command) {
# The lines inside DSC resource declarations don't trigger their breakpoints when executed,
# just like the "configuration" keyword itself. I don't know why, at this point, but just like
# configuration, we'll ignore it so it doesn't clutter up the coverage analysis with useless junk.
return $true
}
if (IsChildOfHashtableDynamicKeyword -Command $Command) {
# The lines inside DSC resource declarations don't trigger their breakpoints when executed,
# just like the "configuration" keyword itself. I don't know why, at this point, but just like
# configuration, we'll ignore it so it doesn't clutter up the coverage analysis with useless junk.
return $true
}

if ($Command.Extent.Text -match '^{?& \$wrappedCmd @PSBoundParameters ?}?$' -and
Expand All @@ -443,12 +440,10 @@ function IsIgnoredCommand {
return $true
}

if ($PSVersionTable.PSVersion.Major -ge 5) {
if ($Command -is [System.Management.Automation.Language.CommandExpressionAst] -and
$Command.Expression[0] -is [System.Management.Automation.Language.BaseCtorInvokeMemberExpressionAst]) {
# Calls to inherited "base(...)" constructor does not trigger breakpoint or tracer hit, ignore.
return $true
}
if ($Command -is [System.Management.Automation.Language.CommandExpressionAst] -and
$Command.Expression[0] -is [System.Management.Automation.Language.BaseCtorInvokeMemberExpressionAst]) {
# Calls to inherited "base(...)" constructor does not trigger breakpoint or tracer hit, ignore.
return $true
}

return $false
Expand All @@ -458,21 +453,9 @@ function IsChildOfHashtableDynamicKeyword {
param ([System.Management.Automation.Language.Ast] $Command)

for ($ast = $Command.Parent; $null -ne $ast; $ast = $ast.Parent) {
if ($PSVersionTable.PSVersion.Major -ge 5) {
# The ast behaves differently for DSC resources with version 5+. There's a new DynamicKeywordStatementAst class,
# and they no longer are represented by CommandAst objects.

if ($ast -is [System.Management.Automation.Language.DynamicKeywordStatementAst] -and
$ast.CommandElements[-1] -is [System.Management.Automation.Language.HashtableAst]) {
return $true
}
}
else {
if ($ast -is [System.Management.Automation.Language.CommandAst] -and
$null -ne $ast.DefiningKeyword -and
$ast.DefiningKeyword.BodyMode -eq [System.Management.Automation.Language.DynamicKeywordBodyMode]::Hashtable) {
return $true
}
if ($ast -is [System.Management.Automation.Language.DynamicKeywordStatementAst] -and
$ast.CommandElements[-1] -is [System.Management.Automation.Language.HashtableAst]) {
return $true
}
}

Expand Down Expand Up @@ -500,14 +483,10 @@ function IsClosingLoopCondition {
function Get-ParentClassName {
param ([System.Management.Automation.Language.Ast] $Ast)

if ($PSVersionTable.PSVersion.Major -ge 5) {
# Classes have been introduced in PowerShell 5.0

$parent = $Ast.Parent
$parent = $Ast.Parent

while ($null -ne $parent -and $parent -isnot [System.Management.Automation.Language.TypeDefinitionAst]) {
$parent = $parent.Parent
}
while ($null -ne $parent -and $parent -isnot [System.Management.Automation.Language.TypeDefinitionAst]) {
$parent = $parent.Parent
}

if ($null -eq $parent) {
Expand Down
4 changes: 2 additions & 2 deletions src/functions/Environment.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
}

function GetPesterOs {
# Prior to v6, PowerShell was solely on Windows. In v6, the $IsWindows variable was introduced.
if ((GetPesterPsVersion) -lt 6) {
# Prior to v7, PowerShell was solely on Windows. In v6, the $IsWindows variable was introduced.
if ((GetPesterPsVersion) -lt 7) {
'Windows'
}
elseif (& $SafeCommands['Get-Variable'] -Name 'IsWindows' -ErrorAction 'Ignore' -ValueOnly ) {
Expand Down
124 changes: 34 additions & 90 deletions src/functions/Mock.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,7 @@ function New-MockBehavior {
}

function EscapeSingleQuotedStringContent ($Content) {
if ($global:PSVersionTable.PSVersion.Major -ge 5) {
[System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($Content)
}
else {
$Content -replace "['‘’‚‛]", '$&$&'
}
[System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($Content)
}

function Create-MockHook ($contextInfo, $InvokeMockCallback) {
Expand Down Expand Up @@ -83,7 +78,7 @@ function Create-MockHook ($contextInfo, $InvokeMockCallback) {
}
}
$cmdletBinding = [Management.Automation.ProxyCommand]::GetCmdletBindingAttribute($metadata)
if ($global:PSVersionTable.PSVersion.Major -ge 3 -and $contextInfo.Command.CommandType -eq 'Cmdlet') {
if ($contextInfo.Command.CommandType -eq 'Cmdlet') {
if ($cmdletBinding -ne '[CmdletBinding()]') {
$cmdletBinding = $cmdletBinding.Insert($cmdletBinding.Length - 2, ',')
}
Expand Down Expand Up @@ -1499,8 +1494,7 @@ function Get-DynamicParametersForCmdlet {
[string] $CmdletName,

[ValidateScript( {
if ($PSVersionTable.PSVersion.Major -ge 3 -and
$null -ne $_ -and
if ($null -ne $_ -and
$_.GetType().FullName -ne 'System.Management.Automation.PSBoundParametersDictionary') {
throw 'The -Parameters argument must be a PSBoundParametersDictionary object ($PSBoundParameters).'
}
Expand All @@ -1525,77 +1519,29 @@ function Get-DynamicParametersForCmdlet {
return
}

if ('5.0.10586.122' -lt $PSVersionTable.PSVersion) {
# Older version of PS required Reflection to do this. It has run into problems on occasion with certain cmdlets,
# such as ActiveDirectory and AzureRM, so we'll take advantage of the newer PSv5 engine features if at all possible.

if ($null -eq $Parameters) {
$paramsArg = @()
}
else {
$paramsArg = @($Parameters)
}

$command = $ExecutionContext.InvokeCommand.GetCommand($CmdletName, [System.Management.Automation.CommandTypes]::Cmdlet, $paramsArg)
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()

foreach ($param in $command.Parameters.Values) {
if (-not $param.IsDynamic) {
continue
}
if ($Parameters.ContainsKey($param.Name)) {
continue
}

$dynParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param.Name, $param.ParameterType, $param.Attributes)
$paramDictionary.Add($param.Name, $dynParam)
}

return $paramDictionary
if ($null -eq $Parameters) {
$paramsArg = @()
}
else {
if ($null -eq $Parameters) {
$Parameters = @{ }
}

$cmdlet = ($command.ImplementingType)::new()

$flags = [System.Reflection.BindingFlags]'Instance, Nonpublic'
$context = $ExecutionContext.GetType().GetField('_context', $flags).GetValue($ExecutionContext)
[System.Management.Automation.Cmdlet].GetProperty('Context', $flags).SetValue($cmdlet, $context, $null)

foreach ($keyValuePair in $Parameters.GetEnumerator()) {
$property = $cmdlet.GetType().GetProperty($keyValuePair.Key)
if ($null -eq $property -or -not $property.CanWrite) {
continue
}

$isParameter = [bool]($property.GetCustomAttributes([System.Management.Automation.ParameterAttribute], $true))
if (-not $isParameter) {
continue
}

$property.SetValue($cmdlet, $keyValuePair.Value, $null)
}

try {
# This unary comma is important in some cases. On Windows 7 systems, the ActiveDirectory module cmdlets
# return objects from this method which implement IEnumerable for some reason, and even cause PowerShell
# to throw an exception when it tries to cast the object to that interface.

# We avoid that problem by wrapping the result of GetDynamicParameters() in a one-element array with the
# unary comma. PowerShell enumerates that array instead of trying to enumerate the goofy object, and
# everyone's happy.
$paramsArg = @($Parameters)
}

# Love the comma. Don't delete it. We don't have a test for this yet, unless we can get the AD module
# on a Server 2008 R2 build server, or until we write some C# code to reproduce its goofy behavior.
$command = $ExecutionContext.InvokeCommand.GetCommand($CmdletName, [System.Management.Automation.CommandTypes]::Cmdlet, $paramsArg)
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()

, $cmdlet.GetDynamicParameters()
foreach ($param in $command.Parameters.Values) {
if (-not $param.IsDynamic) {
continue
}
catch [System.NotImplementedException] {
# Some cmdlets implement IDynamicParameters but then throw a NotImplementedException. I have no idea why. Ignore them.
if ($Parameters.ContainsKey($param.Name)) {
continue
}

$dynParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param.Name, $param.ParameterType, $param.Attributes)
$paramDictionary.Add($param.Name, $dynParam)
}

return $paramDictionary
}

function Get-DynamicParametersForMockedFunction {
Expand Down Expand Up @@ -1852,31 +1798,29 @@ function New-BlockWithoutParameterAliases {
$Block
)
try {
if ($PSVersionTable.PSVersion.Major -ge 3) {
$params = $Metadata.Parameters.Values
$ast = Get-ScriptBlockAST $Block
$blockText = $ast.Extent.Text
$variables = [array]($Ast.FindAll( { param($ast) $ast -is [System.Management.Automation.Language.VariableExpressionAst] }, $true))
[array]::Reverse($variables)
$params = $Metadata.Parameters.Values
$ast = Get-ScriptBlockAST $Block
$blockText = $ast.Extent.Text
$variables = [array]($Ast.FindAll( { param($ast) $ast -is [System.Management.Automation.Language.VariableExpressionAst] }, $true))
[array]::Reverse($variables)

foreach ($var in $variables) {
$varName = $var.VariablePath.UserPath
$length = $varName.Length
foreach ($var in $variables) {
$varName = $var.VariablePath.UserPath
$length = $varName.Length

foreach ($param in $params) {
if ($param.Aliases -contains $varName) {
$startIndex = $var.Extent.StartOffset - $ast.Extent.StartOffset + 1 # move one position after the dollar sign
foreach ($param in $params) {
if ($param.Aliases -contains $varName) {
$startIndex = $var.Extent.StartOffset - $ast.Extent.StartOffset + 1 # move one position after the dollar sign

$blockText = $blockText.Remove($startIndex, $length).Insert($startIndex, $param.Name)
$blockText = $blockText.Remove($startIndex, $length).Insert($startIndex, $param.Name)

break # It is safe to stop checking for further params here, since aliases cannot be shared by parameters
}
break # It is safe to stop checking for further params here, since aliases cannot be shared by parameters
}
}

$Block = [scriptblock]::Create($blockText)
}

$Block = [scriptblock]::Create($blockText)

$Block
}
catch {
Expand Down
2 changes: 1 addition & 1 deletion src/functions/Output.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ function Write-PesterHostMessage {

begin {
# Custom PSHosts without UI will fail with Write-Host. Works in PS5+ due to use of InformationRecords
$HostSupportsOutput = $null -ne $host.UI.RawUI.ForegroundColor -or $PSVersionTable.PSVersion.Major -ge 5
$HostSupportsOutput = $true # Pester 6 requires PS 5.1+ which always supports InformationRecords
if (-not $HostSupportsOutput) { return }

# Source https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#text-formatting
Expand Down
3 changes: 1 addition & 2 deletions src/functions/TestDrive.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,10 @@ function Remove-TestDriveSymbolicLinks ([String] $Path) {

# issue 621 was fixed before PowerShell 6.1
# now there is an issue with calling the Delete method in recent (6.1) builds of PowerShell
if ((GetPesterPSVersion) -ge 6) {
if ((GetPesterPSVersion) -ge 7) {
return
}

# powershell 2-compatible
$reparsePoint = [System.IO.FileAttributes]::ReparsePoint
& $SafeCommands['Get-ChildItem'] -Recurse -Path $Path |
& $SafeCommands['Where-Object'] { ($_.Attributes -band $reparsePoint) -eq $reparsePoint } |
Expand Down
Loading