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
22 changes: 17 additions & 5 deletions src/Cake.Cli/Hosts/BuildScriptHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,26 @@ public override async Task<CakeReport> RunTargetsAsync(IEnumerable<string> targe

private async Task<CakeReport> internalRunTargetAsync()
{
var report = await Engine.RunTargetAsync(_context, _executionStrategy, Settings).ConfigureAwait(false);

if (report != null && !report.IsEmpty)
try
{
_reportPrinter.Write(report);
var report = await Engine.RunTargetAsync(_context, _executionStrategy, Settings).ConfigureAwait(false);

if (report != null && !report.IsEmpty)
{
_reportPrinter.Write(report);
}

return report;
}
catch (CakeReportException cre)
{
if (cre.Report != null && !cre.Report.IsEmpty)
{
_reportPrinter.Write(cre.Report);
}

return report;
throw;
}
}
}
}
10 changes: 8 additions & 2 deletions src/Cake.Cli/Infrastructure/CakeSpectreReportPrinter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ public void Write(CakeReport report)
new Text("Duration", rowStyle)).Footer(
new Text(FormatTime(GetTotalTime(report)), rowStyle)));

table.AddColumn(
new TableColumn(
new Text("Status", rowStyle)));

if (includeSkippedReasonColumn)
{
table.AddColumn(new TableColumn(new Text("Skip Reason", rowStyle)));
Expand All @@ -59,12 +63,14 @@ public void Write(CakeReport report)
{
table.AddRow(new Markup(item.TaskName, itemStyle),
new Markup(FormatDuration(item), itemStyle),
new Markup(item.ExecutionStatus.ToString(), itemStyle),
new Markup(item.SkippedMessage, itemStyle));
}
else
{
table.AddRow(new Markup(item.TaskName, itemStyle),
new Markup(FormatDuration(item), itemStyle));
new Markup(FormatDuration(item), itemStyle),
new Markup(item.ExecutionStatus.ToString(), itemStyle));
}
}

Expand Down Expand Up @@ -122,7 +128,7 @@ private static string FormatDuration(CakeReportEntry item)
{
if (item.ExecutionStatus == CakeTaskExecutionStatus.Skipped)
{
return "Skipped";
return "-";
}

return FormatTime(item.Duration);
Expand Down
36 changes: 18 additions & 18 deletions src/Cake.Core.Tests/Unit/CakeEngineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ public async Task Should_Throw_If_Target_Task_Is_Skipped()
engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings));

// Then
Assert.IsType<CakeException>(result);
Assert.IsType<CakeReportException>(result);
Assert.Equal("Could not reach target 'A' since it was skipped due to a criteria.", result?.Message);
}

Expand Down Expand Up @@ -442,7 +442,7 @@ public async Task Should_Not_Catch_Exceptions_From_Task_If_ContinueOnError_Is_No
engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings));

// Then
Assert.IsType<InvalidOperationException>(result);
Assert.IsType<CakeReportException>(result);
Assert.Equal("Whoopsie", result?.Message);
}

Expand Down Expand Up @@ -705,7 +705,7 @@ public async Task Should_Propagate_Exception_From_Error_Handler()
engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings));

// Then
Assert.IsType<InvalidOperationException>(result);
Assert.IsType<CakeReportException>(result);
Assert.Equal("Totally my fault", result?.Message);
}

Expand Down Expand Up @@ -743,7 +743,7 @@ public async Task Should_Throw_If_Target_Cannot_Be_Reached_Due_To_Constraint()
engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings));

// Then
Assert.IsType<CakeException>(result);
Assert.IsType<CakeReportException>(result);
Assert.Equal("Could not reach target 'B' since it was skipped due to a criteria.", result?.Message);
}

Expand Down Expand Up @@ -826,7 +826,7 @@ public async Task Should_Run_Teardown_After_Last_Running_Task_Even_If_Task_Faile

// Then
Assert.NotNull(result);
Assert.IsType<InvalidOperationException>(result);
Assert.IsType<CakeReportException>(result);
Assert.Equal("Fail", result?.Message);
Assert.Contains(fixture.Log.Entries, x => x.Message == "Executing custom teardown action...");
}
Expand All @@ -849,7 +849,7 @@ public async Task Should_Run_Teardown_If_Setup_Failed()

// Then
Assert.NotNull(result);
Assert.IsType<InvalidOperationException>(result);
Assert.IsType<CakeReportException>(result);
Assert.Equal("Fail", result?.Message);
Assert.Contains(fixture.Log.Entries, x => x.Message == "Executing custom teardown action...");
}
Expand All @@ -872,7 +872,7 @@ public async Task Should_Throw_Exception_Thrown_From_Setup_Action_If_Both_Setup_

