Skip to content

Commit

Permalink
Add option to throw on failure (#1908)
Browse files Browse the repository at this point in the history
* Add option to throw on failure

Throw on failure when enabled. If you want to process the result for some reason after catching the exception, you need to use `$result = try { Invoke-Pester ... } catch { $err = $_ }`, not `try { $result = Invoke-Pester ... } catch { }` because then the assignment will never happen and `$result` will be null.

* Update tst/Pester.RSpec.ts.ps1
  • Loading branch information
nohwnd committed Apr 17, 2021
1 parent 1257422 commit 755543f
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 39 deletions.
17 changes: 17 additions & 0 deletions src/Format.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,20 @@ function Format-Type ([Type]$Value) {

[string]$Value
}

function Join-And ($Items, $Threshold = 2) {

if ($null -eq $items -or $items.count -lt $Threshold) {
$items -join ', '
}
else {
$c = $items.count
($items[0..($c - 2)] -join ', ') + ' and ' + $items[-1]
}
}

function Add-SpaceToNonEmptyString ([string]$Value) {
if ($Value) {
" $Value"
}
}
43 changes: 27 additions & 16 deletions src/Main.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1204,28 +1204,39 @@ function Invoke-Pester {
$run
}

# exit with exit code if we fail and even if we succeed, othwerise we could inherit
# exit code of some other app end exit with it's exit code instead with ours
$failedCount = $run.FailedCount + $run.FailedBlocksCount + $run.FailedContainersCount
if ($PesterPreference.Run.Exit.Value -and 0 -ne $failedCount) {
# exit with the number of failed tests when there are any
# and the exit preference is set. This will fail the run in CI
# when any tests failed.
exit $failedCount
}
else {
# just set exit code but don't fail when the option is not set
# or when there are no failed tests, to ensure that we can run
# multiple successful runs of Invoke-Pester in a row.
$global:LASTEXITCODE = $failedCount
}
}
catch {
Write-ErrorToScreen $_
Write-ErrorToScreen $_ -Throw:$PesterPreference.Run.Throw.Value
if ($PesterPreference.Run.Exit.Value) {
exit -1
}
}

# exit with exit code if we fail and even if we succeed, othwerise we could inherit
# exit code of some other app end exit with it's exit code instead with ours
$failedCount = $run.FailedCount + $run.FailedBlocksCount + $run.FailedContainersCount
if ($PesterPreference.Run.Throw.Value -and 0 -ne $failedCount) {
$messages = combineNonNull @(
$(if (0 -lt $run.FailedCount) { "$($run.FailedCount) test$(if (1 -lt $run.FailedCount) { "s" }) failed" })
$(if (0 -lt $run.FailedBlocksCount) { "$($run.FailedBlocksCount) block$(if (1 -lt $run.FailedBlocksCount) { "s" }) failed" })
$(if (0 -lt $run.FailedContainersCount) { "$($run.FailedContainersCount) container$(if (1 -lt $run.FailedContainersCount) { "s" }) failed" })
)
throw "Pester run failed, because $(Join-And $messages)"
}

if ($PesterPreference.Run.Exit.Value -and 0 -ne $failedCount) {
# exit with the number of failed tests when there are any
# and the exit preference is set. This will fail the run in CI
# when any tests failed.
exit $failedCount
}
else {
# just set exit code but don't fail when the option is not set
# or when there are no failed tests, to ensure that we can run
# multiple successful runs of Invoke-Pester in a row.
$global:LASTEXITCODE = $failedCount
}

}
}

Expand Down
4 changes: 2 additions & 2 deletions src/Pester.Runtime.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ function Invoke-Block ($previousBlock) {
$block.OwnPassed = $result.Success
$block.StandardOutput = $result.StandardOutput

$block.ErrorRecord = $result.ErrorRecord
$block.ErrorRecord.AddRange($result.ErrorRecord)
if ($PesterPreference.Debug.WriteDebugMessages.Value) {
Write-PesterDebugMessage -Scope Runtime "Finished executing body of block $Name"
}
Expand Down Expand Up @@ -673,7 +673,7 @@ function Invoke-TestItem {
}

$Test.StandardOutput = $result.StandardOutput
$Test.ErrorRecord = $result.ErrorRecord
$Test.ErrorRecord.AddRange($result.ErrorRecord)
}
}

Expand Down
21 changes: 20 additions & 1 deletion src/csharp/Pester/RunConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class RunConfiguration : ConfigurationSection
private ContainerInfoArrayOption _container;
private StringOption _testExtension;
private BoolOption _exit;
private BoolOption _throw;
private BoolOption _passThru;
private BoolOption _skipRun;

