Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to throw on failure #1908

Merged
merged 2 commits into from
Apr 17, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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" {
dt "Exception is thrown" {
nohwnd marked this conversation as resolved.
Show resolved Hide resolved

$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"
}
}
}