mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
111 lines
2.6 KiB
Go
111 lines
2.6 KiB
Go
package harness
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/hashicorp/go-multierror"
|
|
"golang.org/x/xerrors"
|
|
)
|
|
|
|
// ExecutionStrategy defines how a TestHarness should execute a set of runs. It
|
|
// essentially defines the concurrency model for a given testing session.
|
|
type ExecutionStrategy interface {
|
|
// Execute runs the given runs in whatever way the strategy wants. An error
|
|
// may only be returned if the strategy has a failure itself, not if any of
|
|
// the runs fail.
|
|
Execute(ctx context.Context, runs []*TestRun) error
|
|
}
|
|
|
|
// TestHarness runs a bunch of registered test runs using the given
|
|
// ExecutionStrategy.
|
|
type TestHarness struct {
|
|
strategy 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 ExecutionStrategy.
|
|
func NewTestHarness(strategy ExecutionStrategy) *TestHarness {
|
|
return &TestHarness{
|
|
strategy: strategy,
|
|
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) {
|
|
h.mut.Lock()
|
|
if h.started {
|
|
h.mut.Unlock()
|
|
panic("harness is already started")
|
|
}
|
|
h.started = true
|
|
h.mut.Unlock()
|
|
|
|
defer close(h.done)
|
|
defer func() {
|
|
e := recover()
|
|
if e != nil {
|
|
err = xerrors.Errorf("execution strategy panicked: %w", e)
|
|
}
|
|
}()
|
|
|
|
start := time.Now()
|
|
defer func() {
|
|
h.mut.Lock()
|
|
defer h.mut.Unlock()
|
|
h.elapsed = time.Since(start)
|
|
}()
|
|
|
|
err = h.strategy.Execute(ctx, h.runs)
|
|
//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")
|
|
}
|
|
|
|
defer func() {
|
|
e := recover()
|
|
if e != nil {
|
|
err = multierror.Append(err, xerrors.Errorf("panic in cleanup: %w", e))
|
|
}
|
|
}()
|
|
|
|
for _, run := range h.runs {
|
|
e := run.Cleanup(ctx)
|
|
if e != nil {
|
|
err = multierror.Append(err, xerrors.Errorf("cleanup for %s failed: %w", run.FullID(), e))
|
|
}
|
|
}
|
|
|
|
//nolint:revive // we use named returns because we mutate it in a defer
|
|
return
|
|
}
|