Skip to content

Commit

Permalink
improve result formatter with the new DocumentFormat
Browse files Browse the repository at this point in the history
  • Loading branch information
adamluzsi committed Jun 30, 2024
1 parent 915a951 commit 6ce07b0
Show file tree
Hide file tree
Showing 4 changed files with 395 additions and 14 deletions.
68 changes: 54 additions & 14 deletions Spec.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package testcase

import (
"context"
"fmt"
"hash/fnv"
"regexp"
"strings"
"sync"
"testing"

"go.llib.dev/testcase/assert"
"go.llib.dev/testcase/internal"
"go.llib.dev/testcase/internal/caller"
"go.llib.dev/testcase/internal/doc"
"go.llib.dev/testcase/internal/teardown"
)

Expand All @@ -28,6 +31,7 @@ func NewSpec(tb testing.TB, opts ...SpecOption) *Spec {
s.sync = true
}
applyGlobal(s)
tb.Cleanup(s.documentResults)
return s
}

Expand All @@ -39,6 +43,7 @@ func newSpec(tb testing.TB, opts ...SpecOption) *Spec {
vars: newVariables(),
immutable: false,
}
s.doc.maker = doc.DocumentFormat{}
for _, to := range opts {
to.setup(s)
}
Expand All @@ -50,10 +55,11 @@ func (spec *Spec) newSubSpec(desc string, opts ...SpecOption) *Spec {
spec.immutable = true
sub := newSpec(spec.testingTB, opts...)
sub.parent = spec
spec.children = append(spec.children, sub)
sub.description = desc
sub.seed = spec.seed
sub.doc.maker = spec.doc.maker
sub.orderer = spec.orderer
sub.description = desc
spec.children = append(spec.children, sub)
return sub
}

Expand Down Expand Up @@ -86,6 +92,12 @@ type Spec struct {

defs []func(*Spec)

doc struct {
once sync.Once
maker doc.Formatter
results []doc.TestingCase
}

immutable bool
vars *variables
parallel bool
Expand Down Expand Up @@ -450,7 +462,6 @@ func (spec *Spec) run(blk func(*T)) {
if h, ok := tb.(helper); ok {
h.Helper()
}
tb.Helper()
spec.runTB(tb, blk)
})
}
Expand Down Expand Up @@ -488,18 +499,16 @@ func (spec *Spec) runTB(tb testing.TB, blk func(*T)) {
tb.Parallel()
}

tb.Cleanup(func() {
var shouldPrint bool
if tb.Failed() {
shouldPrint = true
defer func() {
var contextPath []string
for _, spec := range spec.specsFromParent() {
contextPath = append(contextPath, spec.description)
}
if testing.Verbose() {
shouldPrint = true
}
if shouldPrint {
spec.printDescription(newT(tb, spec))
}
})
spec.doc.results = append(spec.doc.results, doc.TestingCase{
ContextPath: contextPath,
TestFailed: tb.Failed(),
})
}()

test := func(tb testing.TB) {
tb.Helper()
Expand Down Expand Up @@ -578,13 +587,42 @@ func (spec *Spec) Finish() {

spec.orderer.Order(tests)
td := &teardown.Teardown{}
defer spec.documentResults()
defer td.Finish()
for _, tc := range tests {
tc()
}
})
}

func (spec *Spec) documentResults() {
spec.testingTB.Helper()
if spec.parent != nil {
return
}
spec.doc.once.Do(func() {
var collect func(*Spec) []doc.TestingCase
collect = func(spec *Spec) []doc.TestingCase {
var result []doc.TestingCase
result = append(result, spec.doc.results...)
for _, child := range spec.children {
result = append(result, collect(child)...)
}
return result
}

doc, err := spec.doc.maker.MakeDocument(context.Background(), collect(spec))
if err != nil {
spec.testingTB.Errorf("document writer encountered an error: %s", err.Error())
return
}

if 0 < len(doc) {
internal.Log(spec.testingTB, doc)
}
})
}

