Skip to content

Commit

Permalink
Merge pull request #237 from dnephin/golden-vars
Browse files Browse the repository at this point in the history
assert: golden variables
  • Loading branch information
dnephin committed Jun 18, 2022
2 parents 814f970 + 82e8930 commit 4de0c9f
Show file tree
Hide file tree
Showing 13 changed files with 373 additions and 95 deletions.
112 changes: 112 additions & 0 deletions assert/assert_ext_test.go
@@ -0,0 +1,112 @@
package assert_test

import (
"go/parser"
"go/token"
"io/ioutil"
"runtime"
"strings"
"testing"

"gotest.tools/v3/assert"
"gotest.tools/v3/internal/source"
)

func TestEqual_WithGoldenUpdate(t *testing.T) {
t.Run("assert failed with -update=false", func(t *testing.T) {
ft := &fakeTestingT{}
actual := `not this value`
assert.Equal(ft, actual, expectedOne)
assert.Assert(t, ft.failNowed)
})

t.Run("var is updated when -update=true", func(t *testing.T) {
patchUpdate(t)
t.Cleanup(func() {
resetVariable(t, "expectedOne", "")
})

actual := `this is the
actual value
that we are testing
`
assert.Equal(t, actual, expectedOne)

raw, err := ioutil.ReadFile(fileName(t))
assert.NilError(t, err)

expected := "var expectedOne = `this is the\nactual value\nthat we are testing\n`"
assert.Assert(t, strings.Contains(string(raw), expected), "actual=%v", string(raw))
})

t.Run("const is updated when -update=true", func(t *testing.T) {
patchUpdate(t)
t.Cleanup(func() {
resetVariable(t, "expectedTwo", "")
})

actual := `this is the new
expected value
`
assert.Equal(t, actual, expectedTwo)

raw, err := ioutil.ReadFile(fileName(t))
assert.NilError(t, err)

expected := "const expectedTwo = `this is the new\nexpected value\n`"
assert.Assert(t, strings.Contains(string(raw), expected), "actual=%v", string(raw))
})
}

// expectedOne is updated by running the tests with -update
var expectedOne = ``

// expectedTwo is updated by running the tests with -update
const expectedTwo = ``

func patchUpdate(t *testing.T) {
source.Update = true
t.Cleanup(func() {
source.Update = false
})
}

func fileName(t *testing.T) string {
t.Helper()
_, filename, _, ok := runtime.Caller(1)
assert.Assert(t, ok, "failed to get call stack")
return filename
}

func resetVariable(t *testing.T, varName string, value string) {
t.Helper()
_, filename, _, ok := runtime.Caller(1)
assert.Assert(t, ok, "failed to get call stack")

fileset := token.NewFileSet()
astFile, err := parser.ParseFile(fileset, filename, nil, parser.AllErrors|parser.ParseComments)
assert.NilError(t, err)

err = source.UpdateVariable(filename, fileset, astFile, varName, value)
assert.NilError(t, err, "failed to reset file")
}

type fakeTestingT struct {
failNowed bool
failed bool
msgs []string
}

func (f *fakeTestingT) FailNow() {
f.failNowed = true
}

func (f *fakeTestingT) Fail() {
f.failed = true
}

func (f *fakeTestingT) Log(args ...interface{}) {
f.msgs = append(f.msgs, args[0].(string))
}

func (f *fakeTestingT) Helper() {}
8 changes: 4 additions & 4 deletions assert/cmp/compare.go
Expand Up @@ -35,7 +35,7 @@ func DeepEqual(x, y interface{}, opts ...cmp.Option) Comparison {
if diff == "" {
return ResultSuccess
}
return multiLineDiffResult(diff)
return multiLineDiffResult(diff, x, y)
}
}

