Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

assert: golden variables #237

Merged
merged 4 commits into from Jun 18, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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