Skip to content

Commit 844ab46

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 <fredbi@yahoo.com>
1 parent 374235c commit 844ab46

30 files changed

+1616
-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: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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+
"fmt"
9+
"os"
10+
"strings"
11+
"testing"
12+
13+
target "github.com/go-openapi/testify/v2/assert"
14+
)
15+
16+
func TestMain(m *testing.M) {
17+
os.Args = append(os.Args, "-testify.colorized", "-testify.colorized.notty")
18+
flag.Parse()
19+
20+
os.Exit(m.Run())
21+
}
22+
23+
func TestAssertJSONEq(t *testing.T) {
24+
t.Parallel()
25+
26+
mockT := new(mockT)
27+
res := target.JSONEq(mockT, `{"hello": "world", "foo": "bar"}`, `{"hello": "worldwide", "foo": "bar"}`)
28+
29+
target.False(t, res)
30+
31+
output := mockT.errorString()
32+
t.Log(output) // best to visualize the output
33+
target.Contains(t, neuterize(output), neuterize(expectedColorizedDiff))
34+
}
35+
36+
func TestAssertJSONEq_Array(t *testing.T) {
37+
t.Parallel()
38+
39+
mockT := new(mockT)
40+
res := target.JSONEq(mockT, `["foo", {"hello": "world", "nested": "hash"}]`, `["bar", {"nested": "hash", "hello": "world"}]`)
41+
42+
target.False(t, res)
43+
output := mockT.errorString()
44+
t.Log(output) // best to visualize the output
45+
target.Contains(t, neuterize(output), neuterize(expectedColorizedArrayDiff))
46+
}
47+
48+
func neuterize(str string) string {
49+
// remove blanks and replace escape sequences for readability
50+
blankRemover := strings.NewReplacer("\t", "", " ", "", "\x1b", "^[")
51+
return blankRemover.Replace(str)
52+
}
53+
54+
type mockT struct {
55+
errorFmt string
56+
args []any
57+
}
58+
59+
// Helper is like [testing.T.Helper] but does nothing.
60+
func (mockT) Helper() {}
61+
62+
func (m *mockT) Errorf(format string, args ...any) {
63+
m.errorFmt = format
64+
m.args = args
65+
}
66+
67+
func (m *mockT) Failed() bool {
68+
return m.errorFmt != ""
69+
}
70+
71+
func (m *mockT) errorString() string {
72+
return fmt.Sprintf(m.errorFmt, m.args...)
73+
}
74+
75+
// captured output (indentation is not checked)
76+
//
77+
//nolint:staticcheck // indeed we want to check the escape sequences in this test
78+
const (
79+
expectedColorizedDiff = ` Not equal:
80+
expected: map[string]interface {}{"foo":"bar", "hello":"world"}
81+
actual : map[string]interface {}{"foo":"bar", "hello":"worldwide"}
82+
83+
Diff:
84+
--- Expected
85+
+++ Actual
86+
@@ -2,3 +2,3 @@
87+
 (string) (len=3) "foo": (string) (len=3) "bar",
88+
- (string) (len=5) "hello": (string) (len=5) "world"
89+
+ (string) (len=5) "hello": (string) (len=9) "worldwide"
90+
 }
91+

92+
`
93+
94+
expectedColorizedArrayDiff = `Not equal:
95+
expected: []interface {}{"foo", map[string]interface {}{"hello":"world", "nested":"hash"}}
96+
actual : []interface {}{"bar", map[string]interface {}{"hello":"world", "nested":"hash"}}
97+
98+
Diff:
99+
--- Expected
100+
+++ Actual
101+
@@ -1,3 +1,3 @@
102+
 ([]interface {}) (len=2) {
103+
- (string) (len=3) "foo",
104+
+ (string) (len=3) "bar",
105+
 (map[string]interface {}) (len=2) {
106+

107+
`
108+
)

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)