Files
go-golden/golden_test.go

987 lines
22 KiB
Go

package golden
import (
"io/fs"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"runtime"
"testing"
"github.com/golang/mock/gomock"
"github.com/jimeh/envctl"
"github.com/jimeh/go-mocktesting"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func stringPtr(s string) *string {
return &s
}
func funcID(f interface{}) string {
return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
}
func setupDefaultMock(
t *testing.T,
ctrl *gomock.Controller,
) (*MockTestingT, *MockGolden) {
t.Helper()
mt := NewMockTestingT(ctrl)
mg := NewMockGolden(ctrl)
originalDefault := Default
Default = mg
t.Cleanup(func() {
Default = originalDefault
})
return mt, mg
}
func TestDefault(t *testing.T) {
require.IsType(t, &golden{}, Default)
dg := Default.(*golden)
assert.Equal(t, fs.FileMode(0o755), dg.dirMode)
assert.Equal(t, fs.FileMode(0o644), dg.fileMode)
assert.Equal(t, ".golden", dg.suffix)
assert.Equal(t, "testdata", dg.dirname)
assert.Equal(t, funcID(EnvUpdateFunc), funcID(dg.updateFunc))
assert.Equal(t, afero.NewOsFs(), dg.fs)
assert.Equal(t, true, dg.logOnWrite)
}
func TestFile(t *testing.T) {
ctrl := gomock.NewController(t)
mt, mg := setupDefaultMock(t, ctrl)
want := filepath.Join("testdata", t.Name()+".golden")
mt.EXPECT().Helper()
mg.EXPECT().File(mt).Return(want)
got := File(mt)
assert.Equal(t, want, got)
}
func TestGet(t *testing.T) {
ctrl := gomock.NewController(t)
mt, mg := setupDefaultMock(t, ctrl)
want := []byte("foobar\nhello world :)")
mt.EXPECT().Helper()
mg.EXPECT().Get(mt).Return(want)
got := Get(mt)
assert.Equal(t, want, got)
}
func TestSet(t *testing.T) {
t.Cleanup(func() {
t.Log("cleaning up golden files")
err := os.RemoveAll(filepath.Join("testdata", "TestSet"))
require.NoError(t, err)
err = os.Remove(filepath.Join("testdata", "TestSet.golden"))
require.NoError(t, err)
})
content := []byte("This is the default golden file for TestSet ^_^")
Set(t, content)
b, err := ioutil.ReadFile(filepath.Join("testdata", "TestSet.golden"))
require.NoError(t, err)
assert.Equal(t, content, b)
tests := []struct {
name string
file string
content []byte
}{
{
name: "",
file: filepath.Join("testdata", "TestSet", "#00.golden"),
content: []byte("number double-zero strikes again"),
},
{
name: "foobar",
file: filepath.Join("testdata", "TestSet", "foobar.golden"),
content: []byte("foobar here"),
},
{
name: "foo/bar",
file: filepath.Join("testdata", "TestSet", "foo", "bar.golden"),
content: []byte("foo/bar style sub-sub-folders works too"),
},
{
name: "john's lost flip-flop",
file: filepath.Join(
"testdata", "TestSet", "john's_lost_flip-flop.golden",
),
content: []byte("Did John lose his flip-flop again?"),
},
{
name: "thing: it's a thing!",
file: filepath.Join(
"testdata", "TestSet", "thing__it's_a_thing!.golden",
),
content: []byte("A thing? Really? Are we getting lazy? :P"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f := File(t)
Set(t, tt.content)
got, err := ioutil.ReadFile(f)
require.NoError(t, err)
assert.Equal(t, tt.file, f)
assert.Equal(t, tt.content, got)
})
}
}
func TestFileP(t *testing.T) {
ctrl := gomock.NewController(t)
mt, mg := setupDefaultMock(t, ctrl)
want := filepath.Join("testdata", t.Name(), "foobar.golden")
mt.EXPECT().Helper()
mg.EXPECT().FileP(mt, "foobar").Return(want)
got := FileP(mt, "foobar")
assert.Equal(t, want, got)
}
func TestGetP(t *testing.T) {
t.Cleanup(func() {
err := os.RemoveAll(filepath.Join("testdata", "TestGetP"))
require.NoError(t, err)
})
err := os.MkdirAll(filepath.Join("testdata", "TestGetP"), 0o755)
require.NoError(t, err)
content := []byte("this is the named golden file for TestGetP")
err = ioutil.WriteFile( //nolint:gosec
filepath.Join("testdata", "TestGetP", "sub-name.golden"),
content, 0o644,
)
require.NoError(t, err)
got := GetP(t, "sub-name")
assert.Equal(t, content, got)
tests := []struct {
name string
named string
file string
want []byte
}{
{
name: "",
named: "sub-zero-one",
file: filepath.Join(
"testdata", "TestGetP", "#00", "sub-zero-one.golden",
),
want: []byte("number zero-one here"),
},
{
name: "foobar",
named: "email",
file: filepath.Join(
"testdata", "TestGetP", "foobar", "email.golden",
),
want: []byte("foobar email here"),
},
{
name: "foobar",
named: "json",
file: filepath.Join(
"testdata", "TestGetP", "foobar#01", "json.golden",
),
want: []byte("foobar json here"),
},
{
name: "foo/bar",
named: "hello/world",
file: filepath.Join(
"testdata", "TestGetP",
"foo", "bar",
"hello", "world.golden",
),
want: []byte("foo/bar style sub-sub-folders works too"),
},
{
name: "john's lost flip-flop",
named: "left",
file: filepath.Join(
"testdata", "TestGetP", "john's_lost_flip-flop",
"left.golden",
),
want: []byte("Did John lose his left flip-flop again?"),
},
{
name: "john's lost flip-flop",
named: "right",
file: filepath.Join(
"testdata", "TestGetP", "john's_lost_flip-flop#01",
"right.golden",
),
want: []byte("Did John lose his right flip-flop again?"),
},
{
name: "thing: it's",
named: "a thing!",
file: filepath.Join(
"testdata", "TestGetP", "thing__it's", "a_thing!.golden",
),
want: []byte("A thing? Really? Are we getting lazy? :P"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f := FileP(t, tt.named)
dir := filepath.Dir(f)
err := os.MkdirAll(dir, 0o755)
require.NoError(t, err)
err = ioutil.WriteFile(f, tt.want, 0o644) //nolint:gosec
require.NoError(t, err)
got := GetP(t, tt.named)
assert.Equal(t, filepath.FromSlash(tt.file), f)
assert.Equal(t, tt.want, got)
})
}
}
func TestSetP(t *testing.T) {
t.Cleanup(func() {
t.Log("cleaning up golden files")
err := os.RemoveAll(filepath.Join("testdata", "TestSetP"))
require.NoError(t, err)
})
content := []byte("This is the named golden file for TestSetP ^_^")
SetP(t, "sub-name", content)
b, err := ioutil.ReadFile(
filepath.Join("testdata", "TestSetP", "sub-name.golden"),
)
require.NoError(t, err)
assert.Equal(t, content, b)
tests := []struct {
name string
named string
file string
content []byte
}{
{
name: "",
named: "sub-zero-one",
file: filepath.Join(
"testdata", "TestSetP", "#00", "sub-zero-one.golden",
),
content: []byte("number zero-one sub-zero-one strikes again"),
},
{
name: "foobar",
named: "email",
file: filepath.Join(
"testdata", "TestSetP", "foobar", "email.golden",
),
content: []byte("foobar here"),
},
{
name: "foobar",
named: "json",
file: filepath.Join(
"testdata", "TestSetP", "foobar#01", "json.golden",
),
content: []byte("foobar here"),
},
{
name: "john's lost flip-flop",
named: "left",
file: filepath.Join(
"testdata", "TestSetP", "john's_lost_flip-flop",
"left.golden",
),
content: []byte("Did John lose his left flip-flop again?"),
},
{
name: "john's lost flip-flop",
named: "right",
file: filepath.Join(
"testdata", "TestSetP", "john's_lost_flip-flop#01",
"right.golden",
),
content: []byte("Did John lose his right flip-flop again?"),
},
{
name: "thing: it's",
named: "a thing!",
file: filepath.Join(
"testdata", "TestSetP", "thing__it's", "a_thing!.golden",
),
content: []byte("A thing? Really? Are we getting lazy? :P"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f := FileP(t, tt.named)
SetP(t, tt.named, tt.content)
got, err := ioutil.ReadFile(f)
require.NoError(t, err)
assert.Equal(t, tt.file, f)
assert.Equal(t, tt.content, got)
})
}
}
func TestUpdate(t *testing.T) {
for _, tt := range envUpdateFuncTestCases {
t.Run(tt.name, func(t *testing.T) {
envctl.WithClean(tt.env, func() {
got := Update()
assert.Equal(t, tt.want, got)
})
})
}
}
func TestNew(t *testing.T) {
myUpdateFunc := func() bool { return false }
type args struct {
options []Option
}
tests := []struct {
name string
args args
want *golden
}{
{
name: "no options",
args: args{options: nil},
want: &golden{
dirMode: 0o755,
fileMode: 0o644,
suffix: ".golden",
dirname: "testdata",
updateFunc: EnvUpdateFunc,
fs: afero.NewOsFs(),
logOnWrite: true,
},
},
{
name: "all options",
args: args{
options: []Option{
WithDirMode(0o777),
WithFileMode(0o666),
WithSuffix(".gold"),
WithDirname("goldstuff"),
WithUpdateFunc(myUpdateFunc),
WithFs(afero.NewMemMapFs()),
WithSilentWrites(),
},
},
want: &golden{
dirMode: 0o777,
fileMode: 0o666,
suffix: ".gold",
dirname: "goldstuff",
updateFunc: myUpdateFunc,
fs: afero.NewMemMapFs(),
logOnWrite: false,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := New(tt.args.options...)
got, ok := g.(*golden)
require.True(t, ok, "New did not returns a *golden instance")
assert.Equal(t, tt.want.dirMode, got.dirMode)
assert.Equal(t, tt.want.fileMode, got.fileMode)
assert.Equal(t, tt.want.suffix, got.suffix)
assert.Equal(t, tt.want.dirname, got.dirname)
assert.Equal(t, tt.want.logOnWrite, got.logOnWrite)
assert.Equal(t, funcID(tt.want.updateFunc), funcID(got.updateFunc))
assert.IsType(t, tt.want.fs, got.fs)
})
}
}
func Test_golden_File(t *testing.T) {
type fields struct {
suffix *string
dirname *string
}
tests := []struct {
name string
testName string
fields fields
want string
wantAborted bool
wantFailCount int
wantTestOutput []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: "",
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"),
},
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 := &golden{
suffix: *tt.fields.suffix,
dirname: *tt.fields.dirname,
}
mt := mocktesting.NewT(tt.testName)
var got string
mocktesting.Go(func() {
got = g.File(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_golden_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 := afero.NewMemMapFs()
for f, b := range tt.files {
_ = afero.WriteFile(fs, f, b, 0o644)
}
if tt.fields.suffix == nil {
tt.fields.suffix = stringPtr(".golden")
}
if tt.fields.dirname == nil {
tt.fields.dirname = stringPtr("testdata")
}
g := &golden{
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_golden_FileP(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
want string
wantAborted bool
wantFailCount int
wantTestOutput []string
}{
{
name: "top-level",
testName: "TestFooBar",
args: args{name: "yaml"},
want: filepath.Join("testdata", "TestFooBar", "yaml.golden"),
},
{
name: "sub-test",
testName: "TestFooBar/it_is_here",
args: args{name: "json"},
want: filepath.Join(
"testdata", "TestFooBar", "it_is_here", "json.golden",
),
},
{
name: "blank test name",
testName: "",
args: args{name: "json"},
wantAborted: true,
wantFailCount: 1,
wantTestOutput: []string{
"golden: could not determine filename for given " +
"*mocktesting.T instance\n",
},
},
{
name: "custom dirname",
testName: "TestFozBar",
args: args{name: "xml"},
fields: fields{
dirname: stringPtr("goldenfiles"),
},
want: filepath.Join("goldenfiles", "TestFozBar", "xml.golden"),
},
{
name: "custom suffix",
testName: "TestFozBaz",
args: args{name: "toml"},
fields: fields{
suffix: stringPtr(".goldfile"),
},
want: filepath.Join("testdata", "TestFozBaz", "toml.goldfile"),
},
{
name: "custom dirname and suffix",
testName: "TestFozBar",
args: args{name: "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`,
args: args{name: "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 := &golden{
suffix: *tt.fields.suffix,
dirname: *tt.fields.dirname,
}
mt := mocktesting.NewT(tt.testName)
var got string
mocktesting.Go(func() {
got = g.FileP(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")
})
}
}
func Test_golden_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 := afero.NewMemMapFs()
for f, b := range tt.files {
_ = afero.WriteFile(fs, f, b, 0o644)
}
if tt.fields.suffix == nil {
tt.fields.suffix = stringPtr(".golden")
}
if tt.fields.dirname == nil {
tt.fields.dirname = stringPtr("testdata")
}
g := &golden{
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")
})
}
}