From c2c1444c8a1e07f7bd93896d3dd4196b617db00d Mon Sep 17 00:00:00 2001 From: Chris Miles Date: Sat, 1 Aug 2026 03:06:38 +0000 Subject: [PATCH 1/6] feat: improve error visibility --- Makefile | 29 ++++++++++++++++++++ README.md | 47 +++++++++++++------------------- console.go | 12 ++++---- console_test.go | 4 +-- example_test.go | 16 +++++------ examples/validation/main.go | 2 +- examples/validation/main_test.go | 2 +- loader.go | 2 +- loader_test.go | 6 ++-- messages.go | 14 ++++++---- messages_test.go | 46 +++++++++++++++++++++++++------ progress.go | 2 +- progress_test.go | 8 +++--- 13 files changed, 120 insertions(+), 70 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6c61c7e --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +GREEN := $(shell tput -Txterm setaf 2) +WHITE := $(shell tput -Txterm setaf 7) +YELLOW := $(shell tput -Txterm setaf 3) +RESET := $(shell tput -Txterm sgr0) + +.PHONY: help + +HELP_FUN = %help; while(<>) { if (/^([A-Za-z0-9_-]+)\s*:.*\#\#(?:@([A-Za-z0-9_-]+))?\s(.*)$$/) { push @{$$help{$$2 || "other"}}, [$$1, $$3]; $$width = length($$1) if length($$1) > $$width } } print "\n"; for $$category (sort keys %help) { print "${WHITE}$$category${RESET}\n"; for $$entry (@{$$help{$$category}}) { printf " ${YELLOW}%-*s${RESET} ${GREEN}%s${RESET}\n", $$width, $$entry->[0], $$entry->[1] } } + +help: ##@other Show this help. + @perl -e '$(HELP_FUN)' $(MAKEFILE_LIST) + +##@quality +test: ##@quality Run the test suites. + go test ./... + go -C docs test ./... + go -C examples test ./... + +test-race: ##@quality Run the race-enabled test suite. + go test -race ./... + +vet: ##@quality Run Go vet for every module. + go vet ./... + go -C docs vet ./... + go -C examples vet ./... + +##@generation +generate: ##@generation Run Go code generation. + go generate . diff --git a/README.md b/README.md index 61c0a7b..051ff00 100644 --- a/README.md +++ b/README.md @@ -571,7 +571,7 @@ if err := loader.Start(); err != nil { } // · Uploading release loader.Fail("Registry refused upload") -// ✖ Registry refused upload +// ERROR Registry refused upload ``` #### Loader.Start @@ -704,7 +704,7 @@ ErrorMark returns the default console's error indicator. ```go fmt.Println(console.ErrorMark()) -// ✖ +// ERROR ``` #### InfoMark @@ -783,7 +783,7 @@ Error prints an error message through the default console. ```go console.Error("deployment failed") -// ✖ deployment failed +// ERROR deployment failed ``` #### Errorf @@ -792,7 +792,7 @@ Errorf prints a formatted error message through the default console. ```go console.Errorf("deployment failed: %s", "timeout") -// ✖ deployment failed: timeout +// ERROR deployment failed: timeout ``` #### Fatal @@ -804,7 +804,7 @@ console.SetDefault(console.New(console.Config{ Exit: func(code int) { fmt.Println("exit", code) }, })) console.Fatal("invalid configuration") -// ✖ invalid configuration +// ERROR invalid configuration // exit 1 ``` @@ -817,7 +817,7 @@ console.SetDefault(console.New(console.Config{ Exit: func(code int) { fmt.Println("exit", code) }, })) console.Fatalf("invalid port: %d", 0) -// ✖ invalid port: 0 +// ERROR invalid port: 0 // exit 1 ``` @@ -1057,7 +1057,7 @@ if err := progress.Start(); err != nil { } // · Publishing release progress.Fail("Registry refused upload") -// ✖ Registry refused upload +// ERROR Registry refused upload ``` #### Progress.Set @@ -1321,12 +1321,12 @@ fmt.Println(errors.Is(err, console.ErrNonInteractive)) #### ASCIIMarks -ASCIIMarks returns symbols suitable for constrained terminals and plain logs. +ASCIIMarks returns marks suitable for constrained terminals and plain logs. ```go marks := console.ASCIIMarks() fmt.Println(marks.Success, marks.Warn, marks.Error) -// + ! x +// + ! ERROR ``` #### Config @@ -1365,12 +1365,12 @@ fmt.Println(console.Default() != nil) #### DefaultMarks -DefaultMarks returns the Unicode symbols used by a default console. +DefaultMarks returns the semantic marks used by a default console. ```go marks := console.DefaultMarks() fmt.Println(marks.Success, marks.Warn, marks.Error) -// ✔ ! ✖ +// ✔ ! ERROR ``` #### Marks @@ -1954,7 +1954,7 @@ console.Success("API ready\nWorker ready") console.Warn("Configuration is incomplete") // ! Configuration is incomplete console.Error("Port already in use") -// ✖ Port already in use +// ERROR Port already in use ``` ### Plain output and coordinated writers @@ -1972,7 +1972,7 @@ fmt.Fprintln(console.StderrWriter(), "diagnostic output") ```go fmt.Println(console.ActionMark(), console.SuccessMark(), console.ErrorMark()) -// · ✔ ✖ +// · ✔ ERROR fmt.Println(console.Style("release ready", console.StyleBold, console.ColorGreen)) // release ready ``` @@ -2103,7 +2103,7 @@ if err := publish.Start(); err != nil { // · Publishing release defer publish.Stop() publish.Fail("Registry refused upload") -// ✖ Registry refused upload +// ERROR Registry refused upload ``` ### Determinate progress @@ -2261,7 +2261,7 @@ console.List("DATABASE_URL is missing", "PORT must be between 1 and 65535") // • DATABASE_URL is missing // • PORT must be between 1 and 65535 console.Error("Validation failed") -// ✖ Validation failed +// ERROR Validation failed ``` ### Recipe: machine stdout and status stderr @@ -2310,18 +2310,9 @@ fmt.Print(output.String()) ## Development -```sh -go test ./... -go test -race ./... -go -C docs test ./... -go -C examples test ./... -go generate . -go vet ./... -go -C docs vet ./... -go -C examples vet ./... -``` +`docs` and `examples` are separate Go modules so release archives contain only the library. -The docs and examples are separate Go modules so release archives contain only the library. The README API index uses a generator-owned grouping manifest, while each local API target and code sample is generated from the declaration's GoDoc `Example:` block. Focused workflow examples come from standard Go example tests that execute and verify their inline output. Generation validates its marker pair and every example target before writing, so malformed documentation fails without partially changing the README. +Use `make test`, `make test-race`, `make vet`, and `make generate`. The test and vet targets cover all three modules; generation rebuilds the README from its verified GoDoc examples. ## Documentation @@ -2333,8 +2324,8 @@ The docs and examples are separate Go modules so release archives contain only t Before tagging a release: -- Run the root, docs, and examples tests and vet commands above, including the race suite. -- Run `go generate .` and confirm the working tree has no generated or module-file diff. +- Run `make test`, `make test-race`, and `make vet`. +- Run `make generate` and confirm the working tree has no generated or module-file diff. - Choose the next semantic version and review the public API and README output one final time. - Create an annotated tag with `git tag -a vX.Y.Z -m "vX.Y.Z"`, then push it with `git push origin vX.Y.Z`. diff --git a/console.go b/console.go index bf690bd..fc30c4c 100644 --- a/console.go +++ b/console.go @@ -99,20 +99,20 @@ type Marks struct { SpinnerFrames []string } -// DefaultMarks returns the Unicode symbols used by a default console. +// DefaultMarks returns the semantic marks used by a default console. // // Example: // // marks := console.DefaultMarks() // fmt.Println(marks.Success, marks.Warn, marks.Error) -// // ✔ ! ✖ +// // ✔ ! ERROR func DefaultMarks() Marks { return Marks{ Action: "·", Info: "·", Success: "✔", Warn: "!", - Error: "✖", + Error: "ERROR", Debug: "?", Bullet: "•", Pointer: "›", @@ -120,20 +120,20 @@ func DefaultMarks() Marks { } } -// ASCIIMarks returns symbols suitable for constrained terminals and plain logs. +// ASCIIMarks returns marks suitable for constrained terminals and plain logs. // // Example: // // marks := console.ASCIIMarks() // fmt.Println(marks.Success, marks.Warn, marks.Error) -// // + ! x +// // + ! ERROR func ASCIIMarks() Marks { return Marks{ Action: "-", Info: "i", Success: "+", Warn: "!", - Error: "x", + Error: "ERROR", Debug: "?", Bullet: "-", Pointer: ">", diff --git a/console_test.go b/console_test.go index ac001d0..40be036 100644 --- a/console_test.go +++ b/console_test.go @@ -55,7 +55,7 @@ func TestMarkFactoriesReturnDocumentedSymbols(t *testing.T) { Info: "·", Success: "✔", Warn: "!", - Error: "✖", + Error: "ERROR", Debug: "?", Bullet: "•", Pointer: "›", @@ -66,7 +66,7 @@ func TestMarkFactoriesReturnDocumentedSymbols(t *testing.T) { Info: "i", Success: "+", Warn: "!", - Error: "x", + Error: "ERROR", Debug: "?", Bullet: "-", Pointer: ">", diff --git a/example_test.go b/example_test.go index 2c7e44e..afe5897 100644 --- a/example_test.go +++ b/example_test.go @@ -41,14 +41,14 @@ func ExampleAction() { console.Warn("Configuration is incomplete") // ! Configuration is incomplete console.Error("Port already in use") - // ✖ Port already in use + // ERROR Port already in use // Output: // · Building application // ✔ API ready // Worker ready // ! Configuration is incomplete - // ✖ Port already in use + // ERROR Port already in use } // ExamplePrintln demonstrates ordinary output and writer adapters that cooperate with transient displays. @@ -93,12 +93,12 @@ func ExampleStyle() { // @readme:setup:end fmt.Println(console.ActionMark(), console.SuccessMark(), console.ErrorMark()) - // · ✔ ✖ + // · ✔ ERROR fmt.Println(console.Style("release ready", console.StyleBold, console.ColorGreen)) // release ready // Output: - // · ✔ ✖ + // · ✔ ERROR // release ready } @@ -367,13 +367,13 @@ func ExampleNewLoader() { // · Publishing release defer publish.Stop() publish.Fail("Registry refused upload") - // ✖ Registry refused upload + // ERROR Registry refused upload // Output: // · Downloading modules // ✔ Modules ready // · Publishing release - // ✖ Registry refused upload + // ERROR Registry refused upload } // ExampleNewProgress demonstrates determinate work with a durable redirected-output contract. @@ -623,7 +623,7 @@ func Example_validationRecipe() { // • DATABASE_URL is missing // • PORT must be between 1 and 65535 console.Error("Validation failed") - // ✖ Validation failed + // ERROR Validation failed // Output: // ◇ Configuration check @@ -633,7 +633,7 @@ func Example_validationRecipe() { // ! 2 issues need attention // • DATABASE_URL is missing // • PORT must be between 1 and 65535 - // ✖ Validation failed + // ERROR Validation failed } // Example_ciRecipe demonstrates keeping machine output separate from human-facing CI status. diff --git a/examples/validation/main.go b/examples/validation/main.go index 065ced9..17a3800 100644 --- a/examples/validation/main.go +++ b/examples/validation/main.go @@ -40,5 +40,5 @@ func run(stdout, stderr io.Writer) { // • DATABASE_URL is missing // • PORT must be between 1 and 65535 console.Error("Validation failed") - // stderr: ✖ Validation failed + // stderr: ERROR Validation failed } diff --git a/examples/validation/main_test.go b/examples/validation/main_test.go index 759c9c7..f747729 100644 --- a/examples/validation/main_test.go +++ b/examples/validation/main_test.go @@ -21,7 +21,7 @@ func TestRun(t *testing.T) { if got := stdout.String(); got != wantStdout { t.Fatalf("stdout =\n%s\nwant:\n%s", got, wantStdout) } - wantStderr := "✖ Validation failed\n" + wantStderr := "ERROR Validation failed\n" if got := stderr.String(); got != wantStderr { t.Fatalf("stderr = %q, want %q", got, wantStderr) } diff --git a/loader.go b/loader.go index 3f66515..c8405d3 100644 --- a/loader.go +++ b/loader.go @@ -262,7 +262,7 @@ func (l *Loader) Warn(message string) { // } // // · Uploading release // loader.Fail("Registry refused upload") -// // ✖ Registry refused upload +// // ERROR Registry refused upload func (l *Loader) Fail(message string) { l.finish(loaderFinishFail, message) } diff --git a/loader_test.go b/loader_test.go index 12410e3..caec4dc 100644 --- a/loader_test.go +++ b/loader_test.go @@ -279,7 +279,7 @@ func TestLoaderTerminalOutcomesVerifyFirstCallWins(t *testing.T) { name: "fail", finish: func(loader *Loader) { loader.Fail("broken") }, wantStdout: clearTransientLine + "1 work" + clearTransientLine, - wantStderr: "x broken\n", + wantStderr: "ERROR broken\n", }, } @@ -533,7 +533,7 @@ func TestLoaderStderrDoesNotCompletePartialStdout(t *testing.T) { console.Error("failed") requireNoLoaderWrite(t, stdout) - if got, want := stderr.String(), "x failed\n"; got != want { + if got, want := stderr.String(), "ERROR failed\n"; got != want { t.Fatalf("stderr = %q, want %q", got, want) } @@ -726,7 +726,7 @@ func TestLoaderRedirectedOutputUsesStableSemanticLines(t *testing.T) { if got := stdout.String(); got != wantStdout { t.Fatalf("stdout = %q, want %q", got, wantStdout) } - if got, want := stderr.String(), "x checking\n"; got != want { + if got, want := stderr.String(), "ERROR checking\n"; got != want { t.Fatalf("stderr = %q, want %q", got, want) } if strings.ContainsAny(stdout.String()+stderr.String(), "\r\x1b") { diff --git a/messages.go b/messages.go index afb74ba..c710eec 100644 --- a/messages.go +++ b/messages.go @@ -6,6 +6,8 @@ import ( "strings" ) +const errorMarkStyle = "\033[37;41m" + // ANSI style and color codes are grouped here so callers can compose them with Style. const ( // ColorReset resets ANSI styling. @@ -169,7 +171,7 @@ func (c *Console) WarnMark() string { // ErrorMark returns the error indicator using the stderr color policy. func (c *Console) ErrorMark() string { - return c.mark(c.stderr, ColorRed, c.marks.Error) + return c.mark(c.stderr, errorMarkStyle, c.marks.Error) } // DebugMark returns the debug indicator. @@ -364,7 +366,7 @@ func WarnMark() string { return Default().WarnMark() } // Example: inspect the error mark // // fmt.Println(console.ErrorMark()) -// // ✖ +// // ERROR func ErrorMark() string { return Default().ErrorMark() } // DebugMark returns the default console's debug indicator. @@ -444,7 +446,7 @@ func Warnf(format string, arguments ...any) { Default().Warnf(format, arguments. // Example: report an error // // console.Error("deployment failed") -// // ✖ deployment failed +// // ERROR deployment failed func Error(message string) { Default().Error(message) } // Errorf prints a formatted error message through the default console. @@ -452,7 +454,7 @@ func Error(message string) { Default().Error(message) } // Example: report a formatted error // // console.Errorf("deployment failed: %s", "timeout") -// // ✖ deployment failed: timeout +// // ERROR deployment failed: timeout func Errorf(format string, arguments ...any) { Default().Errorf(format, arguments...) } // Fatal prints an error through the default console and exits with status 1. @@ -463,7 +465,7 @@ func Errorf(format string, arguments ...any) { Default().Errorf(format, argument // Exit: func(code int) { fmt.Println("exit", code) }, // })) // console.Fatal("invalid configuration") -// // ✖ invalid configuration +// // ERROR invalid configuration // // exit 1 func Fatal(message string) { Default().Fatal(message) } @@ -475,7 +477,7 @@ func Fatal(message string) { Default().Fatal(message) } // Exit: func(code int) { fmt.Println("exit", code) }, // })) // console.Fatalf("invalid port: %d", 0) -// // ✖ invalid port: 0 +// // ERROR invalid port: 0 // // exit 1 func Fatalf(format string, arguments ...any) { Default().Fatalf(format, arguments...) } diff --git a/messages_test.go b/messages_test.go index a0c0d88..505735c 100644 --- a/messages_test.go +++ b/messages_test.go @@ -394,7 +394,7 @@ func TestSharedOutputTracksOnePhysicalLine(t *testing.T) { if console.partialLine { t.Fatal("partialLine = true after shared stderr newline, want false") } - if got, want := output.String(), "partial x failed\n"; got != want { + if got, want := output.String(), "partial ERROR failed\n"; got != want { t.Fatalf("shared output = %q, want %q", got, want) } } @@ -431,7 +431,7 @@ func TestConsoleSemanticMessagesUseExpectedMarksAndDestinations(t *testing.T) { if got := stdout.String(); got != wantStdout { t.Fatalf("semantic stdout = %q, want %q", got, wantStdout) } - wantStderr := "x failed\nx failed 7\n" + wantStderr := "ERROR failed\nERROR failed 7\n" if got := stderr.String(); got != wantStderr { t.Fatalf("semantic stderr = %q, want %q", got, wantStderr) } @@ -570,11 +570,39 @@ func TestConsoleMarksHonorDestinationColorCapability(t *testing.T) { if got := inverse.ActionMark(); got != "A" { t.Fatalf("redirected stdout action mark = %q, want %q", got, "A") } - if got, want := inverse.ErrorMark(), ColorRed+"E"+ColorReset; got != want { + if got, want := inverse.ErrorMark(), errorMarkStyle+"E"+ColorReset; got != want { t.Fatalf("terminal stderr error mark = %q, want %q", got, want) } } +// TestConsoleErrorMarkUsesAHighContrastTerminalLabel verifies failures remain explicit with or without ANSI styling. +func TestConsoleErrorMarkUsesAHighContrastTerminalLabel(t *testing.T) { + t.Parallel() + + stdout := &descriptorBuffer{descriptor: 71} + stderr := &descriptorBuffer{descriptor: 72} + terminal := New(Config{ + Stdout: stdout, + Stderr: stderr, + Getenv: getenvFrom(nil), + IsTerminal: func(descriptor int) bool { + return descriptor == 72 + }, + }) + if got, want := terminal.ErrorMark(), errorMarkStyle+"ERROR"+ColorReset; got != want { + t.Fatalf("terminal ErrorMark() = %q, want %q", got, want) + } + + redirected := New(Config{ + Stdout: &bytes.Buffer{}, + Stderr: &bytes.Buffer{}, + Getenv: getenvFrom(nil), + }) + if got, want := redirected.ErrorMark(), "ERROR"; got != want { + t.Fatalf("redirected ErrorMark() = %q, want %q", got, want) + } +} + // TestConsoleStyleAppliesOrderedANSISequences verifies styling, colorization, and no-op cases. func TestConsoleStyleAppliesOrderedANSISequences(t *testing.T) { t.Parallel() @@ -686,14 +714,14 @@ func TestFatalMessagesWriteBeforeInjectedExit(t *testing.T) { console.Fatal("first") console.Fatalf("second %d", 2) - wantOutput := "x first\nx second 2\n" + wantOutput := "ERROR first\nERROR second 2\n" if got := stderr.String(); got != wantOutput { t.Fatalf("fatal output = %q, want %q", got, wantOutput) } if want := []int{1, 1}; !reflect.DeepEqual(exitCodes, want) { t.Fatalf("fatal exit codes = %v, want %v", exitCodes, want) } - wantSnapshots := []string{"x first\n", "x first\nx second 2\n"} + wantSnapshots := []string{"ERROR first\n", "ERROR first\nERROR second 2\n"} if !reflect.DeepEqual(outputAtExit, wantSnapshots) { t.Fatalf("output observed by Exit = %q, want %q", outputAtExit, wantSnapshots) } @@ -761,7 +789,7 @@ func TestPackageHelpersRouteThroughDefault(t *testing.T) { {name: "InfoMark", got: InfoMark(), want: ColorGray + "I" + ColorReset}, {name: "SuccessMark", got: SuccessMark(), want: ColorGreen + "S" + ColorReset}, {name: "WarnMark", got: WarnMark(), want: ColorYellow + "W" + ColorReset}, - {name: "ErrorMark", got: ErrorMark(), want: ColorRed + "E" + ColorReset}, + {name: "ErrorMark", got: ErrorMark(), want: errorMarkStyle + "E" + ColorReset}, {name: "DebugMark", got: DebugMark(), want: ColorGray + "D" + ColorReset}, } for _, test := range markTests { @@ -810,8 +838,8 @@ func TestPackageHelpersRouteThroughDefault(t *testing.T) { if got := stdout.String(); got != wantStdout { t.Fatalf("package helper stdout = %q, want %q", got, wantStdout) } - redE := ColorRed + "E" + ColorReset - wantStderr := redE + " error\n" + redE + " error 9\n" + redE + " fatal\n" + redE + " fatal 10\n" + errorE := errorMarkStyle + "E" + ColorReset + wantStderr := errorE + " error\n" + errorE + " error 9\n" + errorE + " fatal\n" + errorE + " fatal 10\n" if got := stderr.String(); got != wantStderr { t.Fatalf("package helper stderr = %q, want %q", got, wantStderr) } @@ -842,7 +870,7 @@ func TestConcurrentSemanticWritesRemainAtomic(t *testing.T) { if index%2 == 0 { want[fmt.Sprintf("i info-%03d\n", index)]++ } else { - want[fmt.Sprintf("x error-%03d\n", index)]++ + want[fmt.Sprintf("ERROR error-%03d\n", index)]++ } go func() { defer wait.Done() diff --git a/progress.go b/progress.go index a043653..66f84aa 100644 --- a/progress.go +++ b/progress.go @@ -318,7 +318,7 @@ func (p *Progress) Complete(message string) { // } // // · Publishing release // progress.Fail("Registry refused upload") -// // ✖ Registry refused upload +// // ERROR Registry refused upload func (p *Progress) Fail(message string) { p.finish(progressFinishFail, message) } diff --git a/progress_test.go b/progress_test.go index 83c8b7b..2ef68e3 100644 --- a/progress_test.go +++ b/progress_test.go @@ -158,7 +158,7 @@ func TestRedirectedProgressAndLoaderDoNotContend(t *testing.T) { if got, want := stdout.String(), "- progress\n- loader\n+ loader updated\n"; got != want { t.Fatalf("stdout = %q, want %q", got, want) } - if got, want := stderr.String(), "x progress updated\n"; got != want { + if got, want := stderr.String(), "ERROR progress updated\n"; got != want { t.Fatalf("stderr = %q, want %q", got, want) } if got := stdout.String() + stderr.String(); strings.ContainsAny(got, "\r\x1b") { @@ -179,7 +179,7 @@ func TestProgressFailAndStop(t *testing.T) { if got, want := stdout.String(), clearTransientLine+"work [------------------------] 0%"+clearTransientLine; got != want { t.Fatalf("stdout = %q, want %q", got, want) } - if got, want := stderr.String(), "x work\n"; got != want { + if got, want := stderr.String(), "ERROR work\n"; got != want { t.Fatalf("stderr = %q, want %q", got, want) } }) @@ -511,7 +511,7 @@ func TestProgressConcurrentLifecycleWritesOneOutcome(t *testing.T) { if got := strings.Count(stdout.String(), "- work\n"); got != 1 { t.Fatalf("iteration %d: start line count = %d, output %q", iteration, got, stdout.String()) } - outcomes := strings.Count(stdout.String(), "+ complete\n") + strings.Count(stderr.String(), "x failed\n") + outcomes := strings.Count(stdout.String(), "+ complete\n") + strings.Count(stderr.String(), "ERROR failed\n") if outcomes != 1 { t.Fatalf("iteration %d: terminal outcome count = %d, stdout %q, stderr %q", iteration, outcomes, stdout.String(), stderr.String()) } @@ -530,7 +530,7 @@ func TestProgressStartPublishesRedirectedActionBeforeTerminalCalls(t *testing.T) wantStderr string }{ {name: "complete", finish: func(progress *Progress) { progress.Complete("done") }, wantStdout: "- work\n+ done\n"}, - {name: "fail", finish: func(progress *Progress) { progress.Fail("failed") }, wantStdout: "- work\n", wantStderr: "x failed\n"}, + {name: "fail", finish: func(progress *Progress) { progress.Fail("failed") }, wantStdout: "- work\n", wantStderr: "ERROR failed\n"}, {name: "stop", finish: func(progress *Progress) { progress.Stop() }, wantStdout: "- work\n"}, } for _, test := range tests { From b30982fffe8a188a832f85990debfac8cacdc2d8 Mon Sep 17 00:00:00 2001 From: Chris Miles Date: Sat, 1 Aug 2026 03:16:00 +0000 Subject: [PATCH 2/6] chore: compact Makefile help boilerplate --- Makefile | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 6c61c7e..c255a84 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,6 @@ -GREEN := $(shell tput -Txterm setaf 2) -WHITE := $(shell tput -Txterm setaf 7) -YELLOW := $(shell tput -Txterm setaf 3) -RESET := $(shell tput -Txterm sgr0) - .PHONY: help -HELP_FUN = %help; while(<>) { if (/^([A-Za-z0-9_-]+)\s*:.*\#\#(?:@([A-Za-z0-9_-]+))?\s(.*)$$/) { push @{$$help{$$2 || "other"}}, [$$1, $$3]; $$width = length($$1) if length($$1) > $$width } } print "\n"; for $$category (sort keys %help) { print "${WHITE}$$category${RESET}\n"; for $$entry (@{$$help{$$category}}) { printf " ${YELLOW}%-*s${RESET} ${GREEN}%s${RESET}\n", $$width, $$entry->[0], $$entry->[1] } } +HELP_FUN = %help; while (<>) { /^([A-Za-z0-9_-]+)\s*:.*\#\#(?:@([A-Za-z0-9_-]+))?\s(.*)$$/ or next; push @{$$help{$$2 || "other"}}, [$$1, $$3]; $$width = length($$1) if length($$1) > $$width } print "\n"; for $$category (sort keys %help) { print "\e[37m$$category\e[0m\n"; for $$entry (@{$$help{$$category}}) { printf " \e[33m%-*s\e[0m \e[32m%s\e[0m\n", $$width, $$entry->[0], $$entry->[1] } } help: ##@other Show this help. @perl -e '$(HELP_FUN)' $(MAKEFILE_LIST) From 51edb0690fe86788c6f747e6641699bf4ead2664 Mon Sep 17 00:00:00 2001 From: Chris Miles Date: Sat, 1 Aug 2026 03:25:39 +0000 Subject: [PATCH 3/6] chore: align Makefile help styling --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c255a84..237878c 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .PHONY: help -HELP_FUN = %help; while (<>) { /^([A-Za-z0-9_-]+)\s*:.*\#\#(?:@([A-Za-z0-9_-]+))?\s(.*)$$/ or next; push @{$$help{$$2 || "other"}}, [$$1, $$3]; $$width = length($$1) if length($$1) > $$width } print "\n"; for $$category (sort keys %help) { print "\e[37m$$category\e[0m\n"; for $$entry (@{$$help{$$category}}) { printf " \e[33m%-*s\e[0m \e[32m%s\e[0m\n", $$width, $$entry->[0], $$entry->[1] } } +HELP_FUN = %help; while (<>) { /^([A-Za-z0-9_-]+)\s*:.*\#\#(?:@([A-Za-z0-9_-]+))?\s(.*)$$/ or next; push @{$$help{$$2 || "other"}}, [$$1, $$3]; $$width = length($$1) if length($$1) > $$width } print "\e[1;97m$(or $(HELP_NAME),$(notdir $(CURDIR)))\e[0m\n\n"; for $$category (sort keys %help) { print "\e[1;97m$$category\e[0m\n"; for $$entry (@{$$help{$$category}}) { printf " \e[1;32m%-*s\e[0m \e[90m%s\e[0m\n", $$width, $$entry->[0], $$entry->[1] } } help: ##@other Show this help. @perl -e '$(HELP_FUN)' $(MAKEFILE_LIST) From 567384ea8aa459f96dc0bd071826ed69fcf958f7 Mon Sep 17 00:00:00 2001 From: Chris Miles Date: Sat, 1 Aug 2026 04:18:15 +0000 Subject: [PATCH 4/6] chore: standardize Makefile task names --- Makefile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 237878c..c4d8a8e 100644 --- a/Makefile +++ b/Makefile @@ -5,16 +5,17 @@ HELP_FUN = %help; while (<>) { /^([A-Za-z0-9_-]+)\s*:.*\#\#(?:@([A-Za-z0-9_-]+)) help: ##@other Show this help. @perl -e '$(HELP_FUN)' $(MAKEFILE_LIST) -##@quality -test: ##@quality Run the test suites. +##@tests +test: ##@tests Run the test suites. go test ./... go -C docs test ./... go -C examples test ./... -test-race: ##@quality Run the race-enabled test suite. +test-race: ##@tests Run the race-enabled test suite. go test -race ./... -vet: ##@quality Run Go vet for every module. +##@analysis +vet: ##@analysis Run Go vet for every module. go vet ./... go -C docs vet ./... go -C examples vet ./... From 6ee662435bac167514dfdd148c5cd443497a8730 Mon Sep 17 00:00:00 2001 From: Chris Miles Date: Sat, 1 Aug 2026 04:40:18 +0000 Subject: [PATCH 5/6] chore: remove phony Makefile declarations --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index c4d8a8e..f10bd21 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,3 @@ -.PHONY: help HELP_FUN = %help; while (<>) { /^([A-Za-z0-9_-]+)\s*:.*\#\#(?:@([A-Za-z0-9_-]+))?\s(.*)$$/ or next; push @{$$help{$$2 || "other"}}, [$$1, $$3]; $$width = length($$1) if length($$1) > $$width } print "\e[1;97m$(or $(HELP_NAME),$(notdir $(CURDIR)))\e[0m\n\n"; for $$category (sort keys %help) { print "\e[1;97m$$category\e[0m\n"; for $$entry (@{$$help{$$category}}) { printf " \e[1;32m%-*s\e[0m \e[90m%s\e[0m\n", $$width, $$entry->[0], $$entry->[1] } } From e1883f6dcd15fdb6b98c45d26ca482c365d0b7d7 Mon Sep 17 00:00:00 2001 From: Chris Miles Date: Sat, 1 Aug 2026 05:32:27 +0000 Subject: [PATCH 6/6] Revert "feat: improve error visibility" This reverts commit c2c1444c8a1e07f7bd93896d3dd4196b617db00d. --- README.md | 47 +++++++++++++++++++------------- console.go | 12 ++++---- console_test.go | 4 +-- example_test.go | 16 +++++------ examples/validation/main.go | 2 +- examples/validation/main_test.go | 2 +- loader.go | 2 +- loader_test.go | 6 ++-- messages.go | 14 ++++------ messages_test.go | 46 ++++++------------------------- progress.go | 2 +- progress_test.go | 8 +++--- 12 files changed, 70 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index 051ff00..61c0a7b 100644 --- a/README.md +++ b/README.md @@ -571,7 +571,7 @@ if err := loader.Start(); err != nil { } // · Uploading release loader.Fail("Registry refused upload") -// ERROR Registry refused upload +// ✖ Registry refused upload ``` #### Loader.Start @@ -704,7 +704,7 @@ ErrorMark returns the default console's error indicator. ```go fmt.Println(console.ErrorMark()) -// ERROR +// ✖ ``` #### InfoMark @@ -783,7 +783,7 @@ Error prints an error message through the default console. ```go console.Error("deployment failed") -// ERROR deployment failed +// ✖ deployment failed ``` #### Errorf @@ -792,7 +792,7 @@ Errorf prints a formatted error message through the default console. ```go console.Errorf("deployment failed: %s", "timeout") -// ERROR deployment failed: timeout +// ✖ deployment failed: timeout ``` #### Fatal @@ -804,7 +804,7 @@ console.SetDefault(console.New(console.Config{ Exit: func(code int) { fmt.Println("exit", code) }, })) console.Fatal("invalid configuration") -// ERROR invalid configuration +// ✖ invalid configuration // exit 1 ``` @@ -817,7 +817,7 @@ console.SetDefault(console.New(console.Config{ Exit: func(code int) { fmt.Println("exit", code) }, })) console.Fatalf("invalid port: %d", 0) -// ERROR invalid port: 0 +// ✖ invalid port: 0 // exit 1 ``` @@ -1057,7 +1057,7 @@ if err := progress.Start(); err != nil { } // · Publishing release progress.Fail("Registry refused upload") -// ERROR Registry refused upload +// ✖ Registry refused upload ``` #### Progress.Set @@ -1321,12 +1321,12 @@ fmt.Println(errors.Is(err, console.ErrNonInteractive)) #### ASCIIMarks -ASCIIMarks returns marks suitable for constrained terminals and plain logs. +ASCIIMarks returns symbols suitable for constrained terminals and plain logs. ```go marks := console.ASCIIMarks() fmt.Println(marks.Success, marks.Warn, marks.Error) -// + ! ERROR +// + ! x ``` #### Config @@ -1365,12 +1365,12 @@ fmt.Println(console.Default() != nil) #### DefaultMarks -DefaultMarks returns the semantic marks used by a default console. +DefaultMarks returns the Unicode symbols used by a default console. ```go marks := console.DefaultMarks() fmt.Println(marks.Success, marks.Warn, marks.Error) -// ✔ ! ERROR +// ✔ ! ✖ ``` #### Marks @@ -1954,7 +1954,7 @@ console.Success("API ready\nWorker ready") console.Warn("Configuration is incomplete") // ! Configuration is incomplete console.Error("Port already in use") -// ERROR Port already in use +// ✖ Port already in use ``` ### Plain output and coordinated writers @@ -1972,7 +1972,7 @@ fmt.Fprintln(console.StderrWriter(), "diagnostic output") ```go fmt.Println(console.ActionMark(), console.SuccessMark(), console.ErrorMark()) -// · ✔ ERROR +// · ✔ ✖ fmt.Println(console.Style("release ready", console.StyleBold, console.ColorGreen)) // release ready ``` @@ -2103,7 +2103,7 @@ if err := publish.Start(); err != nil { // · Publishing release defer publish.Stop() publish.Fail("Registry refused upload") -// ERROR Registry refused upload +// ✖ Registry refused upload ``` ### Determinate progress @@ -2261,7 +2261,7 @@ console.List("DATABASE_URL is missing", "PORT must be between 1 and 65535") // • DATABASE_URL is missing // • PORT must be between 1 and 65535 console.Error("Validation failed") -// ERROR Validation failed +// ✖ Validation failed ``` ### Recipe: machine stdout and status stderr @@ -2310,9 +2310,18 @@ fmt.Print(output.String()) ## Development -`docs` and `examples` are separate Go modules so release archives contain only the library. +```sh +go test ./... +go test -race ./... +go -C docs test ./... +go -C examples test ./... +go generate . +go vet ./... +go -C docs vet ./... +go -C examples vet ./... +``` -Use `make test`, `make test-race`, `make vet`, and `make generate`. The test and vet targets cover all three modules; generation rebuilds the README from its verified GoDoc examples. +The docs and examples are separate Go modules so release archives contain only the library. The README API index uses a generator-owned grouping manifest, while each local API target and code sample is generated from the declaration's GoDoc `Example:` block. Focused workflow examples come from standard Go example tests that execute and verify their inline output. Generation validates its marker pair and every example target before writing, so malformed documentation fails without partially changing the README. ## Documentation @@ -2324,8 +2333,8 @@ Use `make test`, `make test-race`, `make vet`, and `make generate`. The test and Before tagging a release: -- Run `make test`, `make test-race`, and `make vet`. -- Run `make generate` and confirm the working tree has no generated or module-file diff. +- Run the root, docs, and examples tests and vet commands above, including the race suite. +- Run `go generate .` and confirm the working tree has no generated or module-file diff. - Choose the next semantic version and review the public API and README output one final time. - Create an annotated tag with `git tag -a vX.Y.Z -m "vX.Y.Z"`, then push it with `git push origin vX.Y.Z`. diff --git a/console.go b/console.go index fc30c4c..bf690bd 100644 --- a/console.go +++ b/console.go @@ -99,20 +99,20 @@ type Marks struct { SpinnerFrames []string } -// DefaultMarks returns the semantic marks used by a default console. +// DefaultMarks returns the Unicode symbols used by a default console. // // Example: // // marks := console.DefaultMarks() // fmt.Println(marks.Success, marks.Warn, marks.Error) -// // ✔ ! ERROR +// // ✔ ! ✖ func DefaultMarks() Marks { return Marks{ Action: "·", Info: "·", Success: "✔", Warn: "!", - Error: "ERROR", + Error: "✖", Debug: "?", Bullet: "•", Pointer: "›", @@ -120,20 +120,20 @@ func DefaultMarks() Marks { } } -// ASCIIMarks returns marks suitable for constrained terminals and plain logs. +// ASCIIMarks returns symbols suitable for constrained terminals and plain logs. // // Example: // // marks := console.ASCIIMarks() // fmt.Println(marks.Success, marks.Warn, marks.Error) -// // + ! ERROR +// // + ! x func ASCIIMarks() Marks { return Marks{ Action: "-", Info: "i", Success: "+", Warn: "!", - Error: "ERROR", + Error: "x", Debug: "?", Bullet: "-", Pointer: ">", diff --git a/console_test.go b/console_test.go index 40be036..ac001d0 100644 --- a/console_test.go +++ b/console_test.go @@ -55,7 +55,7 @@ func TestMarkFactoriesReturnDocumentedSymbols(t *testing.T) { Info: "·", Success: "✔", Warn: "!", - Error: "ERROR", + Error: "✖", Debug: "?", Bullet: "•", Pointer: "›", @@ -66,7 +66,7 @@ func TestMarkFactoriesReturnDocumentedSymbols(t *testing.T) { Info: "i", Success: "+", Warn: "!", - Error: "ERROR", + Error: "x", Debug: "?", Bullet: "-", Pointer: ">", diff --git a/example_test.go b/example_test.go index afe5897..2c7e44e 100644 --- a/example_test.go +++ b/example_test.go @@ -41,14 +41,14 @@ func ExampleAction() { console.Warn("Configuration is incomplete") // ! Configuration is incomplete console.Error("Port already in use") - // ERROR Port already in use + // ✖ Port already in use // Output: // · Building application // ✔ API ready // Worker ready // ! Configuration is incomplete - // ERROR Port already in use + // ✖ Port already in use } // ExamplePrintln demonstrates ordinary output and writer adapters that cooperate with transient displays. @@ -93,12 +93,12 @@ func ExampleStyle() { // @readme:setup:end fmt.Println(console.ActionMark(), console.SuccessMark(), console.ErrorMark()) - // · ✔ ERROR + // · ✔ ✖ fmt.Println(console.Style("release ready", console.StyleBold, console.ColorGreen)) // release ready // Output: - // · ✔ ERROR + // · ✔ ✖ // release ready } @@ -367,13 +367,13 @@ func ExampleNewLoader() { // · Publishing release defer publish.Stop() publish.Fail("Registry refused upload") - // ERROR Registry refused upload + // ✖ Registry refused upload // Output: // · Downloading modules // ✔ Modules ready // · Publishing release - // ERROR Registry refused upload + // ✖ Registry refused upload } // ExampleNewProgress demonstrates determinate work with a durable redirected-output contract. @@ -623,7 +623,7 @@ func Example_validationRecipe() { // • DATABASE_URL is missing // • PORT must be between 1 and 65535 console.Error("Validation failed") - // ERROR Validation failed + // ✖ Validation failed // Output: // ◇ Configuration check @@ -633,7 +633,7 @@ func Example_validationRecipe() { // ! 2 issues need attention // • DATABASE_URL is missing // • PORT must be between 1 and 65535 - // ERROR Validation failed + // ✖ Validation failed } // Example_ciRecipe demonstrates keeping machine output separate from human-facing CI status. diff --git a/examples/validation/main.go b/examples/validation/main.go index 17a3800..065ced9 100644 --- a/examples/validation/main.go +++ b/examples/validation/main.go @@ -40,5 +40,5 @@ func run(stdout, stderr io.Writer) { // • DATABASE_URL is missing // • PORT must be between 1 and 65535 console.Error("Validation failed") - // stderr: ERROR Validation failed + // stderr: ✖ Validation failed } diff --git a/examples/validation/main_test.go b/examples/validation/main_test.go index f747729..759c9c7 100644 --- a/examples/validation/main_test.go +++ b/examples/validation/main_test.go @@ -21,7 +21,7 @@ func TestRun(t *testing.T) { if got := stdout.String(); got != wantStdout { t.Fatalf("stdout =\n%s\nwant:\n%s", got, wantStdout) } - wantStderr := "ERROR Validation failed\n" + wantStderr := "✖ Validation failed\n" if got := stderr.String(); got != wantStderr { t.Fatalf("stderr = %q, want %q", got, wantStderr) } diff --git a/loader.go b/loader.go index c8405d3..3f66515 100644 --- a/loader.go +++ b/loader.go @@ -262,7 +262,7 @@ func (l *Loader) Warn(message string) { // } // // · Uploading release // loader.Fail("Registry refused upload") -// // ERROR Registry refused upload +// // ✖ Registry refused upload func (l *Loader) Fail(message string) { l.finish(loaderFinishFail, message) } diff --git a/loader_test.go b/loader_test.go index caec4dc..12410e3 100644 --- a/loader_test.go +++ b/loader_test.go @@ -279,7 +279,7 @@ func TestLoaderTerminalOutcomesVerifyFirstCallWins(t *testing.T) { name: "fail", finish: func(loader *Loader) { loader.Fail("broken") }, wantStdout: clearTransientLine + "1 work" + clearTransientLine, - wantStderr: "ERROR broken\n", + wantStderr: "x broken\n", }, } @@ -533,7 +533,7 @@ func TestLoaderStderrDoesNotCompletePartialStdout(t *testing.T) { console.Error("failed") requireNoLoaderWrite(t, stdout) - if got, want := stderr.String(), "ERROR failed\n"; got != want { + if got, want := stderr.String(), "x failed\n"; got != want { t.Fatalf("stderr = %q, want %q", got, want) } @@ -726,7 +726,7 @@ func TestLoaderRedirectedOutputUsesStableSemanticLines(t *testing.T) { if got := stdout.String(); got != wantStdout { t.Fatalf("stdout = %q, want %q", got, wantStdout) } - if got, want := stderr.String(), "ERROR checking\n"; got != want { + if got, want := stderr.String(), "x checking\n"; got != want { t.Fatalf("stderr = %q, want %q", got, want) } if strings.ContainsAny(stdout.String()+stderr.String(), "\r\x1b") { diff --git a/messages.go b/messages.go index c710eec..afb74ba 100644 --- a/messages.go +++ b/messages.go @@ -6,8 +6,6 @@ import ( "strings" ) -const errorMarkStyle = "\033[37;41m" - // ANSI style and color codes are grouped here so callers can compose them with Style. const ( // ColorReset resets ANSI styling. @@ -171,7 +169,7 @@ func (c *Console) WarnMark() string { // ErrorMark returns the error indicator using the stderr color policy. func (c *Console) ErrorMark() string { - return c.mark(c.stderr, errorMarkStyle, c.marks.Error) + return c.mark(c.stderr, ColorRed, c.marks.Error) } // DebugMark returns the debug indicator. @@ -366,7 +364,7 @@ func WarnMark() string { return Default().WarnMark() } // Example: inspect the error mark // // fmt.Println(console.ErrorMark()) -// // ERROR +// // ✖ func ErrorMark() string { return Default().ErrorMark() } // DebugMark returns the default console's debug indicator. @@ -446,7 +444,7 @@ func Warnf(format string, arguments ...any) { Default().Warnf(format, arguments. // Example: report an error // // console.Error("deployment failed") -// // ERROR deployment failed +// // ✖ deployment failed func Error(message string) { Default().Error(message) } // Errorf prints a formatted error message through the default console. @@ -454,7 +452,7 @@ func Error(message string) { Default().Error(message) } // Example: report a formatted error // // console.Errorf("deployment failed: %s", "timeout") -// // ERROR deployment failed: timeout +// // ✖ deployment failed: timeout func Errorf(format string, arguments ...any) { Default().Errorf(format, arguments...) } // Fatal prints an error through the default console and exits with status 1. @@ -465,7 +463,7 @@ func Errorf(format string, arguments ...any) { Default().Errorf(format, argument // Exit: func(code int) { fmt.Println("exit", code) }, // })) // console.Fatal("invalid configuration") -// // ERROR invalid configuration +// // ✖ invalid configuration // // exit 1 func Fatal(message string) { Default().Fatal(message) } @@ -477,7 +475,7 @@ func Fatal(message string) { Default().Fatal(message) } // Exit: func(code int) { fmt.Println("exit", code) }, // })) // console.Fatalf("invalid port: %d", 0) -// // ERROR invalid port: 0 +// // ✖ invalid port: 0 // // exit 1 func Fatalf(format string, arguments ...any) { Default().Fatalf(format, arguments...) } diff --git a/messages_test.go b/messages_test.go index 505735c..a0c0d88 100644 --- a/messages_test.go +++ b/messages_test.go @@ -394,7 +394,7 @@ func TestSharedOutputTracksOnePhysicalLine(t *testing.T) { if console.partialLine { t.Fatal("partialLine = true after shared stderr newline, want false") } - if got, want := output.String(), "partial ERROR failed\n"; got != want { + if got, want := output.String(), "partial x failed\n"; got != want { t.Fatalf("shared output = %q, want %q", got, want) } } @@ -431,7 +431,7 @@ func TestConsoleSemanticMessagesUseExpectedMarksAndDestinations(t *testing.T) { if got := stdout.String(); got != wantStdout { t.Fatalf("semantic stdout = %q, want %q", got, wantStdout) } - wantStderr := "ERROR failed\nERROR failed 7\n" + wantStderr := "x failed\nx failed 7\n" if got := stderr.String(); got != wantStderr { t.Fatalf("semantic stderr = %q, want %q", got, wantStderr) } @@ -570,39 +570,11 @@ func TestConsoleMarksHonorDestinationColorCapability(t *testing.T) { if got := inverse.ActionMark(); got != "A" { t.Fatalf("redirected stdout action mark = %q, want %q", got, "A") } - if got, want := inverse.ErrorMark(), errorMarkStyle+"E"+ColorReset; got != want { + if got, want := inverse.ErrorMark(), ColorRed+"E"+ColorReset; got != want { t.Fatalf("terminal stderr error mark = %q, want %q", got, want) } } -// TestConsoleErrorMarkUsesAHighContrastTerminalLabel verifies failures remain explicit with or without ANSI styling. -func TestConsoleErrorMarkUsesAHighContrastTerminalLabel(t *testing.T) { - t.Parallel() - - stdout := &descriptorBuffer{descriptor: 71} - stderr := &descriptorBuffer{descriptor: 72} - terminal := New(Config{ - Stdout: stdout, - Stderr: stderr, - Getenv: getenvFrom(nil), - IsTerminal: func(descriptor int) bool { - return descriptor == 72 - }, - }) - if got, want := terminal.ErrorMark(), errorMarkStyle+"ERROR"+ColorReset; got != want { - t.Fatalf("terminal ErrorMark() = %q, want %q", got, want) - } - - redirected := New(Config{ - Stdout: &bytes.Buffer{}, - Stderr: &bytes.Buffer{}, - Getenv: getenvFrom(nil), - }) - if got, want := redirected.ErrorMark(), "ERROR"; got != want { - t.Fatalf("redirected ErrorMark() = %q, want %q", got, want) - } -} - // TestConsoleStyleAppliesOrderedANSISequences verifies styling, colorization, and no-op cases. func TestConsoleStyleAppliesOrderedANSISequences(t *testing.T) { t.Parallel() @@ -714,14 +686,14 @@ func TestFatalMessagesWriteBeforeInjectedExit(t *testing.T) { console.Fatal("first") console.Fatalf("second %d", 2) - wantOutput := "ERROR first\nERROR second 2\n" + wantOutput := "x first\nx second 2\n" if got := stderr.String(); got != wantOutput { t.Fatalf("fatal output = %q, want %q", got, wantOutput) } if want := []int{1, 1}; !reflect.DeepEqual(exitCodes, want) { t.Fatalf("fatal exit codes = %v, want %v", exitCodes, want) } - wantSnapshots := []string{"ERROR first\n", "ERROR first\nERROR second 2\n"} + wantSnapshots := []string{"x first\n", "x first\nx second 2\n"} if !reflect.DeepEqual(outputAtExit, wantSnapshots) { t.Fatalf("output observed by Exit = %q, want %q", outputAtExit, wantSnapshots) } @@ -789,7 +761,7 @@ func TestPackageHelpersRouteThroughDefault(t *testing.T) { {name: "InfoMark", got: InfoMark(), want: ColorGray + "I" + ColorReset}, {name: "SuccessMark", got: SuccessMark(), want: ColorGreen + "S" + ColorReset}, {name: "WarnMark", got: WarnMark(), want: ColorYellow + "W" + ColorReset}, - {name: "ErrorMark", got: ErrorMark(), want: errorMarkStyle + "E" + ColorReset}, + {name: "ErrorMark", got: ErrorMark(), want: ColorRed + "E" + ColorReset}, {name: "DebugMark", got: DebugMark(), want: ColorGray + "D" + ColorReset}, } for _, test := range markTests { @@ -838,8 +810,8 @@ func TestPackageHelpersRouteThroughDefault(t *testing.T) { if got := stdout.String(); got != wantStdout { t.Fatalf("package helper stdout = %q, want %q", got, wantStdout) } - errorE := errorMarkStyle + "E" + ColorReset - wantStderr := errorE + " error\n" + errorE + " error 9\n" + errorE + " fatal\n" + errorE + " fatal 10\n" + redE := ColorRed + "E" + ColorReset + wantStderr := redE + " error\n" + redE + " error 9\n" + redE + " fatal\n" + redE + " fatal 10\n" if got := stderr.String(); got != wantStderr { t.Fatalf("package helper stderr = %q, want %q", got, wantStderr) } @@ -870,7 +842,7 @@ func TestConcurrentSemanticWritesRemainAtomic(t *testing.T) { if index%2 == 0 { want[fmt.Sprintf("i info-%03d\n", index)]++ } else { - want[fmt.Sprintf("ERROR error-%03d\n", index)]++ + want[fmt.Sprintf("x error-%03d\n", index)]++ } go func() { defer wait.Done() diff --git a/progress.go b/progress.go index 66f84aa..a043653 100644 --- a/progress.go +++ b/progress.go @@ -318,7 +318,7 @@ func (p *Progress) Complete(message string) { // } // // · Publishing release // progress.Fail("Registry refused upload") -// // ERROR Registry refused upload +// // ✖ Registry refused upload func (p *Progress) Fail(message string) { p.finish(progressFinishFail, message) } diff --git a/progress_test.go b/progress_test.go index 2ef68e3..83c8b7b 100644 --- a/progress_test.go +++ b/progress_test.go @@ -158,7 +158,7 @@ func TestRedirectedProgressAndLoaderDoNotContend(t *testing.T) { if got, want := stdout.String(), "- progress\n- loader\n+ loader updated\n"; got != want { t.Fatalf("stdout = %q, want %q", got, want) } - if got, want := stderr.String(), "ERROR progress updated\n"; got != want { + if got, want := stderr.String(), "x progress updated\n"; got != want { t.Fatalf("stderr = %q, want %q", got, want) } if got := stdout.String() + stderr.String(); strings.ContainsAny(got, "\r\x1b") { @@ -179,7 +179,7 @@ func TestProgressFailAndStop(t *testing.T) { if got, want := stdout.String(), clearTransientLine+"work [------------------------] 0%"+clearTransientLine; got != want { t.Fatalf("stdout = %q, want %q", got, want) } - if got, want := stderr.String(), "ERROR work\n"; got != want { + if got, want := stderr.String(), "x work\n"; got != want { t.Fatalf("stderr = %q, want %q", got, want) } }) @@ -511,7 +511,7 @@ func TestProgressConcurrentLifecycleWritesOneOutcome(t *testing.T) { if got := strings.Count(stdout.String(), "- work\n"); got != 1 { t.Fatalf("iteration %d: start line count = %d, output %q", iteration, got, stdout.String()) } - outcomes := strings.Count(stdout.String(), "+ complete\n") + strings.Count(stderr.String(), "ERROR failed\n") + outcomes := strings.Count(stdout.String(), "+ complete\n") + strings.Count(stderr.String(), "x failed\n") if outcomes != 1 { t.Fatalf("iteration %d: terminal outcome count = %d, stdout %q, stderr %q", iteration, outcomes, stdout.String(), stderr.String()) } @@ -530,7 +530,7 @@ func TestProgressStartPublishesRedirectedActionBeforeTerminalCalls(t *testing.T) wantStderr string }{ {name: "complete", finish: func(progress *Progress) { progress.Complete("done") }, wantStdout: "- work\n+ done\n"}, - {name: "fail", finish: func(progress *Progress) { progress.Fail("failed") }, wantStdout: "- work\n", wantStderr: "ERROR failed\n"}, + {name: "fail", finish: func(progress *Progress) { progress.Fail("failed") }, wantStdout: "- work\n", wantStderr: "x failed\n"}, {name: "stop", finish: func(progress *Progress) { progress.Stop() }, wantStdout: "- work\n"}, } for _, test := range tests {