1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
|
// Package gold implements golden files.
package gold
import (
"bytes"
"encoding/hex"
"flag"
"os"
"path"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
const defaultDir = "_golden"
// _update reports whether golden files update is requested.
//
// Call Init() in TestMain to propagate.
var _update bool
// _clean reports whether all golden files should be removed before
// running tests.
//
// Call Init() in TestMain to propagate.
var _clean bool
// Init should be called in TestMain.
func Init() {
flag.BoolVar(&_update, "update", false, "update golden files")
flag.BoolVar(&_clean, "clean", true, "clean golden files")
flag.Parse()
if _clean && _update {
dir, err := os.ReadDir(defaultDir)
if err != nil {
// Ignore any error.
return
}
for _, f := range dir {
p := filepath.Join(defaultDir, f.Name())
if err := os.RemoveAll(p); err != nil {
panic(err)
}
}
}
}
// filePath returns path to golden file.
func filePath(elems ...string) string {
return filepath.Join(
append([]string{defaultDir}, elems...)...,
)
}
func exists(t testing.TB, elems ...string) bool {
t.Helper()
p := filePath(elems...)
data, err := os.Stat(p)
if err == nil {
if data.IsDir() {
t.Fatalf("golden file %s is directory", p)
}
return true
}
if os.IsNotExist(err) {
return false
}
// Unexpected error
t.Fatal(err)
return false
}
// readFile reads golden file.
func readFile(t testing.TB, elems ...string) []byte {
t.Helper()
p := filePath(elems...)
data, err := os.ReadFile(p) // nolint:gosec // testing
if err != nil {
t.Fatalf("golden file %s: %+v", path.Join(elems...), err)
}
return data
}
func writeFile(t testing.TB, data []byte, elems ...string) {
t.Helper()
p := filePath(elems...)
require.NoError(t, os.MkdirAll(path.Dir(p), 0o700), "make dir for golden files")
require.NoError(t, os.WriteFile(p, data, 0o600), "write golden file")
}
// normalizeNewlines normalizes \r\n (windows) and \r (mac)
// into \n (unix).
func normalizeNewlines(d []byte) []byte {
// replace CR LF \r\n (windows) with LF \n (unix)
d = bytes.ReplaceAll(d, []byte{13, 10}, []byte{10})
// replace CF \r (mac) with LF \n (unix)
d = bytes.ReplaceAll(d, []byte{13}, []byte{10})
return d
}
// Str checks text golden file.
func Str(t testing.TB, s string, name ...string) {
t.Helper()
if len(name) == 0 {
name = []string{"file.txt"}
}
update := _update
if !exists(t, name...) {
t.Log("Populating initial golden file")
update = true
}
if update {
writeFile(t, []byte(s), name...)
}
data := readFile(t, name...)
data = normalizeNewlines(data)
require.Equal(t, string(data), s, "golden file text mismatch")
}
// Bytes check binary golden file.
func Bytes(t testing.TB, data []byte, name ...string) {
t.Helper()
if len(name) == 0 {
name = []string{"file"}
}
// Adding ".raw" prefix to visually distinguish hex and raw.
last := len(name) - 1
rawName := append([]string{}, name...)
rawName[last] += ".raw"
update := _update
if !exists(t, rawName...) {
t.Log("Populating initial golden file")
update = true
}
if update {
// Writing hex dump next to raw binary to make
// git diff more understandable on golden file
// updates.
dump := hex.Dump(data)
dumpName := append([]string{}, name...)
dumpName[last] += ".hex"
writeFile(t, []byte(dump), dumpName...)
// Writing raw file.
writeFile(t, data, rawName...)
}
expected := readFile(t, rawName...)
require.Equal(t, expected, data, "golden file binary mismatch")
}
|