mirror of https://github.com/coder/coder.git
130 lines
3.0 KiB
Go
130 lines
3.0 KiB
Go
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
|
|
}
|