-
Notifications
You must be signed in to change notification settings - Fork 579
/
harness.go
129 lines (111 loc) · 2.96 KB
/
harness.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
package harness
import (
"context"
"sync"
"time"
"github.com/hashicorp/go-multierror"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd/tracing"
)
// TestHarness runs a bunch of registered test runs using the given execution
// strategies.
type TestHarness struct {
runStrategy ExecutionStrategy
cleanupStrategy ExecutionStrategy
mut *sync.Mutex
runIDs map[string]struct{}
runs []*TestRun
started bool
done chan struct{}
elapsed time.Duration
}
// NewTestHarness creates a new TestHarness with the given execution strategies.
func NewTestHarness(runStrategy, cleanupStrategy ExecutionStrategy) *TestHarness {
return &TestHarness{
runStrategy: runStrategy,
cleanupStrategy: cleanupStrategy,
mut: new(sync.Mutex),
runIDs: map[string]struct{}{},
runs: []*TestRun{},
done: make(chan struct{}),
}
}
// Run runs the registered tests using the given ExecutionStrategy. The provided
// context can be used to cancel or set a deadline for the test run. Blocks
// until the tests have finished and returns the test execution error (not
// individual run errors).
//
// Panics if called more than once.
func (h *TestHarness) Run(ctx context.Context) (err error) {
ctx, span := tracing.StartSpan(ctx)
defer span.End()
h.mut.Lock()
if h.started {
h.mut.Unlock()
panic("harness is already started")
}
h.started = true
h.mut.Unlock()
runFns := make([]TestFn, len(h.runs))
for i, run := range h.runs {
runFns[i] = run.Run
}
defer close(h.done)
defer func() {
e := recover()
if e != nil {
err = xerrors.Errorf("panic in harness.Run: %+v", e)
}
}()
start := time.Now()
defer func() {
h.mut.Lock()
defer h.mut.Unlock()
h.elapsed = time.Since(start)
}()
// We don't care about test failures here since they already get recorded
// by the *TestRun.
_, err = h.runStrategy.Run(ctx, runFns)
//nolint:revive // we use named returns because we mutate it in a defer
return
}
// Cleanup should be called after the test run has finished and results have
// been collected.
func (h *TestHarness) Cleanup(ctx context.Context) (err error) {
h.mut.Lock()
defer h.mut.Unlock()
if !h.started {
panic("harness has not started")
}
select {
case <-h.done:
default:
panic("harness has not finished")
}
cleanupFns := make([]TestFn, len(h.runs))
for i, run := range h.runs {
cleanupFns[i] = run.Cleanup
}
defer func() {
e := recover()
if e != nil {
err = xerrors.Errorf("panic in harness.Cleanup: %+v", e)
}
}()
var cleanupErrs []error
cleanupErrs, err = h.cleanupStrategy.Run(ctx, cleanupFns)
if err != nil {
err = xerrors.Errorf("cleanup strategy error: %w", err)
//nolint:revive // we use named returns because we mutate it in a defer
return
}
var merr error
for _, cleanupErr := range cleanupErrs {
if cleanupErr != nil {
merr = multierror.Append(merr, cleanupErr)
}
}
err = merr
//nolint:revive // we use named returns because we mutate it in a defer
return
}