mirror of
https://github.com/jimeh/go-golden.git
synced 2026-02-19 03:16:38 +00:00
wip: some more drastic refactoring
This commit is contained in:
@@ -25,7 +25,6 @@ linters:
|
||||
- asciicheck
|
||||
- bodyclose
|
||||
- deadcode
|
||||
- depguard
|
||||
- durationcheck
|
||||
- errcheck
|
||||
- errorlint
|
||||
@@ -69,9 +68,6 @@ linters:
|
||||
- whitespace
|
||||
|
||||
issues:
|
||||
exclude:
|
||||
- Using the variable on range scope `tt` in function literal
|
||||
- Using the variable on range scope `tc` in function literal
|
||||
exclude-rules:
|
||||
- path: "_test\\.go"
|
||||
linters:
|
||||
|
||||
19
Makefile
19
Makefile
@@ -34,24 +34,19 @@ SHELL := env \
|
||||
# Tools
|
||||
#
|
||||
|
||||
TOOLS += $(TOOLDIR)/gobin
|
||||
$(TOOLDIR)/gobin:
|
||||
GO111MODULE=off go get -u github.com/myitcv/gobin
|
||||
|
||||
# external tool
|
||||
define tool # 1: binary-name, 2: go-import-path
|
||||
TOOLS += $(TOOLDIR)/$(1)
|
||||
|
||||
$(TOOLDIR)/$(1): $(TOOLDIR)/gobin Makefile
|
||||
gobin $(V) "$(2)"
|
||||
$(TOOLDIR)/$(1): Makefile
|
||||
GOBIN="$(CURDIR)/$(TOOLDIR)" go install "$(2)"
|
||||
endef
|
||||
|
||||
$(eval $(call tool,godoc,golang.org/x/tools/cmd/godoc))
|
||||
$(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))
|
||||
$(eval $(call tool,godoc,golang.org/x/tools/cmd/godoc@latest))
|
||||
$(eval $(call tool,gofumpt,mvdan.cc/gofumpt@latest))
|
||||
$(eval $(call tool,goimports,golang.org/x/tools/cmd/goimports@latest))
|
||||
$(eval $(call tool,golangci-lint,github.com/golangci/golangci-lint/cmd/golangci-lint@v1.56))
|
||||
$(eval $(call tool,gomod,github.com/Helcaraxan/gomod@latest))
|
||||
|
||||
.PHONY: tools
|
||||
tools: $(TOOLS)
|
||||
|
||||
46
fs.go
Normal file
46
fs.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package golden
|
||||
|
||||
import "os"
|
||||
|
||||
type FS interface {
|
||||
// MkdirAll creates a directory named path, along with any necessary
|
||||
// parents, and returns nil, or else returns an error. The permission bits
|
||||
// perm (before umask) are used for all directories that MkdirAll creates.
|
||||
MkdirAll(path string, perm os.FileMode) error
|
||||
|
||||
// ReadFile reads the named file and returns the contents. A successful call
|
||||
// returns err == nil, not err == EOF. Because ReadFile reads the whole
|
||||
// file, it does not treat an EOF from Read as an error to be reported.
|
||||
ReadFile(filename string) ([]byte, error)
|
||||
|
||||
// WriteFile writes data to a file named by filename. If the file does not
|
||||
// exist, WriteFile creates it with permissions perm; otherwise WriteFile
|
||||
// truncates it before writing, without changing permissions.
|
||||
WriteFile(name string, data []byte, perm os.FileMode) error
|
||||
}
|
||||
|
||||
type fsImpl struct{}
|
||||
|
||||
var _ FS = fsImpl{}
|
||||
|
||||
// NewFS returns a new FS instance which operates against the host file system
|
||||
// via calls to functions in the os package.
|
||||
func NewFS() FS {
|
||||
return fsImpl{}
|
||||
}
|
||||
|
||||
// DefaultFS is the default FS instance used by all top-level package functions,
|
||||
// including the Default Golden instance, and also the New function.
|
||||
var DefaultFS = NewFS()
|
||||
|
||||
func (fsImpl) MkdirAll(path string, perm os.FileMode) error {
|
||||
return os.MkdirAll(path, perm)
|
||||
}
|
||||
|
||||
func (fsImpl) ReadFile(filename string) ([]byte, error) {
|
||||
return os.ReadFile(filename)
|
||||
}
|
||||
|
||||
func (fsImpl) WriteFile(filename string, data []byte, perm os.FileMode) error {
|
||||
return os.WriteFile(filename, data, perm)
|
||||
}
|
||||
131
fs_test.go
Normal file
131
fs_test.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package golden
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMkdirAll(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
perm os.FileMode
|
||||
wantErr bool
|
||||
}{
|
||||
{"create new dir", "newdir", 0o755, false},
|
||||
{"create nested dirs", "nested/dir/structure", 0o755, false},
|
||||
{"invalid path", string([]byte{0, 0}), 0o755, true},
|
||||
}
|
||||
|
||||
fs := NewFS()
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
path := filepath.Join(tempDir, tt.path)
|
||||
err := fs.MkdirAll(path, tt.perm)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
_, err := os.Stat(path)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadFile(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
sampleFilePath := filepath.Join(tempDir, "sample.txt")
|
||||
sampleContent := []byte("Hello, world!")
|
||||
err := os.WriteFile(sampleFilePath, sampleContent, 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
filename string
|
||||
want []byte
|
||||
wantErr bool
|
||||
}{
|
||||
{"read existing file", sampleFilePath, sampleContent, false},
|
||||
{"file does not exist", "nonexistent.txt", nil, true},
|
||||
}
|
||||
|
||||
fs := NewFS()
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := fs.ReadFile(tt.filename)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, string(tt.want), string(got))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteFile(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
filename string
|
||||
data []byte
|
||||
perm os.FileMode
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"write to new file",
|
||||
"newfile.txt",
|
||||
[]byte("new content"),
|
||||
0o644,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"overwrite existing file",
|
||||
"existing.txt",
|
||||
[]byte("overwritten content"),
|
||||
0o644,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid filename",
|
||||
string([]byte{0, 0}),
|
||||
[]byte("invalid filename"),
|
||||
0o644,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"non-existent directory",
|
||||
"nonexistentdir/newfile.txt",
|
||||
[]byte("this will fail"),
|
||||
0o644,
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
fs := NewFS()
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
filePath := filepath.Join(tempDir, tt.filename)
|
||||
err := fs.WriteFile(filePath, tt.data, tt.perm)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
content, err := os.ReadFile(filePath)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.data, content)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
14
go.mod
14
go.mod
@@ -1,12 +1,14 @@
|
||||
module github.com/jimeh/go-golden
|
||||
|
||||
go 1.15
|
||||
go 1.18
|
||||
|
||||
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
|
||||
github.com/stretchr/testify v1.7.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
github.com/stretchr/testify v1.9.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
47
go.sum
47
go.sum
@@ -1,51 +1,16 @@
|
||||
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/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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=
|
||||
github.com/jimeh/go-mocktesting v0.1.0/go.mod h1:xnekQ6yP/ull2ewkOp1CbgH7Dym7nbKa/t96XWrIiH8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
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=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
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=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
173
gold.go
Normal file
173
gold.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package golden
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/jimeh/go-golden/sanitize"
|
||||
)
|
||||
|
||||
// gold is the underlying struct that implements the Golden interface.
|
||||
type gold struct {
|
||||
// dirMode determines the file system permissions of any folders created to
|
||||
// hold golden files.
|
||||
dirMode os.FileMode
|
||||
|
||||
// fileMode determines the file system permissions of any created or updated
|
||||
// golden files written to disk.
|
||||
fileMode os.FileMode
|
||||
|
||||
// suffix determines the filename suffix for all golden files. Typically
|
||||
// this should be ".golden", but can be changed here if needed.
|
||||
suffix string
|
||||
|
||||
// dirname is the name of the top-level directory at the root of the package
|
||||
// which holds all golden files. Typically this should be "testdata", but
|
||||
// can be changed here if needed.
|
||||
dirname string
|
||||
|
||||
// updateFunc is used to determine if golden files should be updated or
|
||||
// not. Its boolean return value is returned by Update().
|
||||
updateFunc UpdateFunc
|
||||
|
||||
// fs is used for all file system operations. This enables providing custom
|
||||
// afero.fs instances which can be useful for testing purposes.
|
||||
fs FS
|
||||
|
||||
// logOnWrite determines if a message is logged with t.Logf when a golden
|
||||
// file is written to with either of the set methods.
|
||||
logOnWrite bool
|
||||
}
|
||||
|
||||
// Ensure golden satisfies Golden interface.
|
||||
var _ Golden = &gold{}
|
||||
|
||||
func (g *gold) Do(t TestingT, data []byte) []byte {
|
||||
t.Helper()
|
||||
|
||||
if g.Update() {
|
||||
g.Set(t, data)
|
||||
}
|
||||
|
||||
return g.Get(t)
|
||||
}
|
||||
|
||||
func (g *gold) 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 *gold) File(t TestingT) string {
|
||||
t.Helper()
|
||||
|
||||
return g.file(t, "")
|
||||
}
|
||||
|
||||
func (g *gold) FileP(t TestingT, name string) string {
|
||||
t.Helper()
|
||||
|
||||
if name == "" {
|
||||
t.Fatalf("golden: name cannot be empty")
|
||||
}
|
||||
|
||||
return g.file(t, name)
|
||||
}
|
||||
|
||||
func (g *gold) file(t TestingT, name string) string {
|
||||
t.Helper()
|
||||
|
||||
if t.Name() == "" {
|
||||
t.Fatalf("golden: could not determine filename for TestingT instance")
|
||||
}
|
||||
|
||||
base := []string{g.dirname, filepath.FromSlash(t.Name())}
|
||||
if name != "" {
|
||||
base = append(base, name)
|
||||
}
|
||||
|
||||
f := filepath.Clean(filepath.Join(base...) + g.suffix)
|
||||
|
||||
dirty := strings.Split(f, string(os.PathSeparator))
|
||||
clean := make([]string, 0, len(dirty))
|
||||
for _, s := range dirty {
|
||||
clean = append(clean, sanitize.Filename(s))
|
||||
}
|
||||
|
||||
return strings.Join(clean, string(os.PathSeparator))
|
||||
}
|
||||
|
||||
func (g *gold) Get(t TestingT) []byte {
|
||||
t.Helper()
|
||||
|
||||
return g.get(t, "")
|
||||
}
|
||||
|
||||
func (g *gold) GetP(t TestingT, name string) []byte {
|
||||
t.Helper()
|
||||
|
||||
if name == "" {
|
||||
t.Fatalf("golden: name cannot be empty")
|
||||
}
|
||||
|
||||
return g.get(t, name)
|
||||
}
|
||||
|
||||
func (g *gold) get(t TestingT, name string) []byte {
|
||||
t.Helper()
|
||||
|
||||
f := g.file(t, name)
|
||||
|
||||
b, err := g.fs.ReadFile(f)
|
||||
if err != nil {
|
||||
t.Fatalf("golden: %s", err.Error())
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (g *gold) Set(t TestingT, data []byte) {
|
||||
t.Helper()
|
||||
|
||||
g.set(t, "", data)
|
||||
}
|
||||
|
||||
func (g *gold) SetP(t TestingT, name string, data []byte) {
|
||||
t.Helper()
|
||||
|
||||
if name == "" {
|
||||
t.Fatalf("golden: name cannot be empty")
|
||||
}
|
||||
|
||||
g.set(t, name, data)
|
||||
}
|
||||
|
||||
func (g *gold) set(t TestingT, name string, data []byte) {
|
||||
t.Helper()
|
||||
|
||||
f := g.file(t, name)
|
||||
dir := filepath.Dir(f)
|
||||
|
||||
if g.logOnWrite {
|
||||
t.Logf("golden: writing golden file: %s", f)
|
||||
}
|
||||
|
||||
err := g.fs.MkdirAll(dir, g.dirMode)
|
||||
if err != nil {
|
||||
t.Fatalf("golden: failed to create directory: %s", err.Error())
|
||||
}
|
||||
|
||||
err = g.fs.WriteFile(f, data, g.fileMode)
|
||||
if err != nil {
|
||||
t.Fatalf("golden: filed to write file: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (g *gold) Update() bool {
|
||||
return g.updateFunc()
|
||||
}
|
||||
528
gold_test.go
Normal file
528
gold_test.go
Normal file
@@ -0,0 +1,528 @@
|
||||
package golden
|
||||
|
||||
// func Test_gold_File(t *testing.T) {
|
||||
// type fields struct {
|
||||
// suffix *string
|
||||
// dirname *string
|
||||
// }
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// testName string
|
||||
// fields fields
|
||||
// want string
|
||||
// wantFatals []string
|
||||
// }{
|
||||
// {
|
||||
// name: "top-level",
|
||||
// testName: "TestFooBar",
|
||||
// want: filepath.Join("testdata", "TestFooBar.golden"),
|
||||
// },
|
||||
// {
|
||||
// name: "sub-test",
|
||||
// testName: "TestFooBar/it_is_here",
|
||||
// want: filepath.Join(
|
||||
// "testdata", "TestFooBar", "it_is_here.golden",
|
||||
// ),
|
||||
// },
|
||||
// {
|
||||
// name: "blank test name",
|
||||
// testName: "",
|
||||
// wantFatals: []string{
|
||||
// "golden: could not determine filename for TestingT instance",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: "custom dirname",
|
||||
// testName: "TestFozBar",
|
||||
// fields: fields{
|
||||
// dirname: stringPtr("goldenfiles"),
|
||||
// },
|
||||
// want: filepath.Join("goldenfiles", "TestFozBar.golden"),
|
||||
// },
|
||||
// {
|
||||
// name: "custom suffix",
|
||||
// testName: "TestFozBaz",
|
||||
// fields: fields{
|
||||
// suffix: stringPtr(".goldfile"),
|
||||
// },
|
||||
// want: filepath.Join("testdata", "TestFozBaz.goldfile"),
|
||||
// },
|
||||
// {
|
||||
// name: "custom dirname and suffix",
|
||||
// testName: "TestFozBar",
|
||||
// fields: fields{
|
||||
// dirname: stringPtr("goldenfiles"),
|
||||
// suffix: stringPtr(".goldfile"),
|
||||
// },
|
||||
// want: filepath.Join("goldenfiles", "TestFozBar.goldfile"),
|
||||
// },
|
||||
// {
|
||||
// name: "invalid chars in test name",
|
||||
// testName: `TestFooBar/foo?<>:*|"bar`,
|
||||
// want: filepath.Join(
|
||||
// "testdata", "TestFooBar", "foo_______bar.golden",
|
||||
// ),
|
||||
// },
|
||||
// }
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// if tt.fields.suffix == nil {
|
||||
// tt.fields.suffix = stringPtr(".golden")
|
||||
// }
|
||||
// if tt.fields.dirname == nil {
|
||||
// tt.fields.dirname = stringPtr("testdata")
|
||||
// }
|
||||
|
||||
// g := &gold{
|
||||
// suffix: *tt.fields.suffix,
|
||||
// dirname: *tt.fields.dirname,
|
||||
// }
|
||||
|
||||
// ft := &fakeTestingT{name: tt.testName}
|
||||
|
||||
// var got string
|
||||
// testInGoroutine(t, func() {
|
||||
// got = g.File(ft)
|
||||
// })
|
||||
|
||||
// assert.Equal(t, tt.want, got)
|
||||
// assert.Equal(t, tt.wantFatals, ft.fatals)
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// func Test_gold_FileP(t *testing.T) {
|
||||
// type fields struct {
|
||||
// suffix *string
|
||||
// dirname *string
|
||||
// }
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// testName string
|
||||
// goldenName string
|
||||
// fields fields
|
||||
// want string
|
||||
// wantFatals []string
|
||||
// }{
|
||||
// {
|
||||
// name: "top-level",
|
||||
// testName: "TestFooBar",
|
||||
// goldenName: "yaml",
|
||||
// want: filepath.Join("testdata", "TestFooBar", "yaml.golden"),
|
||||
// },
|
||||
// {
|
||||
// name: "sub-test",
|
||||
// testName: "TestFooBar/it_is_here",
|
||||
// goldenName: "json",
|
||||
// want: filepath.Join(
|
||||
// "testdata", "TestFooBar", "it_is_here", "json.golden",
|
||||
// ),
|
||||
// },
|
||||
// {
|
||||
// name: "blank test name",
|
||||
// testName: "",
|
||||
// goldenName: "json",
|
||||
// wantFatals: []string{
|
||||
// "golden: could not determine filename for TestintT instance",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: "custom dirname",
|
||||
// testName: "TestFozBar",
|
||||
// goldenName: "xml",
|
||||
// fields: fields{
|
||||
// dirname: stringPtr("goldenfiles"),
|
||||
// },
|
||||
// want: filepath.Join("goldenfiles", "TestFozBar", "xml.golden"),
|
||||
// },
|
||||
// {
|
||||
// name: "custom suffix",
|
||||
// testName: "TestFozBaz",
|
||||
// goldenName: "toml",
|
||||
// fields: fields{
|
||||
// suffix: stringPtr(".goldfile"),
|
||||
// },
|
||||
// want: filepath.Join("testdata", "TestFozBaz", "toml.goldfile"),
|
||||
// },
|
||||
// {
|
||||
// name: "custom dirname and suffix",
|
||||
// testName: "TestFozBar",
|
||||
// goldenName: "json",
|
||||
// fields: fields{
|
||||
// dirname: stringPtr("goldenfiles"),
|
||||
// suffix: stringPtr(".goldfile"),
|
||||
// },
|
||||
// want: filepath.Join("goldenfiles", "TestFozBar", "json.goldfile"),
|
||||
// },
|
||||
// {
|
||||
// name: "invalid chars in test name",
|
||||
// testName: `TestFooBar/foo?<>:*|"bar`,
|
||||
// goldenName: "yml",
|
||||
// want: filepath.Join(
|
||||
// "testdata", "TestFooBar", "foo_______bar", "yml.golden",
|
||||
// ),
|
||||
// },
|
||||
// }
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// if tt.fields.suffix == nil {
|
||||
// tt.fields.suffix = stringPtr(".golden")
|
||||
// }
|
||||
// if tt.fields.dirname == nil {
|
||||
// tt.fields.dirname = stringPtr("testdata")
|
||||
// }
|
||||
|
||||
// g := &gold{
|
||||
// suffix: *tt.fields.suffix,
|
||||
// dirname: *tt.fields.dirname,
|
||||
// }
|
||||
|
||||
// ft := &fakeTestingT{name: tt.testName}
|
||||
|
||||
// var got string
|
||||
// testInGoroutine(t, func() {
|
||||
// got = g.FileP(ft, tt.goldenName)
|
||||
// })
|
||||
|
||||
// assert.Equal(t, tt.want, got)
|
||||
// assert.Equal(t, tt.wantFatals, ft.fatals)
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// func Test_gold_Get(t *testing.T) {
|
||||
// type fields struct {
|
||||
// suffix *string
|
||||
// dirname *string
|
||||
// }
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// testName string
|
||||
// fields fields
|
||||
// files map[string][]byte
|
||||
// want []byte
|
||||
// wantAborted bool
|
||||
// wantFailCount int
|
||||
// wantTestOutput []string
|
||||
// }{
|
||||
// {
|
||||
// name: "file exists",
|
||||
// testName: "TestFooBar",
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join("testdata", "TestFooBar.golden"): []byte(
|
||||
// "foo: bar\nhello: world",
|
||||
// ),
|
||||
// },
|
||||
// want: []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// {
|
||||
// name: "file is missing",
|
||||
// testName: "TestFooBar",
|
||||
// files: map[string][]byte{},
|
||||
// wantAborted: true,
|
||||
// wantFailCount: 1,
|
||||
// wantTestOutput: []string{
|
||||
// "golden: open " + filepath.Join(
|
||||
// "testdata", "TestFooBar.golden",
|
||||
// ) + ": file does not exist\n",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: "sub-test file exists",
|
||||
// testName: "TestFooBar/it_is_here",
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join(
|
||||
// "testdata", "TestFooBar", "it_is_here.golden",
|
||||
// ): []byte("this is really here ^_^\n"),
|
||||
// },
|
||||
// want: []byte("this is really here ^_^\n"),
|
||||
// },
|
||||
// {
|
||||
// name: "sub-test file is missing",
|
||||
// testName: "TestFooBar/not_really_here",
|
||||
// files: map[string][]byte{},
|
||||
// wantAborted: true,
|
||||
// wantFailCount: 1,
|
||||
// wantTestOutput: []string{
|
||||
// "golden: open " + filepath.Join(
|
||||
// "testdata", "TestFooBar", "not_really_here.golden",
|
||||
// ) + ": file does not exist\n",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: "blank test name",
|
||||
// testName: "",
|
||||
// wantAborted: true,
|
||||
// wantFailCount: 1,
|
||||
// wantTestOutput: []string{
|
||||
// "golden: could not determine filename for given " +
|
||||
// "*mocktesting.T instance\n",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: "custom dirname",
|
||||
// testName: "TestFozBar",
|
||||
// fields: fields{
|
||||
// dirname: stringPtr("goldenfiles"),
|
||||
// },
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join("goldenfiles", "TestFozBar.golden"): []byte(
|
||||
// "foo: bar\nhello: world",
|
||||
// ),
|
||||
// },
|
||||
// want: []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// {
|
||||
// name: "custom suffix",
|
||||
// testName: "TestFozBaz",
|
||||
// fields: fields{
|
||||
// suffix: stringPtr(".goldfile"),
|
||||
// },
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join("testdata", "TestFozBaz.goldfile"): []byte(
|
||||
// "foo: bar\nhello: world",
|
||||
// ),
|
||||
// },
|
||||
// want: []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// {
|
||||
// name: "custom dirname and suffix",
|
||||
// testName: "TestFozBar",
|
||||
// fields: fields{
|
||||
// dirname: stringPtr("goldenfiles"),
|
||||
// suffix: stringPtr(".goldfile"),
|
||||
// },
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join("goldenfiles", "TestFozBar.goldfile"): []byte(
|
||||
// "foo: bar\nhello: world",
|
||||
// ),
|
||||
// },
|
||||
// want: []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// {
|
||||
// name: "invalid chars in test name",
|
||||
// testName: `TestFooBar/foo?<>:*|"bar`,
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join(
|
||||
// "testdata", "TestFooBar", "foo_______bar.golden",
|
||||
// ): []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// want: []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// }
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// fs := NewFS() // TODO: Replace with in-memory stub FS.
|
||||
// for f, b := range tt.files {
|
||||
// _ = fs.WriteFile(f, b, 0o644)
|
||||
// }
|
||||
|
||||
// if tt.fields.suffix == nil {
|
||||
// tt.fields.suffix = stringPtr(".golden")
|
||||
// }
|
||||
// if tt.fields.dirname == nil {
|
||||
// tt.fields.dirname = stringPtr("testdata")
|
||||
// }
|
||||
|
||||
// g := &gold{
|
||||
// suffix: *tt.fields.suffix,
|
||||
// dirname: *tt.fields.dirname,
|
||||
// fs: fs,
|
||||
// }
|
||||
|
||||
// mt := mocktesting.NewT(tt.testName)
|
||||
|
||||
// var got []byte
|
||||
// mocktesting.Go(func() {
|
||||
// got = g.Get(mt)
|
||||
// })
|
||||
|
||||
// assert.Equal(t, tt.want, got)
|
||||
// assert.Equal(t, tt.wantAborted, mt.Aborted(), "aborted")
|
||||
// assert.Equal(t,
|
||||
// tt.wantFailCount, mt.FailedCount(), "failed count",
|
||||
// )
|
||||
// assert.Equal(t, tt.wantTestOutput, mt.Output(), "test output")
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// func Test_gold_GetP(t *testing.T) {
|
||||
// type args struct {
|
||||
// name string
|
||||
// }
|
||||
// type fields struct {
|
||||
// suffix *string
|
||||
// dirname *string
|
||||
// }
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// testName string
|
||||
// args args
|
||||
// fields fields
|
||||
// files map[string][]byte
|
||||
// want []byte
|
||||
// wantAborted bool
|
||||
// wantFailCount int
|
||||
// wantTestOutput []string
|
||||
// }{
|
||||
// {
|
||||
// name: "file exists",
|
||||
// testName: "TestFooBar",
|
||||
// args: args{name: "yaml"},
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join("testdata", "TestFooBar", "yaml.golden"): []byte(
|
||||
// "foo: bar\nhello: world",
|
||||
// ),
|
||||
// },
|
||||
// want: []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// {
|
||||
// name: "file is missing",
|
||||
// testName: "TestFooBar",
|
||||
// args: args{name: "yaml"},
|
||||
// files: map[string][]byte{},
|
||||
// wantAborted: true,
|
||||
// wantFailCount: 1,
|
||||
// wantTestOutput: []string{
|
||||
// "golden: open " + filepath.Join(
|
||||
// "testdata", "TestFooBar", "yaml.golden",
|
||||
// ) + ": file does not exist\n",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: "sub-test file exists",
|
||||
// testName: "TestFooBar/it_is_here",
|
||||
// args: args{name: "plain"},
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join(
|
||||
// "testdata", "TestFooBar", "it_is_here", "plain.golden",
|
||||
// ): []byte("this is really here ^_^\n"),
|
||||
// },
|
||||
// want: []byte("this is really here ^_^\n"),
|
||||
// },
|
||||
// {
|
||||
// name: "sub-test file is missing",
|
||||
// testName: "TestFooBar/not_really_here",
|
||||
// args: args{name: "plain"},
|
||||
// files: map[string][]byte{},
|
||||
// wantAborted: true,
|
||||
// wantFailCount: 1,
|
||||
// wantTestOutput: []string{
|
||||
// "golden: open " + filepath.Join(
|
||||
// "testdata", "TestFooBar", "not_really_here", "plain.golden",
|
||||
// ) + ": file does not exist\n",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: "blank test name",
|
||||
// testName: "",
|
||||
// args: args{name: "plain"},
|
||||
// wantAborted: true,
|
||||
// wantFailCount: 1,
|
||||
// wantTestOutput: []string{
|
||||
// "golden: could not determine filename for given " +
|
||||
// "*mocktesting.T instance\n",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: "blank name",
|
||||
// testName: "TestFooBar",
|
||||
// args: args{name: ""},
|
||||
// wantAborted: true,
|
||||
// wantFailCount: 1,
|
||||
// wantTestOutput: []string{
|
||||
// "golden: name cannot be empty\n",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: "custom dirname",
|
||||
// testName: "TestFozBar",
|
||||
// args: args{name: "yaml"},
|
||||
// fields: fields{
|
||||
// dirname: stringPtr("goldenfiles"),
|
||||
// },
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join(
|
||||
// "goldenfiles", "TestFozBar", "yaml.golden",
|
||||
// ): []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// want: []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// {
|
||||
// name: "custom suffix",
|
||||
// testName: "TestFozBaz",
|
||||
// args: args{name: "yaml"},
|
||||
// fields: fields{
|
||||
// suffix: stringPtr(".goldfile"),
|
||||
// },
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join(
|
||||
// "testdata", "TestFozBaz", "yaml.goldfile",
|
||||
// ): []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// want: []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// {
|
||||
// name: "custom dirname and suffix",
|
||||
// testName: "TestFozBar",
|
||||
// args: args{name: "yaml"},
|
||||
// fields: fields{
|
||||
// dirname: stringPtr("goldenfiles"),
|
||||
// suffix: stringPtr(".goldfile"),
|
||||
// },
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join(
|
||||
// "goldenfiles", "TestFozBar", "yaml.goldfile",
|
||||
// ): []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// want: []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// {
|
||||
// name: "invalid chars in test name",
|
||||
// testName: `TestFooBar/foo?<>:*|"bar`,
|
||||
// args: args{name: "trash"},
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join(
|
||||
// "testdata", "TestFooBar", "foo_______bar", "trash.golden",
|
||||
// ): []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// want: []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// }
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// fs := NewFS() // TODO: Replace with in-memory stub FS
|
||||
// for f, b := range tt.files {
|
||||
// _ = fs.WriteFile(f, b, 0o644)
|
||||
// }
|
||||
|
||||
// if tt.fields.suffix == nil {
|
||||
// tt.fields.suffix = stringPtr(".golden")
|
||||
// }
|
||||
// if tt.fields.dirname == nil {
|
||||
// tt.fields.dirname = stringPtr("testdata")
|
||||
// }
|
||||
|
||||
// g := &gold{
|
||||
// suffix: *tt.fields.suffix,
|
||||
// dirname: *tt.fields.dirname,
|
||||
// fs: fs,
|
||||
// }
|
||||
|
||||
// mt := mocktesting.NewT(tt.testName)
|
||||
|
||||
// var got []byte
|
||||
// mocktesting.Go(func() {
|
||||
// got = g.GetP(mt, tt.args.name)
|
||||
// })
|
||||
|
||||
// assert.Equal(t, tt.want, got)
|
||||
// assert.Equal(t, tt.wantAborted, mt.Aborted(), "aborted")
|
||||
// assert.Equal(t,
|
||||
// tt.wantFailCount, mt.FailedCount(), "failed count",
|
||||
// )
|
||||
// assert.Equal(t, tt.wantTestOutput, mt.Output(), "test output")
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
455
golden.go
455
golden.go
@@ -118,119 +118,37 @@
|
||||
// testdata/TestExampleMyStructTabularP/full_struct/xml.golden
|
||||
package golden
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
import "os"
|
||||
|
||||
"github.com/jimeh/go-golden/sanitize"
|
||||
"github.com/spf13/afero"
|
||||
var (
|
||||
// DefaultGolden is the default Golden instance used by all top-level
|
||||
// package functions.
|
||||
DefaultGolden = New()
|
||||
|
||||
// DefaultDirMode is the default file system permissions used for any
|
||||
// created directories to hold golden files.
|
||||
DefaultDirMode = os.FileMode(0o755)
|
||||
|
||||
// DefaultFileMode is the default file system permissions used for any
|
||||
// created or updated golden files written to disk.
|
||||
DefaultFileMode = os.FileMode(0o644)
|
||||
|
||||
// DefaultSuffix is the default filename suffix used for all golden files.
|
||||
DefaultSuffix = ".golden"
|
||||
|
||||
// DefaultDirname is the default name of the top-level directory used to
|
||||
// hold golden files.
|
||||
DefaultDirname = "testdata"
|
||||
|
||||
// DefaultUpdateFunc is the default function used to determine if golden
|
||||
// files should be updated or not. It is called by Update().
|
||||
DefaultUpdateFunc = EnvUpdateFunc
|
||||
|
||||
// DefaultLogOnWrite is the default value for logOnWrite on all Golden
|
||||
// instances.
|
||||
DefaultLogOnWrite = true
|
||||
)
|
||||
|
||||
//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 {
|
||||
Fatal(args ...interface{})
|
||||
Fatalf(format string, args ...interface{})
|
||||
Helper()
|
||||
Log(args ...interface{})
|
||||
Logf(format string, args ...interface{})
|
||||
Name() string
|
||||
}
|
||||
|
||||
// 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 TestingT) string {
|
||||
t.Helper()
|
||||
|
||||
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 TestingT) []byte {
|
||||
t.Helper()
|
||||
|
||||
return Default.Get(t)
|
||||
}
|
||||
|
||||
// Set writes given data to the golden file for the given *testing.T instance as
|
||||
// determined by t.Name(). If writing fails it will fail the test by calling
|
||||
// t.Fatal() with error details.
|
||||
func Set(t *testing.T, data []byte) {
|
||||
t.Helper()
|
||||
|
||||
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 TestingT, name string) string {
|
||||
t.Helper()
|
||||
|
||||
return Default.FileP(t, name)
|
||||
}
|
||||
|
||||
// GetP returns the content of the specifically named golden file belonging
|
||||
// to the given *testing.T instance as determined by t.Name(). If no golden file
|
||||
// can be found/read, it will fail the test with t.Fatal().
|
||||
//
|
||||
// This is very similar to Get(), but it allows multiple different golden files
|
||||
// to be used within the same one *testing.T instance.
|
||||
func GetP(t *testing.T, name string) []byte {
|
||||
t.Helper()
|
||||
|
||||
return Default.GetP(t, name)
|
||||
}
|
||||
|
||||
// SetP writes given data of the specifically named golden file belonging to
|
||||
// the given *testing.T instance as determined by t.Name(). If writing fails it
|
||||
// will fail the test with t.Fatal() detailing the error.
|
||||
//
|
||||
// This is very similar to Set(), but it allows multiple different golden files
|
||||
// to be used within the same one *testing.T instance.
|
||||
func SetP(t *testing.T, name string, data []byte) {
|
||||
t.Helper()
|
||||
|
||||
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
|
||||
// to determine if golden.Set() or golden.SetP() should be called or not.
|
||||
//
|
||||
// Default behavior uses EnvUpdateFunc() to check if the "GOLDEN_UPDATE"
|
||||
// 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 Default.Update()
|
||||
}
|
||||
|
||||
// Golden handles all interactions with golden files. The top-level package
|
||||
// functions proxy through to a default global Golden instance.
|
||||
type Golden interface {
|
||||
@@ -291,14 +209,14 @@ type Golden interface {
|
||||
// New returns a new Golden instance. Used to create custom Golden instances.
|
||||
// See the the various Option functions for details of what can be customized.
|
||||
func New(options ...Option) Golden {
|
||||
g := &golden{
|
||||
dirMode: 0o755,
|
||||
fileMode: 0o644,
|
||||
suffix: ".golden",
|
||||
dirname: "testdata",
|
||||
updateFunc: EnvUpdateFunc,
|
||||
fs: afero.NewOsFs(),
|
||||
logOnWrite: true,
|
||||
g := &gold{
|
||||
dirMode: DefaultDirMode,
|
||||
fileMode: DefaultFileMode,
|
||||
suffix: DefaultSuffix,
|
||||
dirname: DefaultDirname,
|
||||
updateFunc: DefaultUpdateFunc,
|
||||
fs: DefaultFS,
|
||||
logOnWrite: DefaultLogOnWrite,
|
||||
}
|
||||
|
||||
for _, opt := range options {
|
||||
@@ -308,245 +226,88 @@ func New(options ...Option) Golden {
|
||||
return g
|
||||
}
|
||||
|
||||
type Option interface {
|
||||
apply(*golden)
|
||||
// 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 DefaultGolden.Do(t, data)
|
||||
}
|
||||
|
||||
type optionFunc func(*golden)
|
||||
// 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()
|
||||
|
||||
func (fn optionFunc) apply(g *golden) {
|
||||
fn(g)
|
||||
return DefaultGolden.DoP(t, name, data)
|
||||
}
|
||||
|
||||
// WithDirMode sets the file system permissions used for any folders created to
|
||||
// hold golden files.
|
||||
// File returns the filename of the golden file for the given *testing.T
|
||||
// instance as determined by t.Name().
|
||||
func File(t TestingT) string {
|
||||
t.Helper()
|
||||
|
||||
return DefaultGolden.File(t)
|
||||
}
|
||||
|
||||
// FileP returns the filename of the specifically named golden file for the
|
||||
// given *testing.T instance as determined by t.Name().
|
||||
func FileP(t TestingT, name string) string {
|
||||
t.Helper()
|
||||
|
||||
return DefaultGolden.FileP(t, name)
|
||||
}
|
||||
|
||||
// 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 TestingT) []byte {
|
||||
t.Helper()
|
||||
|
||||
return DefaultGolden.Get(t)
|
||||
}
|
||||
|
||||
// GetP returns the content of the specifically named golden file belonging
|
||||
// to the given *testing.T instance as determined by t.Name(). If no golden file
|
||||
// can be found/read, it will fail the test with t.Fatal().
|
||||
//
|
||||
// When this option is not provided, the default value is 0o755.
|
||||
func WithDirMode(mode os.FileMode) Option {
|
||||
return optionFunc(func(g *golden) {
|
||||
g.dirMode = mode
|
||||
})
|
||||
// This is very similar to Get(), but it allows multiple different golden files
|
||||
// to be used within the same one *testing.T instance.
|
||||
func GetP(t TestingT, name string) []byte {
|
||||
t.Helper()
|
||||
|
||||
return DefaultGolden.GetP(t, name)
|
||||
}
|
||||
|
||||
// WithFileMode sets the file system permissions used for any created or updated
|
||||
// golden files written to.
|
||||
// Set writes given data to the golden file for the given *testing.T instance as
|
||||
// determined by t.Name(). If writing fails it will fail the test by calling
|
||||
// t.Fatal() with error details.
|
||||
func Set(t TestingT, data []byte) {
|
||||
t.Helper()
|
||||
|
||||
DefaultGolden.Set(t, data)
|
||||
}
|
||||
|
||||
// SetP writes given data of the specifically named golden file belonging to
|
||||
// the given *testing.T instance as determined by t.Name(). If writing fails it
|
||||
// will fail the test with t.Fatal() detailing the error.
|
||||
//
|
||||
// When this option is not provided, the default value is 0o644.
|
||||
func WithFileMode(mode os.FileMode) Option {
|
||||
return optionFunc(func(g *golden) {
|
||||
g.fileMode = mode
|
||||
})
|
||||
// This is very similar to Set(), but it allows multiple different golden files
|
||||
// to be used within the same one *testing.T instance.
|
||||
func SetP(t TestingT, name string, data []byte) {
|
||||
t.Helper()
|
||||
|
||||
DefaultGolden.SetP(t, name, data)
|
||||
}
|
||||
|
||||
// WithSuffix sets the filename suffix used for all golden files.
|
||||
// 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.
|
||||
//
|
||||
// When this option is not provided, the default value is ".golden".
|
||||
func WithSuffix(suffix string) Option {
|
||||
return optionFunc(func(g *golden) {
|
||||
g.suffix = suffix
|
||||
})
|
||||
}
|
||||
|
||||
// WithDirname sets the name of the top-level directory used to hold golden
|
||||
// files.
|
||||
//
|
||||
// When this option is not provided, the default value is "testdata".
|
||||
func WithDirname(name string) Option {
|
||||
return optionFunc(func(g *golden) {
|
||||
g.dirname = name
|
||||
})
|
||||
}
|
||||
|
||||
// WithUpdateFunc sets the function used to determine if golden files should be
|
||||
// updated or not. Essentially the provided UpdateFunc is called by Update().
|
||||
//
|
||||
// When this option is not provided, the default value is EnvUpdateFunc.
|
||||
func WithUpdateFunc(fn UpdateFunc) Option {
|
||||
return optionFunc(func(g *golden) {
|
||||
g.updateFunc = fn
|
||||
})
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return optionFunc(func(g *golden) {
|
||||
g.fs = fs
|
||||
})
|
||||
}
|
||||
|
||||
// WithSilentWrites silences the "golden: writing [...]" log messages whenever
|
||||
// set functions write a golden file to disk.
|
||||
func WithSilentWrites() Option {
|
||||
return optionFunc(func(g *golden) {
|
||||
g.logOnWrite = false
|
||||
})
|
||||
}
|
||||
|
||||
// golden is the underlying struct that implements the Golden interface.
|
||||
type golden struct {
|
||||
// dirMode determines the file system permissions of any folders created to
|
||||
// hold golden files.
|
||||
dirMode os.FileMode
|
||||
|
||||
// fileMode determines the file system permissions of any created or updated
|
||||
// golden files written to disk.
|
||||
fileMode os.FileMode
|
||||
|
||||
// suffix determines the filename suffix for all golden files. Typically
|
||||
// this should be ".golden", but can be changed here if needed.
|
||||
suffix string
|
||||
|
||||
// dirname is the name of the top-level directory at the root of the package
|
||||
// which holds all golden files. Typically this should be "testdata", but
|
||||
// can be changed here if needed.
|
||||
dirname string
|
||||
|
||||
// updateFunc is used to determine if golden files should be updated or
|
||||
// not. Its boolean return value is returned by Update().
|
||||
updateFunc UpdateFunc
|
||||
|
||||
// fs is used for all file system operations. This enables providing custom
|
||||
// afero.fs instances which can be useful for testing purposes.
|
||||
fs afero.Fs
|
||||
|
||||
// logOnWrite determines if a message is logged with t.Logf when a golden
|
||||
// file is written to with either of the set methods.
|
||||
logOnWrite bool
|
||||
}
|
||||
|
||||
// Ensure golden satisfies Golden interface.
|
||||
var _ Golden = &golden{}
|
||||
|
||||
func (g *golden) File(t TestingT) string {
|
||||
t.Helper()
|
||||
|
||||
return g.file(t, "")
|
||||
}
|
||||
|
||||
func (g *golden) Get(t TestingT) []byte {
|
||||
t.Helper()
|
||||
|
||||
return g.get(t, "")
|
||||
}
|
||||
|
||||
func (g *golden) Set(t TestingT, data []byte) {
|
||||
t.Helper()
|
||||
|
||||
g.set(t, "", data)
|
||||
}
|
||||
|
||||
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 g.file(t, name)
|
||||
}
|
||||
|
||||
func (g *golden) GetP(t TestingT, name string) []byte {
|
||||
t.Helper()
|
||||
|
||||
if name == "" {
|
||||
t.Fatal("golden: name cannot be empty")
|
||||
}
|
||||
|
||||
return g.get(t, name)
|
||||
}
|
||||
|
||||
func (g *golden) SetP(t TestingT, name string, data []byte) {
|
||||
t.Helper()
|
||||
|
||||
if name == "" {
|
||||
t.Fatal("golden: name cannot be empty")
|
||||
}
|
||||
|
||||
g.set(t, name, data)
|
||||
}
|
||||
|
||||
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() == "" {
|
||||
t.Fatalf(
|
||||
"golden: could not determine filename for given %T instance", t,
|
||||
)
|
||||
}
|
||||
|
||||
base := []string{g.dirname, filepath.FromSlash(t.Name())}
|
||||
if name != "" {
|
||||
base = append(base, name)
|
||||
}
|
||||
|
||||
f := filepath.Clean(filepath.Join(base...) + g.suffix)
|
||||
|
||||
dirty := strings.Split(f, string(os.PathSeparator))
|
||||
clean := make([]string, 0, len(dirty))
|
||||
for _, s := range dirty {
|
||||
clean = append(clean, sanitize.Filename(s))
|
||||
}
|
||||
|
||||
return strings.Join(clean, string(os.PathSeparator))
|
||||
}
|
||||
|
||||
func (g *golden) get(t TestingT, name string) []byte {
|
||||
t.Helper()
|
||||
|
||||
f := g.file(t, name)
|
||||
|
||||
b, err := afero.ReadFile(g.fs, f)
|
||||
if err != nil {
|
||||
t.Fatalf("golden: %s", err.Error())
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (g *golden) set(t TestingT, name string, data []byte) {
|
||||
t.Helper()
|
||||
|
||||
f := g.file(t, name)
|
||||
dir := filepath.Dir(f)
|
||||
|
||||
if g.logOnWrite {
|
||||
t.Logf("golden: writing golden file: %s", f)
|
||||
}
|
||||
|
||||
err := g.fs.MkdirAll(dir, g.dirMode)
|
||||
if err != nil {
|
||||
t.Fatalf("golden: failed to create directory: %s", err.Error())
|
||||
}
|
||||
|
||||
err = afero.WriteFile(g.fs, f, data, g.fileMode)
|
||||
if err != nil {
|
||||
t.Fatalf("golden: filed to write file: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (g *golden) Update() bool {
|
||||
return g.updateFunc()
|
||||
// Default behavior uses EnvUpdateFunc() to check if the "GOLDEN_UPDATE"
|
||||
// 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()
|
||||
}
|
||||
|
||||
@@ -16,8 +16,7 @@ type MyStruct struct {
|
||||
|
||||
// TestExampleMyStruct reads/writes the following golden file:
|
||||
//
|
||||
// testdata/TestExampleMyStruct.golden
|
||||
//
|
||||
// testdata/TestExampleMyStruct.golden
|
||||
func TestExampleMyStruct(t *testing.T) {
|
||||
got, err := json.Marshal(&MyStruct{Foo: "Bar"})
|
||||
require.NoError(t, err)
|
||||
@@ -32,9 +31,8 @@ func TestExampleMyStruct(t *testing.T) {
|
||||
|
||||
// TestExampleMyStructTabular reads/writes the following golden files:
|
||||
//
|
||||
// testdata/TestExampleMyStructTabular/empty_struct.golden
|
||||
// testdata/TestExampleMyStructTabular/full_struct.golden
|
||||
//
|
||||
// testdata/TestExampleMyStructTabular/empty_struct.golden
|
||||
// testdata/TestExampleMyStructTabular/full_struct.golden
|
||||
func TestExampleMyStructTabular(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -60,9 +58,8 @@ func TestExampleMyStructTabular(t *testing.T) {
|
||||
|
||||
// TestExampleMyStructP reads/writes the following golden file:
|
||||
//
|
||||
// testdata/TestExampleMyStructP/json.golden
|
||||
// testdata/TestExampleMyStructP/xml.golden
|
||||
//
|
||||
// testdata/TestExampleMyStructP/json.golden
|
||||
// testdata/TestExampleMyStructP/xml.golden
|
||||
func TestExampleMyStructP(t *testing.T) {
|
||||
gotJSON, _ := json.Marshal(&MyStruct{Foo: "Bar"})
|
||||
gotXML, _ := xml.Marshal(&MyStruct{Foo: "Bar"})
|
||||
@@ -78,11 +75,10 @@ func TestExampleMyStructP(t *testing.T) {
|
||||
|
||||
// TestExampleMyStructTabularP reads/writes the following golden file:
|
||||
//
|
||||
// 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
|
||||
func TestExampleMyStructTabularP(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -1,306 +0,0 @@
|
||||
// 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)
|
||||
}
|
||||
1620
golden_test.go
1620
golden_test.go
File diff suppressed because it is too large
Load Diff
80
options.go
Normal file
80
options.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package golden
|
||||
|
||||
import "os"
|
||||
|
||||
type Option interface {
|
||||
apply(*gold)
|
||||
}
|
||||
|
||||
type optionFunc func(*gold)
|
||||
|
||||
func (fn optionFunc) apply(g *gold) {
|
||||
fn(g)
|
||||
}
|
||||
|
||||
// WithDirMode sets the file system permissions used for any folders created to
|
||||
// hold golden files.
|
||||
//
|
||||
// When this option is not provided, the default value is 0o755.
|
||||
func WithDirMode(mode os.FileMode) Option {
|
||||
return optionFunc(func(g *gold) {
|
||||
g.dirMode = mode
|
||||
})
|
||||
}
|
||||
|
||||
// WithFileMode sets the file system permissions used for any created or updated
|
||||
// golden files written to.
|
||||
//
|
||||
// When this option is not provided, the default value is 0o644.
|
||||
func WithFileMode(mode os.FileMode) Option {
|
||||
return optionFunc(func(g *gold) {
|
||||
g.fileMode = mode
|
||||
})
|
||||
}
|
||||
|
||||
// WithSuffix sets the filename suffix used for all golden files.
|
||||
//
|
||||
// When this option is not provided, the default value is ".golden".
|
||||
func WithSuffix(suffix string) Option {
|
||||
return optionFunc(func(g *gold) {
|
||||
g.suffix = suffix
|
||||
})
|
||||
}
|
||||
|
||||
// WithDirname sets the name of the top-level directory used to hold golden
|
||||
// files.
|
||||
//
|
||||
// When this option is not provided, the default value is "testdata".
|
||||
func WithDirname(name string) Option {
|
||||
return optionFunc(func(g *gold) {
|
||||
g.dirname = name
|
||||
})
|
||||
}
|
||||
|
||||
// WithUpdateFunc sets the function used to determine if golden files should be
|
||||
// updated or not. Essentially the provided UpdateFunc is called by Update().
|
||||
//
|
||||
// When this option is not provided, the default value is EnvUpdateFunc.
|
||||
func WithUpdateFunc(fn UpdateFunc) Option {
|
||||
return optionFunc(func(g *gold) {
|
||||
g.updateFunc = fn
|
||||
})
|
||||
}
|
||||
|
||||
// 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 FS) Option {
|
||||
return optionFunc(func(g *gold) {
|
||||
g.fs = fs
|
||||
})
|
||||
}
|
||||
|
||||
// WithSilentWrites silences the "golden: writing [...]" log messages whenever
|
||||
// set functions write a golden file to disk.
|
||||
func WithSilentWrites() Option {
|
||||
return optionFunc(func(g *gold) {
|
||||
g.logOnWrite = false
|
||||
})
|
||||
}
|
||||
275
test/testfs/testfs.go
Normal file
275
test/testfs/testfs.go
Normal file
@@ -0,0 +1,275 @@
|
||||
package testfs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Node struct {
|
||||
data []byte
|
||||
perm os.FileMode
|
||||
isDir bool
|
||||
}
|
||||
|
||||
type FS struct {
|
||||
Pwd string
|
||||
Nodes map[string]*Node
|
||||
}
|
||||
|
||||
func New() *FS {
|
||||
return &FS{
|
||||
Pwd: "/root",
|
||||
Nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o700, isDir: true},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *FS) MkdirAll(name string, perm os.FileMode) error {
|
||||
if !path.IsAbs(name) && name != "" {
|
||||
name = path.Join(fs.Pwd, name)
|
||||
}
|
||||
|
||||
dirs := []string{name}
|
||||
for d := path.Dir(name); d != "/"; d = path.Dir(d) {
|
||||
dirs = append(dirs, d)
|
||||
}
|
||||
dirs = append(dirs, "/")
|
||||
|
||||
for i := len(dirs) - 1; i >= 0; i-- {
|
||||
dir := dirs[i]
|
||||
parent := path.Dir(dir)
|
||||
|
||||
if info, ok := fs.Nodes[dir]; ok {
|
||||
if !info.isDir {
|
||||
return &os.PathError{
|
||||
Op: "mkdir",
|
||||
Path: dir,
|
||||
Err: errors.New("not a directory"),
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
parentInfo, ok := fs.Nodes[parent]
|
||||
if !ok {
|
||||
return &os.PathError{
|
||||
Op: "mkdir",
|
||||
Path: parent,
|
||||
Err: errors.New("no such file or directory"),
|
||||
}
|
||||
}
|
||||
if !parentInfo.isDir {
|
||||
return &os.PathError{
|
||||
Op: "mkdir",
|
||||
Path: parent,
|
||||
Err: errors.New("not a directory"),
|
||||
}
|
||||
}
|
||||
// Ensure all parent directories have execute permissions, and direct
|
||||
// parent also has write permission.
|
||||
if parentInfo.perm&0o100 == 0 || i == 1 && parentInfo.perm&0o200 == 0 {
|
||||
return &os.PathError{
|
||||
Op: "mkdir",
|
||||
Path: dir,
|
||||
Err: errors.New("permission denied"),
|
||||
}
|
||||
}
|
||||
|
||||
fs.Nodes[dir] = &Node{perm: perm, isDir: true}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *FS) ReadFile(name string) ([]byte, error) {
|
||||
if !path.IsAbs(name) && name != "" {
|
||||
name = path.Join(fs.Pwd, name)
|
||||
}
|
||||
|
||||
_, err := fs.checkParents(name, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, ok := fs.Nodes[name]
|
||||
if !ok {
|
||||
return nil, &os.PathError{
|
||||
Op: "open",
|
||||
Path: name,
|
||||
Err: errors.New("no such file or directory"),
|
||||
}
|
||||
}
|
||||
if info.isDir {
|
||||
return nil, &os.PathError{
|
||||
Op: "open",
|
||||
Path: name,
|
||||
Err: errors.New("is a directory"),
|
||||
}
|
||||
}
|
||||
if info.perm&0o400 == 0 {
|
||||
return nil, &os.PathError{
|
||||
Op: "open",
|
||||
Path: name,
|
||||
Err: errors.New("permission denied"),
|
||||
}
|
||||
}
|
||||
|
||||
return info.data, nil
|
||||
}
|
||||
|
||||
func (fs *FS) WriteFile(name string, data []byte, perm os.FileMode) error {
|
||||
if !path.IsAbs(name) && name != "" {
|
||||
name = path.Join(fs.Pwd, name)
|
||||
}
|
||||
|
||||
parent, err := fs.checkParents(name, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, ok := fs.Nodes[name]
|
||||
if ok {
|
||||
if info.isDir {
|
||||
return &os.PathError{
|
||||
Op: "open",
|
||||
Path: name,
|
||||
Err: errors.New("is a directory"),
|
||||
}
|
||||
}
|
||||
}
|
||||
// Return error if file exists and has no write permission, or if the file
|
||||
// does not exist and the direct parent has no write permission.
|
||||
if ok && info.perm&0o200 == 0 || !ok && parent.perm&0o200 == 0 {
|
||||
return &os.PathError{
|
||||
Op: "open",
|
||||
Path: name,
|
||||
Err: errors.New("permission denied"),
|
||||
}
|
||||
}
|
||||
|
||||
fs.Nodes[name] = &Node{data: data, perm: perm}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *FS) Remove(name string) error {
|
||||
if !path.IsAbs(name) && name != "" {
|
||||
name = path.Join(fs.Pwd, name)
|
||||
}
|
||||
|
||||
parent, err := fs.checkParents(name, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if parent != nil && parent.perm&0o200 == 0 {
|
||||
return &os.PathError{
|
||||
Op: "remove",
|
||||
Path: name,
|
||||
Err: errors.New("permission denied"),
|
||||
}
|
||||
}
|
||||
|
||||
info, ok := fs.Nodes[name]
|
||||
if !ok {
|
||||
return &os.PathError{
|
||||
Op: "remove",
|
||||
Path: name,
|
||||
Err: errors.New("no such file or directory"),
|
||||
}
|
||||
}
|
||||
if info.perm&0o200 == 0 {
|
||||
return &os.PathError{
|
||||
Op: "remove",
|
||||
Path: name,
|
||||
Err: errors.New("permission denied"),
|
||||
}
|
||||
}
|
||||
if info.isDir {
|
||||
for p := range fs.Nodes {
|
||||
if strings.HasPrefix(p, name) && p != name {
|
||||
return &os.PathError{
|
||||
Op: "remove",
|
||||
Path: name,
|
||||
Err: errors.New("directory not empty"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete(fs.Nodes, name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *FS) Exists(name string) bool {
|
||||
if !path.IsAbs(name) && name != "" {
|
||||
name = path.Join(fs.Pwd, name)
|
||||
}
|
||||
|
||||
_, ok := fs.Nodes[name]
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
func (fs *FS) FileMode(name string) (os.FileMode, error) {
|
||||
if !path.IsAbs(name) && name != "" {
|
||||
name = path.Join(fs.Pwd, name)
|
||||
}
|
||||
|
||||
if info, ok := fs.Nodes[name]; ok {
|
||||
return info.perm, nil
|
||||
}
|
||||
|
||||
return 0, &os.PathError{
|
||||
Op: "open",
|
||||
Path: name,
|
||||
Err: os.ErrNotExist,
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *FS) checkParents(absPath string, noExistError bool) (*Node, error) {
|
||||
var parents []string
|
||||
for d := path.Dir(absPath); d != "/"; d = path.Dir(d) {
|
||||
parents = append(parents, d)
|
||||
}
|
||||
parents = append(parents, "/")
|
||||
var directParent *Node
|
||||
|
||||
for i := 0; i < len(parents); i++ {
|
||||
dir := parents[i]
|
||||
info, ok := fs.Nodes[dir]
|
||||
if !ok && noExistError {
|
||||
return nil, &os.PathError{
|
||||
Op: "open",
|
||||
Path: dir,
|
||||
Err: errors.New("no such file or directory"),
|
||||
}
|
||||
}
|
||||
if info != nil && !info.isDir {
|
||||
return nil, &os.PathError{
|
||||
Op: "open",
|
||||
Path: dir,
|
||||
Err: errors.New("not a directory"),
|
||||
}
|
||||
}
|
||||
// Ensure all parent directories have execute permissions.
|
||||
if info != nil && info.perm&0o100 == 0 {
|
||||
return nil, &os.PathError{
|
||||
Op: "open",
|
||||
Path: dir,
|
||||
Err: errors.New("permission denied"),
|
||||
}
|
||||
}
|
||||
if i == 0 {
|
||||
directParent = info
|
||||
}
|
||||
}
|
||||
|
||||
return directParent, nil
|
||||
}
|
||||
670
test/testfs/testfs_test.go
Normal file
670
test/testfs/testfs_test.go
Normal file
@@ -0,0 +1,670 @@
|
||||
package testfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFSMkdirAll(t *testing.T) {
|
||||
type args struct {
|
||||
path string
|
||||
perm os.FileMode
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
nodes map[string]*Node
|
||||
want map[string]*Node
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "create relative new dir",
|
||||
args: args{path: "newdir", perm: 0o755},
|
||||
want: map[string]*Node{
|
||||
"/root/newdir": {perm: 0o755, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create absolute new dir",
|
||||
args: args{path: "/opt/newdir", perm: 0o755},
|
||||
want: map[string]*Node{
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
"/opt/newdir": {perm: 0o755, isDir: true},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "create relative nested dirs",
|
||||
args: args{path: "nested/dir/structure", perm: 0o755},
|
||||
want: map[string]*Node{
|
||||
"/root/nested": {perm: 0o755, isDir: true},
|
||||
"/root/nested/dir": {perm: 0o755, isDir: true},
|
||||
"/root/nested/dir/structure": {perm: 0o755, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create absolute nested dirs",
|
||||
args: args{path: "/opt/nested/dir/structure", perm: 0o755},
|
||||
want: map[string]*Node{
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
"/opt/nested": {perm: 0o755, isDir: true},
|
||||
"/opt/nested/dir": {perm: 0o755, isDir: true},
|
||||
"/opt/nested/dir/structure": {perm: 0o755, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create relative nested dirs with other perms",
|
||||
args: args{path: "nested/dir/structure", perm: 0o750},
|
||||
want: map[string]*Node{
|
||||
"/root/nested": {perm: 0o750, isDir: true},
|
||||
"/root/nested/dir": {perm: 0o750, isDir: true},
|
||||
"/root/nested/dir/structure": {perm: 0o750, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create absolute nested dirs with other perms",
|
||||
args: args{path: "/opt/nested/dir/structure", perm: 0o750},
|
||||
want: map[string]*Node{
|
||||
"/opt": {perm: 0o750, isDir: true},
|
||||
"/opt/nested": {perm: 0o750, isDir: true},
|
||||
"/opt/nested/dir": {perm: 0o750, isDir: true},
|
||||
"/opt/nested/dir/structure": {perm: 0o750, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create relative nested dirs with existing dirs",
|
||||
args: args{path: "nested/dir/structure", perm: 0o755},
|
||||
want: map[string]*Node{
|
||||
"/root/nested": {perm: 0o755, isDir: true},
|
||||
"/root/nested/dir": {perm: 0o755, isDir: true},
|
||||
"/root/nested/dir/structure": {perm: 0o755, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create absolute nested dirs with existing dirs",
|
||||
args: args{path: "/root/nested/dir/structure", perm: 0o755},
|
||||
want: map[string]*Node{
|
||||
"/root/nested": {perm: 0o755, isDir: true},
|
||||
"/root/nested/dir": {perm: 0o755, isDir: true},
|
||||
"/root/nested/dir/structure": {perm: 0o755, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create relative under file",
|
||||
args: args{path: "file/newdir", perm: 0o755},
|
||||
nodes: map[string]*Node{
|
||||
"/root/file": {perm: 0o644},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "create absolute under file",
|
||||
args: args{path: "/root/file/newdir", perm: 0o755},
|
||||
nodes: map[string]*Node{
|
||||
"/root/file": {perm: 0o644},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "create relative directory without execute permission",
|
||||
args: args{path: "dir/newdir", perm: 0o755},
|
||||
nodes: map[string]*Node{
|
||||
"/root": {perm: 0o644},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "create absolute directory without execute permission",
|
||||
args: args{path: "/root/dir/newdir", perm: 0o755},
|
||||
nodes: map[string]*Node{
|
||||
"/root": {perm: 0o644},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "create relative directory without write permission",
|
||||
args: args{path: "dir/newdir", perm: 0o755},
|
||||
nodes: map[string]*Node{
|
||||
"/root": {perm: 0o444},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "create absolute directory without write permission",
|
||||
args: args{path: "/root/dir/newdir", perm: 0o755},
|
||||
nodes: map[string]*Node{
|
||||
"/root": {perm: 0o444},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fs := &FS{
|
||||
Pwd: "/root",
|
||||
Nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o700, isDir: true},
|
||||
},
|
||||
}
|
||||
|
||||
for fp, info := range tt.nodes {
|
||||
fs.Nodes[fp] = info
|
||||
}
|
||||
|
||||
err := fs.MkdirAll(tt.args.path, tt.args.perm)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
|
||||
for fp, info := range tt.want {
|
||||
got := fs.Nodes[fp]
|
||||
assert.Equal(t, info, got, "path: %s", fp)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFSReadFile(t *testing.T) {
|
||||
type args struct {
|
||||
name string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
nodes map[string]*Node
|
||||
want []byte
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "relative read existing file",
|
||||
args: args{name: "file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o755, isDir: true},
|
||||
"/root/file.txt": {data: []byte("file content"), perm: 0o644},
|
||||
},
|
||||
want: []byte("file content"),
|
||||
},
|
||||
{
|
||||
name: "absolute read existing file",
|
||||
args: args{name: "/opt/file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
"/opt/file.txt": {data: []byte("file content"), perm: 0o644},
|
||||
},
|
||||
want: []byte("file content"),
|
||||
},
|
||||
{
|
||||
name: "relative file does not exist",
|
||||
args: args{name: "nonexistent.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o755, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "absolute file does not exist",
|
||||
args: args{name: "/opt/nonexistent.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative file is a directory",
|
||||
args: args{name: "dir"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o755, isDir: true},
|
||||
"/root/dir": {perm: 0o755, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "absolute file is a directory",
|
||||
args: args{name: "/opt/dir"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
"/opt/dir": {perm: 0o755, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative file permission denied",
|
||||
args: args{name: "file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o755, isDir: true},
|
||||
"/root/file.txt": {data: []byte("file content"), perm: 0o200},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative no directory read permission",
|
||||
args: args{name: "file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o355, isDir: true},
|
||||
"/root/file.txt": {data: []byte("file content"), perm: 0o644},
|
||||
},
|
||||
want: []byte("file content"),
|
||||
},
|
||||
{
|
||||
name: "relative no directory execute permission",
|
||||
args: args{name: "file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o655, isDir: true},
|
||||
"/root/file.txt": {data: []byte("file content"), perm: 0o200},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative no grandparent directory execute permission",
|
||||
args: args{name: "foo/file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o655, isDir: true},
|
||||
"/root/foo": {perm: 0o755, isDir: true},
|
||||
"/root/foo/file.txt": {data: []byte("hello"), perm: 0o200},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fs := &FS{
|
||||
Pwd: "/root",
|
||||
Nodes: tt.nodes,
|
||||
}
|
||||
|
||||
got, err := fs.ReadFile(tt.args.name)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFSWriteFile(t *testing.T) {
|
||||
type args struct {
|
||||
name string
|
||||
data []byte
|
||||
perm os.FileMode
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
nodes map[string]*Node
|
||||
wantPath string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "relative write to new file",
|
||||
args: args{
|
||||
name: "newfile.txt",
|
||||
data: []byte("new content"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/tmp/newfile.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/tmp": {perm: 0o755, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "absolute write to new file",
|
||||
args: args{
|
||||
name: "/opt/newfile.txt",
|
||||
data: []byte("new content"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/opt/newfile.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "relative overwrite existing file",
|
||||
args: args{
|
||||
name: "existing.txt",
|
||||
data: []byte("overwritten"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/tmp/existing.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/tmp": {perm: 0o755, isDir: true},
|
||||
"/tmp/existing": {data: []byte("existing"), perm: 0o644},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "absolute overwrite existing file",
|
||||
args: args{
|
||||
name: "/opt/existing.txt",
|
||||
data: []byte("overwritten"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/opt/existing.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
"/opt/existing": {data: []byte("existing"), perm: 0o644},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "relative overwrite file permissions denied",
|
||||
args: args{
|
||||
name: "existing.txt",
|
||||
data: []byte("overwritten"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/tmp/existing.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/tmp": {perm: 0o755, isDir: true},
|
||||
"/tmp/existing.txt": {data: []byte("existing"), perm: 0o400},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "absolute overwrite file permissions denied",
|
||||
args: args{
|
||||
name: "/opt/existing.txt",
|
||||
data: []byte("overwritten"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/opt/existing.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
"/opt/existing.txt": {data: []byte("existing"), perm: 0o400},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative overwrite directory",
|
||||
args: args{
|
||||
name: "dir",
|
||||
data: []byte("overwritten"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/tmp/dir",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/tmp": {perm: 0o755, isDir: true},
|
||||
"/tmp/dir": {perm: 0o644, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "absolute overwrite directory",
|
||||
args: args{
|
||||
name: "/opt/dir",
|
||||
data: []byte("overwritten"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/opt/dir",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
"/opt/dir": {perm: 0o644, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative write to non-existent directory",
|
||||
args: args{
|
||||
name: "nonexistentdir/newfile.txt",
|
||||
data: []byte("this will fail"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/tmp/nonexistentdir/newfile.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/tmp": {perm: 0o755, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "absolute write to non-existent directory",
|
||||
args: args{
|
||||
name: "/opt/nonexistentdir/newfile.txt",
|
||||
data: []byte("this will fail"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/opt/nonexistentdir/newfile.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative write parent directory is a file",
|
||||
args: args{
|
||||
name: "file/newfile.txt",
|
||||
data: []byte("this will fail"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/tmp/file/newfile.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/tmp": {perm: 0o755, isDir: true},
|
||||
"/tmp/file": {data: []byte("file content"), perm: 0o644},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative no parent directory write permission denied",
|
||||
args: args{
|
||||
name: "dir/newfile.txt",
|
||||
data: []byte("this will fail"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/tmp/dir/newfile.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/tmp": {perm: 0o755, isDir: true},
|
||||
"/tmp/dir": {perm: 0o500, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative no parent directory execute permission denied",
|
||||
args: args{
|
||||
name: "dir/newfile.txt",
|
||||
data: []byte("this will fail"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/tmp/dir/newfile.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/tmp": {perm: 0o755, isDir: true},
|
||||
"/tmp/dir": {perm: 0o600, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fs := &FS{
|
||||
Pwd: "/tmp",
|
||||
Nodes: tt.nodes,
|
||||
}
|
||||
|
||||
err := fs.WriteFile(tt.args.name, tt.args.data, tt.args.perm)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
if _, ok := tt.nodes[tt.wantPath]; ok {
|
||||
assert.Equal(t,
|
||||
tt.nodes[tt.wantPath],
|
||||
fs.Nodes[tt.wantPath],
|
||||
)
|
||||
} else {
|
||||
assert.NotContains(t, fs.Nodes, tt.wantPath)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
|
||||
got := fs.Nodes[tt.wantPath]
|
||||
assert.Equal(t, tt.args.data, got.data)
|
||||
assert.Equal(t, tt.args.perm, got.perm)
|
||||
assert.Equal(t, false, got.isDir)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFSRemove(t *testing.T) {
|
||||
type args struct {
|
||||
name string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
nodes map[string]*Node
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "relative remove existing file",
|
||||
args: args{name: "file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o755, isDir: true},
|
||||
"/root/file.txt": {data: []byte("file content"), perm: 0o644},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "absolute remove existing file",
|
||||
args: args{name: "/opt/file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
"/opt/file.txt": {data: []byte("file content"), perm: 0o644},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "relative file does not exist",
|
||||
args: args{name: "nonexistent.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o755, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "absolute file does not exist",
|
||||
args: args{name: "/opt/nonexistent.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative file is a directory",
|
||||
args: args{name: "dir"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o755, isDir: true},
|
||||
"/root/dir": {perm: 0o755, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "absolute file is a directory",
|
||||
args: args{name: "/opt/dir"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
"/opt/dir": {perm: 0o755, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "relative file permission denied",
|
||||
args: args{name: "file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/root": {perm: 0o755, isDir: true},
|
||||
"/root/file.txt": {data: []byte("file content"), perm: 0o400},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "absolute file permission denied",
|
||||
args: args{name: "/opt/file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
"/opt/file.txt": {data: []byte("file content"), perm: 0o400},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative no directory write permission",
|
||||
args: args{name: "file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/root": {perm: 0o555, isDir: true},
|
||||
"/root/file.txt": {data: []byte("file content"), perm: 0o644},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative no directory execute permission",
|
||||
args: args{name: "file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/root": {perm: 0o655, isDir: true},
|
||||
"/root/file.txt": {data: []byte("file content"), perm: 0o644},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative no grandparent directory execute permission",
|
||||
args: args{name: "file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/root": {perm: 0o655, isDir: true},
|
||||
"/root/dir": {perm: 0o755, isDir: true},
|
||||
"/root/dir/file.txt": {
|
||||
data: []byte("file content"), perm: 0o644,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fs := &FS{
|
||||
Pwd: "/root",
|
||||
Nodes: tt.nodes,
|
||||
}
|
||||
|
||||
err := fs.Remove(tt.args.name)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
10
testingt.go
Normal file
10
testingt.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package golden
|
||||
|
||||
// TestingT is a interface describing a sub-set of methods of *testing.T which
|
||||
// golden uses.
|
||||
type TestingT interface {
|
||||
Fatalf(format string, args ...interface{})
|
||||
Helper()
|
||||
Logf(format string, args ...interface{})
|
||||
Name() string
|
||||
}
|
||||
30
testingt_test.go
Normal file
30
testingt_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package golden
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type fakeTestingT struct {
|
||||
helper bool
|
||||
name string
|
||||
logs []string
|
||||
fatals []string
|
||||
}
|
||||
|
||||
func (m *fakeTestingT) Helper() {
|
||||
m.helper = true
|
||||
}
|
||||
|
||||
func (m *fakeTestingT) Fatalf(format string, args ...interface{}) {
|
||||
m.fatals = append(m.fatals, fmt.Sprintf(format, args...))
|
||||
runtime.Goexit()
|
||||
}
|
||||
|
||||
func (m *fakeTestingT) Logf(format string, args ...interface{}) {
|
||||
m.logs = append(m.logs, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (m *fakeTestingT) Name() string {
|
||||
return m.name
|
||||
}
|
||||
Reference in New Issue
Block a user