// Then
Assert.NotNull(result);
Assert.IsType<InvalidOperationException>(result);
Assert.IsType<CakeReportException>(result);
Assert.Equal("Setup", result?.Message);
}

Expand Down Expand Up @@ -988,7 +988,7 @@ public async Task Should_Log_Teardown_Exception_If_Both_Setup_And_Teardown_Actio
engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings));

// Then
Assert.IsType<InvalidOperationException>(result);
Assert.IsType<CakeReportException>(result);
Assert.Contains(fixture.Log.Entries, x => x.Message.StartsWith("Teardown error: Teardown #1"));
Assert.Contains(fixture.Log.Entries, x => x.Message.StartsWith("Teardown error: Teardown #2"));
}
Expand All @@ -1010,7 +1010,7 @@ public async Task Should_Throw_Exception_Thrown_From_Task_If_Both_Task_And_Teard

// Then
Assert.NotNull(result);
Assert.IsType<InvalidOperationException>(result);
Assert.IsType<CakeReportException>(result);
Assert.Equal("Task", result?.Message);
}

Expand All @@ -1032,7 +1032,7 @@ public async Task Should_Log_Teardown_Exception_If_Both_Task_And_Teardown_Action
engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings));

// Then
Assert.IsType<InvalidOperationException>(result);
Assert.IsType<CakeReportException>(result);
Assert.Contains(fixture.Log.Entries, x => x.Message.StartsWith("Teardown error: Teardown #1"));
Assert.Contains(fixture.Log.Entries, x => x.Message.StartsWith("Teardown error: Teardown #2"));
}
Expand Down Expand Up @@ -1291,7 +1291,7 @@ public async Task Should_Run_Task_Teardown_After_Each_Running_Task_Even_If_Task_

// Then
Assert.NotNull(exception);
Assert.IsType<InvalidOperationException>(exception);
Assert.IsType<CakeReportException>(exception);
Assert.Equal("Fail", exception?.Message);
Assert.Equal(
new List<string>
Expand Down Expand Up @@ -1326,7 +1326,7 @@ public async Task Should_Run_Task_Teardown_If_Task_Setup_Failed()

// Then
Assert.NotNull(exception);
Assert.IsType<InvalidOperationException>(exception);
Assert.IsType<CakeReportException>(exception);
Assert.Equal("Fail", exception?.Message);
Assert.Equal(
new List<string>
Expand Down Expand Up @@ -1355,7 +1355,7 @@ public async Task Should_Throw_Exception_Thrown_From_Task_Setup_Action_If_Both_T

// Then
Assert.NotNull(result);
Assert.IsType<InvalidOperationException>(result);
Assert.IsType<CakeReportException>(result);
Assert.Equal("Task Setup: A", result?.Message);
}

Expand All @@ -1377,7 +1377,7 @@ public async Task Should_Throw_Exception_Occurring_In_Task_Teardown_If_No_Previo

// Then
Assert.NotNull(result);
Assert.IsType<InvalidOperationException>(result);
Assert.IsType<CakeReportException>(result);
Assert.Equal("Task Teardown: A", result?.Message);
}

Expand All @@ -1401,7 +1401,7 @@ public async Task Should_Log_Task_Teardown_Exception_If_Both_Task_Setup_And_Task

// Then
Assert.NotNull(result);
Assert.IsType<InvalidOperationException>(result);
Assert.IsType<CakeReportException>(result);
Assert.Equal("Task Setup: A", result?.Message);
Assert.Contains(fixture.Log.Entries, x => x.Message.StartsWith("Task Teardown error (A):"));
}
Expand All @@ -1424,7 +1424,7 @@ public async Task Should_Log_Exception_Thrown_From_Task_If_Both_Task_And_Task_Te

// Then
Assert.NotNull(result);
Assert.IsType<InvalidOperationException>(result);
Assert.IsType<CakeReportException>(result);
Assert.Equal("Task: A", result?.Message);
}

Expand Down Expand Up @@ -1691,7 +1691,7 @@ public async Task Should_Throw_If_Any_Target_Task_Is_Skipped()
engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings));

// Then
Assert.IsType<CakeException>(result);
Assert.IsType<CakeReportException>(result);
Assert.Equal("Could not reach target 'B' since it was skipped due to a criteria.", result?.Message);
}

Expand Down Expand Up @@ -1762,7 +1762,7 @@ public async Task Should_Not_Catch_Exceptions_From_Task_If_ContinueOnError_Is_No
engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings));

// Then
Assert.IsType<InvalidOperationException>(result);
Assert.IsType<CakeReportException>(result);
Assert.Equal("Whoopsie", result?.Message);
}

