How to specify what to do if a Pester assertion fails? - powershell

If you wanted to say, 1 should equal 1, and if it doesn't then break, what would be the most eloquent way to do this in powershell with pester avoid code duplication?
Eg
{1 | should be 1} else {break}
rather than
1 | should be 1
if (1 -ne 1) {break}

There's no built in functionality for breaking the test execution when a failure occurs at the moment, but it has been discussed here: https://github.com/pester/Pester/issues/360 with some wrokarounds such as this one:
BeforeEach {
$FailedCount = InModuleScope -ModuleName Pester { $Pester.FailedCount }
if ($FailedCount -gt 0) {
Set-ItResult -Skipped -Because 'previous test failed'
}
}
Another option might be to break your tests up into several different Pester scripts. Then you could have some high level or initial tests that you check for success on first and if they have not all passed then you skip execution of the remaining test scripts.
For example:
$Failed = (Invoke-Pester -Path First.tests.ps1 -PassThru).FailedCount
If ($Failed -eq 0) {
Invoke-Pester -Path Second.tests.ps1
..
}

I landed here looking for a way to make one test have a dependency on another.
#Dependency in Pester v3.4.0
Describe 'testing pester' {
$dep = #{}
context 'when in here' {
It 'when the dependent test fails' {
$dep.aSpecificDependency = 'failed'
'a' | should be 'b'
$dep.aSpecificDependency = 'passed'
}
It 'then this is inconclusive' {
if($dep.ContainsKey('aSpecificDependency') -and $dep.aSpecificDependency -ne 'passed'){
Set-TestInconclusive -Message 'aSpecificDependency did not pass.'
}
'a' | should be 'a'
}
It 'and this should run fine' {
if($dep.ContainsKey('aDifferentDependency') -and $dep.aDifferentDependency -ne 'passed'){
Set-TestInconclusive -Message 'aDifferentDependency did not pass.'
}
'a' | should be 'a'
}
if($dep.ContainsKey('aSpecificDependency') -and $dep.aSpecificDependency -ne 'passed'){
return
}
It 'stops before running this one' {
'a' | should be 'a'
}
}
context 'when looking at another section' {
It 'goes fine' {
'a' | should be 'a'
}
}
if($dep.ContainsKey('aSpecificDependency') -and $dep.aSpecificDependency -ne 'passed'){
return
}
context 'when looking at another section' {
It 'or you could stop on that too' {
'a' | should be 'a'
}
}
}

Related

Pester test if list is empty do not run the tests

