diff --git a/src/Cake.Cli/Hosts/BuildScriptHost.cs b/src/Cake.Cli/Hosts/BuildScriptHost.cs index 63a3c07ad1..7dd7ee1bc9 100644 --- a/src/Cake.Cli/Hosts/BuildScriptHost.cs +++ b/src/Cake.Cli/Hosts/BuildScriptHost.cs @@ -84,14 +84,26 @@ public override async Task RunTargetsAsync(IEnumerable targe private async Task 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; + } } } } \ No newline at end of file diff --git a/src/Cake.Cli/Infrastructure/CakeSpectreReportPrinter.cs b/src/Cake.Cli/Infrastructure/CakeSpectreReportPrinter.cs index 4e14ce2f05..f076484cb5 100644 --- a/src/Cake.Cli/Infrastructure/CakeSpectreReportPrinter.cs +++ b/src/Cake.Cli/Infrastructure/CakeSpectreReportPrinter.cs @@ -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))); @@ -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)); } } @@ -122,7 +128,7 @@ private static string FormatDuration(CakeReportEntry item) { if (item.ExecutionStatus == CakeTaskExecutionStatus.Skipped) { - return "Skipped"; + return "-"; } return FormatTime(item.Duration); diff --git a/src/Cake.Core.Tests/Unit/CakeEngineTests.cs b/src/Cake.Core.Tests/Unit/CakeEngineTests.cs index 99cc3940fe..cb91a79372 100644 --- a/src/Cake.Core.Tests/Unit/CakeEngineTests.cs +++ b/src/Cake.Core.Tests/Unit/CakeEngineTests.cs @@ -216,7 +216,7 @@ public async Task Should_Throw_If_Target_Task_Is_Skipped() engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then - Assert.IsType(result); + Assert.IsType(result); Assert.Equal("Could not reach target 'A' since it was skipped due to a criteria.", result?.Message); } @@ -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(result); + Assert.IsType(result); Assert.Equal("Whoopsie", result?.Message); } @@ -705,7 +705,7 @@ public async Task Should_Propagate_Exception_From_Error_Handler() engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then - Assert.IsType(result); + Assert.IsType(result); Assert.Equal("Totally my fault", result?.Message); } @@ -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(result); + Assert.IsType(result); Assert.Equal("Could not reach target 'B' since it was skipped due to a criteria.", result?.Message); } @@ -826,7 +826,7 @@ public async Task Should_Run_Teardown_After_Last_Running_Task_Even_If_Task_Faile // Then Assert.NotNull(result); - Assert.IsType(result); + Assert.IsType(result); Assert.Equal("Fail", result?.Message); Assert.Contains(fixture.Log.Entries, x => x.Message == "Executing custom teardown action..."); } @@ -849,7 +849,7 @@ public async Task Should_Run_Teardown_If_Setup_Failed() // Then Assert.NotNull(result); - Assert.IsType(result); + Assert.IsType(result); Assert.Equal("Fail", result?.Message); Assert.Contains(fixture.Log.Entries, x => x.Message == "Executing custom teardown action..."); } @@ -872,7 +872,7 @@ public async Task Should_Throw_Exception_Thrown_From_Setup_Action_If_Both_Setup_ // Then Assert.NotNull(result); - Assert.IsType(result); + Assert.IsType(result); Assert.Equal("Setup", result?.Message); } @@ -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(result); + Assert.IsType(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")); } @@ -1010,7 +1010,7 @@ public async Task Should_Throw_Exception_Thrown_From_Task_If_Both_Task_And_Teard // Then Assert.NotNull(result); - Assert.IsType(result); + Assert.IsType(result); Assert.Equal("Task", result?.Message); } @@ -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(result); + Assert.IsType(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")); } @@ -1291,7 +1291,7 @@ public async Task Should_Run_Task_Teardown_After_Each_Running_Task_Even_If_Task_ // Then Assert.NotNull(exception); - Assert.IsType(exception); + Assert.IsType(exception); Assert.Equal("Fail", exception?.Message); Assert.Equal( new List @@ -1326,7 +1326,7 @@ public async Task Should_Run_Task_Teardown_If_Task_Setup_Failed() // Then Assert.NotNull(exception); - Assert.IsType(exception); + Assert.IsType(exception); Assert.Equal("Fail", exception?.Message); Assert.Equal( new List @@ -1355,7 +1355,7 @@ public async Task Should_Throw_Exception_Thrown_From_Task_Setup_Action_If_Both_T // Then Assert.NotNull(result); - Assert.IsType(result); + Assert.IsType(result); Assert.Equal("Task Setup: A", result?.Message); } @@ -1377,7 +1377,7 @@ public async Task Should_Throw_Exception_Occurring_In_Task_Teardown_If_No_Previo // Then Assert.NotNull(result); - Assert.IsType(result); + Assert.IsType(result); Assert.Equal("Task Teardown: A", result?.Message); } @@ -1401,7 +1401,7 @@ public async Task Should_Log_Task_Teardown_Exception_If_Both_Task_Setup_And_Task // Then Assert.NotNull(result); - Assert.IsType(result); + Assert.IsType(result); Assert.Equal("Task Setup: A", result?.Message); Assert.Contains(fixture.Log.Entries, x => x.Message.StartsWith("Task Teardown error (A):")); } @@ -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(result); + Assert.IsType(result); Assert.Equal("Task: A", result?.Message); } @@ -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(result); + Assert.IsType(result); Assert.Equal("Could not reach target 'B' since it was skipped due to a criteria.", result?.Message); } @@ -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(result); + Assert.IsType(result); Assert.Equal("Whoopsie", result?.Message); } diff --git a/src/Cake.Core/CakeEngine.cs b/src/Cake.Core/CakeEngine.cs index 81e779840e..72a77361a0 100644 --- a/src/Cake.Core/CakeEngine.cs +++ b/src/Cake.Core/CakeEngine.cs @@ -205,7 +205,8 @@ public async Task RunTargetAsync(ICakeContext context, IExecutionStr { exceptionWasThrown = true; thrownException = ex; - throw; + + throw new CakeReportException(report, ex.Message, ex); } finally { @@ -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); + } } } diff --git a/src/Cake.Core/CakeReportException.cs b/src/Cake.Core/CakeReportException.cs new file mode 100644 index 0000000000..e5cdf9f75b --- /dev/null +++ b/src/Cake.Core/CakeReportException.cs @@ -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 +{ + /// + /// Represent errors that occur during script execution. + /// + public sealed class CakeReportException : Exception + { + /// + /// Gets or sets the Cake Report. + /// + public CakeReport Report { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public CakeReportException() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public CakeReportException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public CakeReportException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The Cake Report. + public CakeReportException(CakeReport report) + { + Report = report; + } + + /// + /// Initializes a new instance of the class. + /// + /// The Cake Report. + /// The error message that explains the reason for the exception. + public CakeReportException(CakeReport report, string message) + : base(message) + { + Report = report; + } + + /// + /// Initializes a new instance of the class. + /// + /// The Cake Report. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public CakeReportException(CakeReport report, string message, Exception innerException) + : base(message, innerException) + { + Report = report; + } + } +} \ No newline at end of file diff --git a/src/Cake.Core/CakeReportPrinter.cs b/src/Cake.Core/CakeReportPrinter.cs index 07e495482c..a8b561a227 100644 --- a/src/Cake.Core/CakeReportPrinter.cs +++ b/src/Cake.Core/CakeReportPrinter.cs @@ -49,13 +49,13 @@ 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) @@ -63,14 +63,14 @@ public void Write(CakeReport 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 { @@ -134,7 +134,7 @@ private string FormatDuration(CakeReportEntry item) { if (item.ExecutionStatus == CakeTaskExecutionStatus.Skipped) { - return "Skipped"; + return "-"; } return FormatTime(item.Duration);