Skip to content

Commit 9f372f1

Browse files
committed
(#4475) Always show task execution summary
Currently, Cake prints out a task execution summary on each successful execution. This summary includes which tasks were executed or skipped, and the duration of the task. However, then an execution of Cake fails, the task summary is not printed, and it is necessary to scroll through the Cake output, to find out what task failed. You also lose the information about which tasks, if any, were skipped. Any changes to this workflow will have to be done carefully though, as we don't want to alter how tasks are executed, in what order, or what is done in the cases of ContinueOnError, etc. There are a number of unit tests that check for this execution, and these will need to continue to pass. This commit aims to address this problem by introducing a new CakeReportException, which has a property called Report of type CakeReport. That way, when an exception needs to be thrown, the current execution status report can be passed along with it, and from there, the task execution summary can be printed. The introduction of the CakeReportException has meant that a number of unit tests have had to be updated, since there is an expectation that an InvalidOperationException will be returned, however, I think that this is "ok", since it hasn't meant a change to how the workflow of task executions completes.
1 parent d6c0cc3 commit 9f372f1

File tree

6 files changed

+142
-46
lines changed

6 files changed

+142
-46
lines changed

src/Cake.Cli/Hosts/BuildScriptHost.cs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,26 @@ public override async Task<CakeReport> RunTargetsAsync(IEnumerable<string> targe
8484

8585
private async Task<CakeReport> internalRunTargetAsync()
8686
{
87-
var report = await Engine.RunTargetAsync(_context, _executionStrategy, Settings).ConfigureAwait(false);
88-
89-
if (report != null && !report.IsEmpty)
87+
try
9088
{
91-
_reportPrinter.Write(report);
89+
var report = await Engine.RunTargetAsync(_context, _executionStrategy, Settings).ConfigureAwait(false);
90+
91+
if (report != null && !report.IsEmpty)
92+
{
93+
_reportPrinter.Write(report);
94+
}
95+
96+
return report;
9297
}
98+
catch (CakeReportException cre)
99+
{
100+
if (cre.Report != null && !cre.Report.IsEmpty)
101+
{
102+
_reportPrinter.Write(cre.Report);
103+
}
93104

94-
return report;
105+
throw;
106+
}
95107
}
96108
}
97109
}

src/Cake.Cli/Infrastructure/CakeSpectreReportPrinter.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ public void Write(CakeReport report)
4646
new Text("Duration", rowStyle)).Footer(
4747
new Text(FormatTime(GetTotalTime(report)), rowStyle)));
4848

49+
table.AddColumn(
50+
new TableColumn(
51+
new Text("Status", rowStyle)));
52+
4953
if (includeSkippedReasonColumn)
5054
{
5155
table.AddColumn(new TableColumn(new Text("Skip Reason", rowStyle)));
@@ -59,12 +63,14 @@ public void Write(CakeReport report)
5963
{
6064
table.AddRow(new Markup(item.TaskName, itemStyle),
6165
new Markup(FormatDuration(item), itemStyle),
66+
new Markup(item.ExecutionStatus.ToString(), itemStyle),
6267
new Markup(item.SkippedMessage, itemStyle));
6368
}
6469
else
6570
{
6671
table.AddRow(new Markup(item.TaskName, itemStyle),
67-
new Markup(FormatDuration(item), itemStyle));
72+
new Markup(FormatDuration(item), itemStyle),
73+
new Markup(item.ExecutionStatus.ToString(), itemStyle));
6874
}
6975
}
7076

@@ -122,7 +128,7 @@ private static string FormatDuration(CakeReportEntry item)
122128
{
123129
if (item.ExecutionStatus == CakeTaskExecutionStatus.Skipped)
124130
{
125-
return "Skipped";
131+
return "-";
126132
}
127133

128134
return FormatTime(item.Duration);

src/Cake.Core.Tests/Unit/CakeEngineTests.cs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ public async Task Should_Throw_If_Target_Task_Is_Skipped()
216216
engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings));
217217

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

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

444444
// Then
445-
Assert.IsType<InvalidOperationException>(result);
445+
Assert.IsType<CakeReportException>(result);
446446
Assert.Equal("Whoopsie", result?.Message);
447447
}
448448

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

