This repository has been archived by the owner on Oct 31, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 8
/
testing.go
280 lines (243 loc) · 6.74 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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
package testing
import (
"fmt"
"io/ioutil"
"log"
"os"
"runtime"
"strings"
"sync"
"sync/atomic"
)
// T is the interface that mimics the standard library *testing.T.
//
// In unit tests you can just pass a *testing.T struct. At runtime, outside
// of tests, you can pass in a RuntimeT struct from this package.
type T interface {
Cleanup(func())
Error(args ...interface{})
Errorf(format string, args ...interface{})
Fail()
FailNow()
Failed() bool
Fatal(args ...interface{})
Fatalf(format string, args ...interface{})
Helper()
Log(args ...interface{})
Logf(format string, args ...interface{})
Name() string
Parallel()
Setenv(key string, value string)
Skip(args ...interface{})
SkipNow()
Skipf(format string, args ...interface{})
Skipped() bool
TempDir() string
}
// TB is the interface common to T and B, copied from the standard library
// *testing.TB.
//
// This interface should be used as the type of the testing argument to any
// test helper function that exists in the main codebase, which may be invoked
// by tests of type *testing.T and *testing.B.
type TB interface {
Cleanup(func())
Error(args ...interface{})
Errorf(format string, args ...interface{})
Fail()
FailNow()
Failed() bool
Fatal(args ...interface{})
Fatalf(format string, args ...interface{})
Helper()
Log(args ...interface{})
Logf(format string, args ...interface{})
Name() string
Setenv(key string, value string)
Skip(args ...interface{})
SkipNow()
Skipf(format string, args ...interface{})
Skipped() bool
TempDir() string
}
var tempDirReplacer struct {
sync.Once
r *strings.Replacer
}
// RuntimeT implements T and can be instantiated and run at runtime to
// mimic *testing.T behavior. Unlike *testing.T, this will simply panic
// for calls to Fatal. For calls to Error, you'll have to check the errors
// list to determine whether to exit yourself.
//
// Parallel does not do anything.
type RuntimeT struct {
mu sync.RWMutex // guards this group of fields
skipped bool
failed bool
isEnvSet bool
isParallel bool
tempDirOnce sync.Once
tempDir string
tempDirErr error
tempDirSeq int32
cleanups []func() // optional functions to be called at the end of the test
cleanupName string // Name of the cleanup function.
cleanupPc []uintptr // The stack trace at the point where Cleanup was called.
}
// The maximum number of stack frames to go through when skipping helper functions for
// the purpose of decorating log messages.
const maxStackLen = 50
func (t *RuntimeT) Error(args ...interface{}) {
log.Println(fmt.Sprintln(args...))
t.Fail()
}
func (t *RuntimeT) Errorf(format string, args ...interface{}) {
log.Printf(format, args...)
t.Fail()
}
func (t *RuntimeT) Fail() {
t.failed = true
}
func (t *RuntimeT) FailNow() {
panic("testing.T failed, see logs for output (if any)")
}
func (t *RuntimeT) Failed() bool {
return t.failed
}
func (t *RuntimeT) Fatal(args ...interface{}) {
log.Print(args...)
t.FailNow()
}
func (t *RuntimeT) Fatalf(format string, args ...interface{}) {
log.Printf(format, args...)
t.FailNow()
}
func (t *RuntimeT) Log(args ...interface{}) {
log.Println(fmt.Sprintln(args...))
}
func (t *RuntimeT) Logf(format string, args ...interface{}) {
log.Println(fmt.Sprintf(format, args...))
}
func (t *RuntimeT) Name() string {
return ""
}
func (t *RuntimeT) Parallel() {
if t.isEnvSet {
panic("t.Parallel called after t.Setenv; cannot set environment variables in parallel tests")
}
t.isParallel = true
}
func (t *RuntimeT) Setenv(key string, value string) {
if t.isParallel {
panic("t.Setenv called after t.Parallel; cannot set environment variables in parallel tests")
}
t.isEnvSet = true
prevValue, ok := os.LookupEnv(key)
if err := os.Setenv(key, value); err != nil {
t.Fatalf("cannot set environment variable: %v", err)
}
if ok {
t.Cleanup(func() {
os.Setenv(key, prevValue)
})
} else {
t.Cleanup(func() {
os.Unsetenv(key)
})
}
}
func (t *RuntimeT) Skip(args ...interface{}) {
log.Print(args...)
t.SkipNow()
}
func (t *RuntimeT) SkipNow() {
t.skipped = true
}
func (t *RuntimeT) Skipf(format string, args ...interface{}) {
log.Printf(format, args...)
t.SkipNow()
}
func (t *RuntimeT) Skipped() bool {
return t.skipped
}
// TempDir returns a temporary directory for the test to use.
// The directory is automatically removed by Cleanup when the test and
// all its subtests complete.
// Each subsequent call to t.TempDir returns a unique directory;
// if the directory creation fails, TempDir terminates the test by calling Fatal.
//
// This logic is copied from the standard go library
func (t *RuntimeT) TempDir() string {
// Use a single parent directory for all the temporary directories
// created by a test, each numbered sequentially.
t.tempDirOnce.Do(func() {
t.Helper()
// ioutil.TempDir doesn't like path separators in its pattern,
// so mangle the name to accommodate subtests.
tempDirReplacer.Do(func() {
tempDirReplacer.r = strings.NewReplacer("/", "_", "\\", "_", ":", "_")
})
pattern := tempDirReplacer.r.Replace(t.Name())
t.tempDir, t.tempDirErr = ioutil.TempDir("", pattern)
if t.tempDirErr == nil {
t.Cleanup(func() {
if err := os.RemoveAll(t.tempDir); err != nil {
t.Errorf("TempDir RemoveAll cleanup: %v", err)
}
})
}
})
if t.tempDirErr != nil {
t.Fatalf("TempDir: %v", t.tempDirErr)
}
seq := atomic.AddInt32(&t.tempDirSeq, 1)
dir := fmt.Sprintf("%s%c%03d", t.tempDir, os.PathSeparator, seq)
if err := os.Mkdir(dir, 0777); err != nil {
t.Fatalf("TempDir: %v", err)
}
return dir
}
func (t *RuntimeT) Helper() {}
// Cleanup registers a function to be called when the test and all its
// subtests complete. Cleanup functions will be called in last added,
// first called order.
//
// This logic is copied from the standard go library
func (t *RuntimeT) Cleanup(f func()) {
var pc [maxStackLen]uintptr
// Skip two extra frames to account for this function and runtime.Callers itself.
n := runtime.Callers(2, pc[:])
cleanupPc := pc[:n]
fn := func() {
defer func() {
t.mu.Lock()
defer t.mu.Unlock()
t.cleanupName = ""
t.cleanupPc = nil
}()
name := callerName(0)
t.mu.Lock()
t.cleanupName = name
t.cleanupPc = cleanupPc
t.mu.Unlock()
f()
}
t.mu.Lock()
defer t.mu.Unlock()
t.cleanups = append(t.cleanups, fn)
}
// callerName gives the function name (qualified with a package path)
// for the caller after skip frames (where 0 means the current function).
//
// This logic is copied from the standard go library
func callerName(skip int) string {
// Make room for the skip PC.
var pc [1]uintptr
n := runtime.Callers(skip+2, pc[:]) // skip + runtime.Callers + callerName
if n == 0 {
panic("testing: zero callers found")
}
frames := runtime.CallersFrames(pc[:n])
frame, _ := frames.Next()
return frame.Function
}