Expand Down Expand Up @@ -102,7 +102,7 @@ func Equal(x, y interface{}) Comparison {
return ResultSuccess
case isMultiLineStringCompare(x, y):
diff := format.UnifiedDiff(format.DiffConfig{A: x.(string), B: y.(string)})
return multiLineDiffResult(diff)
return multiLineDiffResult(diff, x, y)
}
return ResultFailureTemplate(`
{{- printf "%v" .Data.x}} (
Expand All @@ -128,12 +128,12 @@ func isMultiLineStringCompare(x, y interface{}) bool {
return strings.Contains(strX, "\n") || strings.Contains(strY, "\n")
}

func multiLineDiffResult(diff string) Result {
func multiLineDiffResult(diff string, x, y interface{}) Result {
return ResultFailureTemplate(`
--- {{ with callArg 0 }}{{ formatNode . }}{{else}}←{{end}}
+++ {{ with callArg 1 }}{{ formatNode . }}{{else}}→{{end}}
{{ .Data.diff }}`,
map[string]interface{}{"diff": diff})
map[string]interface{}{"diff": diff, "x": x, "y": y})
}

// Len succeeds if the sequence has the expected length.
Expand Down
5 changes: 5 additions & 0 deletions assert/cmp/result.go
Expand Up @@ -69,6 +69,11 @@ func (r templatedResult) FailureMessage(args []ast.Expr) string {
return msg
}

func (r templatedResult) UpdatedExpected(stackIndex int) error {
// TODO: would be nice to have structured data instead of a map
return source.UpdateExpectedValue(stackIndex+1, r.data["x"], r.data["y"])
}

// ResultFailureTemplate returns a Result with a template string and data which
// can be used to format a failure message. The template may access data from .Data,
// the comparison args with the callArg function, and the formatNode function may
Expand Down
10 changes: 4 additions & 6 deletions golden/golden.go
Expand Up @@ -18,13 +18,11 @@ import (
"gotest.tools/v3/assert"
"gotest.tools/v3/assert/cmp"
"gotest.tools/v3/internal/format"
"gotest.tools/v3/internal/source"
)

var flagUpdate bool

func init() {
flag.BoolVar(&flagUpdate, "update", false, "update golden files")
flag.BoolVar(&flagUpdate, "test.update-golden", false, "deprecated flag")
flag.BoolVar(&source.Update, "test.update-golden", false, "deprecated flag")
}

type helperT interface {
Expand All @@ -46,7 +44,7 @@ var NormalizeCRLFToLF = os.Getenv("GOTESTTOOLS_GOLDEN_NormalizeCRLFToLF") != "fa

// FlagUpdate returns true when the -update flag has been set.
func FlagUpdate() bool {
return flagUpdate
return source.Update
}

// Open opens the file in ./testdata
Expand Down Expand Up @@ -180,7 +178,7 @@ func compare(actual []byte, filename string) (cmp.Result, []byte) {
}

func update(filename string, actual []byte) error {
if !flagUpdate {
if !source.Update {
return nil
}
if dir := filepath.Dir(Path(filename)); dir != "." {
Expand Down
7 changes: 4 additions & 3 deletions golden/golden_test.go
Expand Up @@ -9,6 +9,7 @@ import (
"gotest.tools/v3/assert"
"gotest.tools/v3/assert/cmp"
"gotest.tools/v3/fs"
"gotest.tools/v3/internal/source"
)

type fakeT struct {
Expand Down Expand Up @@ -190,10 +191,10 @@ func TestGoldenAssertBytes(t *testing.T) {
}

func setUpdateFlag(t *testing.T) func() {
orig := flagUpdate
flagUpdate = true
orig := source.Update
source.Update = true
undo := func() {
flagUpdate = orig
source.Update = orig
}
t.Cleanup(undo)
return undo
Expand Down
29 changes: 26 additions & 3 deletions icmd/command_test.go
Expand Up @@ -12,7 +12,6 @@ import (
exec "golang.org/x/sys/execabs"
"gotest.tools/v3/assert"
"gotest.tools/v3/fs"
"gotest.tools/v3/golden"
"gotest.tools/v3/internal/maint"
)

Expand Down Expand Up @@ -120,9 +119,22 @@ func TestResult_Match_NotMatched(t *testing.T) {
}
err := result.match(exp)
assert.ErrorContains(t, err, "Failures")
golden.Assert(t, err.Error(), "result-match-no-match.golden")
assert.Equal(t, err.Error(), expectedMatch)
}

var expectedMatch = `
Command: binary arg1
ExitCode: 99 (timeout)
Error: exit code 99
Stdout: the output
Stderr: the stderr
Failures:
ExitCode was 99 expected 101
Expected command to finish, but it hit the timeout
Expected stdout to contain "Something else"
Expected stderr to contain "[NOTHING]"`

func newLockedBuffer(s string) *lockedBuffer {
return &lockedBuffer{buf: *bytes.NewBufferString(s)}
}
Expand All @@ -140,9 +152,20 @@ func TestResult_Match_NotMatchedNoError(t *testing.T) {
}
err := result.match(exp)
assert.ErrorContains(t, err, "Failures")
golden.Assert(t, err.Error(), "result-match-no-match-no-error.golden")
assert.Equal(t, err.Error(), expectedResultMatchNoMatch)
}

var expectedResultMatchNoMatch = `
Command: binary arg1
ExitCode: 0
Stdout: the output
Stderr: the stderr
Failures:
ExitCode was 0 expected 101
Expected stdout to contain "Something else"
Expected stderr to contain "[NOTHING]"`

func TestResult_Match_Match(t *testing.T) {
result := &Result{
Cmd: exec.Command("binary", "arg1"),
Expand Down
10 changes: 0 additions & 10 deletions icmd/testdata/result-match-no-match-no-error.golden

This file was deleted.

12 changes: 0 additions & 12 deletions icmd/testdata/result-match-no-match.golden

This file was deleted.

21 changes: 21 additions & 0 deletions internal/assert/result.go
@@ -1,6 +1,7 @@
package assert

import (
"errors"
"fmt"
"go/ast"

Expand All @@ -25,6 +26,22 @@ func RunComparison(
return true
}

if source.Update {
if updater, ok := result.(updateExpected); ok {
const stackIndex = 3 // Assert/Check, assert, RunComparison
err := updater.UpdatedExpected(stackIndex)
switch {
case err == nil:
return true
case errors.Is(err, source.ErrNotFound):
// do nothing, fallthrough to regular failure message
default:
t.Log("failed to update source", err)
return false
}
}
}

var message string
switch typed := result.(type) {
case resultWithComparisonArgs:
Expand Down Expand Up @@ -52,6 +69,10 @@ type resultBasic interface {
FailureMessage() string
}

type updateExpected interface {
UpdatedExpected(stackIndex int) error
}

// filterPrintableExpr filters the ast.Expr slice to only include Expr that are
// easy to read when printed and contain relevant information to an assertion.
//
Expand Down
2 changes: 1 addition & 1 deletion internal/source/defers.go
Expand Up @@ -28,7 +28,7 @@ func guessDefer(node ast.Node) (ast.Node, error) {
defers := collectDefers(node)
switch len(defers) {
case 0:
return nil, fmt.Errorf("failed to expression in defer")
return nil, fmt.Errorf("failed to find expression in defer")
case 1:
return defers[0].Call, nil
default:
Expand Down

0 comments on commit 4de0c9f

Please sign in to comment.