doctor: collapse healthy output to one line, add --verbose

A healthy host triggered ~20 PASS rows with details — too noisy for
the common case. Default now prints only fail/warn rows plus a
summary footer; an all-pass run collapses to a single line. Pass
--verbose / -v for the full per-check output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Thales Maciel 2026-05-01 14:18:09 -03:00
parent 09a3ef812f
commit 9b5cbed32d
No known key found for this signature in database
GPG key ID: 33112E6833C34679
4 changed files with 142 additions and 7 deletions

View file

@ -71,7 +71,8 @@ to diagnose host readiness problems.
}
func (d *deps) newDoctorCommand() *cobra.Command {
return &cobra.Command{
var verbose bool
cmd := &cobra.Command{
Use: "doctor",
Short: "Check host and runtime readiness",
Long: strings.TrimSpace(`
@ -85,8 +86,10 @@ Run 'banger doctor':
- after upgrading the host kernel or banger itself
- when 'banger vm run' fails with an unclear error
Exit code is non-zero if any check fails. Warnings are reported but
do not fail the run.
By default, prints failing and warning checks only and a summary
footer; a healthy host collapses to a single line. Pass --verbose to
print every check with its details. Exit code is non-zero if any
check fails. Warnings are reported but do not fail the run.
`),
Args: noArgsUsage("usage: banger doctor"),
RunE: func(cmd *cobra.Command, args []string) error {
@ -94,7 +97,7 @@ do not fail the run.
if err != nil {
return err
}
if err := printDoctorReport(cmd.OutOrStdout(), report); err != nil {
if err := printDoctorReport(cmd.OutOrStdout(), report, verbose); err != nil {
return err
}
if report.HasFailures() {
@ -103,6 +106,8 @@ do not fail the run.
return nil
},
}
cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "show every check (default: only failures and warnings)")
return cmd
}
func newVersionCommand() *cobra.Command {

View file

@ -133,12 +133,15 @@ func TestDoctorCommandPrintsReportAndFailsOnHardFailures(t *testing.T) {
t.Fatalf("Execute() error = %v, want doctor failure", err)
}
output := stdout.String()
if !strings.Contains(output, "PASS\truntime bundle") {
t.Fatalf("output = %q, want runtime bundle pass", output)
if strings.Contains(output, "PASS\truntime bundle") {
t.Fatalf("output = %q, brief default should hide PASS rows", output)
}
if !strings.Contains(output, "FAIL\tfeature nat") {
t.Fatalf("output = %q, want feature nat fail", output)
}
if !strings.Contains(output, "1 passed, 0 warnings, 1 failure") {
t.Fatalf("output = %q, want summary footer", output)
}
}
func TestDoctorCommandReturnsUnderlyingError(t *testing.T) {

View file

@ -272,9 +272,34 @@ func printKernelCatalogTable(out anyWriter, entries []api.KernelCatalogEntry) er
// -- doctor printer -------------------------------------------------
func printDoctorReport(out anyWriter, report system.Report) error {
func printDoctorReport(out anyWriter, report system.Report, verbose bool) error {
colorWriter, _ := out.(io.Writer)
var passes, warns, fails int
for _, c := range report.Checks {
switch c.Status {
case system.CheckStatusPass:
passes++
case system.CheckStatusWarn:
warns++
case system.CheckStatusFail:
fails++
}
}
if !verbose && warns == 0 && fails == 0 {
msg := fmt.Sprintf("all %d checks passed", passes)
if colorWriter != nil {
msg = style.Pass(colorWriter, msg)
}
_, err := fmt.Fprintln(out, msg)
return err
}
for _, check := range report.Checks {
if !verbose && check.Status == system.CheckStatusPass {
continue
}
status := strings.ToUpper(string(check.Status))
if colorWriter != nil {
switch check.Status {
@ -295,5 +320,19 @@ func printDoctorReport(out anyWriter, report system.Report) error {
}
}
}
if !verbose {
if _, err := fmt.Fprintf(out, "\n%d passed, %s, %s\n", passes, pluralCount(warns, "warning"), pluralCount(fails, "failure")); err != nil {
return err
}
}
return nil
}
func pluralCount(n int, word string) string {
if n == 1 {
return fmt.Sprintf("%d %s", n, word)
}
return fmt.Sprintf("%d %ss", n, word)
}

View file

@ -0,0 +1,88 @@
package cli
import (
"bytes"
"strings"
"testing"
"banger/internal/system"
)
func TestPrintDoctorReport_BriefAllPass(t *testing.T) {
report := system.Report{}
report.AddPass("first", "detail one")
report.AddPass("second", "detail two")
report.AddPass("third")
var buf bytes.Buffer
if err := printDoctorReport(&buf, report, false); err != nil {
t.Fatalf("printDoctorReport: %v", err)
}
got := buf.String()
want := "all 3 checks passed\n"
if got != want {
t.Fatalf("brief all-pass output\n got: %q\nwant: %q", got, want)
}
}
func TestPrintDoctorReport_BriefHidesPassDetails(t *testing.T) {
report := system.Report{}
report.AddPass("first", "detail one")
report.AddWarn("second", "warn detail")
report.AddPass("third", "detail three")
report.AddFail("fourth", "fail detail")
var buf bytes.Buffer
if err := printDoctorReport(&buf, report, false); err != nil {
t.Fatalf("printDoctorReport: %v", err)
}
got := buf.String()
if strings.Contains(got, "PASS") || strings.Contains(got, "first") || strings.Contains(got, "third") {
t.Fatalf("brief mode leaked PASS rows: %q", got)
}
for _, want := range []string{"WARN\tsecond", "warn detail", "FAIL\tfourth", "fail detail"} {
if !strings.Contains(got, want) {
t.Fatalf("missing %q in brief output: %q", want, got)
}
}
if !strings.Contains(got, "2 passed, 1 warning, 1 failure") {
t.Fatalf("missing summary footer in: %q", got)
}
}
func TestPrintDoctorReport_BriefSummaryPlurals(t *testing.T) {
report := system.Report{}
report.AddPass("a")
report.AddWarn("b")
report.AddWarn("c")
var buf bytes.Buffer
if err := printDoctorReport(&buf, report, false); err != nil {
t.Fatalf("printDoctorReport: %v", err)
}
if !strings.Contains(buf.String(), "1 passed, 2 warnings, 0 failures") {
t.Fatalf("plural counts wrong: %q", buf.String())
}
}
func TestPrintDoctorReport_VerboseShowsEverything(t *testing.T) {
report := system.Report{}
report.AddPass("first", "detail one")
report.AddWarn("second", "warn detail")
var buf bytes.Buffer
if err := printDoctorReport(&buf, report, true); err != nil {
t.Fatalf("printDoctorReport: %v", err)
}
got := buf.String()
for _, want := range []string{"PASS\tfirst", "detail one", "WARN\tsecond", "warn detail"} {
if !strings.Contains(got, want) {
t.Fatalf("verbose mode missing %q: %q", want, got)
}
}
if strings.Contains(got, "passed,") {
t.Fatalf("verbose mode should not print summary footer: %q", got)
}
}