1
1
package testutils
2
2
3
3
import (
4
+ "io/fs"
5
+ "maps"
4
6
"os"
5
7
"path/filepath"
8
+ "slices"
6
9
"strings"
10
+ "sync"
7
11
"testing"
8
12
9
13
"github.com/stretchr/testify/require"
@@ -25,7 +29,8 @@ func init() {
25
29
}
26
30
27
31
type goldenOptions struct {
28
- goldenPath string
32
+ goldenPath string
33
+ goldenTracker * GoldenTracker
29
34
}
30
35
31
36
// GoldenOption is a supported option reference to change the golden files comparison.
@@ -40,9 +45,16 @@ func WithGoldenPath(path string) GoldenOption {
40
45
}
41
46
}
42
47
43
- // LoadWithUpdateFromGolden loads the element from a plaintext golden file.
44
- // It will update the file if the update flag is used prior to loading it.
45
- func LoadWithUpdateFromGolden (t * testing.T , data string , opts ... GoldenOption ) string {
48
+ // WithGoldenTracker sets the golden tracker to mark the golden as used.
49
+ func WithGoldenTracker (gt * GoldenTracker ) GoldenOption {
50
+ return func (o * goldenOptions ) {
51
+ if gt != nil {
52
+ o .goldenTracker = gt
53
+ }
54
+ }
55
+ }
56
+
57
+ func parseOptions (t * testing.T , opts ... GoldenOption ) goldenOptions {
46
58
t .Helper ()
47
59
48
60
o := goldenOptions {
@@ -53,6 +65,16 @@ func LoadWithUpdateFromGolden(t *testing.T, data string, opts ...GoldenOption) s
53
65
opt (& o )
54
66
}
55
67
68
+ return o
69
+ }
70
+
71
+ // LoadWithUpdateFromGolden loads the element from a plaintext golden file.
72
+ // It will update the file if the update flag is used prior to loading it.
73
+ func LoadWithUpdateFromGolden (t * testing.T , data string , opts ... GoldenOption ) string {
74
+ t .Helper ()
75
+
76
+ o := parseOptions (t , opts ... )
77
+
56
78
if update {
57
79
t .Logf ("updating golden file %s" , o .goldenPath )
58
80
err := os .MkdirAll (filepath .Dir (o .goldenPath ), 0750 )
@@ -64,6 +86,10 @@ func LoadWithUpdateFromGolden(t *testing.T, data string, opts ...GoldenOption) s
64
86
want , err := os .ReadFile (o .goldenPath )
65
87
require .NoError (t , err , "Cannot load golden file" )
66
88
89
+ if o .goldenTracker != nil {
90
+ o .goldenTracker .MarkUsed (t , WithGoldenPath (o .goldenPath ))
91
+ }
92
+
67
93
return string (want )
68
94
}
69
95
@@ -121,3 +147,82 @@ func GoldenPath(t *testing.T) string {
121
147
func UpdateEnabled () bool {
122
148
return update
123
149
}
150
+
151
+ // GoldenTracker is a structure to track used golden files in tests.
152
+ type GoldenTracker struct {
153
+ mu * sync.Mutex
154
+ used map [string ]struct {}
155
+ }
156
+
157
+ // NewGoldenTracker create a new [GoldenTracker] that checks if golden files are used.
158
+ func NewGoldenTracker (t * testing.T ) GoldenTracker {
159
+ t .Helper ()
160
+
161
+ gt := GoldenTracker {
162
+ mu : & sync.Mutex {},
163
+ used : make (map [string ]struct {}),
164
+ }
165
+
166
+ require .False (t , strings .Contains (t .Name (), "/" ),
167
+ "Setup: %T should be used from a parent test, %s is not" , gt , t .Name ())
168
+
169
+ if slices .ContainsFunc (RunningTests (), func (r string ) bool {
170
+ prefix := t .Name () + "/"
171
+ return strings .HasPrefix (r , prefix ) && len (r ) > len (prefix )
172
+ }) {
173
+ t .Logf ("%T disabled, can't work on partial tests" , gt )
174
+ return gt
175
+ }
176
+
177
+ t .Cleanup (func () {
178
+ if t .Failed () {
179
+ return
180
+ }
181
+
182
+ goldenPath := GoldenPath (t )
183
+
184
+ var entries []string
185
+ err := filepath .WalkDir (goldenPath , func (path string , entry fs.DirEntry , err error ) error {
186
+ require .NoError (t , err , "TearDown: Reading test golden files %s" , path )
187
+ if path == goldenPath {
188
+ return nil
189
+ }
190
+ entries = append (entries , path )
191
+ return nil
192
+ })
193
+ require .NoError (t , err , "TearDown: Walking test golden files %s" , goldenPath )
194
+
195
+ gt .mu .Lock ()
196
+ defer gt .mu .Unlock ()
197
+
198
+ t .Log ("Checking golden files in" , goldenPath )
199
+ var unused []string
200
+ for _ , e := range entries {
201
+ if _ , ok := gt .used [e ]; ok {
202
+ continue
203
+ }
204
+ unused = append (unused , e )
205
+ }
206
+ require .Empty (t , unused , "TearDown: Unused golden files have been found, known are %#v" ,
207
+ slices .Collect (maps .Keys (gt .used )))
208
+ })
209
+
210
+ return gt
211
+ }
212
+
213
+ // MarkUsed marks a golden file as being used.
214
+ func (gt * GoldenTracker ) MarkUsed (t * testing.T , opts ... GoldenOption ) {
215
+ t .Helper ()
216
+
217
+ gt .mu .Lock ()
218
+ defer gt .mu .Unlock ()
219
+
220
+ o := parseOptions (t , opts ... )
221
+ require .Nil (t , o .goldenTracker , "Setup: GoldenTracker option is not supported" )
222
+ gt .used [o .goldenPath ] = struct {}{}
223
+
224
+ basePath := filepath .Dir (o .goldenPath )
225
+ if basePath == GoldenPath (t ) {
226
+ gt .used [basePath ] = struct {}{}
227
+ }
228
+ }
0 commit comments