Path: blob/main/components/common-go/testing/fixtures.go
2498 views
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package testing56import (7"bytes"8"encoding/json"9"errors"10"flag"11"fmt"12"io/fs"13"os"14"path/filepath"15"reflect"16"strings"17"testing"1819"github.com/go-test/deep"20"google.golang.org/protobuf/encoding/protojson"21"google.golang.org/protobuf/proto"22)2324var update = flag.Bool("update", false, "update .golden files")25var force = flag.Bool("force", false, "overwrite .golden files even if they already exist")2627// 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.28type FixtureTest struct {29T *testing.T30Path string31GoldPath func(path string) string32Test FixtureTestFunc33Fixture func() interface{}34Gold func() interface{}35}3637// FixtureTestFunc implements the actual fixture test38type FixtureTestFunc func(t *testing.T, fixture interface{}) interface{}3940// Run executes the fixture test - do not forget to call this one41func (ft *FixtureTest) Run() {42t := ft.T4344fixtures, err := filepath.Glob(ft.Path)45if err != nil {46t.Error("cannot list test fixtures: ", err)47return48}4950for _, fn := range fixtures {51t.Run(fn, func(t *testing.T) {52fd, err := os.ReadFile(fn)53if err != nil {54t.Errorf("cannot read %s: %v", fn, err)55return56}5758fixture := ft.Fixture()59if typ := reflect.TypeOf(fixture); typ.Kind() != reflect.Ptr {60t.Error("Fixture() did not return a pointer")61return62}63if msg, ok := fixture.(proto.Message); ok {64err = protojson.Unmarshal(fd, msg)65if err != nil {66t.Errorf("cannot unmarshal %s: %v", fn, err)67return68}69} else {70err = json.Unmarshal(fd, fixture)71if err != nil {72t.Errorf("cannot unmarshal %s: %v", fn, err)73return74}75}7677result := ft.Test(t, fixture)78if result == nil {79// Test routine is expected to complain using t.Errorf80t.Logf("test routine for %s returned nil - continuing", fn)81return82}83if typ := reflect.TypeOf(result); typ.Kind() != reflect.Ptr {84t.Error("Test() did not return a pointer")85return86}8788actual, err := json.MarshalIndent(result, "", " ")89if err != nil {90t.Errorf("cannot marshal status for %s: %v", fn, err)91return92}9394goldenFilePath := fmt.Sprintf("%s.golden", strings.TrimSuffix(fn, filepath.Ext(fn)))95if ft.GoldPath != nil {96goldenFilePath = ft.GoldPath(fn)97}98if *update {99if _, err := os.Stat(goldenFilePath); *force || errors.Is(err, fs.ErrNotExist) {100err = os.WriteFile(goldenFilePath, actual, 0600)101if err != nil {102t.Errorf("cannot write gold standard %s: %v", goldenFilePath, err)103return104}105106t.Logf("Wrote new gold standard in %s", goldenFilePath)107} else {108t.Logf("Did not overwrite gold standard in %s", goldenFilePath)109}110}111112expected, err := os.ReadFile(goldenFilePath)113if err != nil {114t.Errorf("cannot read golden file %s: %v", goldenFilePath, err)115return116}117expected = bytes.TrimSpace(expected)118119if !bytes.Equal(actual, expected) {120expectedResult := ft.Gold()121if typ := reflect.TypeOf(expectedResult); typ.Kind() != reflect.Ptr {122t.Error("Gold() did not return a pointer")123return124}125126err = json.Unmarshal(expected, expectedResult)127if err != nil {128t.Errorf("cannot unmarshal JSON %s: %v", goldenFilePath, err)129return130}131132diff := deep.Equal(expectedResult, result)133if len(diff) > 0 {134t.Errorf("fixture %s: %v", fn, diff)135}136137return138}139})140}141}142143144