Skip to content

Commit

Permalink
Fix issues with test runner command.
Browse files Browse the repository at this point in the history
  • Loading branch information
jmalloc committed Nov 25, 2024
1 parent 4ed7b77 commit 3b80f31
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 147 deletions.
9 changes: 0 additions & 9 deletions internal/runner/assertion.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
package runner

import (
"strings"
)

var (
separator = strings.Repeat("=", 10)
newLine = []byte("\n")
)

// assertionExecutor is an impelmentation of [test.AssertionVisitor] that
// performs assertions within the context of a test.
type assertionExecutor[T TestingT[T]] struct {
Expand Down
121 changes: 15 additions & 106 deletions internal/runner/bless.go
Original file line number Diff line number Diff line change
@@ -1,107 +1,32 @@
package runner

import (
"bytes"
"fmt"
"io"
"os"
"regexp"
"strings"

"github.com/dogmatiq/aureus/internal/test"
)

// BlessStrategy is a strategy for blessing failed tests.
type BlessStrategy interface {
// bless updates the file containing an assertion's expected output to match
// its actual output.
//
// a is the assertion that failed. r is the file containing the blessed
// output that will replace the current expectation.
bless(
t LoggerT,
a test.Assertion,
r *os.File,
)
}

// BlessDisabled is a [BlessStrategy] that explicitly disables blessing of
// failed tests.
//
// It implies that the -aureus.bless flag on the command line is ignored.
type BlessDisabled struct{}

func (*BlessDisabled) bless(LoggerT, test.Assertion, *os.File) {}

// BlessAvailable is a [BlessStrategy] that instructs the user that blessing may
// be activated by using the -aureus.bless flag on the command line.
type BlessAvailable struct {
PackagePath string
}
// BlessStrategy is a strategy for accepting failed test output as the new
// expectation, known as "blessing" the output.
type BlessStrategy int

func (s *BlessAvailable) bless(
t LoggerT,
_ test.Assertion,
_ *os.File,
) {
t.Helper()

atoms := strings.Split(t.Name(), "/")
for i, atom := range atoms {
atoms[i] = "^" + regexp.QuoteMeta(atom) + "$"
}
pattern := strings.Join(atoms, "/")

var w bytes.Buffer
w.WriteString("To accept the current output as correct, use the -aureus.bless flag:")
w.WriteString("\n\n")
w.WriteString(" go test ")

if s.PackagePath == "" {
// this will work, typically, but some packages may complain about not
// understanding the -aureus.bless flag
w.WriteString("./...")
} else {
w.WriteString(shellQuote(s.PackagePath))
}

w.WriteString(" -aureus.bless -run ")
w.WriteString(shellQuote(pattern))

logSection(
t,
"BLESS",
w.Bytes(),
false,
)
}
const (
// BlessAvailable is a [BlessStrategy] that instructs the user that blessing
// may be activated by using the -aureus.bless flag on the command line.
BlessAvailable BlessStrategy = iota

// BlessEnabled is a [BlessStrategy] that explicitly enables blessing of failed
// tests.
type BlessEnabled struct{}
// BlessEnabled is a [BlessStrategy] that explicitly enables blessing of
// failed tests.
BlessEnabled

func (s *BlessEnabled) bless(
t LoggerT,
a test.Assertion,
r *os.File,
) {
t.Helper()

message := `The current output has been blessed. Future runs will consider this output correct.`
if err := edit(a, r); err != nil {
message = "Unable to bless output: " + err.Error()
}

logSection(
t,
"BLESS",
[]byte(message),
false,
)

}
// BlessDisabled is a [BlessStrategy] that explicitly disables blessing of
// failed tests.
BlessDisabled
)

func edit(a test.Assertion, r *os.File) error {
func bless(a test.Assertion, r *os.File) error {
// TODO: Tests are loaded using an [fs.FS], so the file system is not
// necessarily the host file system.
//
Expand Down Expand Up @@ -229,19 +154,3 @@ func fileSize(f *os.File) (int64, error) {
}
return stat.Size(), nil
}

func shellQuote(s string) string {
var w strings.Builder
w.WriteByte('\'')

for _, r := range s {
if r == '\'' {
w.WriteString("'\"'\"'")
} else {
w.WriteRune(r)
}
}

w.WriteByte('\'')
return w.String()
}
11 changes: 10 additions & 1 deletion internal/runner/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,18 @@ type LoggerT interface {
Log(...any)
}

// FailerT is the subset of the [testing.TB] that supports logging and failure
// reporting.
type FailerT interface {
LoggerT
SkipNow()
Fail()
Failed() bool
}

// TestingT is a constraint for types that are compatible with [testing.T].
type TestingT[T any] interface {
LoggerT
FailerT

SkipNow()
Fail()
Expand Down
94 changes: 73 additions & 21 deletions internal/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Runner[T TestingT[T]] struct {
GenerateOutput OutputGenerator[T]
TrimSpace bool // TODO: make this a loader concern
BlessStrategy BlessStrategy
PackagePath string
}

// Run makes the assertions described by all documents within a [TestSuite].
Expand Down Expand Up @@ -49,10 +50,10 @@ func (r *Runner[T]) assert(t T, a test.Assertion) {
t.Helper()
logSection(
t,
"INPUT",
fmt.Sprintf("INPUT (%s)", location(a.Input)),
a.Input.Data,
"\x1b[2m",
r.TrimSpace,
location(a.Input),
)

f, err := generateOutput(t, r.GenerateOutput, a.Input, a.Output)
Expand All @@ -79,8 +80,20 @@ func (r *Runner[T]) assert(t T, a test.Assertion) {
return
}

messages := []string{
"\x1b[1mTo run this test again, use:\n\n" +
" \x1b[2m" + r.goTestCommand(t) + "\x1b[0m",
}

if diff == nil {
logSection(t, "OUTPUT", a.Output.Data, r.TrimSpace, location(a.Output))
logSection(
t,
fmt.Sprintf("OUTPUT (%s)", location(a.Output)),
a.Output.Data,
"\x1b[33;2m",
r.TrimSpace,
messages...,
)
return
}

Expand All @@ -90,9 +103,40 @@ func (r *Runner[T]) assert(t T, a test.Assertion) {
return
}

logSection(t, "OUTPUT DIFF", diff, true)
r.BlessStrategy.bless(t, a, f)
t.Fail()
switch r.BlessStrategy {
case BlessAvailable:
t.Fail()
messages = append(
messages,
"\x1b[1mTo \x1b[33maccept this output as correct\x1b[37m from now on, add the \x1b[33m-aureus.bless\x1b[37m flag:\n\n"+
" \x1b[2m"+r.goTestCommand(t)+" -aureus.bless\x1b[0m",
)

case BlessDisabled:
t.Fail()

case BlessEnabled:
if err := bless(a, f); err != nil {
t.Log("unable to bless output:", err)
t.Fail()
return
}

messages = append(
messages,
"The current \x1b[33moutput has been blessed\x1b[0m. Future runs will consider this output correct.",
)
}

logSection(
t,
"OUTPUT DIFF",
diff,
"",
true,
messages...,
)

}

func location(c test.Content) string {
Expand All @@ -111,14 +155,17 @@ func log(t LoggerT, fn func(w *strings.Builder)) {

func logSection(
t LoggerT,
name string,
data []byte,
title string,
body []byte,
bodyANSI string,
trimSpace bool,
extra ...any,
messages ...string,
) {
t.Helper()

log(t, func(w *strings.Builder) {
w.WriteString("\x1b[0m")

w.WriteString("\n")
w.WriteString("\n")

Expand All @@ -127,15 +174,7 @@ func logSection(

w.WriteString("\x1b[7m") // inverse
w.WriteString(" ")
w.WriteString(name)

if len(extra) > 0 {
w.WriteString(" (")
for _, v := range extra {
fmt.Fprint(w, v)
}
w.WriteByte(')')
}
w.WriteString(title)

w.WriteString(" ")
w.WriteString("\x1b[27m") // reset inverse
Expand All @@ -144,17 +183,25 @@ func logSection(
w.WriteString("\x1b[1mβ”‚\x1b[0m\n")

if trimSpace {
data = bytes.TrimSpace(data)
body = bytes.TrimSpace(body)
}

for _, line := range bytes.Split(data, newLine) {
for _, line := range bytes.Split(body, newLine) {
w.WriteString("\x1b[1mβ”‚\x1b[0m ")
w.WriteString(bodyANSI)
w.Write(line)
w.WriteByte('\n')
w.WriteString("\x1b[0m\n")
}

w.WriteString("\x1b[1mβ”‚\x1b[0m\n")
w.WriteString("\x1b[1m╰────\x1b[0m────\x1b[2mβ”€β”€β”ˆ\x1b[0m\n")

for _, t := range messages {
w.WriteString("\n")
w.WriteString("\x1b[33m✦\x1b[0m ")
w.WriteString(t)
w.WriteString("\x1b[0m\n")
}
})
}

Expand All @@ -180,3 +227,8 @@ func computeDiff(

return diff.ColorDiff(wantLoc, wantData, gotLoc, gotData), nil
}

var (
separator = strings.Repeat("=", 10)
newLine = []byte("\n")
)
4 changes: 2 additions & 2 deletions internal/runner/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func TestRunner(t *testing.T) {
) error {
return prettyPrint(in, out)
},
BlessStrategy: &BlessDisabled{},
BlessStrategy: BlessDisabled,
}

runner.Run(t, tst)
Expand All @@ -49,7 +49,7 @@ func TestRunner(t *testing.T) {
) error {
return prettyPrint(in, out)
},
BlessStrategy: &BlessDisabled{},
BlessStrategy: BlessDisabled,
}

x := &testingT{T: t}
Expand Down
Loading

0 comments on commit 3b80f31

Please sign in to comment.