forked from MarvinJWendt/testza
-
Notifications
You must be signed in to change notification settings - Fork 0
/
snapshot.go
152 lines (135 loc) 路 5 KB
/
snapshot.go
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
package testza
import (
"fmt"
"io/ioutil"
"os"
"path"
"regexp"
"strconv"
"strings"
"github.com/MarvinJWendt/testza/internal"
"github.com/davecgh/go-spew/spew"
"github.com/pterm/pterm"
)
// SnapshotCreate creates a snapshot of an object, which can be validated in future test runs.
// Using this function directly will override previous snapshots with the same name.
// You most likely want to use SnapshotCreateOrValidate.
//
// NOTICE: \r\n will be replaced with \n to make the files consistent between operating systems.
//
// Example:
// testza.SnapshotCreate(t.Name(), objectToBeSnapshotted)
func SnapshotCreate(name string, snapshotObject interface{}) error {
dir := getCurrentScriptDirectory() + "/testdata/snapshots/"
return snapshotCreateForDir(dir, name, snapshotObject)
}
func snapshotCreateForDir(dir string, name string, snapshotObject interface{}) error {
err := os.MkdirAll(dir, 0755)
if err != nil {
return fmt.Errorf("creating snapshot failed: %w", err)
}
originalSpewConfig := spew.Config.DisablePointerAddresses
spew.Config.DisablePointerAddresses = true
dump := strings.ReplaceAll(spew.Sdump(snapshotObject), "\r\n", "\n")
err = ioutil.WriteFile(path.Clean(dir+name+".testza"), []byte(dump), 0755)
if err != nil {
return fmt.Errorf("creating snapshot failed: %w", err)
}
spew.Config.DisablePointerAddresses = originalSpewConfig
return nil
}
// SnapshotValidate validates an already exisiting snapshot of an object.
// You most likely want to use SnapshotCreateOrValidate.
//
// NOTICE: \r\n will be replaced with \n to make the files consistent between operating systems.
//
// Example:
// testza.SnapshotValidate(t, t.Name(), objectToBeValidated)
// testza.SnapshotValidate(t, t.Name(), objectToBeValidated, "Optional message")
func SnapshotValidate(t testRunner, name string, actual interface{}, msg ...interface{}) error {
dir := getCurrentScriptDirectory() + "/testdata/snapshots/"
return snapshotValidateFromDir(dir, t, name, actual, msg...)
}
var snapshotStringMatcher = regexp.MustCompile(`(?m)^\(.+?\)\s\(len=\d+\)\s(".+")$`)
func snapshotValidateFromDir(dir string, t testRunner, name string, actual interface{}, msg ...interface{}) error {
snapshotPath := path.Clean(dir + name + ".testza")
snapshotContent, err := ioutil.ReadFile(snapshotPath)
snapshot := strings.ReplaceAll(string(snapshotContent), "\r\n", "\n")
if err != nil {
return fmt.Errorf("validating snapshot failed: %w", err)
}
originalSpewConfig := spew.Config.DisablePointerAddresses
spew.Config.DisablePointerAddresses = true
if spew.Sdump(actual) != snapshot {
var diffObject *internal.Object
if strActual, ok := actual.(string); ok {
if match := snapshotStringMatcher.FindStringSubmatch(snapshot); len(match) > 0 {
if unquoted, err := strconv.Unquote(match[1]); err == nil {
object := internal.NewDiffObject(unquoted, strActual, true)
diffObject = &object
}
}
}
if diffObject == nil {
object := internal.NewDiffObject(snapshot, spew.Sdump(actual), true)
diffObject = &object
}
internal.Fail(t,
generateMsg(msg,
fmt.Sprintf("Snapshot '%s' failed to validate", name)),
internal.Objects{
*diffObject,
{
Name: "Expected",
NameStyle: pterm.NewStyle(pterm.FgLightGreen),
Data: snapshot,
DataStyle: pterm.NewStyle(pterm.FgGreen),
Raw: true,
},
{
Name: "Actual",
NameStyle: pterm.NewStyle(pterm.FgLightRed),
Data: spew.Sdump(actual),
DataStyle: pterm.NewStyle(pterm.FgRed),
Raw: true,
},
})
}
spew.Config.DisablePointerAddresses = originalSpewConfig
return nil
}
// SnapshotCreateOrValidate creates a snapshot of an object which can be used in future test runs.
// It is good practice to name your snapshots the same as the test they are created in.
// You can do that automatically by using t.Name() as the second parameter, if you are using the inbuilt test system of Go.
// If a snapshot already exists, the function will not create a new one, but validate the exisiting one.
// To re-create a snapshot, you can delete the according file in /testdata/snapshots/.
//
// NOTICE: \r\n will be replaced with \n to make the files consistent between operating systems.
//
// Example:
// testza.SnapshotCreateOrValidate(t, t.Name(), object)
// testza.SnapshotCreateOrValidate(t, t.Name(), object, "Optional Message")
func SnapshotCreateOrValidate(t testRunner, name string, object interface{}, msg ...interface{}) error {
dir := getCurrentScriptDirectory() + "/testdata/snapshots/"
snapshotPath := path.Clean(dir + name + ".testza")
if strings.Contains(name, "/") {
err := os.MkdirAll(path.Dir(snapshotPath), 0755)
if err != nil {
return fmt.Errorf("creating snapshot directories failed: %w", err)
}
}
if _, err := os.Stat(snapshotPath); err == nil {
err = snapshotValidateFromDir(dir, t, name, object, msg...)
if err != nil {
return err
}
} else if os.IsNotExist(err) {
err = snapshotCreateForDir(dir, name, object)
if err != nil {
return err
}
} else {
return err
}
return nil
}