I would like to be able to skip tests if list is empty.
a very simplified example:
No name is -eq to "jens", therefore the $newlist would be empty, and of course the test will fail, but how do i prevent it from going though this test if the list is empty?
context {
BeforeAll{
$List = #(Harry, Hanne, Hans)
$newlist = #()
foreach ($name in $List) {
if (($name -eq "Jens")) {
$name += $newlist
}
}
}
It "The maximum name length is 10 characters" {
$newlist |ForEach-Object {$_.length | Should -BeIn (1..10) -Because "The maximum name length is 10 characters"}
}
}
fail message:
Expected collection #(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) to contain 0, because The maximum name length is 10 characters, but it was not found.
You can achieve this by using Set-ItResult, which is a Pester cmdlet that allows you to force a specific result. For example:
Describe 'tests' {
context 'list with content' {
BeforeAll {
$List = #('Harry', 'Hanne', 'Hans')
$newlist = #()
foreach ($name in $List) {
if (($name -eq "Jens")) {
$newlist += $name
}
}
}
It "The maximum name length is 10 characters" {
if (-not $newlist) {
Set-ItResult -Skipped
}
else {
$newlist | ForEach-Object { $_.length | Should -BeIn (1..10) -Because "The maximum name length is 10 characters" }
}
}
}
}
Note that there was an error in your example ($newlist wasn't being updated with name, you were doing the reverse) which I've corrected above, but your test doesn't actually fail for me in this example (before adding the Set-ItResult logic). I think this is because by using ForEach-Object with an empty array as an input the Should never gets executed when its empty, so with this approach your test would just pass because it never evaluates anything.

Powershell access caller scope variables across modules

I have the following method declared in a module I've called Common.psm1:
function Test-Any ([ScriptBlock]$FilterScript = $null)
{
begin {
$done = $false
}
process {
if (!$done)
{
if (!$FilterScript -or ($FilterScript | Invoke-Expression)){
$done = $true
}
}
}
end {
$done
}
}
Set-Alias any Test-Any -Scope Global
Now in another module, I have the following validation:
$id = 1
if($notifications | any { $_.Id -eq $id })
{
# do stuff here
return
}
I receive the following error:
Invoke-Expression : The variable '$id' cannot be retrieved because it has not been set.
The interesting thing is that if I move the Test-Any definition to the calling module, it works like a charm.
How can I make this work without copying Test-Any to my other modules and without changing this syntax:
if($notifications | any { $_.Id -eq $id })
EDIT 1:
There seems to be some debate about whether or not my code should work. Feel free to try this on your own machine:
function Test-Any ([ScriptBlock]$FilterScript = $null)
{
begin {
$done = $false
}
process {
if (!$done)
{
if (!$FilterScript -or ($FilterScript | Invoke-Expression)){
$done = $true
}
}
}
end {
$done
}
}
Set-Alias any Test-Any -Scope Global
$id = 3
$myArray = #(
#{Id = 1},
#{Id = 2},
#{Id = 3},
#{Id = 4},
#{Id = 5},
#{Id = 6},
#{Id = 7},
#{Id = 8}
)
$myEmptyArray = #()
$myArray | any #returns true
$myArray | any {$_.Id -eq $id} #returns true
$myEmptyArray | any #returns false
$myEmptyArray | any {$_.Id -eq $id} #returns false
EDIT 2:
I just discovered that you only encounter this issue, when Test-Any resides in one loaded module and the calling code resides in a second module using Set-StrictMode -Version Latest. If you turn off StrictMode, you don't get the error, but it also doesn't work.
EDIT 3:
Needless to say this works perfectly fine:
$sb = [Scriptblock]::Create("{ `$_.Id -eq $id }")
if($notifications | any $sb)
But seriously takes away from the simplicity and intuitiveness I am trying to obtain
Invoke-Expression (which, when possible, should be avoided) implicitly recreates the script block passed from the caller's scope, via its string representation, in the context of the module, which invalidates any references to the caller's state in the script-block code (because modules generally don't see an outside caller's state, except for the global scope).
The solution is to execute the script block as-is, but provide it pipeline input as passed to the module function:
# Note: New-Module creates a *dynamic* (in-memory only) module,
# but the behavior applies equally to regular, persisted modules.
$null = New-Module {
function Test-Any ([ScriptBlock] $FilterScript)
{
begin {
$done = $false
}
process {
if (!$done)
{
# Note the use of $_ | ... to provide pipeline input
# and the use of ForEach-Object to evaluate the script block.
if (!$FilterScript -or ($_ | ForEach-Object $FilterScript)) {
$done = $true
}
}
}
end {
$done
}
}
}
# Sample call. Should yield $true
$id = 1
#{ Id = 2 }, #{ Id = 1 } | Test-Any { $_.Id -eq $id }
Note: The Test-Any function in this answer uses a similar approach, but tries to optimize processing by stopping further pipeline processing - which, however, comes at the expense of incurring an on-demand compilation penalty the first time the function is called in the session, because - as of PowerShell 7.2 - you cannot (directly) stop a pipeline on demand from user code - see GitHub issue #3821.

PowerShell: Multiple Conditions within IF Statement

Im struggling to understand why the following IF statement doesn't work...
I have a PS array:
$lunchArray = #('Pizza', 'Sushi', 'Sandwich')
However the following Foreach/IF statement doesn't work as expected.
foreach ($lunch in $lunchArray) {
if ($lunch -eq 'Pizza' -and $lunch -eq 'Sushi') {
"YAY" # not working...
}
}
What am I doing wrong here?
TIA
$lunchArray = #('Pizza', 'Sushi', 'Sandwich')
foreach ($lunch in $lunchArray) {
if ('Pizza' -eq $lunch -or 'Sushi' -eq $lunch) {
"YAY" # not working...
}
}

Powershell Switch - Multiple Clauses

