golden.go

 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}