1package golden
2
3import (
4 "flag"
5 "os"
6 "path/filepath"
7 "runtime"
8 "strconv"
9 "strings"
10 "testing"
11
12 "github.com/aymanbagabas/go-udiff"
13)
14
15var update = flag.Bool("update", false, "update .golden files")
16
17// RequireEqual is a helper function to assert the given output is
18// the expected from the golden files, printing its diff in case it is not.
19//
20// Golden files contain the raw expected output of your tests, which can
21// contain control codes and escape sequences. When comparing the output of
22// your tests, [RequireEqual] will escape the control codes and sequences
23// before comparing the output with the golden files.
24//
25// You can update the golden files by running your tests with the -update flag.
26func RequireEqual(tb testing.TB, out []byte) {
27 tb.Helper()
28
29 golden := filepath.Join("testdata", tb.Name()+".golden")
30 if *update {
31 if err := os.MkdirAll(filepath.Dir(golden), 0o755); err != nil { //nolint: gomnd
32 tb.Fatal(err)
33 }
34 if err := os.WriteFile(golden, out, 0o600); err != nil { //nolint: gomnd
35 tb.Fatal(err)
36 }
37 }
38
39 goldenBts, err := os.ReadFile(golden)
40 if err != nil {
41 tb.Fatal(err)
42 }
43
44 goldenStr := normalizeWindowsLineBreaks(string(goldenBts))
45 goldenStr = escapeSeqs(goldenStr)
46 outStr := escapeSeqs(string(out))
47
48 diff := udiff.Unified("golden", "run", goldenStr, outStr)
49 if diff != "" {
50 tb.Fatalf("output does not match, expected:\n\n%s\n\ngot:\n\n%s\n\ndiff:\n\n%s", goldenStr, outStr, diff)
51 }
52}
53
54// RequireEqualEscape is a helper function to assert the given output is
55// the expected from the golden files, printing its diff in case it is not.
56//
57// Deprecated: Use [RequireEqual] instead.
58func RequireEqualEscape(tb testing.TB, out []byte, escapes bool) {
59 RequireEqual(tb, out)
60}
61
62// escapeSeqs escapes control codes and escape sequences from the given string.
63// The only preserved exception is the newline character.
64func escapeSeqs(in string) string {
65 s := strings.Split(in, "\n")
66 for i, l := range s {
67 q := strconv.Quote(l)
68 q = strings.TrimPrefix(q, `"`)
69 q = strings.TrimSuffix(q, `"`)
70 s[i] = q
71 }
72 return strings.Join(s, "\n")
73}
74
75// normalizeWindowsLineBreaks replaces all \r\n with \n.
76// This is needed because Git for Windows checks out with \r\n by default.
77func normalizeWindowsLineBreaks(str string) string {
78 if runtime.GOOS == "windows" {
79 return strings.ReplaceAll(str, "\r\n", "\n")
80 }
81 return str
82}