wip: unfinished further tweaks

This commit is contained in:
2024-03-02 11:56:56 +00:00
parent e0a9c7bd77
commit f42dbf9010
8 changed files with 574 additions and 268 deletions

5
.gitignore vendored
View File

@@ -1,5 +1,6 @@
bin/*
coverage.out
testdata/*
!testdata/TestExample*
testdata/TestFile*
testdata/TestGet*
testdata/TestSet*

View File

@@ -51,6 +51,7 @@ $(eval $(call tool,gofumpt,mvdan.cc/gofumpt))
$(eval $(call tool,goimports,golang.org/x/tools/cmd/goimports))
$(eval $(call tool,golangci-lint,github.com/golangci/golangci-lint/cmd/golangci-lint@v1.44))
$(eval $(call tool,gomod,github.com/Helcaraxan/gomod))
$(eval $(call tool,mockgen,mockgen,github.com/golang/mock/mockgen@v1.6.0))
.PHONY: tools
tools: $(TOOLS)

1
go.mod
View File

@@ -3,6 +3,7 @@ module github.com/jimeh/go-golden
go 1.15
require (
github.com/golang/mock v1.6.0
github.com/jimeh/envctl v0.1.0
github.com/jimeh/go-mocktesting v0.1.0
github.com/spf13/afero v1.6.0

18
go.sum
View File

@@ -1,5 +1,7 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/jimeh/envctl v0.1.0 h1:KTv3D+pi5M4/PgFVE/W8ssWqiZP3pDJ8Cga50L+1avo=
github.com/jimeh/envctl v0.1.0/go.mod h1:aM27ffBbO1yUBKUzgJGCUorS4z+wyh+qhQe1ruxXZZo=
github.com/jimeh/go-mocktesting v0.1.0 h1:y0tLABo3V4i9io7m6TiXdXbU3IVMjtPvWkr+A0+aLTM=
@@ -16,15 +18,31 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

278
golden.go
View File

@@ -2,124 +2,120 @@
// with a focus on simplicity through it's default behavior.
//
// Golden file names are based on the name of the test function and any subtest
// names by calling t.Name(). File names are sanitized to ensure they're
// names by calling t.Name(). File names are sanitized to ensure they are
// compatible with Linux, macOS and Windows systems regardless of what
// characters might be in a subtest's name.
//
// Usage
// # Usage
//
// Typical usage should look something like this:
//
// func TestExampleMyStruct(t *testing.T) {
// got, err := json.Marshal(&MyStruct{Foo: "Bar"})
// require.NoError(t, err)
// func TestExampleMyStruct(t *testing.T) {
// got, err := json.Marshal(&MyStruct{Foo: "Bar"})
// require.NoError(t, err)
//
// if golden.Update() {
// golden.Set(t, got)
// }
// want := golden.Get(t)
// want := golden.Do(t, got)
//
// assert.Equal(t, want, got)
// }
// assert.Equal(t, want, got)
// }
//
// The above example will read/write to:
// The above example will attempt to read/write to:
//
// testdata/TestExampleMyStruct.golden
// testdata/TestExampleMyStruct.golden
//
// The call to golden.Do() is equivalent to:
//
// if golden.Update() {
// golden.Set(t, got)
// }
// want := golden.Get(t)
//
// To update the golden file (have golden.Update() return true), simply set the
// GOLDEN_UPDATE environment variable to one of "1", "y", "t", "yes", "on", or
// "true" when running tests.
//
// Sub-Tests
// # Sub-Tests
//
// As the golden filename is based on t.Name(), it works with sub-tests too,
// ensuring each sub-test gets it's own golden file. For example:
//
// func TestExampleMyStructTabular(t *testing.T) {
// tests := []struct {
// name string
// obj *MyStruct
// }{
// {name: "empty struct", obj: &MyStruct{}},
// {name: "full struct", obj: &MyStruct{Foo: "Bar"}},
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// got, err := json.Marshal(tt.obj)
// require.NoError(t, err)
// func TestExampleMyStructTabular(t *testing.T) {
// tests := []struct {
// name string
// obj *MyStruct
// }{
// {name: "empty struct", obj: &MyStruct{}},
// {name: "full struct", obj: &MyStruct{Foo: "Bar"}},
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// got, err := json.Marshal(tt.obj)
// require.NoError(t, err)
//
// if golden.Update() {
// golden.Set(t, got)
// }
// want := golden.Get(t)
// want := golden.Do(t, got)
//
// assert.Equal(t, want, got)
// })
// }
// }
// assert.Equal(t, want, got)
// })
// }
// }
//
// The above example will read/write to:
//
// testdata/TestExampleMyStructTabular/empty_struct.golden
// testdata/TestExampleMyStructTabular/full_struct.golden
// testdata/TestExampleMyStructTabular/empty_struct.golden
// testdata/TestExampleMyStructTabular/full_struct.golden
//
// Multiple Golden Files in a Single Test
// # Multiple Golden Files in a Single Test
//
// The "P" suffixed methods, GetP(), SetP(), and FileP(), all take a name
// The "P" suffixed methods, GetP(), SetP(), DoP(), and FileP(), all take a name
// argument which allows using specific golden files within a given *testing.T
// instance.
//
// func TestExampleMyStructP(t *testing.T) {
// gotJSON, _ := json.Marshal(&MyStruct{Foo: "Bar"})
// gotXML, _ := xml.Marshal(&MyStruct{Foo: "Bar"})
// func TestExampleMyStructP(t *testing.T) {
// gotJSON, _ := json.Marshal(&MyStruct{Foo: "Bar"})
// gotXML, _ := xml.Marshal(&MyStruct{Foo: "Bar"})
//
// if golden.Update() {
// golden.SetP(t, "json", gotJSON)
// golden.SetP(t, "xml", gotXML)
// }
// wantJSON := golden.DoP(t, "json", gotJSON)
// wantXML := golden.DoP(t, "xml", gotXML)
//
// assert.Equal(t, golden.GetP(t, "json"), gotJSON)
// assert.Equal(t, golden.GetP(t, "xml"), gotXML)
// }
// assert.Equal(t, wantJSON, gotJSON)
// assert.Equal(t, wantXML, gotXML)
// }
//
// The above example will read/write to:
//
// testdata/TestExampleMyStructP/json.golden
// testdata/TestExampleMyStructP/xml.golden
// testdata/TestExampleMyStructP/json.golden
// testdata/TestExampleMyStructP/xml.golden
//
// This works with tabular tests too of course:
//
// func TestExampleMyStructTabularP(t *testing.T) {
// tests := []struct {
// name string
// obj *MyStruct
// }{
// {name: "empty struct", obj: &MyStruct{}},
// {name: "full struct", obj: &MyStruct{Foo: "Bar"}},
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// gotJSON, _ := json.Marshal(tt.obj)
// gotXML, _ := xml.Marshal(tt.obj)
// func TestExampleMyStructTabularP(t *testing.T) {
// tests := []struct {
// name string
// obj *MyStruct
// }{
// {name: "empty struct", obj: &MyStruct{}},
// {name: "full struct", obj: &MyStruct{Foo: "Bar"}},
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// gotJSON, _ := json.Marshal(tt.obj)
// gotXML, _ := xml.Marshal(tt.obj)
//
// if golden.Update() {
// golden.SetP(t, "json", gotJSON)
// golden.SetP(t, "xml", gotXML)
// }
// wantJSON := golden.DoP(t, "json", gotJSON)
// wantXML := golden.DoP(t, "xml", gotXML)
//
// assert.Equal(t, golden.GetP(t, "json"), gotJSON)
// assert.Equal(t, golden.GetP(t, "xml"), gotXML)
// })
// }
// }
// assert.Equal(t, wantJSON, gotJSON)
// assert.Equal(t, wantXML, gotXML)
// })
// }
// }
//
// The above example will read/write to:
//
// testdata/TestExampleMyStructTabularP/empty_struct/json.golden
// testdata/TestExampleMyStructTabularP/empty_struct/xml.golden
// testdata/TestExampleMyStructTabularP/full_struct/json.golden
// testdata/TestExampleMyStructTabularP/full_struct/xml.golden
//
// testdata/TestExampleMyStructTabularP/empty_struct/json.golden
// testdata/TestExampleMyStructTabularP/empty_struct/xml.golden
// testdata/TestExampleMyStructTabularP/full_struct/json.golden
// testdata/TestExampleMyStructTabularP/full_struct/xml.golden
package golden
import (
@@ -132,12 +128,11 @@ import (
"github.com/spf13/afero"
)
//go:generate mockgen -source=golden.go -destination=golden_mock_test.go -package golden -self_package github.com/jimeh/go-golden
// TestingT is a interface describing a sub-set of methods of *testing.T which
// golden uses.
type TestingT interface {
Error(args ...interface{})
Errorf(format string, args ...interface{})
FailNow()
Fatal(args ...interface{})
Fatalf(format string, args ...interface{})
Helper()
@@ -146,23 +141,25 @@ type TestingT interface {
Name() string
}
var defaultGolden = New()
// Default is the default Golden instance used by all top-level package
// functions.
var Default = New()
// File returns the filename of the golden file for the given *testing.T
// instance as determined by t.Name().
func File(t *testing.T) string {
func File(t TestingT) string {
t.Helper()
return defaultGolden.File(t)
return Default.File(t)
}
// Get returns the content of the golden file for the given *testing.T instance
// as determined by t.Name(). If no golden file can be found/read, it will fail
// the test by calling t.Fatal().
func Get(t *testing.T) []byte {
func Get(t TestingT) []byte {
t.Helper()
return defaultGolden.Get(t)
return Default.Get(t)
}
// Set writes given data to the golden file for the given *testing.T instance as
@@ -171,15 +168,24 @@ func Get(t *testing.T) []byte {
func Set(t *testing.T, data []byte) {
t.Helper()
defaultGolden.Set(t, data)
Default.Set(t, data)
}
// Do is a convenience function for calling Update(), Set(), and Get() in a
// single call. If Update() returns true, data will be written to the golden
// file using Set(), before reading it back with Get().
func Do(t TestingT, data []byte) []byte {
t.Helper()
return Default.Do(t, data)
}
// FileP returns the filename of the specifically named golden file for the
// given *testing.T instance as determined by t.Name().
func FileP(t *testing.T, name string) string {
func FileP(t TestingT, name string) string {
t.Helper()
return defaultGolden.FileP(t, name)
return Default.FileP(t, name)
}
// GetP returns the content of the specifically named golden file belonging
@@ -191,7 +197,7 @@ func FileP(t *testing.T, name string) string {
func GetP(t *testing.T, name string) []byte {
t.Helper()
return defaultGolden.GetP(t, name)
return Default.GetP(t, name)
}
// SetP writes given data of the specifically named golden file belonging to
@@ -203,7 +209,16 @@ func GetP(t *testing.T, name string) []byte {
func SetP(t *testing.T, name string, data []byte) {
t.Helper()
defaultGolden.SetP(t, name, data)
Default.SetP(t, name, data)
}
// DoP is a convenience function for calling Update(), SetP(), and GetP() in a
// single call. If Update() returns true, data will be written to the golden
// file using SetP(), before reading it back with GetP().
func DoP(t TestingT, name string, data []byte) []byte {
t.Helper()
return Default.DoP(t, name, data)
}
// Update returns true when golden is set to update golden files. Should be used
@@ -213,7 +228,7 @@ func SetP(t *testing.T, name string, data []byte) {
// environment variable is set to a truthy value. To customize create a custom
// *Golden instance with New() and set a new UpdateFunc value.
func Update() bool {
return defaultGolden.Update()
return Default.Update()
}
// Golden handles all interactions with golden files. The top-level package
@@ -233,6 +248,11 @@ type Golden interface {
// test by calling t.Fatal() with error details.
Set(t TestingT, data []byte)
// Do is a convenience function for calling Update(), Set(), and Get() in a
// single call. If Update() returns true, data will be written to the golden
// file using Set(), before reading it back with Get().
Do(t TestingT, data []byte) []byte
// FileP returns the filename of the specifically named golden file for the
// given TestingT instance as determined by t.Name().
FileP(t TestingT, name string) string
@@ -253,6 +273,11 @@ type Golden interface {
// files to be used within the same one TestingT instance.
SetP(t TestingT, name string, data []byte)
// DoP is a convenience function for calling Update(), SetP(), and GetP() in
// a single call. If Update() returns true, data will be written to the
// golden file using SetP(), before reading it back with GetP().
DoP(t TestingT, name string, data []byte) []byte
// Update returns true when golden is set to update golden files. Should be
// used to determine if golden.Set() or golden.SetP() should be called or
// not.
@@ -342,7 +367,8 @@ func WithUpdateFunc(fn UpdateFunc) Option {
})
}
// WithFs sets s afero.Fs instance which is used to read/write all golden files.
// WithFs sets the afero.Fs instance which is used for all file system
// operations to read/write golden files.
//
// When this option is not provided, the default value is afero.NewOsFs().
func WithFs(fs afero.Fs) Option {
@@ -394,55 +420,75 @@ type golden struct {
// Ensure golden satisfies Golden interface.
var _ Golden = &golden{}
func (s *golden) File(t TestingT) string {
func (g *golden) File(t TestingT) string {
t.Helper()
return s.file(t, "")
return g.file(t, "")
}
func (s *golden) Get(t TestingT) []byte {
func (g *golden) Get(t TestingT) []byte {
t.Helper()
return s.get(t, "")
return g.get(t, "")
}
func (s *golden) Set(t TestingT, data []byte) {
func (g *golden) Set(t TestingT, data []byte) {
t.Helper()
s.set(t, "", data)
g.set(t, "", data)
}
func (s *golden) FileP(t TestingT, name string) string {
func (g *golden) Do(t TestingT, data []byte) []byte {
t.Helper()
if g.Update() {
g.Set(t, data)
}
return g.Get(t)
}
func (g *golden) FileP(t TestingT, name string) string {
t.Helper()
if name == "" {
t.Fatalf("golden: test name cannot be empty")
}
return s.file(t, name)
return g.file(t, name)
}
func (s *golden) GetP(t TestingT, name string) []byte {
func (g *golden) GetP(t TestingT, name string) []byte {
t.Helper()
if name == "" {
t.Fatal("golden: name cannot be empty")
}
return s.get(t, name)
return g.get(t, name)
}
func (s *golden) SetP(t TestingT, name string, data []byte) {
func (g *golden) SetP(t TestingT, name string, data []byte) {
t.Helper()
if name == "" {
t.Fatal("golden: name cannot be empty")
}
s.set(t, name, data)
g.set(t, name, data)
}
func (s *golden) file(t TestingT, name string) string {
func (g *golden) DoP(t TestingT, name string, data []byte) []byte {
t.Helper()
if g.Update() {
g.SetP(t, name, data)
}
return g.GetP(t, name)
}
func (g *golden) file(t TestingT, name string) string {
t.Helper()
if t.Name() == "" {
@@ -451,12 +497,12 @@ func (s *golden) file(t TestingT, name string) string {
)
}
base := []string{s.dirname, filepath.FromSlash(t.Name())}
base := []string{g.dirname, filepath.FromSlash(t.Name())}
if name != "" {
base = append(base, name)
}
f := filepath.Clean(filepath.Join(base...) + s.suffix)
f := filepath.Clean(filepath.Join(base...) + g.suffix)
dirty := strings.Split(f, string(os.PathSeparator))
clean := make([]string, 0, len(dirty))
@@ -467,12 +513,12 @@ func (s *golden) file(t TestingT, name string) string {
return strings.Join(clean, string(os.PathSeparator))
}
func (s *golden) get(t TestingT, name string) []byte {
func (g *golden) get(t TestingT, name string) []byte {
t.Helper()
f := s.file(t, name)
f := g.file(t, name)
b, err := afero.ReadFile(s.fs, f)
b, err := afero.ReadFile(g.fs, f)
if err != nil {
t.Fatalf("golden: %s", err.Error())
}
@@ -480,27 +526,27 @@ func (s *golden) get(t TestingT, name string) []byte {
return b
}
func (s *golden) set(t TestingT, name string, data []byte) {
func (g *golden) set(t TestingT, name string, data []byte) {
t.Helper()
f := s.file(t, name)
f := g.file(t, name)
dir := filepath.Dir(f)
if s.logOnWrite {
if g.logOnWrite {
t.Logf("golden: writing golden file: %s", f)
}
err := s.fs.MkdirAll(dir, s.dirMode)
err := g.fs.MkdirAll(dir, g.dirMode)
if err != nil {
t.Fatalf("golden: failed to create directory: %s", err.Error())
}
err = afero.WriteFile(s.fs, f, data, s.fileMode)
err = afero.WriteFile(g.fs, f, data, g.fileMode)
if err != nil {
t.Fatalf("golden: filed to write file: %s", err.Error())
}
}
func (s *golden) Update() bool {
return s.updateFunc()
func (g *golden) Update() bool {
return g.updateFunc()
}

306
golden_mock_test.go Normal file
View File

@@ -0,0 +1,306 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: golden.go
// Package golden is a generated GoMock package.
package golden
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
)
// MockTestingT is a mock of TestingT interface.
type MockTestingT struct {
ctrl *gomock.Controller
recorder *MockTestingTMockRecorder
}
// MockTestingTMockRecorder is the mock recorder for MockTestingT.
type MockTestingTMockRecorder struct {
mock *MockTestingT
}
// NewMockTestingT creates a new mock instance.
func NewMockTestingT(ctrl *gomock.Controller) *MockTestingT {
mock := &MockTestingT{ctrl: ctrl}
mock.recorder = &MockTestingTMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockTestingT) EXPECT() *MockTestingTMockRecorder {
return m.recorder
}
// Fatal mocks base method.
func (m *MockTestingT) Fatal(args ...interface{}) {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Fatal", varargs...)
}
// Fatal indicates an expected call of Fatal.
func (mr *MockTestingTMockRecorder) Fatal(args ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fatal", reflect.TypeOf((*MockTestingT)(nil).Fatal), args...)
}
// Fatalf mocks base method.
func (m *MockTestingT) Fatalf(format string, args ...interface{}) {
m.ctrl.T.Helper()
varargs := []interface{}{format}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Fatalf", varargs...)
}
// Fatalf indicates an expected call of Fatalf.
func (mr *MockTestingTMockRecorder) Fatalf(format interface{}, args ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{format}, args...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fatalf", reflect.TypeOf((*MockTestingT)(nil).Fatalf), varargs...)
}
// Helper mocks base method.
func (m *MockTestingT) Helper() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Helper")
}
// Helper indicates an expected call of Helper.
func (mr *MockTestingTMockRecorder) Helper() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Helper", reflect.TypeOf((*MockTestingT)(nil).Helper))
}
// Log mocks base method.
func (m *MockTestingT) Log(args ...interface{}) {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Log", varargs...)
}
// Log indicates an expected call of Log.
func (mr *MockTestingTMockRecorder) Log(args ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Log", reflect.TypeOf((*MockTestingT)(nil).Log), args...)
}
// Logf mocks base method.
func (m *MockTestingT) Logf(format string, args ...interface{}) {
m.ctrl.T.Helper()
varargs := []interface{}{format}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Logf", varargs...)
}
// Logf indicates an expected call of Logf.
func (mr *MockTestingTMockRecorder) Logf(format interface{}, args ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{format}, args...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logf", reflect.TypeOf((*MockTestingT)(nil).Logf), varargs...)
}
// Name mocks base method.
func (m *MockTestingT) Name() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Name")
ret0, _ := ret[0].(string)
return ret0
}
// Name indicates an expected call of Name.
func (mr *MockTestingTMockRecorder) Name() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockTestingT)(nil).Name))
}
// MockGolden is a mock of Golden interface.
type MockGolden struct {
ctrl *gomock.Controller
recorder *MockGoldenMockRecorder
}
// MockGoldenMockRecorder is the mock recorder for MockGolden.
type MockGoldenMockRecorder struct {
mock *MockGolden
}
// NewMockGolden creates a new mock instance.
func NewMockGolden(ctrl *gomock.Controller) *MockGolden {
mock := &MockGolden{ctrl: ctrl}
mock.recorder = &MockGoldenMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockGolden) EXPECT() *MockGoldenMockRecorder {
return m.recorder
}
// Do mocks base method.
func (m *MockGolden) Do(t TestingT, data []byte) []byte {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Do", t, data)
ret0, _ := ret[0].([]byte)
return ret0
}
// Do indicates an expected call of Do.
func (mr *MockGoldenMockRecorder) Do(t, data interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockGolden)(nil).Do), t, data)
}
// DoP mocks base method.
func (m *MockGolden) DoP(t TestingT, name string, data []byte) []byte {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DoP", t, name, data)
ret0, _ := ret[0].([]byte)
return ret0
}
// DoP indicates an expected call of DoP.
func (mr *MockGoldenMockRecorder) DoP(t, name, data interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DoP", reflect.TypeOf((*MockGolden)(nil).DoP), t, name, data)
}
// File mocks base method.
func (m *MockGolden) File(t TestingT) string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "File", t)
ret0, _ := ret[0].(string)
return ret0
}
// File indicates an expected call of File.
func (mr *MockGoldenMockRecorder) File(t interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "File", reflect.TypeOf((*MockGolden)(nil).File), t)
}
// FileP mocks base method.
func (m *MockGolden) FileP(t TestingT, name string) string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FileP", t, name)
ret0, _ := ret[0].(string)
return ret0
}
// FileP indicates an expected call of FileP.
func (mr *MockGoldenMockRecorder) FileP(t, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FileP", reflect.TypeOf((*MockGolden)(nil).FileP), t, name)
}
// Get mocks base method.
func (m *MockGolden) Get(t TestingT) []byte {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", t)
ret0, _ := ret[0].([]byte)
return ret0
}
// Get indicates an expected call of Get.
func (mr *MockGoldenMockRecorder) Get(t interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockGolden)(nil).Get), t)
}
// GetP mocks base method.
func (m *MockGolden) GetP(t TestingT, name string) []byte {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetP", t, name)
ret0, _ := ret[0].([]byte)
return ret0
}
// GetP indicates an expected call of GetP.
func (mr *MockGoldenMockRecorder) GetP(t, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetP", reflect.TypeOf((*MockGolden)(nil).GetP), t, name)
}
// Set mocks base method.
func (m *MockGolden) Set(t TestingT, data []byte) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Set", t, data)
}
// Set indicates an expected call of Set.
func (mr *MockGoldenMockRecorder) Set(t, data interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockGolden)(nil).Set), t, data)
}
// SetP mocks base method.
func (m *MockGolden) SetP(t TestingT, name string, data []byte) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetP", t, name, data)
}
// SetP indicates an expected call of SetP.
func (mr *MockGoldenMockRecorder) SetP(t, name, data interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetP", reflect.TypeOf((*MockGolden)(nil).SetP), t, name, data)
}
// Update mocks base method.
func (m *MockGolden) Update() bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update")
ret0, _ := ret[0].(bool)
return ret0
}
// Update indicates an expected call of Update.
func (mr *MockGoldenMockRecorder) Update() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockGolden)(nil).Update))
}
// MockOption is a mock of Option interface.
type MockOption struct {
ctrl *gomock.Controller
recorder *MockOptionMockRecorder
}
// MockOptionMockRecorder is the mock recorder for MockOption.
type MockOptionMockRecorder struct {
mock *MockOption
}
// NewMockOption creates a new mock instance.
func NewMockOption(ctrl *gomock.Controller) *MockOption {
mock := &MockOption{ctrl: ctrl}
mock.recorder = &MockOptionMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockOption) EXPECT() *MockOptionMockRecorder {
return m.recorder
}
// apply mocks base method.
func (m *MockOption) apply(arg0 *golden) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "apply", arg0)
}
// apply indicates an expected call of apply.
func (mr *MockOptionMockRecorder) apply(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "apply", reflect.TypeOf((*MockOption)(nil).apply), arg0)
}

View File

@@ -1,6 +1,7 @@
package golden
import (
"io/fs"
"io/ioutil"
"os"
"path/filepath"
@@ -8,6 +9,7 @@ import (
"runtime"
"testing"
"github.com/golang/mock/gomock"
"github.com/jimeh/envctl"
"github.com/jimeh/go-mocktesting"
"github.com/spf13/afero"
@@ -19,113 +21,68 @@ func stringPtr(s string) *string {
return &s
}
func funcID(f interface{}) string {
return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
}
func setupDefaultMock(
t *testing.T,
ctrl *gomock.Controller,
) (*MockTestingT, *MockGolden) {
t.Helper()
mt := NewMockTestingT(ctrl)
mg := NewMockGolden(ctrl)
originalDefault := Default
Default = mg
t.Cleanup(func() {
Default = originalDefault
})
return mt, mg
}
func TestDefault(t *testing.T) {
require.IsType(t, &golden{}, Default)
dg := Default.(*golden)
assert.Equal(t, fs.FileMode(0o755), dg.dirMode)
assert.Equal(t, fs.FileMode(0o644), dg.fileMode)
assert.Equal(t, ".golden", dg.suffix)
assert.Equal(t, "testdata", dg.dirname)
assert.Equal(t, funcID(EnvUpdateFunc), funcID(dg.updateFunc))
assert.Equal(t, afero.NewOsFs(), dg.fs)
assert.Equal(t, true, dg.logOnWrite)
}
func TestFile(t *testing.T) {
got := File(t)
ctrl := gomock.NewController(t)
mt, mg := setupDefaultMock(t, ctrl)
assert.Equal(t, filepath.Join("testdata", "TestFile.golden"), got)
want := filepath.Join("testdata", t.Name()+".golden")
tests := []struct {
name string
want string
}{
{
name: "",
want: filepath.Join("testdata", "TestFile", "#00.golden"),
},
{
name: "foobar",
want: filepath.Join("testdata", "TestFile", "foobar.golden"),
},
{
name: "foo/bar",
want: filepath.Join("testdata", "TestFile", "foo", "bar.golden"),
},
{
name: `"foobar"`,
want: filepath.Join("testdata", "TestFile", "_foobar_.golden"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := File(t)
mt.EXPECT().Helper()
mg.EXPECT().File(mt).Return(want)
assert.Equal(t, tt.want, got)
})
}
got := File(mt)
assert.Equal(t, want, got)
}
func TestGet(t *testing.T) {
t.Cleanup(func() {
err := os.RemoveAll(filepath.Join("testdata", "TestGet"))
require.NoError(t, err)
err = os.Remove(filepath.Join("testdata", "TestGet.golden"))
require.NoError(t, err)
})
ctrl := gomock.NewController(t)
mt, mg := setupDefaultMock(t, ctrl)
err := os.MkdirAll("testdata", 0o755)
require.NoError(t, err)
want := []byte("foobar\nhello world :)")
content := []byte("foobar\nhello world :)")
err = ioutil.WriteFile( //nolint:gosec
filepath.Join("testdata", "TestGet.golden"), content, 0o644,
)
require.NoError(t, err)
mt.EXPECT().Helper()
mg.EXPECT().Get(mt).Return(want)
got := Get(t)
assert.Equal(t, content, got)
got := Get(mt)
tests := []struct {
name string
file string
want []byte
}{
{
name: "",
file: filepath.Join("testdata", "TestGet", "#00.golden"),
want: []byte("number double-zero here"),
},
{
name: "foobar",
file: filepath.Join("testdata", "TestGet", "foobar.golden"),
want: []byte("foobar here"),
},
{
name: "foo/bar",
file: filepath.Join("testdata", "TestGet", "foo", "bar.golden"),
want: []byte("foo/bar style sub-sub-folders works too"),
},
{
name: "john's lost flip-flop",
file: filepath.Join(
"testdata", "TestGet", "john's_lost_flip-flop.golden",
),
want: []byte("Did John lose his flip-flop again?"),
},
{
name: "thing: it's a thing!",
file: filepath.Join(
"testdata", "TestGet", "thing__it's_a_thing!.golden",
),
want: []byte("A thing? Really? Are we getting lazy? :P"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f := File(t)
dir := filepath.Dir(f)
err := os.MkdirAll(dir, 0o755)
require.NoError(t, err)
err = ioutil.WriteFile(f, tt.want, 0o644) //nolint:gosec
require.NoError(t, err)
got := Get(t)
assert.Equal(t, tt.file, f)
assert.Equal(t, tt.want, got)
})
}
assert.Equal(t, want, got)
}
func TestSet(t *testing.T) {
@@ -196,54 +153,17 @@ func TestSet(t *testing.T) {
}
func TestFileP(t *testing.T) {
got := FileP(t, "sub-name")
assert.Equal(t,
filepath.Join("testdata", "TestFileP", "sub-name.golden"), got,
)
ctrl := gomock.NewController(t)
mt, mg := setupDefaultMock(t, ctrl)
tests := []struct {
name string
named string
want string
}{
{
name: "",
named: "sub-thing",
want: filepath.Join(
"testdata", "TestFileP", "#00", "sub-thing.golden",
),
},
{
name: "fozbaz",
named: "email",
want: filepath.Join(
"testdata", "TestFileP", "fozbaz", "email.golden",
),
},
{
name: "fozbaz",
named: "json",
want: filepath.Join(
"testdata", "TestFileP", "fozbaz#01", "json.golden",
),
},
{
name: "foo/bar",
named: "hello/world",
want: filepath.Join(
"testdata", "TestFileP",
"foo", "bar",
"hello", "world.golden",
),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := FileP(t, tt.named)
want := filepath.Join("testdata", t.Name(), "foobar.golden")
assert.Equal(t, tt.want, got)
})
}
mt.EXPECT().Helper()
mg.EXPECT().FileP(mt, "foobar").Return(want)
got := FileP(mt, "foobar")
assert.Equal(t, want, got)
}
func TestGetP(t *testing.T) {
@@ -506,19 +426,12 @@ func TestNew(t *testing.T) {
got, ok := g.(*golden)
require.True(t, ok, "New did not returns a *golden instance")
gotUpdateFunc := runtime.FuncForPC(
reflect.ValueOf(got.updateFunc).Pointer(),
).Name()
wantUpdateFunc := runtime.FuncForPC(
reflect.ValueOf(tt.want.updateFunc).Pointer(),
).Name()
assert.Equal(t, tt.want.dirMode, got.dirMode)
assert.Equal(t, tt.want.fileMode, got.fileMode)
assert.Equal(t, tt.want.suffix, got.suffix)
assert.Equal(t, tt.want.dirname, got.dirname)
assert.Equal(t, tt.want.logOnWrite, got.logOnWrite)
assert.Equal(t, wantUpdateFunc, gotUpdateFunc)
assert.Equal(t, funcID(tt.want.updateFunc), funcID(got.updateFunc))
assert.IsType(t, tt.want.fs, got.fs)
})
}

View File

@@ -1,6 +1,7 @@
package golden
import (
"flag"
"os"
"strings"
)
@@ -24,3 +25,22 @@ func EnvUpdateFunc() bool {
return false
}
var (
updateFlagSet *flag.FlagSet
updateFlag bool
)
// UpdateFunc returns a function that checks a -update flag is set.
func FlagUpdateFunc() bool {
if updateFlagSet == nil {
updateFlagSet = flag.NewFlagSet("golden", flag.ContinueOnError)
updateFlagSet.BoolVar(&updateFlag,
"update", false, "update golden files",
)
}
_ = updateFlagSet.Parse(os.Args[1:])
return false
}