Skip to content

Commit bdf9d89

Browse files
committed
feat: added optional support for colorized output
This PR adds support for colorized output, with minimal extra dependencies. - Actual / Expected in equal colorized - Diff output is colorized It is enabled with the specific extra blank import: _ "github.com/go-openapi/testify/enable/colors/v2" When enabled, colorized output is: * enabled by go test flag -testify.colorized * or by environment variable TESTIFY_COLORIZED=true By default, colors are chose to be rendered on a dark terminal. You may use darker colors on a bright terminal with: * go test flag -testify.colorized -testify.theme=light * or by environment variable TESTIFY_THEME=light This work is inspired and adapts the following PRs: * github.com/stretchr#1467 * github.com/stretchr#1480 * github.com/stretchr#1232 * github.com/stretchr#994 Signed-off-by: Frederic BIDON <[email protected]>
1 parent 374235c commit bdf9d89

30 files changed

+1620
-570
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ This is the go-openapi fork of the great [testify](https://github.com/stretchr/t
2424
Main features:
2525

2626
* zero external dependencies
27-
* opt-in dependencies for extra features (e.g. asserting YAML)
27+
* opt-in dependencies for extra features (e.g. asserting YAML, colorized output)
2828
* [searchable documentation][doc-url]
2929

3030
## Announcements
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Package colors is an indirection to handle colorized output.
5+
//
6+
// This package allows the builder to override the indirection with an alternative implementation
7+
// of colorized printers.
8+
package colors
9+
10+
import (
11+
colorstub "github.com/go-openapi/testify/v2/internal/assertions/enable/colors"
12+
)
13+
14+
// Enable registers colorized options for pretty-printing the output of assertions.
15+
//
16+
// The provided enabler defers the initialization, so we may retrieve flags after command line parsing
17+
// or other initialization tasks.
18+
//
19+
// This is not intended for concurrent use.
20+
func Enable(enabler func() []Option) {
21+
colorstub.Enable(enabler)
22+
}
23+
24+
// re-exposed internal types.
25+
type (
26+
// Option is a colorization option.
27+
Option = colorstub.Option
28+
29+
// Theme is a colorization theme for testify output.
30+
Theme = colorstub.Theme
31+
)
32+
33+
// WithEnable enables colorization.
34+
func WithEnable(enabled bool) Option {
35+
return colorstub.WithEnable(enabled)
36+
}
37+
38+
// WithSanitizedTheme sets a colorization theme from a string.
39+
func WithSanitizedTheme(theme string) Option {
40+
return colorstub.WithSanitizedTheme(theme)
41+
}
42+
43+
// WithTheme sets a colorization theme.
44+
func WithTheme(theme Theme) Option {
45+
return colorstub.WithTheme(theme)
46+
}
47+
48+
// WithDark sets the [ThemeDark] color theme.
49+
func WithDark() Option {
50+
return colorstub.WithDark()
51+
}
52+
53+
// WithLight sets the [ThemeLight] color theme.
54+
func WithLight() Option {
55+
return colorstub.WithLight()
56+
}

assert/enable/yaml/enable_yaml.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers
2+
// SPDX-License-Identifier: Apache-2.0
3+
14
// Package yaml is an indirection to handle YAML deserialization.
25
//
36
// This package allows the builder to override the indirection with an alternative implementation

docs/doc-site/examples/EXAMPLES.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,72 @@ name: Alice
435435

436436
---
437437

438+
## Colorized Output (Optional)
439+
440+
Testify can colorize test failure output for better readability. This is an opt-in feature.
441+
442+
### Enabling Colors
443+
444+
```go
445+
import (
446+
"testing"
447+
"github.com/go-openapi/testify/v2/assert"
448+
_ "github.com/go-openapi/testify/enable/colors/v2" // Enable colorized output
449+
)
450+
451+
func TestExample(t *testing.T) {
452+
assert.Equal(t, "expected", "actual") // Failure will be colorized
453+
}
454+
```
455+
456+
### Activation
457+
458+
Colors are activated via command line flag or environment variable:
459+
460+
```bash
461+
# Via flag
462+
go test -v -testify.colorized ./...
463+
464+
# Via environment variable
465+
TESTIFY_COLORIZED=true go test -v ./...
466+
```
467+
468+
### Themes
469+
470+
Two themes are available for different terminal backgrounds:
471+
472+
```bash
473+
# Dark theme (default) - bright colors for dark terminals
474+
go test -v -testify.colorized ./...
475+
476+
# Light theme - normal colors for light terminals
477+
go test -v -testify.colorized -testify.theme=light ./...
478+
479+
# Or via environment
480+
TESTIFY_COLORIZED=true TESTIFY_THEME=light go test -v ./...
481+
```
482+
483+
### CI Environments
484+
485+
By default, colorization is disabled when output is not a terminal. To force colors in CI environments that support ANSI codes:
486+
487+
```bash
488+
TESTIFY_COLORIZED=true TESTIFY_COLORIZED_NOTTY=true go test -v ./...
489+
```
490+
491+
### What Gets Colorized
492+
493+
- **Expected values** in assertion failures (green)
494+
- **Actual values** in assertion failures (red)
495+
- **Diff output**:
496+
- Deleted lines (red)
497+
- Inserted lines (yellow)
498+
- Context lines (green)
499+
500+
**Note:** Without the `enable/colors` import, output remains uncolored (no panic, just no colors).
501+
502+
---
503+
438504
## Best Practices
439505

440506
1. **Use `require` for preconditions** - Stop test immediately if setup fails

enable/colors/assertions_test.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package colors
5+
6+
import (
7+
"fmt"
8+
"os"
9+
"strings"
10+
"testing"
11+
12+
target "github.com/go-openapi/testify/v2/assert"
13+
colorstub "github.com/go-openapi/testify/v2/assert/enable/colors"
14+
)
15+
16+
func TestMain(m *testing.M) {
17+
// we can't easily simulate arg flags in CI (uses gotestsum etc).
18+
// Similarly, env vars are evaluated too early.
19+
colorstub.Enable(
20+
func() []colorstub.Option {
21+
return []colorstub.Option{
22+
colorstub.WithEnable(true),
23+
colorstub.WithSanitizedTheme(flags.theme),
24+
}
25+
})
26+
27+
os.Exit(m.Run())
28+
}
29+
30+
func TestAssertJSONEq(t *testing.T) {
31+
t.Parallel()
32+
33+
mockT := new(mockT)
34+
res := target.JSONEq(mockT, `{"hello": "world", "foo": "bar"}`, `{"hello": "worldwide", "foo": "bar"}`)
35+
36+
target.False(t, res)
37+
38+
output := mockT.errorString()
39+
t.Log(output) // best to visualize the output
40+
target.Contains(t, neuterize(output), neuterize(expectedColorizedDiff))
41+
}
42+
43+
func TestAssertJSONEq_Array(t *testing.T) {
44+
t.Parallel()
45+
46+
mockT := new(mockT)
47+
res := target.JSONEq(mockT, `["foo", {"hello": "world", "nested": "hash"}]`, `["bar", {"nested": "hash", "hello": "world"}]`)
48+
49+
target.False(t, res)
50+
output := mockT.errorString()
51+
t.Log(output) // best to visualize the output
52+
target.Contains(t, neuterize(output), neuterize(expectedColorizedArrayDiff))
53+
}
54+
55+
func neuterize(str string) string {
56+
// remove blanks and replace escape sequences for readability
57+
blankRemover := strings.NewReplacer("\t", "", " ", "", "\x1b", "^[")
58+
return blankRemover.Replace(str)
59+
}
60+
61+
type mockT struct {
62+
errorFmt string
63+
args []any
64+
}
65+
66+
// Helper is like [testing.T.Helper] but does nothing.
67+
func (mockT) Helper() {}
68+
69+
func (m *mockT) Errorf(format string, args ...any) {
70+
m.errorFmt = format
71+
m.args = args
72+
}
73+
74+
func (m *mockT) Failed() bool {
75+
return m.errorFmt != ""
76+
}
77+
78+
func (m *mockT) errorString() string {
79+
return fmt.Sprintf(m.errorFmt, m.args...)
80+
}
81+
82+
// captured output (indentation is not checked)
83+
//
84+
//nolint:staticcheck // indeed we want to check the escape sequences in this test
85+
const (
86+
expectedColorizedDiff = ` Not equal:
87+
expected: map[string]interface {}{"foo":"bar", "hello":"world"}
88+
actual : map[string]interface {}{"foo":"bar", "hello":"worldwide"}
89+
90+
Diff:
91+
--- Expected
92+
+++ Actual
93+
@@ -2,3 +2,3 @@
94+
 (string) (len=3) "foo": (string) (len=3) "bar",
95+
- (string) (len=5) "hello": (string) (len=5) "world"
96+
+ (string) (len=5) "hello": (string) (len=9) "worldwide"
97+
 }
98+

99+
`
100+
101+
expectedColorizedArrayDiff = `Not equal:
102+
expected: []interface {}{"foo", map[string]interface {}{"hello":"world", "nested":"hash"}}
103+
actual : []interface {}{"bar", map[string]interface {}{"hello":"world", "nested":"hash"}}
104+
105+
Diff:
106+
--- Expected
107+
+++ Actual
108+
@@ -1,3 +1,3 @@
109+
 ([]interface {}) (len=2) {
110+
- (string) (len=3) "foo",
111+
+ (string) (len=3) "bar",
112+
 (map[string]interface {}) (len=2) {
113+

114+
`
115+
)

enable/colors/doc.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Package colors enables colorized tests with basic and portable ANSI terminal codes.
5+
//
6+
// Colorization is disabled by default when the standard output is not a terminal.
7+
//
8+
// Colors are somewhat limited, but the package works on unix and windows without any extra dependencies.
9+
//
10+
// # Command line arguments
11+
//
12+
// - testify.colorized={true|false}
13+
// - testify.theme={dark|light}
14+
// - testify.colorized.notty={true|false} (enable colorization even when the output is not a terminal)
15+
//
16+
// The default theme used is dark.
17+
//
18+
// To run tests on a terminal with colorized output:
19+
//
20+
// - run: go test -v -testify.colorized ./...
21+
//
22+
// # Environment variables
23+
//
24+
// Colorization may be enabled from environment:
25+
//
26+
// - TESTIFY_COLORIZED=true
27+
// - TESTIFY_THEME=dark
28+
// - TESTIFY_COLORIZED_NOTTY=true
29+
//
30+
// Command line arguments take precedence over environment.
31+
package colors

enable/colors/enable.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package colors
5+
6+
import (
7+
"flag"
8+
"os"
9+
"strconv"
10+
"strings"
11+
12+
"golang.org/x/term"
13+
14+
colorstub "github.com/go-openapi/testify/v2/assert/enable/colors"
15+
)
16+
17+
const (
18+
envVarColorize = "TESTIFY_COLORIZED"
19+
envVarTheme = "TESTIFY_THEME"
20+
envVarNoTTY = "TESTIFY_COLORIZED_NOTTY"
21+
)
22+
23+
var flags cliFlags //nolint:gochecknoglobals // it's okay to store the state CLI flags in a package global
24+
25+
type cliFlags struct {
26+
colorized bool
27+
theme string
28+
notty bool
29+
}
30+
31+
func init() { //nolint:gochecknoinits // it's okay: we want to declare CLI flags when a blank import references this package
32+
isTerminal := term.IsTerminal(1)
33+
34+
flag.BoolVar(&flags.colorized, "testify.colorized", colorizeFromEnv(), "testify: colorized output")
35+
flag.StringVar(&flags.theme, "testify.theme", themeFromEnv(), "testify: color theme (light,dark)")
36+
flag.BoolVar(&flags.notty, "testify.colorized.notty", nottyFromEnv(), "testify: force colorization, even if not a tty")
37+
38+
colorstub.Enable(
39+
func() []colorstub.Option {
40+
return []colorstub.Option{
41+
colorstub.WithEnable(flags.colorized && (isTerminal || flags.notty)),
42+
colorstub.WithSanitizedTheme(flags.theme),
43+
}
44+
})
45+
}
46+
47+
func colorizeFromEnv() bool {
48+
envColorize := os.Getenv(envVarColorize)
49+
isEnvConfigured, _ := strconv.ParseBool(envColorize)
50+
51+
return isEnvConfigured
52+
}
53+
54+
func themeFromEnv() string {
55+
envTheme := os.Getenv(envVarTheme)
56+
57+
return strings.ToLower(envTheme)
58+
}
59+
60+
func nottyFromEnv() bool {
61+
envNoTTY := os.Getenv(envVarNoTTY)
62+
isEnvNoTTY, _ := strconv.ParseBool(envNoTTY)
63+
64+
return isEnvNoTTY
65+
}

enable/colors/go.mod

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module github.com/go-openapi/testify/enable/colors/v2
2+
3+
require (
4+
github.com/go-openapi/testify/v2 v2.1.8
5+
golang.org/x/term v0.39.0
6+
)
7+
8+
require golang.org/x/sys v0.40.0 // indirect
9+
10+
replace github.com/go-openapi/testify/v2 => ../..
11+
12+
go 1.24.0

enable/colors/go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
2+
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
3+
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
4+
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=

enable/yaml/assertions_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers
2+
// SPDX-License-Identifier: Apache-2.0
3+
14
package yaml
25

36
import (

0 commit comments

Comments
 (0)