Expand All @@ -47,6 +48,7 @@ public RunConfiguration(IDictionary configuration) : this()
Container = configuration.GetArrayOrNull<ContainerInfo>(nameof(Container)) ?? Container;
TestExtension = configuration.GetObjectOrNull<string>(nameof(TestExtension)) ?? TestExtension;
Exit = configuration.GetValueOrNull<bool>(nameof(Exit)) ?? Exit;
Throw = configuration.GetValueOrNull<bool>(nameof(Throw)) ?? Throw;
PassThru = configuration.GetValueOrNull<bool>(nameof(PassThru)) ?? PassThru;
SkipRun = configuration.GetValueOrNull<bool>(nameof(SkipRun)) ?? SkipRun;
}
Expand All @@ -59,7 +61,8 @@ public RunConfiguration() : base("Run configuration.")
ScriptBlock = new ScriptBlockArrayOption("ScriptBlocks containing tests to be executed.", new ScriptBlock[0]);
Container = new ContainerInfoArrayOption("ContainerInfo objects containing tests to be executed.", new ContainerInfo[0]);
TestExtension = new StringOption("Filter used to identify test files.", ".Tests.ps1");
Exit = new BoolOption("Exit with non-zero exit code when the test run fails.", false);
Exit = new BoolOption("Exit with non-zero exit code when the test run fails. When used together with Throw, throwing an exception is preferred.", false);
Throw = new BoolOption("Throw an exception when test run fails. When used together with Exit, throwing an exception is preferred.", false);
PassThru = new BoolOption("Return result object to the pipeline after finishing the test run.", false);
SkipRun = new BoolOption("Runs the discovery phase but skips run. Use it with PassThru to get object populated with all tests.", false);
}
Expand Down Expand Up @@ -160,6 +163,22 @@ public BoolOption Exit
}
}

public BoolOption Throw
{
get { return _throw; }
set
{
if (_throw == null)
{
_throw = value;
}
else
{
_throw = new BoolOption(_throw, value.Value);
}
}
}

public BoolOption PassThru
{
get { return _passThru; }
Expand Down
10 changes: 8 additions & 2 deletions src/functions/Output.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -761,7 +761,8 @@ function Write-ErrorToScreen {
param (
[Parameter(Mandatory)]
$Err,
[string] $ErrorMargin
[string] $ErrorMargin,
[switch] $Throw
)

$multipleErrors = 1 -lt $Err.Count
Expand All @@ -780,7 +781,12 @@ function Write-ErrorToScreen {
}

$withMargin = ($out -split [Environment]::NewLine) -replace '(?m)^', $ErrorMargin -join [Environment]::NewLine
& $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Fail "$withMargin"
if ($Throw) {
throw $withMargin
}
else {
& $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Fail "$withMargin"
}
}

function Write-BlockToScreen {
Expand Down
18 changes: 0 additions & 18 deletions src/functions/assertions/HaveParameter.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,6 @@ function Should-HaveParameter (
throw "The ParameterName can't be empty"
}

#region HelperFunctions
function Join-And ($Items, $Threshold = 2) {

if ($null -eq $items -or $items.count -lt $Threshold) {
$items -join ', '
}
else {
$c = $items.count
($items[0..($c - 2)] -join ', ') + ' and ' + $items[-1]
}
}

function Add-SpaceToNonEmptyString ([string]$Value) {
if ($Value) {
" $Value"
}
}

function Get-ParameterInfo {
param(
[Parameter( Mandatory = $true )]
Expand Down
55 changes: 55 additions & 0 deletions tst/Pester.RSpec.ts.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1533,4 +1533,59 @@ i -PassThru:$PassThru {
}

}

b "Pester can throw on failed run" {
t "Exception is thrown" {

$sb1 = {
Describe "d1" {
It "i1" {
1 | Should -Be 0
}

It "i2" {
1 | Should -Be 0
}
}

Describe "d2" {
BeforeAll {
throw "fail block in Run"
}

It "i3" {
1 | Should -Be 1
}
}

Describe "d3" {
throw "fail block in Discovery"
}
}

$sb2 = {
throw "fail container"
}


$result = try {
$c = @{
Run = @{
ScriptBlock = $sb1, $sb2
PassThru = $true
Throw = $true
}
}

Invoke-Pester -Configuration $c
}
catch {
$err = $_
}

# result should be passed before throwing
$result | Verify-NotNull
$err | Verify-Equal "Pester run failed, because 3 tests failed, 1 block failed and 2 containers failed"
}
}
}

0 comments on commit 755543f

Please sign in to comment.