264 lines
4.6 KiB
Go
264 lines
4.6 KiB
Go
package helper
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
type testStatus struct {
|
|
parent *testStatus
|
|
|
|
m sync.Mutex
|
|
errors []string
|
|
childFailed bool
|
|
panicValue any
|
|
handled bool
|
|
}
|
|
|
|
func (s *testStatus) setChildFailed() {
|
|
// the status object is allowed to be nil, to simplify
|
|
// marking a child as failed when it doesn't have a parent.
|
|
if s == nil {
|
|
return
|
|
}
|
|
|
|
// propagate to potential parents first, to avoid
|
|
// having multiple locks simultaneous.
|
|
s.parent.setChildFailed()
|
|
|
|
s.m.Lock()
|
|
defer s.m.Unlock()
|
|
s.childFailed = true
|
|
}
|
|
|
|
func (s *testStatus) addError(msg string) {
|
|
s.parent.setChildFailed()
|
|
|
|
s.m.Lock()
|
|
defer s.m.Unlock()
|
|
s.errors = append(s.errors, msg)
|
|
}
|
|
|
|
func (s *testStatus) hasError(text string) bool {
|
|
s.m.Lock()
|
|
defer s.m.Unlock()
|
|
|
|
for _, msg := range s.errors {
|
|
if strings.Contains(msg, text) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *testStatus) setPanic(v any) {
|
|
s.parent.setChildFailed()
|
|
|
|
s.m.Lock()
|
|
defer s.m.Unlock()
|
|
s.panicValue = v
|
|
}
|
|
|
|
func (s *testStatus) getPanic() any {
|
|
s.m.Lock()
|
|
defer s.m.Unlock()
|
|
return s.panicValue
|
|
}
|
|
|
|
func (s *testStatus) setHandled() {
|
|
s.m.Lock()
|
|
defer s.m.Unlock()
|
|
s.handled = true
|
|
}
|
|
|
|
func (s *testStatus) hasFailed() bool {
|
|
s.m.Lock()
|
|
defer s.m.Unlock()
|
|
return s.childFailed || s.panicValue != nil || len(s.errors) > 0
|
|
}
|
|
|
|
func (s *testStatus) hasFailedChildren() bool {
|
|
s.m.Lock()
|
|
defer s.m.Unlock()
|
|
return s.childFailed
|
|
}
|
|
|
|
func (s *testStatus) wasHandled() bool {
|
|
s.m.Lock()
|
|
defer s.m.Unlock()
|
|
return s.handled
|
|
}
|
|
|
|
func (s *testStatus) log(t *testing.T, name string) {
|
|
s.m.Lock()
|
|
defer s.m.Unlock()
|
|
|
|
success := true
|
|
|
|
for _, msg := range s.errors {
|
|
t.Logf("%s: %s", name, msg)
|
|
success = false
|
|
}
|
|
|
|
if s.panicValue != nil {
|
|
t.Logf("%s: panic: %v", name, s.panicValue)
|
|
success = false
|
|
}
|
|
|
|
if s.childFailed {
|
|
t.Logf("%s: has failed children", name)
|
|
success = false
|
|
}
|
|
|
|
if success {
|
|
t.Logf("%s: success", name)
|
|
}
|
|
}
|
|
|
|
type mockTest struct {
|
|
*mockTester
|
|
*testStatus
|
|
name string
|
|
}
|
|
|
|
func (m *mockTest) run(f func(*mockTest)) (success bool) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
m.setPanic(r)
|
|
}
|
|
success = !m.hasFailed()
|
|
}()
|
|
|
|
f(m)
|
|
return
|
|
}
|
|
|
|
func (m *mockTest) Errorf(f string, args ...any) {
|
|
m.addError(fmt.Sprintf(f, args...))
|
|
}
|
|
|
|
func (m *mockTest) Run(name string, f func(*mockTest)) bool {
|
|
child := &mockTest{
|
|
mockTester: m.mockTester,
|
|
testStatus: m.create(m.testStatus, m.name+"/"+name),
|
|
name: m.name + "/" + name,
|
|
}
|
|
return child.run(f)
|
|
}
|
|
|
|
type mockTester struct {
|
|
t *testing.T
|
|
m sync.Mutex
|
|
results map[string]*testStatus
|
|
}
|
|
|
|
func (m *mockTester) create(parent *testStatus, name string) *testStatus {
|
|
m.m.Lock()
|
|
defer m.m.Unlock()
|
|
|
|
if _, ok := m.results[name]; ok {
|
|
m.t.Fatalf("%s: test already exists", name)
|
|
return nil
|
|
}
|
|
|
|
if m.results == nil {
|
|
m.results = make(map[string]*testStatus)
|
|
}
|
|
|
|
s := &testStatus{parent: parent}
|
|
m.results[name] = s
|
|
return s
|
|
}
|
|
|
|
func (m *mockTester) get(name string) *testStatus {
|
|
m.m.Lock()
|
|
defer m.m.Unlock()
|
|
|
|
if s, ok := m.results[name]; ok {
|
|
return s
|
|
}
|
|
|
|
m.t.Fatalf("%s: test doesn't exist", name)
|
|
return nil
|
|
}
|
|
|
|
// run the test.
|
|
func (m *mockTester) run(name string, f func(*mockTest)) bool {
|
|
t := &mockTest{
|
|
mockTester: m,
|
|
testStatus: m.create(nil, name),
|
|
name: name,
|
|
}
|
|
return t.run(f)
|
|
}
|
|
|
|
// panic if the named test doesn't exist or doesn't have an error containing the given text.
|
|
func (m *mockTester) expectError(name string, text string) {
|
|
if s := m.get(name); s != nil {
|
|
s.setHandled()
|
|
|
|
if !s.hasError(text) {
|
|
m.t.Errorf("%s: doesn't contain error message: %s", name, text)
|
|
s.log(m.t, name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *mockTester) expectFailedChildren(name string) {
|
|
if s := m.get(name); s != nil {
|
|
s.setHandled()
|
|
|
|
if !s.hasFailedChildren() {
|
|
m.t.Errorf("%s: doesn't have failed children", name)
|
|
s.log(m.t, name)
|
|
}
|
|
}
|
|
}
|
|
|
|
// panic if the named test doesn't exist or didn't panic (and returns the panic value)
|
|
func (m *mockTester) expectPanic(name string) any {
|
|
if s := m.get(name); s != nil {
|
|
s.setHandled()
|
|
|
|
if r := s.getPanic(); r != nil {
|
|
return r
|
|
}
|
|
|
|
m.t.Errorf("%s: didn't panic", name)
|
|
s.log(m.t, name)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// panic if the named test doesn't exist or has failed.
|
|
func (m *mockTester) expectSuccess(name string) {
|
|
if s := m.get(name); s != nil {
|
|
s.setHandled()
|
|
|
|
if s.hasFailed() {
|
|
m.t.Errorf("%s: failed", name)
|
|
s.log(m.t, name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *mockTester) expectAllHandled() {
|
|
m.m.Lock()
|
|
defer m.m.Unlock()
|
|
|
|
if len(m.results) == 0 {
|
|
m.t.Errorf("no tests were run")
|
|
return
|
|
}
|
|
|
|
for name, s := range m.results {
|
|
if !s.wasHandled() {
|
|
m.t.Errorf("%s: not handled", name)
|
|
s.log(m.t, name)
|
|
}
|
|
}
|
|
}
|