2022-12-08 13:05:19 -03:00

143 lines
3.6 KiB
Go

// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License.AGPL.txt in the project root for license information.
package testing
import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"io/fs"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/go-test/deep"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)
var update = flag.Bool("update", false, "update .golden files")
var force = flag.Bool("force", false, "overwrite .golden files even if they already exist")
// FixtureTest is a test that is based on fixture and golden files. This is very convenient to test a largely variable surface with many variants.
type FixtureTest struct {
T *testing.T
Path string
GoldPath func(path string) string
Test FixtureTestFunc
Fixture func() interface{}
Gold func() interface{}
}
// FixtureTestFunc implements the actual fixture test
type FixtureTestFunc func(t *testing.T, fixture interface{}) interface{}
// Run executes the fixture test - do not forget to call this one
func (ft *FixtureTest) Run() {
t := ft.T
fixtures, err := filepath.Glob(ft.Path)
if err != nil {
t.Error("cannot list test fixtures: ", err)
return
}
for _, fn := range fixtures {
t.Run(fn, func(t *testing.T) {
fd, err := os.ReadFile(fn)
if err != nil {
t.Errorf("cannot read %s: %v", fn, err)
return
}
fixture := ft.Fixture()
if typ := reflect.TypeOf(fixture); typ.Kind() != reflect.Ptr {
t.Error("Fixture() did not return a pointer")
return
}
if msg, ok := fixture.(proto.Message); ok {
err = protojson.Unmarshal(fd, msg)
if err != nil {
t.Errorf("cannot unmarshal %s: %v", fn, err)
return
}
} else {
err = json.Unmarshal(fd, fixture)
if err != nil {
t.Errorf("cannot unmarshal %s: %v", fn, err)
return
}
}
result := ft.Test(t, fixture)
if result == nil {
// Test routine is expected to complain using t.Errorf
t.Logf("test routine for %s returned nil - continuing", fn)
return
}
if typ := reflect.TypeOf(result); typ.Kind() != reflect.Ptr {
t.Error("Test() did not return a pointer")
return
}
actual, err := json.MarshalIndent(result, "", " ")
if err != nil {
t.Errorf("cannot marshal status for %s: %v", fn, err)
return
}
goldenFilePath := fmt.Sprintf("%s.golden", strings.TrimSuffix(fn, filepath.Ext(fn)))
if ft.GoldPath != nil {
goldenFilePath = ft.GoldPath(fn)
}
if *update {
if _, err := os.Stat(goldenFilePath); *force || errors.Is(err, fs.ErrNotExist) {
err = os.WriteFile(goldenFilePath, actual, 0600)
if err != nil {
t.Errorf("cannot write gold standard %s: %v", goldenFilePath, err)
return
}
t.Logf("Wrote new gold standard in %s", goldenFilePath)
} else {
t.Logf("Did not overwrite gold standard in %s", goldenFilePath)
}
}
expected, err := os.ReadFile(goldenFilePath)
if err != nil {
t.Errorf("cannot read golden file %s: %v", goldenFilePath, err)
return
}
expected = bytes.TrimSpace(expected)
if !bytes.Equal(actual, expected) {
expectedResult := ft.Gold()
if typ := reflect.TypeOf(expectedResult); typ.Kind() != reflect.Ptr {
t.Error("Gold() did not return a pointer")
return
}
err = json.Unmarshal(expected, expectedResult)
if err != nil {
t.Errorf("cannot unmarshal JSON %s: %v", goldenFilePath, err)
return
}
diff := deep.Equal(expectedResult, result)
if len(diff) > 0 {
t.Errorf("fixture %s: %v", fn, diff)
}
return
}
})
}
}