Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ This is the go-openapi fork of the great [testify](https://github.com/stretchr/t
Main features:

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

## Announcements
Expand Down
56 changes: 56 additions & 0 deletions assert/enable/colors/enable_colors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0

// Package colors is an indirection to handle colorized output.
//
// This package allows the builder to override the indirection with an alternative implementation
// of colorized printers.
package colors

import (
colorstub "github.com/go-openapi/testify/v2/internal/assertions/enable/colors"
)

// Enable registers colorized options for pretty-printing the output of assertions.
//
// The provided enabler defers the initialization, so we may retrieve flags after command line parsing
// or other initialization tasks.
//
// This is not intended for concurrent use.
func Enable(enabler func() []Option) {
colorstub.Enable(enabler)
}

// re-exposed internal types.
type (
// Option is a colorization option.
Option = colorstub.Option

// Theme is a colorization theme for testify output.
Theme = colorstub.Theme
)

// WithEnable enables colorization.
func WithEnable(enabled bool) Option {
return colorstub.WithEnable(enabled)
}

// WithSanitizedTheme sets a colorization theme from a string.
func WithSanitizedTheme(theme string) Option {
return colorstub.WithSanitizedTheme(theme)
}

// WithTheme sets a colorization theme.
func WithTheme(theme Theme) Option {
return colorstub.WithTheme(theme)
}

// WithDark sets the [ThemeDark] color theme.
func WithDark() Option {
return colorstub.WithDark()
}

// WithLight sets the [ThemeLight] color theme.
func WithLight() Option {
return colorstub.WithLight()
}
3 changes: 3 additions & 0 deletions assert/enable/yaml/enable_yaml.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0

// Package yaml is an indirection to handle YAML deserialization.
//
// This package allows the builder to override the indirection with an alternative implementation
Expand Down
66 changes: 66 additions & 0 deletions docs/doc-site/examples/EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,72 @@ name: Alice

---

## Colorized Output (Optional)

Testify can colorize test failure output for better readability. This is an opt-in feature.

### Enabling Colors

```go
import (
"testing"
"github.com/go-openapi/testify/v2/assert"
_ "github.com/go-openapi/testify/enable/colors/v2" // Enable colorized output
)

func TestExample(t *testing.T) {
assert.Equal(t, "expected", "actual") // Failure will be colorized
}
```

### Activation

Colors are activated via command line flag or environment variable:

```bash
# Via flag
go test -v -testify.colorized ./...

# Via environment variable
TESTIFY_COLORIZED=true go test -v ./...
```

### Themes

Two themes are available for different terminal backgrounds:

```bash
# Dark theme (default) - bright colors for dark terminals
go test -v -testify.colorized ./...

# Light theme - normal colors for light terminals
go test -v -testify.colorized -testify.theme=light ./...

# Or via environment
TESTIFY_COLORIZED=true TESTIFY_THEME=light go test -v ./...
```

### CI Environments

By default, colorization is disabled when output is not a terminal. To force colors in CI environments that support ANSI codes:

```bash
TESTIFY_COLORIZED=true TESTIFY_COLORIZED_NOTTY=true go test -v ./...
```

### What Gets Colorized

- **Expected values** in assertion failures (green)
- **Actual values** in assertion failures (red)
- **Diff output**:
- Deleted lines (red)
- Inserted lines (yellow)
- Context lines (green)

**Note:** Without the `enable/colors` import, output remains uncolored (no panic, just no colors).

---

## Best Practices

1. **Use `require` for preconditions** - Stop test immediately if setup fails
Expand Down
115 changes: 115 additions & 0 deletions enable/colors/assertions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0

package colors

import (
"fmt"
"os"
"strings"
"testing"

target "github.com/go-openapi/testify/v2/assert"
colorstub "github.com/go-openapi/testify/v2/assert/enable/colors"
)

func TestMain(m *testing.M) {
// we can't easily simulate arg flags in CI (uses gotestsum etc).
// Similarly, env vars are evaluated too early.
colorstub.Enable(
func() []colorstub.Option {
return []colorstub.Option{
colorstub.WithEnable(true),
colorstub.WithSanitizedTheme(flags.theme),
}
})

os.Exit(m.Run())
}

func TestAssertJSONEq(t *testing.T) {
t.Parallel()

mockT := new(mockT)
res := target.JSONEq(mockT, `{"hello": "world", "foo": "bar"}`, `{"hello": "worldwide", "foo": "bar"}`)

target.False(t, res)

output := mockT.errorString()
t.Log(output) // best to visualize the output
target.Contains(t, neuterize(output), neuterize(expectedColorizedDiff))
}

func TestAssertJSONEq_Array(t *testing.T) {
t.Parallel()

mockT := new(mockT)
res := target.JSONEq(mockT, `["foo", {"hello": "world", "nested": "hash"}]`, `["bar", {"nested": "hash", "hello": "world"}]`)

target.False(t, res)
output := mockT.errorString()
t.Log(output) // best to visualize the output
target.Contains(t, neuterize(output), neuterize(expectedColorizedArrayDiff))
}

func neuterize(str string) string {
// remove blanks and replace escape sequences for readability
blankRemover := strings.NewReplacer("\t", "", " ", "", "\x1b", "^[")
return blankRemover.Replace(str)
}

type mockT struct {
errorFmt string
args []any
}

// Helper is like [testing.T.Helper] but does nothing.
func (mockT) Helper() {}

func (m *mockT) Errorf(format string, args ...any) {
m.errorFmt = format
m.args = args
}

func (m *mockT) Failed() bool {
return m.errorFmt != ""
}

func (m *mockT) errorString() string {
return fmt.Sprintf(m.errorFmt, m.args...)
}

// captured output (indentation is not checked)
//
//nolint:staticcheck // indeed we want to check the escape sequences in this test
const (
expectedColorizedDiff = ` Not equal:
expected: map[string]interface {}{"foo":"bar", "hello":"world"}
actual : map[string]interface {}{"foo":"bar", "hello":"worldwide"}

Diff:
--- Expected
+++ Actual
@@ -2,3 +2,3 @@
 (string) (len=3) "foo": (string) (len=3) "bar",
- (string) (len=5) "hello": (string) (len=5) "world"
+ (string) (len=5) "hello": (string) (len=9) "worldwide"
 }

`

expectedColorizedArrayDiff = `Not equal:
expected: []interface {}{"foo", map[string]interface {}{"hello":"world", "nested":"hash"}}
actual : []interface {}{"bar", map[string]interface {}{"hello":"world", "nested":"hash"}}

Diff:
--- Expected
+++ Actual
@@ -1,3 +1,3 @@
 ([]interface {}) (len=2) {
- (string) (len=3) "foo",
+ (string) (len=3) "bar",
 (map[string]interface {}) (len=2) {

`
)
31 changes: 31 additions & 0 deletions enable/colors/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0

// Package colors enables colorized tests with basic and portable ANSI terminal codes.
//
// Colorization is disabled by default when the standard output is not a terminal.
//
// Colors are somewhat limited, but the package works on unix and windows without any extra dependencies.
//
// # Command line arguments
//
// - testify.colorized={true|false}
// - testify.theme={dark|light}
// - testify.colorized.notty={true|false} (enable colorization even when the output is not a terminal)
//
// The default theme used is dark.
//
// To run tests on a terminal with colorized output:
//
// - run: go test -v -testify.colorized ./...
//
// # Environment variables
//
// Colorization may be enabled from environment:
//
// - TESTIFY_COLORIZED=true
// - TESTIFY_THEME=dark
// - TESTIFY_COLORIZED_NOTTY=true
//
// Command line arguments take precedence over environment.
package colors
65 changes: 65 additions & 0 deletions enable/colors/enable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0

package colors

import (
"flag"
"os"
"strconv"
"strings"

"golang.org/x/term"

colorstub "github.com/go-openapi/testify/v2/assert/enable/colors"
)

const (
envVarColorize = "TESTIFY_COLORIZED"
envVarTheme = "TESTIFY_THEME"
envVarNoTTY = "TESTIFY_COLORIZED_NOTTY"
)

var flags cliFlags //nolint:gochecknoglobals // it's okay to store the state CLI flags in a package global

type cliFlags struct {
colorized bool
theme string
notty bool
}

func init() { //nolint:gochecknoinits // it's okay: we want to declare CLI flags when a blank import references this package
isTerminal := term.IsTerminal(1)

flag.BoolVar(&flags.colorized, "testify.colorized", colorizeFromEnv(), "testify: colorized output")
flag.StringVar(&flags.theme, "testify.theme", themeFromEnv(), "testify: color theme (light,dark)")
flag.BoolVar(&flags.notty, "testify.colorized.notty", nottyFromEnv(), "testify: force colorization, even if not a tty")

colorstub.Enable(
func() []colorstub.Option {
return []colorstub.Option{
colorstub.WithEnable(flags.colorized && (isTerminal || flags.notty)),
colorstub.WithSanitizedTheme(flags.theme),
}
})
}

func colorizeFromEnv() bool {
envColorize := os.Getenv(envVarColorize)
isEnvConfigured, _ := strconv.ParseBool(envColorize)

return isEnvConfigured
}

func themeFromEnv() string {
envTheme := os.Getenv(envVarTheme)

return strings.ToLower(envTheme)
}

func nottyFromEnv() bool {
envNoTTY := os.Getenv(envVarNoTTY)
isEnvNoTTY, _ := strconv.ParseBool(envNoTTY)

return isEnvNoTTY
}
12 changes: 12 additions & 0 deletions enable/colors/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module github.com/go-openapi/testify/enable/colors/v2

require (
github.com/go-openapi/testify/v2 v2.1.8
golang.org/x/term v0.39.0
)

require golang.org/x/sys v0.40.0 // indirect

replace github.com/go-openapi/testify/v2 => ../..

go 1.24.0
4 changes: 4 additions & 0 deletions enable/colors/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
3 changes: 3 additions & 0 deletions enable/yaml/assertions_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0

package yaml

import (
Expand Down
3 changes: 3 additions & 0 deletions enable/yaml/forward_assertions_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0

package yaml

import (
Expand Down
3 changes: 3 additions & 0 deletions enable/yaml/forward_requirements_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0

package yaml

import (
Expand Down
Loading
Loading