From 7e6efa36591aa4a00bb975797aa90e2440c03d3c Mon Sep 17 00:00:00 2001 From: Mehul Kar Date: Mon, 15 May 2023 18:43:39 -0700 Subject: [PATCH] feat: Print failed tasks at the bottom of the run --- .../runsummary/format_execution_summary.go | 67 ++++++++++++------- cli/internal/runsummary/run_summary.go | 11 +++ turborepo-tests/integration/tests/continue.t | 3 + .../integration/tests/errors-only.t | 2 + .../integration/tests/one_script_error.t | 3 + 5 files changed, 63 insertions(+), 23 deletions(-) diff --git a/cli/internal/runsummary/format_execution_summary.go b/cli/internal/runsummary/format_execution_summary.go index 37092beb06a1b..c6ac39a3f5da9 100644 --- a/cli/internal/runsummary/format_execution_summary.go +++ b/cli/internal/runsummary/format_execution_summary.go @@ -1,7 +1,9 @@ package runsummary import ( + "fmt" "os" + "strings" "time" "github.com/fatih/color" @@ -16,6 +18,7 @@ func (rsm *Meta) printExecutionSummary() { attempted := summary.ExecutionSummary.attempted successful := summary.ExecutionSummary.cached + summary.ExecutionSummary.success + failed := rsm.RunSummary.getFailedTasks() // Note: ExecutionSummary.failure exists, but we need the task names cached := summary.ExecutionSummary.cached // TODO: can we use a method on ExecutionSummary here? duration := time.Since(summary.ExecutionSummary.startedAt).Truncate(time.Millisecond) @@ -32,39 +35,57 @@ func (rsm *Meta) printExecutionSummary() { } } - if attempted == 0 { - ui.Output("") // Clear the line - ui.Warn("No tasks were executed as part of this run.") + lineData := []summaryLine{ + {header: "Tasks", trailer: util.Sprintf("${BOLD_GREEN}%v successful${RESET}${GRAY}, %v total", successful, attempted)}, + {header: "Cached", trailer: util.Sprintf("%v cached${RESET}${GRAY}, %v total", cached, attempted)}, + {header: "Time", trailer: util.Sprintf("%v${RESET} %v", duration, maybeFullTurbo)}, } - ui.Output("") // Clear the line - spacer := " " // 4 chars - - var lines []string - - // The only difference between these two branches is that when there is a run summary - // we print the path to that file and we adjust the whitespace in the printed text so it aligns. - // We could just always align to account for the summary line, but that would require a whole - // bunch of test output assertions to change. if rsm.getPath().FileExists() { - lines = []string{ - util.Sprintf("${BOLD} Tasks:${BOLD_GREEN}%s%v successful${RESET}${GRAY}, %v total${RESET}", spacer, successful, attempted), - util.Sprintf("${BOLD} Cached:%s%v cached${RESET}${GRAY}, %v total${RESET}", spacer, cached, attempted), - util.Sprintf("${BOLD} Time:%s%v${RESET} %v${RESET}", spacer, duration, maybeFullTurbo), - util.Sprintf("${BOLD}Summary:%s%s${RESET}", spacer, rsm.getPath()), + l := summaryLine{header: "Summary", trailer: util.Sprintf("%s", rsm.getPath())} + lineData = append(lineData, l) + } + + if len(failed) > 0 { + formatted := []string{} + for _, t := range failed { + formatted = append(formatted, util.Sprintf("${BOLD_RED}%s${RESET}", t.TaskID)) } - } else { - lines = []string{ - util.Sprintf("${BOLD} Tasks:${BOLD_GREEN}%s%v successful${RESET}${GRAY}, %v total${RESET}", spacer, successful, attempted), - util.Sprintf("${BOLD}Cached:%s%v cached${RESET}${GRAY}, %v total${RESET}", spacer, cached, attempted), - util.Sprintf("${BOLD} Time:%s%v${RESET} %v${RESET}", spacer, duration, maybeFullTurbo), + l := summaryLine{header: "Failed", trailer: strings.Join(formatted, ", ")} + lineData = append(lineData, l) + } + + // Some info we need for left padding + maxlength := 0 + for _, sl := range lineData { + if len(sl.header) > maxlength { + maxlength = len(sl.header) } } - // Print the real thing + lines := []string{} + for _, sl := range lineData { + paddedHeader := fmt.Sprintf("%*s", maxlength, sl.header) + line := util.Sprintf("${BOLD}%s: %s${RESET}", paddedHeader, sl.trailer) + lines = append(lines, line) + } + + // Print the lines to terminal + if attempted == 0 { + ui.Output("") // Clear the line + ui.Warn("No tasks were executed as part of this run.") + } + + ui.Output("") // Clear the line + for _, line := range lines { ui.Output(line) } ui.Output("") } + +type summaryLine struct { + header string + trailer string +} diff --git a/cli/internal/runsummary/run_summary.go b/cli/internal/runsummary/run_summary.go index b1770204ce4ba..51ee5c38fe0b4 100644 --- a/cli/internal/runsummary/run_summary.go +++ b/cli/internal/runsummary/run_summary.go @@ -230,6 +230,17 @@ func (summary *RunSummary) TrackTask(taskID string) (func(outcome executionEvent return summary.ExecutionSummary.run(taskID) } +func (summary *RunSummary) getFailedTasks() []*TaskSummary { + failed := []*TaskSummary{} + + for _, t := range summary.Tasks { + if *t.Execution.exitCode != 0 { + failed = append(failed, t) + } + } + return failed +} + // Save saves the run summary to a file func (rsm *Meta) save() error { json, err := rsm.FormatJSON() diff --git a/turborepo-tests/integration/tests/continue.t b/turborepo-tests/integration/tests/continue.t index f750394d8aea1..ae802eb09bd0d 100644 --- a/turborepo-tests/integration/tests/continue.t +++ b/turborepo-tests/integration/tests/continue.t @@ -21,6 +21,7 @@ Run without --continue Tasks: 0 successful, 1 total Cached: 0 cached, 1 total Time:\s*[\.0-9]+m?s (re) + Failed: some-lib#build ERROR run failed: command exited (1) [1] @@ -45,6 +46,7 @@ Run without --continue, and with only errors. Tasks: 0 successful, 1 total Cached: 0 cached, 1 total Time:\s*[\.0-9]+m?s (re) + Failed: some-lib#build ERROR run failed: command exited (1) [1] @@ -80,6 +82,7 @@ Run with --continue Tasks: 1 successful, 3 total Cached: 0 cached, 3 total Time:\s*[\.0-9]+m?s (re) + Failed: some-lib#build, other-app#build ERROR run failed: command exited (1) [1] diff --git a/turborepo-tests/integration/tests/errors-only.t b/turborepo-tests/integration/tests/errors-only.t index 81eab3ef3c531..25233796dcdc6 100644 --- a/turborepo-tests/integration/tests/errors-only.t +++ b/turborepo-tests/integration/tests/errors-only.t @@ -54,6 +54,7 @@ Setup Tasks: 0 successful, 1 total Cached: 0 cached, 1 total Time:\s*[\.0-9]+m?s (re) + Failed: app-a#builderror ERROR run failed: command exited (1) [1] @@ -83,6 +84,7 @@ Setup Tasks: 0 successful, 1 total Cached: 0 cached, 1 total Time:\s*[\.0-9]+m?s (re) + Failed: app-a#builderror2 ERROR run failed: command exited (1) [1] diff --git a/turborepo-tests/integration/tests/one_script_error.t b/turborepo-tests/integration/tests/one_script_error.t index 6f33fe16c5a37..0a9c018b18590 100644 --- a/turborepo-tests/integration/tests/one_script_error.t +++ b/turborepo-tests/integration/tests/one_script_error.t @@ -29,6 +29,7 @@ Note that npm reports any failed script as exit code 1, even though we "exit 2" Tasks: 1 successful, 2 total Cached: 0 cached, 2 total Time:\s*[\.0-9]+m?s (re) + Failed: my-app#error ERROR run failed: command exited (1) [1] @@ -59,6 +60,7 @@ Make sure error isn't cached Tasks: 1 successful, 2 total Cached: 1 cached, 2 total Time:\s*[\.0-9]+m?s (re) + Failed: my-app#error ERROR run failed: command exited (1) [1] @@ -95,6 +97,7 @@ Make sure error code isn't swallowed with continue Tasks: 2 successful, 3 total Cached: 1 cached, 3 total Time:\s*[\.0-9]+m?s (re) + Failed: my-app#error ERROR run failed: command exited (1) [1]