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) } } }