featurebase/test/glue.go

126 lines
3.6 KiB
Go

// Copyright 2021 Molecula Corp. All rights reserved.
package test
import (
"log"
"os"
"sync"
"testing"
"github.com/featurebasedb/featurebase/v3/testhook"
)
// glue lets us make a thing which isn't a testing.TB, but can be passed
// around to the functions that take testing.TB.
// DirCleaner represents the subset of the testing.TB interface
// we care about, allowing us to take objects which behave like
// that without importing all of testing to get them.
type DirCleaner interface {
Helper()
Errorf(string, ...interface{})
Fatalf(string, ...interface{})
Fatal(...interface{})
Logf(string, ...interface{})
Name() string
TempDir() string
Cleanup(func())
Skip(...interface{})
}
// Verify that a TB is a DirCleaner
var _ DirCleaner = testing.TB(nil)
var _ DirCleaner = &wholeTestRunWrapper{}
// wholeTestRun is a thing that's shaped a bit like testing.TB,
// but it can be used across all the tests, running its cleanup functions
// at the very end of the testing process. this lets us create clusters
// using the same code and logic we would for per-test things, except
// substituting this, and then have a single global post-test-hook run
// their cleanup.
type wholeTestRun struct {
setup sync.Once
mu sync.Mutex
tempDirs []string
cleanupFuncs []func()
}
// wholeTestRunWrapper is a test-specific thing that can refer to the
// global shared state, but also forwards everything *except* TempDir,
// Cleanup, and Logf to the tb it's created with.
type wholeTestRunWrapper struct {
testing.TB
}
// globalT is a system-wide wholeTestRun. the first time it's used, for any
// reason, it registers a cleanup with testhook, which will run at the end
// of the TestMain stuff, but before any previously-registered cleanup
// functions (such as the auditor stuff) so it can ensure that everything's
// been deleted. Basically this exists to let us call `tb.TempDir` on
// things and get a directory which outlives the current test.
var globalT wholeTestRun
// NewWholeTestRun produces a wholeTestRunWrapper around TB, which overrides
// a couple of the TB's methods to get whole-test-friendly behaviors.
func NewWholeTestRun(tb testing.TB) *wholeTestRunWrapper {
return &wholeTestRunWrapper{TB: tb}
}
func (w *wholeTestRun) Teardown() {
w.mu.Lock()
defer w.mu.Unlock()
for _, fn := range w.cleanupFuncs {
fn()
}
for _, path := range w.tempDirs {
// disregard errors because we don't care that much about them;
// we assume RemoveAll probably works unless something's wrong.
// see the comments on the testing package's internal removeAll,
// which suggests the errors only happen on Windows, which we
// don't support.
_ = os.RemoveAll(path)
}
}
func (w *wholeTestRun) Setup() {
w.setup.Do(func() {
testhook.RegisterPostTestHook(func() error {
w.Teardown()
return nil
})
})
}
// We provide a Logf here because the wholeTestRunWrapper could
// be set as a tb-replacement for something which lasts past the
// test that created it. That shouldn't happen, probably.
func (w *wholeTestRunWrapper) Logf(msg string, args ...interface{}) {
log.Printf(msg, args...)
}
func (w *wholeTestRun) TempDir(tb DirCleaner) string {
w.Setup()
w.mu.Lock()
defer w.mu.Unlock()
path, err := os.MkdirTemp("", "test-temp-")
if err != nil {
tb.Fatalf("creating temp dir: %v", err)
}
globalT.tempDirs = append(globalT.tempDirs, path)
return path
}
func (w *wholeTestRun) Cleanup(fn func()) {
w.Setup()
w.mu.Lock()
defer w.mu.Unlock()
w.cleanupFuncs = append(w.cleanupFuncs, fn)
}
func (w *wholeTestRunWrapper) TempDir() string {
return globalT.TempDir(w.TB)
}
func (w *wholeTestRunWrapper) Cleanup(fn func()) {
globalT.Cleanup(fn)
}