color/internal/helper/test_test.go
2025-03-06 13:01:21 -05:00

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