-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #377 from elisasre/feat/golden
Add golden pkg
- Loading branch information
Showing
2 changed files
with
242 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
// Package golden provides standard way to write tests with golden files. | ||
package golden | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"strings" | ||
|
||
"github.com/elisasre/go-common/v2" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
var overrideTestData = common.StringToBool(os.Getenv("OVERRIDE_TEST_DATA")) | ||
|
||
type T interface { | ||
Errorf(format string, args ...interface{}) | ||
FailNow() | ||
Name() string | ||
} | ||
|
||
// Equal asserts that the golden file content is equal to the data. | ||
func Equal(t T, data []byte) bool { | ||
return assert.Equal(t, File(t, data), data) | ||
} | ||
|
||
// EqualString asserts that the golden file content is equal to the data in string format. | ||
func EqualString(t T, data []byte) bool { | ||
return assert.Equal(t, FileString(t, data), string(data)) | ||
} | ||
|
||
// FileString returns the output of golden.File as a string. | ||
func FileString(t T, data []byte) string { return string(File(t, data)) } | ||
|
||
// File returns the golden file content for the test. | ||
// If OVERRIDE_TEST_DATA env is set to true, the golden file will be created with the content of the data. | ||
// OVERRIDE_TEST_DATA is read only once at the start of the test and it's value is not updated. | ||
// Depending of the test structure the golden file and it's directories arew created in | ||
// ./testdata/{testFuncName}/{subTestName}.golden or ./testdata/{testFuncName}/{testFuncName}.golden. | ||
func File(t T, data []byte) []byte { return file(t, data, overrideTestData) } | ||
|
||
func file(t T, data []byte, override bool) []byte { | ||
split := strings.SplitN(t.Name(), "/", 2) | ||
mainTestName := t.Name() | ||
testName := t.Name() | ||
if len(split) == 2 { | ||
mainTestName = split[0] | ||
testName = strings.ReplaceAll(split[1], "/", "_") | ||
} | ||
|
||
folderName := fmt.Sprintf("./testdata/%s", mainTestName) | ||
fileName := strings.ReplaceAll(fmt.Sprintf("%s/%s.golden", folderName, testName), " ", "_") | ||
if override { | ||
require.NoError(t, os.MkdirAll(folderName, 0o755)) | ||
require.NoError(t, os.WriteFile(fileName, data, 0o600)) | ||
} | ||
|
||
b, err := os.ReadFile(fileName) | ||
require.NoError(t, err) | ||
return b | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
package golden | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
type test struct { | ||
name string | ||
mt mockT | ||
data string | ||
expectedData string | ||
expectedPath string | ||
} | ||
|
||
func TestFileString(t *testing.T) { | ||
t.Cleanup(func() { assert.NoError(t, os.RemoveAll("./testdata"), "failed to remove testdata") }) | ||
tests := []test{ | ||
{ | ||
name: "plain test func", | ||
mt: mockT{name: "TestPlainFunc"}, | ||
data: "some data", | ||
expectedData: "some data", | ||
expectedPath: "./testdata/TestPlainFunc/TestPlainFunc.golden", | ||
}, | ||
{ | ||
name: "sub test", | ||
mt: mockT{name: "TestFunc/subtest"}, | ||
data: "other data", | ||
expectedData: "other data", | ||
expectedPath: "./testdata/TestFunc/subtest.golden", | ||
}, | ||
{ | ||
name: "second sub test", | ||
mt: mockT{name: "TestFunc/subtest_other"}, | ||
data: "yet another data", | ||
expectedData: "yet another data", | ||
expectedPath: "./testdata/TestFunc/subtest_other.golden", | ||
}, | ||
{ | ||
name: "nested sub test", | ||
mt: mockT{name: "TestFunc/subtest/nested"}, | ||
data: "nested data", | ||
expectedData: "nested data", | ||
expectedPath: "./testdata/TestFunc/subtest_nested.golden", | ||
}, | ||
{ | ||
name: "parent of sub test", | ||
mt: mockT{name: "TestFunc"}, | ||
data: "parent data", | ||
expectedData: "parent data", | ||
expectedPath: "./testdata/TestFunc/TestFunc.golden", | ||
}, | ||
} | ||
|
||
t.Run("create", func(t *testing.T) { | ||
for _, tt := range tests { | ||
tt := tt | ||
t.Run(tt.name, func(t *testing.T) { | ||
mt := &tt.mt | ||
got := string(file(mt, []byte(tt.data), true)) | ||
assertResult(t, tt, mt, got) | ||
}) | ||
} | ||
}) | ||
|
||
t.Run("read only", func(t *testing.T) { | ||
for _, tt := range tests { | ||
tt := tt | ||
t.Run(tt.name, func(t *testing.T) { | ||
mt := &tt.mt | ||
got := FileString(mt, []byte(tt.data)) | ||
assertResult(t, tt, mt, got) | ||
}) | ||
} | ||
}) | ||
|
||
const suffix = " override" | ||
|
||
t.Run("override", func(t *testing.T) { | ||
for _, tt := range tests { | ||
tt := tt | ||
tt.data += suffix | ||
tt.expectedData += suffix | ||
t.Run(tt.name, func(t *testing.T) { | ||
mt := &tt.mt | ||
got := string(file(mt, []byte(tt.data), true)) | ||
assertResult(t, tt, mt, got) | ||
}) | ||
} | ||
}) | ||
|
||
t.Run("read only after override", func(t *testing.T) { | ||
for _, tt := range tests { | ||
tt := tt | ||
tt.data += suffix | ||
tt.expectedData += suffix | ||
t.Run(tt.name, func(t *testing.T) { | ||
mt := &tt.mt | ||
got := FileString(mt, []byte(tt.data)) | ||
assertResult(t, tt, mt, got) | ||
}) | ||
} | ||
}) | ||
} | ||
|
||
func TestFolderDoesNotExist(t *testing.T) { | ||
mt := mockT{name: "TestDirFail"} | ||
got := File(&mt, []byte("data")) | ||
assert.Empty(t, got) | ||
assert.True(t, mt.failed) | ||
assert.Contains(t, mt.msg, "open ./testdata/TestDirFail/TestDirFail.golden: no such file or directory") | ||
assert.NoDirExists(t, "./testdata/TestDirFail") | ||
} | ||
|
||
func TestEqual(t *testing.T) { | ||
t.Cleanup(func() { assert.NoError(t, os.RemoveAll("./testdata"), "failed to remove testdata") }) | ||
|
||
data := []byte("some data") | ||
assert.NoError(t, os.MkdirAll("./testdata/TestSomeBytes", 0o755)) | ||
assert.NoError(t, os.WriteFile("./testdata/TestSomeBytes/TestSomeBytes.golden", data, 0o600)) | ||
|
||
mt := mockT{name: "TestSomeBytes"} | ||
got := Equal(&mt, data) | ||
assert.True(t, got) | ||
assert.Empty(t, mt.msg) | ||
assert.False(t, mt.failed) | ||
} | ||
|
||
func TestEqualString(t *testing.T) { | ||
t.Cleanup(func() { assert.NoError(t, os.RemoveAll("./testdata"), "failed to remove testdata") }) | ||
|
||
data := []byte("some string") | ||
assert.NoError(t, os.MkdirAll("./testdata/TestSomeString", 0o755)) | ||
assert.NoError(t, os.WriteFile("./testdata/TestSomeString/TestSomeString.golden", data, 0o600)) | ||
|
||
mt := mockT{name: "TestSomeString"} | ||
got := EqualString(&mt, data) | ||
assert.True(t, got) | ||
assert.Empty(t, mt.msg) | ||
assert.False(t, mt.failed) | ||
} | ||
|
||
func TestEqualS_No_Match(t *testing.T) { | ||
t.Cleanup(func() { assert.NoError(t, os.RemoveAll("./testdata"), "failed to remove testdata") }) | ||
|
||
data := []byte("some string") | ||
assert.NoError(t, os.MkdirAll("./testdata/TestSomeString", 0o755)) | ||
assert.NoError(t, os.WriteFile("./testdata/TestSomeString/TestSomeString.golden", data, 0o600)) | ||
|
||
mt := mockT{name: "TestSomeString"} | ||
got := EqualString(&mt, []byte("other string")) | ||
assert.False(t, got) | ||
assert.Contains(t, mt.msg, "Not equal:") | ||
assert.False(t, mt.failed) // In case of assert failure, FailNow is not called | ||
} | ||
|
||
func assertResult(t *testing.T, tt test, mt *mockT, got string) { | ||
t.Helper() | ||
assert.Equal(t, tt.expectedData, got) | ||
assert.Empty(t, mt.msg) | ||
assert.False(t, mt.failed) | ||
assert.FileExists(t, tt.expectedPath) | ||
b, err := os.ReadFile(tt.expectedPath) | ||
require.NoError(t, err) | ||
assert.Equal(t, tt.expectedData, string(b)) | ||
} | ||
|
||
type mockT struct { | ||
name string | ||
failed bool | ||
msg string | ||
} | ||
|
||
func (m *mockT) Name() string { return m.name } | ||
func (m *mockT) Errorf(f string, args ...interface{}) { m.msg = fmt.Sprintf(f, args...) } | ||
func (m *mockT) FailNow() { m.failed = true } |