forked from strangelove-ventures/interchaintest
-
Notifications
You must be signed in to change notification settings - Fork 0
/
reporter.go
222 lines (185 loc) · 5.12 KB
/
reporter.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
package testreporter
import (
"encoding/json"
"fmt"
"io"
"time"
)
// T is a subset of testing.TB,
// representing only the methods required by the reporter.
type T interface {
Name() string
Cleanup(func())
Skip(...any)
Parallel()
Failed() bool
Skipped() bool
}
type Reporter struct {
w io.WriteCloser
in chan Message
writerDone chan error
}
func NewReporter(w io.WriteCloser) *Reporter {
r := &Reporter{
w: w,
in: make(chan Message, 256), // Arbitrary size that seems unlikely to be filled.
writerDone: make(chan error, 1),
}
go r.write()
r.in <- BeginSuiteMessage{StartedAt: time.Now()}
return r
}
// write runs in its own goroutine to continually output reporting messages.
// Allowing all writes to happen in a single goroutine avoids any lock contention
// that could happen with a mutex guarding concurrent writes to the io.Writer.
func (r *Reporter) write() {
enc := json.NewEncoder(r.w)
enc.SetEscapeHTML(false)
for m := range r.in {
if err := enc.Encode(JSONMessage(m)); err != nil {
panic(fmt.Errorf("reporter failed to encode message; tests cannot continue: %w", err))
}
}
r.writerDone <- r.w.Close()
}
// Close closes the reporter and blocks until its results are flushed
// to the underlying writer.
func (r *Reporter) Close() error {
r.in <- FinishSuiteMessage{
FinishedAt: time.Now(),
}
close(r.in)
return <-r.writerDone
}
// trackTest tracks the test start and finish time.
// It also records which labels are present on the test.
func (r *Reporter) TrackTest(t T) {
name := t.Name()
r.in <- BeginTestMessage{
Name: name,
StartedAt: time.Now(),
}
t.Cleanup(func() {
r.in <- FinishTestMessage{
Name: name,
FinishedAt: time.Now(),
Failed: t.Failed(),
Skipped: t.Skipped(),
}
})
}
// TrackParallel tracks when the pause begins for a parallel test
// and when it continues to resume.
func (r *Reporter) TrackParallel(t T) {
name := t.Name()
r.in <- PauseTestMessage{
Name: name,
When: time.Now(),
}
t.Parallel()
r.in <- ContinueTestMessage{
Name: name,
When: time.Now(),
}
}
// TrackSkip records a the reason for a test being skipped,
// and calls t.Skip.
func (r *Reporter) TrackSkip(t T, format string, args ...any) {
now := time.Now()
msg := fmt.Sprintf(format, args...)
r.in <- TestSkipMessage{
Name: t.Name(),
When: now,
Message: msg,
}
t.Skip(msg)
}
// RelayerExecReporter returns a RelayerExecReporter associated with t.
func (r *Reporter) RelayerExecReporter(t T) *RelayerExecReporter {
return &RelayerExecReporter{r: r, testName: t.Name()}
}
// RelayerExecReporter provides one method that satisfies the ibc.RelayerExecReporter interface.
// Instances of RelayerExecReporter must be retrieved through (*Reporter).RelayerExecReporter.
type RelayerExecReporter struct {
r *Reporter
testName string
}
// TrackRelayerExec tracks the execution of an individual relayer command.
func (r *RelayerExecReporter) TrackRelayerExec(
containerName string,
command []string,
stdout, stderr string,
exitCode int,
startedAt, finishedAt time.Time,
err error,
) {
var errMsg string
if err != nil {
errMsg = err.Error()
}
r.r.in <- RelayerExecMessage{
Name: r.testName,
StartedAt: startedAt,
FinishedAt: finishedAt,
ContainerName: containerName,
Command: command,
Stdout: stdout,
Stderr: stderr,
ExitCode: exitCode,
Error: errMsg,
}
}
// TestifyT returns a TestifyReporter which will track logged errors in test.
// Typically you will use this with the New method on the require or assert package:
//
// req := require.New(reporter.TestifyT(t))
// // ...
// req.NoError(err, "failed to foo the bar")
func (r *Reporter) TestifyT(t TestifyT) *TestifyReporter {
return &TestifyReporter{r: r, t: t}
}
// TestifyT is a superset of the testify/require.TestingT interface.
type TestifyT interface {
Name() string
Errorf(format string, args ...any)
FailNow()
}
// TestifyReporter wraps a Reporter to satisfy the testify/require.TestingT interface.
// This allows the reporter to track logged errors.
type TestifyReporter struct {
r *Reporter
t TestifyT
}
// Errorf records the error message in r's Reporter
// and then passes through to r's underlying TestifyT.
func (r *TestifyReporter) Errorf(format string, args ...any) {
now := time.Now()
r.r.in <- TestErrorMessage{
Name: r.t.Name(),
Message: fmt.Sprintf(format, args...),
When: now,
}
r.t.Errorf(format, args...)
}
// FailNow passes through to r's TestifyT.
// It does not need to log another message
// because r's Reporter should be tracking the test already.
func (r *TestifyReporter) FailNow() {
r.t.FailNow()
}
// NewNopReporter returns a reporter that does not write anywhere.
func NewNopReporter() *Reporter {
return NewReporter(newNopWriteCloser())
}
// nopWriteCloser is a no-op io.WriteCloser used to satisfy the interchaintest TestReporter type.
// Because the relayer is used in-process, all logs are simply streamed to the test log.
type nopWriteCloser struct {
io.Writer
}
func (nopWriteCloser) Close() error {
return nil
}
func newNopWriteCloser() io.WriteCloser {
return nopWriteCloser{Writer: io.Discard}
}