Skip to content

Commit

Permalink
Merge pull request #377 from elisasre/feat/golden
Browse files Browse the repository at this point in the history
Add golden pkg
  • Loading branch information
heppu authored Sep 3, 2024
2 parents 6dd0e56 + 83518da commit 0373ab1
Show file tree
Hide file tree
Showing 2 changed files with 242 additions and 0 deletions.
61 changes: 61 additions & 0 deletions v2/golden/file.go
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
}
181 changes: 181 additions & 0 deletions v2/golden/file_test.go
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 }

0 comments on commit 0373ab1

Please sign in to comment.