707707
// Then
708-
Assert.IsType<InvalidOperationException>(result);
708+
Assert.IsType<CakeReportException>(result);
709709
Assert.Equal("Totally my fault", result?.Message);
710710
}
711711

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

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

@@ -826,7 +826,7 @@ public async Task Should_Run_Teardown_After_Last_Running_Task_Even_If_Task_Faile
826826

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

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

873873
// Then
874874
Assert.NotNull(result);
875-
Assert.IsType<InvalidOperationException>(result);
875+
Assert.IsType<CakeReportException>(result);
876876
Assert.Equal("Setup", result?.Message);
877877
}
878878

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

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

10111011
// Then
10121012
Assert.NotNull(result);
1013-
Assert.IsType<InvalidOperationException>(result);
1013+
Assert.IsType<CakeReportException>(result);
10141014
Assert.Equal("Task", result?.Message);
10151015
}
10161016

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

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

12921292
// Then
12931293
Assert.NotNull(exception);
1294-
Assert.IsType<InvalidOperationException>(exception);
1294+
Assert.IsType<CakeReportException>(exception);
12951295
Assert.Equal("Fail", exception?.Message);
12961296
Assert.Equal(
12971297
new List<string>
@@ -1326,7 +1326,7 @@ public async Task Should_Run_Task_Teardown_If_Task_Setup_Failed()
13261326

13271327
// Then
13281328
Assert.NotNull(exception);
1329-
Assert.IsType<InvalidOperationException>(exception);
1329+
Assert.IsType<CakeReportException>(exception);
13301330
Assert.Equal("Fail", exception?.Message);
13311331
Assert.Equal(
13321332
new List<string>
@@ -1355,7 +1355,7 @@ public async Task Should_Throw_Exception_Thrown_From_Task_Setup_Action_If_Both_T
13551355

13561356
// Then
13571357
Assert.NotNull(result);
1358-
Assert.IsType<InvalidOperationException>(result);
1358+
Assert.IsType<CakeReportException>(result);
13591359
Assert.Equal("Task Setup: A", result?.Message);
13601360
}
13611361

@@ -1377,7 +1377,7 @@ public async Task Should_Throw_Exception_Occurring_In_Task_Teardown_If_No_Previo
13771377

13781378
// Then
13791379
Assert.NotNull(result);
1380-
Assert.IsType<InvalidOperationException>(result);
1380+
Assert.IsType<CakeReportException>(result);
13811381
Assert.Equal("Task Teardown: A", result?.Message);
13821382
}
13831383

@@ -1401,7 +1401,7 @@ public async Task Should_Log_Task_Teardown_Exception_If_Both_Task_Setup_And_Task
14011401

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

14251425
// Then
14261426
Assert.NotNull(result);
1427-
Assert.IsType<InvalidOperationException>(result);
1427+
Assert.IsType<CakeReportException>(result);
14281428
Assert.Equal("Task: A", result?.Message);
14291429
}
14301430

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

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

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

17641764
// Then
1765-
Assert.IsType<InvalidOperationException>(result);
1765+
Assert.IsType<CakeReportException>(result);
17661766
Assert.Equal("Whoopsie", result?.Message);
17671767
}
17681768

