-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
assert: first draft of inline golden variables
- Loading branch information
Showing
10 changed files
with
240 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package assert_test | ||
|
||
import ( | ||
"fmt" | ||
"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("value is updated when -update=true", func(t *testing.T) { | ||
patchUpdate(t) | ||
ft := &fakeTestingT{} | ||
|
||
actual := `this is the | ||
actual value | ||
that we are testing against` | ||
assert.Equal(ft, actual, expectedOne) | ||
|
||
// reset | ||
fmt.Println("WHHHHHHHHHHY") | ||
assert.Equal(ft, "\n\n\n", expectedOne) | ||
}) | ||
} | ||
|
||
var expectedOne = ` | ||
` | ||
|
||
func patchUpdate(t *testing.T) { | ||
source.Update = true | ||
t.Cleanup(func() { | ||
source.Update = false | ||
}) | ||
} | ||
|
||
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() {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,127 @@ | ||
package source | ||
|
||
import "flag" | ||
import ( | ||
"bytes" | ||
"errors" | ||
"flag" | ||
"fmt" | ||
"go/ast" | ||
"go/format" | ||
"go/parser" | ||
"go/token" | ||
"os" | ||
"runtime" | ||
"strings" | ||
) | ||
|
||
// Update is set by the -update flag. It indicates the user running the tests | ||
// would like to update any golden values. | ||
var Update bool | ||
|
||
func init() { | ||
flag.BoolVar(&Update, "update", false, "update golden files") | ||
flag.BoolVar(&Update, "update", false, "update golden values") | ||
} | ||
|
||
// ErrNotFound indicates that UpdateExpectedValue failed to find the | ||
// variable to update, likely because it is not a package level variable. | ||
var ErrNotFound = fmt.Errorf("failed to find variable for update of golden value") | ||
|
||
// UpdateExpectedValue looks for a package-level variable with a name that | ||
// starts with expected in the arguments to the caller. If the variable is | ||
// found, the value of the variable will be updated to value of the other | ||
// argument to the caller. | ||
func UpdateExpectedValue(stackIndex int, x, y interface{}) error { | ||
_, filename, line, ok := runtime.Caller(stackIndex + 1) | ||
if !ok { | ||
return errors.New("failed to get call stack") | ||
} | ||
debug("call stack position: %s:%d", filename, line) | ||
|
||
fileset := token.NewFileSet() | ||
astFile, err := parser.ParseFile(fileset, filename, nil, parser.AllErrors|parser.ParseComments) | ||
if err != nil { | ||
return fmt.Errorf("failed to parse source file %s: %w", filename, err) | ||
} | ||
|
||
debug("before modification: %v", debugFormatNode{astFile}) | ||
|
||
expr, err := getCallExprArgs(fileset, astFile, line) | ||
if err != nil { | ||
return fmt.Errorf("call from %s:%d: %w", filename, line, err) | ||
} | ||
|
||
if len(expr) < 3 { | ||
debug("not enough arguments %d: %v", | ||
len(expr), debugFormatNode{Node: &ast.CallExpr{Args: expr}}) | ||
return ErrNotFound | ||
} | ||
|
||
argIndex, varName := getVarNameForExpectedValueArg(expr) | ||
if argIndex < 0 || varName == "" { | ||
debug("no arguments started with the word 'expected': %v", | ||
debugFormatNode{Node: &ast.CallExpr{Args: expr}}) | ||
return ErrNotFound | ||
} | ||
|
||
value := x | ||
if argIndex == 1 { | ||
value = y | ||
} | ||
|
||
obj := astFile.Scope.Objects[varName] | ||
if obj == nil { | ||
return ErrNotFound | ||
} | ||
if obj.Kind != ast.Con && obj.Kind != ast.Var { | ||
debug("can only update var and const, found %v", obj.Kind) | ||
return ErrNotFound | ||
} | ||
|
||
spec, ok := obj.Decl.(*ast.ValueSpec) | ||
if !ok { | ||
debug("can only update *ast.ValueSpec, found %T", obj.Decl) | ||
return ErrNotFound | ||
} | ||
if len(spec.Names) != 1 { | ||
debug("more than one name in ast.ValueSpec") | ||
return ErrNotFound | ||
} | ||
|
||
// TODO: allow a function to wrap the string literal | ||
spec.Values[0] = &ast.BasicLit{ | ||
Kind: token.STRING, | ||
// TODO: safer | ||
Value: "`" + value.(string) + "`", | ||
} | ||
|
||
debug("after modification: %v", debugFormatNode{astFile}) | ||
|
||
var buf bytes.Buffer | ||
if err := format.Node(&buf, fileset, astFile); err != nil { | ||
return fmt.Errorf("failed to format file after update: %w", err) | ||
} | ||
|
||
fh, err := os.Create(filename) | ||
if err != nil { | ||
return fmt.Errorf("failed to open file %v: %w", filename, err) | ||
} | ||
if _, err = fh.Write(buf.Bytes()); err != nil { | ||
return fmt.Errorf("failed to write file %v: %w", filename, err) | ||
} | ||
if err := fh.Sync(); err != nil { | ||
return fmt.Errorf("failed to sync file %v: %w", filename, err) | ||
} | ||
return nil | ||
} | ||
|
||
func getVarNameForExpectedValueArg(expr []ast.Expr) (int, string) { | ||
for i := 1; i < 3; i++ { | ||
switch e := expr[i].(type) { | ||
case *ast.Ident: | ||
if strings.HasPrefix(strings.ToLower(e.Name), "expected") { | ||
return i, e.Name | ||
} | ||
} | ||
} | ||
return -1, "" | ||
} |