Expand Down
29 changes: 15 additions & 14 deletions src/Cake.Core/CakeEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,8 @@ public async Task<CakeReport> RunTargetAsync(ICakeContext context, IExecutionStr
{
exceptionWasThrown = true;
thrownException = ex;
throw;

throw new CakeReportException(report, ex.Message, ex);
}
finally
{
Expand Down Expand Up @@ -351,20 +352,20 @@ private async Task ExecuteTaskAsync(ICakeContext context, IExecutionStrategy str
PerformTaskTeardown(context, strategy, task, stopWatch.Elapsed, false, taskException);

_log.Verbose($"Completed in {stopWatch.Elapsed}");
}

// Add the task results to the report
if (IsDelegatedTask(task))
{
report.AddDelegated(task.Name, stopWatch.Elapsed);
}
else if (taskException is null)
{
report.Add(task.Name, CakeReportEntryCategory.Task, stopWatch.Elapsed);
}
else
{
report.AddFailed(task.Name, stopWatch.Elapsed);
// Add the task results to the report
if (IsDelegatedTask(task))
{
report.AddDelegated(task.Name, stopWatch.Elapsed);
}
else if (taskException is null)
{
report.Add(task.Name, CakeReportEntryCategory.Task, stopWatch.Elapsed);
}
else
{
report.AddFailed(task.Name, stopWatch.Elapsed);
}
}
}

Expand Down
77 changes: 77 additions & 0 deletions src/Cake.Core/CakeReportException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;

namespace Cake.Core
{
/// <summary>
/// Represent errors that occur during script execution.
/// </summary>
public sealed class CakeReportException : Exception
{
/// <summary>
/// Gets or sets the Cake Report.
/// </summary>
public CakeReport Report { get; set; }

/// <summary>
/// Initializes a new instance of the <see cref="CakeReportException"/> class.
/// </summary>
public CakeReportException()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="CakeReportException"/> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public CakeReportException(string message)
: base(message)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="CakeReportException"/> class.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified.</param>
public CakeReportException(string message, Exception innerException)
: base(message, innerException)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="CakeReportException"/> class.
/// </summary>
/// <param name="report">The Cake Report.</param>
public CakeReportException(CakeReport report)
{
Report = report;
}

/// <summary>
/// Initializes a new instance of the <see cref="CakeReportException"/> class.
/// </summary>
/// <param name="report">The Cake Report.</param>
/// <param name="message">The error message that explains the reason for the exception.</param>
public CakeReportException(CakeReport report, string message)
: base(message)
{
Report = report;
}

/// <summary>
/// Initializes a new instance of the <see cref="CakeReportException"/> class.
/// </summary>
/// <param name="report">The Cake Report.</param>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified.</param>
public CakeReportException(CakeReport report, string message, Exception innerException)
: base(message, innerException)
{
Report = report;
}
}
}
14 changes: 7 additions & 7 deletions src/Cake.Core/CakeReportPrinter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,28 +49,28 @@ public void Write(CakeReport report)
}

maxTaskNameLength++;
string lineFormat = "{0,-" + maxTaskNameLength + "}{1,-20}";
string lineFormat = "{0,-" + maxTaskNameLength + "}{1,-20}{2,-20}";
_console.ForegroundColor = ConsoleColor.Green;

// Write header.
_console.WriteLine();
_console.WriteLine(lineFormat, "Task", "Duration");
_console.WriteLine(new string('-', 20 + maxTaskNameLength));
_console.WriteLine(lineFormat, "Task", "Duration", "Status");
_console.WriteLine(new string('-', 40 + maxTaskNameLength));

// Write task status.
foreach (var item in report)
{
if (ShouldWriteTask(item))
{
_console.ForegroundColor = GetItemForegroundColor(item);
_console.WriteLine(lineFormat, item.TaskName, FormatDuration(item));
_console.WriteLine(lineFormat, item.TaskName, FormatDuration(item), item.ExecutionStatus);
}
}

// Write footer.
_console.ForegroundColor = ConsoleColor.Green;
_console.WriteLine(new string('-', 20 + maxTaskNameLength));
_console.WriteLine(lineFormat, "Total:", FormatTime(GetTotalTime(report)));
_console.WriteLine(new string('-', 40 + maxTaskNameLength));
_console.WriteLine(lineFormat, "Total:", FormatTime(GetTotalTime(report)), string.Empty);
}
finally
{
Expand Down Expand Up @@ -134,7 +134,7 @@ private string FormatDuration(CakeReportEntry item)
{
if (item.ExecutionStatus == CakeTaskExecutionStatus.Skipped)
{
return "Skipped";
return "-";
}

return FormatTime(item.Duration);
Expand Down
Loading