Files
tasks/common/testing/testlogger/testlogger_test.go
Tim Deeb-SwihartandGitHub 8f987baf17 testlogger: actually fail tests when we're supposed to (#7874)
## What changed?

This changes all methods that fail tests on the test logger to
**forcibly** fail it by calling `t.Fatalf`.

It adds the stack trace to the failure message as we'd otherwise have no
information on the _path_ to the failure, which is why we initially used
`panic`

## Why?

While testing a separate project I determined that panics from OSS' test
logger (and the internal one it is based on!) were being caught by our
application code, obscuring issues that should cause tests to fail.

## How did you test it?
- [ ] built
- [ ] run locally and tested manually
- [x] covered by existing tests
- [ ] added new unit test(s)
- [ ] added new functional test(s)

## Potential risks

This may fail more tests than we currently do. This is a Good Thing.
2025-06-10 15:54:58 -04:00

149 lines
4.0 KiB
Go

package testlogger_test
import (
"fmt"
"sync/atomic"
"testing"
"github.com/stretchr/testify/require"
"go.temporal.io/server/common/log/tag"
"go.temporal.io/server/common/testing/testlogger"
)
// mockT wraps a testing.T such that we can know if a test is failed.
type mockT struct {
*testing.T
failure atomic.Pointer[string]
}
// Errorf implements testlogger.TestingT.
func (m *mockT) Errorf(msg string, args ...any) {
s := fmt.Sprintf("ERROR: "+msg, args...)
m.failure.Store(&s)
}
// Fail implements testlogger.TestingT.
func (m *mockT) Fail() {
s := "Fail() called"
m.failure.Store(&s)
}
// FailNow implements testlogger.TestingT.
func (m *mockT) FailNow() {
s := "FailNow() called"
m.failure.Store(&s)
}
// Failed implements testlogger.TestingT.
func (m *mockT) Failed() bool {
return m.failure.Load() != nil
}
// Fatal implements testlogger.TestingT.
func (m *mockT) Fatal(args ...any) {
s := fmt.Sprintf("FATAL: %v", args...)
m.failure.Store(&s)
}
// Fatalf implements testlogger.TestingT.
func (m *mockT) Fatalf(format string, args ...any) {
s := fmt.Sprintf(format, args...)
m.failure.Store(&s)
}
var _ testlogger.TestingT = (*mockT)(nil)
func assertMatches(t *testing.T, level testlogger.Level, msg string, tags []tag.Tag) {
tl := testlogger.NewTestLogger(t, testlogger.FailOnAnyUnexpectedError)
tl.Expect(level, msg, tags...)
switch level {
case testlogger.Error:
tl.Error(msg, tags...)
case testlogger.Fatal:
require.Panics(t, func() {
tl.Fatal(msg, tags...)
})
case testlogger.DPanic:
require.NotPanics(t, func() {
tl.DPanic(msg, tags...)
})
case testlogger.Panic:
require.Panics(t, func() {
tl.Panic(msg, tags...)
})
default:
t.Fatalf("Unknown log level %s", level)
}
}
func TestTestLogger_ExpectationsMatch(t *testing.T) {
for _, level := range []testlogger.Level{testlogger.Error, testlogger.DPanic, testlogger.Panic, testlogger.Fatal} {
t.Run(level.String()+" with tags", func(t *testing.T) {
assertMatches(t, level, "message with tags", []tag.Tag{tag.NewStringTag("key", "value")})
})
t.Run(level.String()+" without tags", func(t *testing.T) {
assertMatches(t, level, "message without tags", nil)
})
t.Run(level.String()+" no message only tags", func(t *testing.T) {
assertMatches(t, level, "", []tag.Tag{tag.NewStringTag("key", "value")})
})
}
}
func assertFails(t *testing.T, level testlogger.Level, msg string, tags []tag.Tag) {
mt := &mockT{T: t}
tl := testlogger.NewTestLogger(mt, testlogger.FailOnAnyUnexpectedError)
switch level {
case testlogger.Error:
tl.Error(msg, tags...)
require.True(t, mt.Failed())
case testlogger.Fatal:
tl.Fatal(msg, tags...)
require.True(t, mt.Failed())
case testlogger.DPanic:
tl.DPanic(msg, tags...)
require.True(t, mt.Failed())
case testlogger.Panic:
tl.Panic(msg, tags...)
require.True(t, mt.Failed())
default:
t.Fatalf("Unknown log level %s", level)
}
}
func TestTestLogger_Uncaught(t *testing.T) {
// Non-panicking levels
for _, level := range []testlogger.Level{testlogger.Error, testlogger.DPanic} {
t.Run(level.String()+" with tags", func(t *testing.T) {
assertFails(t, level, "message with tags", []tag.Tag{tag.NewStringTag("key", "value")})
})
t.Run(level.String()+" without tags", func(t *testing.T) {
assertFails(t, level, "message without tags", nil)
})
t.Run(level.String()+" no message only tags", func(t *testing.T) {
assertFails(t, level, "", []tag.Tag{tag.NewStringTag("key", "value")})
})
}
// Panicking levels
for _, level := range []testlogger.Level{testlogger.Panic, testlogger.Fatal} {
t.Run(level.String()+" with tags", func(t *testing.T) {
require.Panics(t, func() {
assertFails(t, level, "message with tags", []tag.Tag{tag.NewStringTag("key", "value")})
})
})
t.Run(level.String()+" without tags", func(t *testing.T) {
require.Panics(t, func() {
assertFails(t, level, "message without tags", nil)
})
})
t.Run(level.String()+" no message only tags", func(t *testing.T) {
require.Panics(t, func() {
assertFails(t, level, "", []tag.Tag{tag.NewStringTag("key", "value")})
})
})
}
}