I'm creating a script that will be updating an Excel spreadsheet depending on conditions.
This is what I currently have:
if ($endIRs -ne $null) {
$endIRs | ForEach-Object {
try {
$classification = $_.Classification
$priority = $_.Priority
$title = $_.Title
$id = $_.Id
switch ($classification) {
{($_ -eq 'Reports') -and ($priority -eq '1')} {
$GeAppsReportSheet.Cells.Item(8,2).Interior.ColorIndex = 3
$GeAppsReportSheet.Cells.Item(8,2) = 'RED'
}
#more switch statements to go here
}
catch {#catch tickets with $classification not listed}
}
}
The $endIRs at the start holds a series of high priority 'incidents' that have been logged in the last 12 hours. If there is none, everything will be 'GREEN' which is set by default.
What I am trying to achieve with the switch statement is if (($classification -eq 'Reports') -and ($priority -eq '1')) {'change the cell colour and text'} which I can do on its own, but I need it to check if the priority is "1" or "2" and do something different against the "Reports" classification cell in the spreadsheet.
Can you do an if statement within the switch statement, or is there a better way to do it?
You can use $true as the switch condition and put the checks as scriptblock values:
switch ($true) {
{($classification -eq 'Reports') -and ($priority -eq '1')} {
...
}
# ...
# more switch statements to go here
# ...
default {
...
}
}
I never really liked this approach, though. Always looked like an ugly hack to me. I'd prefer a if..elseif..else control structure:
if ($classification -eq 'Reports' -and $priority -eq '1') {
...
} elseif (...) {
...
} elseif (...) {
...
} else {
...
}
Edit: Of course you can also use a "regular" switch statement and nest other conditionals in the action scriptblocks:
switch ($classification) {
'Reports' {
if ($priority -eq '1') {
...
} elseif ($priority -eq '2') {
...
}
}
# ...
# more switch statements to go here
# ...
default {
...
}
}

Powershell scripting for url custom monitoring

I am trying to build a custom script for URL monitoring. I am able to run the URL's from the file and enter the same in a logfile(named with time stamp).
Till here I have completed
Issue is when I compare the values from present(present timestamp) and previous logfile(previous timestamp).
This portion is not working fine. Please help me correct it.
Here is my code trying to compare value line by line from present logfile and previous logfile and run commands to generate output:
# New log is new logfile data
$Newlog = Get-Content $URLlogfile
$old_file = Dir C:\Scripts\logs | Sort CreationTime -Descending | Select Name -last 1
# Old log is Old logfile data
$oldlog = Get-Content $old_file -ErrorAction SilentlyContinue
Foreach($logdata in $Newlog) {
$url = ($logdata.Split(" "))[0]
$nodename = ($logdata.Split(" "))[1]
$statuscheck = ($logdata.Split(" "))[2]
$description = ($logdata.Split(" "))[3]
$statuscode = ($logdata.Split(" "))[4]
Foreach($log1data in $oldlog) {
$url1 = ($log1data.Split(" "))[0]
$nodename1 = ($log1data.Split(" "))[1]
$statuscheck1 = ($log1data.Split(" "))[2]
$description1 = ($log1data.Split(" "))[3]
$statuscode1 = ($log1data.Split(" "))[4]
While ($url = $url1) {
if ($statuscheck = $statuscheck1 ) {
write-output "output is same"
} elseif ($statuscheck = Fail) {
While ($statuscheck1 = Pass) {
write-output "$url is down at $nodename1- testing event sent"
}
} elseif ($statuscheck = Pass) {
While ($statuscheck1 = Fail) {
write-output "$url is up at $nodename1- testing event sent"
}
}
}
Break
}
}
#At end am clearing the old logs except present one
dir C:\Scripts\logs -recurse | where { ((get-date)-$_.creationTime).minutes -gt 3 } | remove-item -force
Per the comment from BenH, the following part of your code needs correcting as follows:
If ($url -eq $url1) {
if ($statuscheck -eq $statuscheck1 ) {
write-output "output is same"
} elseif ($statuscheck -eq 'Fail' -and $statuscheck1 -eq 'Pass') {
write-output "$url is down at $nodename1- testing event sent"
} elseif ($statuscheck -eq 'Pass' -and $statuscheck1 -eq 'Fail') {
write-output "$url is up at $nodename1- testing event sent"
}
}
Corrections:
In your comparison statements the = needs to be -eq. In PowerShell = always assigns a value.
In your comparison statements Pass and Fail need to be surrounded by single quotes so they are treated as strings (otherwise they are treated like function statements, for functions which don't exist).
I've replaced the While statements with If statements. I'm not sure what the intent of those was but I think they'd just get stuck in an infinite loop as the variable they test is never changed from within the loop.