src/Cake.Core/CakeEngine.cs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ public async Task<CakeReport> RunTargetAsync(ICakeContext context, IExecutionStr
205205
{
206206
exceptionWasThrown = true;
207207
thrownException = ex;
208-
throw;
208+
209+
throw new CakeReportException(report, ex.Message, ex);
209210
}
210211
finally
211212
{
@@ -351,20 +352,20 @@ private async Task ExecuteTaskAsync(ICakeContext context, IExecutionStrategy str
351352
PerformTaskTeardown(context, strategy, task, stopWatch.Elapsed, false, taskException);
352353

353354
_log.Verbose($"Completed in {stopWatch.Elapsed}");
354-
}
355355

356-
// Add the task results to the report
357-
if (IsDelegatedTask(task))
358-
{
359-
report.AddDelegated(task.Name, stopWatch.Elapsed);
360-
}
361-
else if (taskException is null)
362-
{
363-
report.Add(task.Name, CakeReportEntryCategory.Task, stopWatch.Elapsed);
364-
}
365-
else
366-
{
367-
report.AddFailed(task.Name, stopWatch.Elapsed);
356+
// Add the task results to the report
357+
if (IsDelegatedTask(task))
358+
{
359+
report.AddDelegated(task.Name, stopWatch.Elapsed);
360+
}
361+
else if (taskException is null)
362+
{
363+
report.Add(task.Name, CakeReportEntryCategory.Task, stopWatch.Elapsed);
364+
}
365+
else
366+
{
367+
report.AddFailed(task.Name, stopWatch.Elapsed);
368+
}
368369
}
369370
}
370371

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
7+
namespace Cake.Core
8+
{
9+
/// <summary>
10+
/// Represent errors that occur during script execution.
11+
/// </summary>
12+
public sealed class CakeReportException : Exception
13+
{
14+
/// <summary>
15+
/// Gets or sets the Cake Report.
16+
/// </summary>
17+
public CakeReport Report { get; set; }
18+
19+
/// <summary>
20+
/// Initializes a new instance of the <see cref="CakeReportException"/> class.
21+
/// </summary>
22+
public CakeReportException()
23+
{
24+
}
25+
26+
/// <summary>
27+
/// Initializes a new instance of the <see cref="CakeReportException"/> class.
28+
/// </summary>
29+
/// <param name="message">The message that describes the error.</param>
30+
public CakeReportException(string message)
31+
: base(message)
32+
{
33+
}
34+
35+
/// <summary>
36+
/// Initializes a new instance of the <see cref="CakeReportException"/> class.
37+
/// </summary>
38+
/// <param name="message">The error message that explains the reason for the exception.</param>
39+
/// <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>
40+
public CakeReportException(string message, Exception innerException)
41+
: base(message, innerException)
42+
{
43+
}
44+
45+
/// <summary>
46+
/// Initializes a new instance of the <see cref="CakeReportException"/> class.
47+
/// </summary>
48+
/// <param name="report">The Cake Report.</param>
49+
public CakeReportException(CakeReport report)
50+
{
51+
Report = report;
52+
}
53+
54+
/// <summary>
55+
/// Initializes a new instance of the <see cref="CakeReportException"/> class.
56+
/// </summary>
57+
/// <param name="report">The Cake Report.</param>
58+
/// <param name="message">The error message that explains the reason for the exception.</param>
59+
public CakeReportException(CakeReport report, string message)
60+
: base(message)
61+
{
62+
Report = report;
63+
}
64+
65+
/// <summary>
66+
/// Initializes a new instance of the <see cref="CakeReportException"/> class.
67+
/// </summary>
68+
/// <param name="report">The Cake Report.</param>
69+
/// <param name="message">The error message that explains the reason for the exception.</param>
70+
/// <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>
71+
public CakeReportException(CakeReport report, string message, Exception innerException)
72+
: base(message, innerException)
73+
{
74+
Report = report;
75+
}
76+
}
77+
}

src/Cake.Core/CakeReportPrinter.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,28 +49,28 @@ public void Write(CakeReport report)
4949
}
5050

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

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

6060
// Write task status.
6161
foreach (var item in report)
6262
{
6363
if (ShouldWriteTask(item))
6464
{
6565
_console.ForegroundColor = GetItemForegroundColor(item);
66-
_console.WriteLine(lineFormat, item.TaskName, FormatDuration(item));
66+
_console.WriteLine(lineFormat, item.TaskName, FormatDuration(item), item.ExecutionStatus);
6767
}
6868
}
6969

7070
// Write footer.
7171
_console.ForegroundColor = ConsoleColor.Green;
72-
_console.WriteLine(new string('-', 20 + maxTaskNameLength));
73-
_console.WriteLine(lineFormat, "Total:", FormatTime(GetTotalTime(report)));
72+
_console.WriteLine(new string('-', 40 + maxTaskNameLength));
73+
_console.WriteLine(lineFormat, "Total:", FormatTime(GetTotalTime(report)), string.Empty);
7474
}
7575
finally
7676
{
@@ -134,7 +134,7 @@ private string FormatDuration(CakeReportEntry item)
134134
{
135135
if (item.ExecutionStatus == CakeTaskExecutionStatus.Skipped)
136136
{
137-
return "Skipped";
137+
return "-";
138138
}
139139

140140
return FormatTime(item.Duration);

0 commit comments

Comments
 (0)