Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/common-go/testing/fixtures.go
2498 views
1
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package testing
6
7
import (
8
"bytes"
9
"encoding/json"
10
"errors"
11
"flag"
12
"fmt"
13
"io/fs"
14
"os"
15
"path/filepath"
16
"reflect"
17
"strings"
18
"testing"
19
20
"github.com/go-test/deep"
21
"google.golang.org/protobuf/encoding/protojson"
22
"google.golang.org/protobuf/proto"
23
)
24
25
var update = flag.Bool("update", false, "update .golden files")
26
var force = flag.Bool("force", false, "overwrite .golden files even if they already exist")
27
28
// 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.
29
type FixtureTest struct {
30
T *testing.T
31
Path string
32
GoldPath func(path string) string
33
Test FixtureTestFunc
34
Fixture func() interface{}
35
Gold func() interface{}
36
}
37
38
// FixtureTestFunc implements the actual fixture test
39
type FixtureTestFunc func(t *testing.T, fixture interface{}) interface{}
40
41
// Run executes the fixture test - do not forget to call this one
42
func (ft *FixtureTest) Run() {
43
t := ft.T
44
45
fixtures, err := filepath.Glob(ft.Path)
46
if err != nil {
47
t.Error("cannot list test fixtures: ", err)
48
return
49
}
50
51
for _, fn := range fixtures {
52
t.Run(fn, func(t *testing.T) {
53
fd, err := os.ReadFile(fn)
54
if err != nil {
55
t.Errorf("cannot read %s: %v", fn, err)
56
return
57
}
58
59
fixture := ft.Fixture()
60
if typ := reflect.TypeOf(fixture); typ.Kind() != reflect.Ptr {
61
t.Error("Fixture() did not return a pointer")
62
return
63
}
64
if msg, ok := fixture.(proto.Message); ok {
65
err = protojson.Unmarshal(fd, msg)
66
if err != nil {
67
t.Errorf("cannot unmarshal %s: %v", fn, err)
68
return
69
}
70
} else {
71
err = json.Unmarshal(fd, fixture)
72
if err != nil {
73
t.Errorf("cannot unmarshal %s: %v", fn, err)
74
return
75
}
76
}
77
78
result := ft.Test(t, fixture)
79
if result == nil {
80
// Test routine is expected to complain using t.Errorf
81
t.Logf("test routine for %s returned nil - continuing", fn)
82
return
83
}
84
if typ := reflect.TypeOf(result); typ.Kind() != reflect.Ptr {
85
t.Error("Test() did not return a pointer")
86
return
87
}
88
89
actual, err := json.MarshalIndent(result, "", " ")
90
if err != nil {
91
t.Errorf("cannot marshal status for %s: %v", fn, err)
92
return
93
}
94
95
goldenFilePath := fmt.Sprintf("%s.golden", strings.TrimSuffix(fn, filepath.Ext(fn)))
96
if ft.GoldPath != nil {
97
goldenFilePath = ft.GoldPath(fn)
98
}
99
if *update {
100
if _, err := os.Stat(goldenFilePath); *force || errors.Is(err, fs.ErrNotExist) {
101
err = os.WriteFile(goldenFilePath, actual, 0600)
102
if err != nil {
103
t.Errorf("cannot write gold standard %s: %v", goldenFilePath, err)
104
return
105
}
106
107
t.Logf("Wrote new gold standard in %s", goldenFilePath)
108
} else {
109
t.Logf("Did not overwrite gold standard in %s", goldenFilePath)
110
}
111
}
112
113
expected, err := os.ReadFile(goldenFilePath)
114
if err != nil {
115
t.Errorf("cannot read golden file %s: %v", goldenFilePath, err)
116
return
117
}
118
expected = bytes.TrimSpace(expected)
119
120
if !bytes.Equal(actual, expected) {
121
expectedResult := ft.Gold()
122
if typ := reflect.TypeOf(expectedResult); typ.Kind() != reflect.Ptr {
123
t.Error("Gold() did not return a pointer")
124
return
125
}
126
127
err = json.Unmarshal(expected, expectedResult)
128
if err != nil {
129
t.Errorf("cannot unmarshal JSON %s: %v", goldenFilePath, err)
130
return
131
}
132
133
diff := deep.Equal(expectedResult, result)
134
if len(diff) > 0 {
135
t.Errorf("fixture %s: %v", fn, diff)
136
}
137
138
return
139
}
140
})
141
}
142
}
143
144