-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtesting.go
236 lines (213 loc) · 6.08 KB
/
testing.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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
package testing
import (
"reflect"
"testing"
)
// TestConfig ...
//
// See ret1_test.go file for usage example.
type TestConfig struct {
t *testing.T
// C sets the function used to check if the observed return value (non error) matches
// the expected return value. By default, reflect.DeepEqual is used. However, the github.com/google/go-cmp
// package may also be used. See: https://github.com/google/go-cmp.
C Comparator
// Fatal marks the test as having failed and stops its execution.
//
// See: https://golang.org/pkg/testing/#T.FailNow
Fatal bool
}
// NewTestConfig creates a new TestConfig.
func NewTestConfig(t *testing.T) *TestConfig {
return &TestConfig{
t: t,
}
}
// Run1 is used when testing a function that returns a single (non-error) value. tc is a struct that is
// expected to have a field named: ExpOut. It can be of the relevant type, but if you want to test
// for panics, then it must be interface{} so you can use PanicExpected (which is an error type).
//
// See ret1_test.go file for usage example.
//
// NOTE: Be wary of how Go interprets in-place constants. If you are expecting a float64, then don't type
// 1. Instead type 1.0. See: https://blog.golang.org/constants.
func (tcfg TestConfig) Run1(name string, tc interface{}, f func(t *testing.T) interface{}) {
tcfg.run2(name, tc, func(t *testing.T) (interface{}, error) {
out := f(t)
return out, nil
}, 1)
}
// RunErr is used when testing a function that returns a single error value. tc is a struct that is
// expected to have a field named: ExpErr of type error.
//
// See ret1_test.go file for usage example.
func (tcfg TestConfig) RunErr(name string, tc interface{}, f func(t *testing.T) error) {
tcfg.run2(name, tc, func(t *testing.T) (interface{}, error) {
err := f(t)
return nil, err
}, 2)
}
// Run2 is used when testing a function that returns a value and an error. tc is a struct that is
// expected to have 2 fields named: ExpOut and ExpErr (of type error). ExpOut can be of the relevant
// type, but if you want to test for panics, then it must be interface{} so you can use
// PanicExpected (which is an error type).
//
// See ret2_test.go file for usage example.
//
// NOTE: Be wary of how Go interprets in-place constants. If you are expecting a float64, then don't type
// 1. Instead type 1.0. See: https://blog.golang.org/constants.
func (tcfg TestConfig) Run2(name string, tc interface{}, f func(t *testing.T) (interface{}, error)) {
tcfg.run2(name, tc, f, 0)
}
func (tcfg TestConfig) run2(name string, tc interface{}, f func(t *testing.T) (interface{}, error), mode int) {
comparator := tcfg.C
if comparator == nil {
comparator = reflect.DeepEqual
}
var errChecker errComparator
errChecker = deepEqual
// Expected Error
_expErr, found := structVal(tc, "ExpErr")
if !found {
if mode != 1 {
panic("ExpErr field not found in test case")
}
}
expErr, _ := _expErr.(error)
if expErr == CustomTest {
tcfg.t.Run(name, func(t *testing.T) { f(t) })
return
}
// Expected output value
expOut, found := structVal(tc, "ExpOut")
if !found {
if mode != 2 {
panic("ExpOut field not found in test case")
}
}
//
// Expected Value
//
var neq = Not{PanicExpected}
if expOut == CustomTest {
tcfg.t.Run(name, func(t *testing.T) { f(t) })
return
} else if expOut == neq {
// expErr = neq
// expOut = nil
} else if expOut == PanicExpected {
expErr = PanicExpected
expOut = nil
}
var notVal bool
if x, ok := expOut.(Not); ok {
notVal = true
expOut = x.Val
compCpy := comparator
comparator = func(x, y interface{}) bool { return !compCpy(x, y) }
}
//
// Expected Error
//
var (
notError bool
any bool
)
if expErr != nil {
err := expErr
FOR:
for {
// Do we recognise this error as an internal type?
switch x := err.(type) {
case Not:
notError = !notError
errCheckerCpy := errChecker
errChecker = func(err, target error) bool { return !errCheckerCpy(err, target) }
if x.Val == nil || x.Val.(error) == nil {
err = nil
break FOR
} else {
err = x.Val.(error)
continue FOR
}
case Is:
errChecker = is
if x.Err == nil {
err = nil
break FOR
} else {
err = x.Err
continue FOR
}
case ErrContains:
err = x
break FOR
default:
switch err {
case PanicExpected:
break FOR
case ErrAny:
// ErrAny & Not together is equivalent to checking for nil
if notError {
notError = !notError
errCheckerCpy := errChecker
errChecker = func(err, target error) bool { return !errCheckerCpy(err, target) }
err = nil
} else {
any = true
}
break FOR
default:
err = x
break FOR
}
}
}
expErr = err
}
tcfg.t.Run(name, func(t *testing.T) {
gotVal, gotErr := func(t *testing.T) (gotVal interface{}, gotErr error) {
defer func() {
if recover() != nil {
gotErr = PanicExpected
}
}()
return f(t)
}(t)
if any {
if !((expErr.(error) == nil && gotErr == nil) || (expErr.(error) != nil && gotErr != nil) && comparator(gotVal, expOut)) {
if mode == 0 {
t.Logf("got (%s, %s) ; want (%s, %s)", fmtVal(gotVal, false), fmtError(gotErr, false), fmtVal(expOut, notVal), fmtError(expErr, notError))
} else {
if expErr == PanicExpected || expErr == neq {
t.Logf("got %s ; want %s", fmtVal(gotVal, false), fmtError(expErr, notError))
} else {
t.Logf("got %s ; want %s", fmtVal(gotVal, false), fmtVal(expOut, notVal))
}
}
if tcfg.Fatal {
t.FailNow()
} else {
t.Fail()
}
}
return
}
if !(errChecker(gotErr, expErr) && (gotErr == PanicExpected || comparator(gotVal, expOut))) {
if mode == 0 {
t.Logf("got (%s, %s) ; want (%s, %s)", fmtVal(gotVal, false), fmtError(gotErr, false), fmtVal(expOut, notVal), fmtError(expErr, notError))
} else {
if expErr == PanicExpected || expErr == neq {
t.Logf("got %s ; want %s", fmtVal(gotVal, false), fmtError(expErr, notError))
} else {
t.Logf("got %s ; want %s", fmtVal(gotVal, false), fmtVal(expOut, notVal))
}
}
if tcfg.Fatal {
t.FailNow()
} else {
t.Fail()
}
}
})
}