func (spec *Spec) withFinishUsingTestingTB(tb testing.TB, blk func()) {
spec.testingTB.Helper()
tb.Helper()
Expand Down Expand Up @@ -669,6 +707,8 @@ func (spec *Spec) getTagSet() map[string]struct{} {
return tagsSet
}

// addTest registers a testing block to be executed as part of the Spec.
// the main purpose is to enable test execution order manipulation throught the TESTCASE_SEED.
func (spec *Spec) addTest(blk func()) {
spec.testingTB.Helper()

Expand Down
162 changes: 162 additions & 0 deletions internal/doc/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package doc

import (
"context"
"fmt"
"os"
"strings"

"go.llib.dev/testcase/internal"
)

type Formatter interface {
MakeDocument(context.Context, []TestingCase) (string, error)
}

type TestingCase struct {
// ContextPath is the Testing ContextPath
ContextPath []string
// TestFailed tells if the test failed
TestFailed bool
}

type DocumentFormat struct{}

func (gen DocumentFormat) MakeDocument(ctx context.Context, tcs []TestingCase) (string, error) {
node := newNode()
for _, tc := range tcs {
node.Add(tc)
}
var document string
if gen.hasFailed(node) || internal.Verbose() {
document = gen.generateDocumentString(node, "")
}
return document, nil
}

type colourCode string

const (
red colourCode = "91m"
green colourCode = "92m"
)

func colourise(code colourCode, text string) string {
if isColourCodingSupported() {
return fmt.Sprintf("\033[%s%s\033[0m", code, text)
}
return text
}

func newNode() *node {
return &node{Nodes: make(nodes)}
}

type node struct {
Nodes nodes
TestingCase TestingCase
}

type nodes map[string]*node

func (n *node) Add(tc TestingCase) {
n.cd(tc.ContextPath).TestingCase = tc
}

func (n *node) cd(path []string) *node {
current := n
for _, part := range path {
if current.Nodes == nil {
current.Nodes = make(nodes)
}
if _, ok := current.Nodes[part]; !ok {
current.Nodes[part] = newNode()
}
current = current.Nodes[part]
}
return current
}

func (gen DocumentFormat) hasFailed(n *node) bool {
for _, child := range n.Nodes {
if child.TestingCase.TestFailed {
return true
}
if gen.hasFailed(child) {
return true
}
}
return false
}

func (gen DocumentFormat) generateDocumentString(n *node, indent string) string {
var sb strings.Builder
for key, child := range n.Nodes {
sb.WriteString(indent)
var (
line = key
colour = green
)
if child.TestingCase.TestFailed {
line += " [FAIL]"
colour = red
}
if len(child.Nodes) == 0 {
line = colourise(colour, line)
}
sb.WriteString(line)
sb.WriteString("\n")
sb.WriteString(gen.generateDocumentString(child, indent+" "))
}
return sb.String()
}

var colourSupportingTerms = map[string]struct{}{
"xterm-256color": {},
"xterm-88color": {},
"xterm-16color": {},
"gnome-terminal": {},
"screen": {},
"konsole": {},
"terminator": {},
"aterm": {},
"linux": {}, // default terminal type for Linux systems
"urxvt": {}, // popular terminal emulator for Unix-like systems
"konsole-256color": {}, // 256-color version of Konsole
"gnome-terminal-256color": {}, // 256-color version of Gnome Terminal
"xfce4-terminal": {}, // terminal emulator for Xfce desktop environment
"terminator-256color": {}, // 256-color version of Terminator
"alacritty": {}, // modern terminal emulator with GPU acceleration
"kitty": {}, // fast, feature-rich, GPU-based terminal emulator
"hyper": {}, // terminal built on web technologies
"wezterm": {}, // highly configurable, GPU-accelerated terminal
"iterm2": {}, // popular terminal emulator for macOS
"st": {}, // simple terminal from the suckless project
"rxvt-unicode-256color": {}, // 256-color version of rxvt-unicode
"rxvt-256color": {}, // 256-color version of rxvt
"foot": {}, // lightweight Wayland terminal emulator
"mlterm": {}, // multilingual terminal emulator
"putty": {}, // popular SSH and telnet client
}

var colourlessTerms = map[string]struct{}{
"dumb": {},
"vt100": {},
"ansi": {},
"ansi.sys": {},
"vt52": {},
}

func isColourCodingSupported() bool {
term, ok := os.LookupEnv("TERM")
if !ok {
return false
}
if _, ok := colourlessTerms[term]; ok {
return false
}
if _, ok := colourSupportingTerms[term]; ok {
return true
}
return false
}
Loading

0 comments on commit 6ce07b0

Please sign in to comment.