Skip to content

Commit 167a1cc

Browse files
committed
softassert for test hooks
1 parent 63a2849 commit 167a1cc

File tree

5 files changed

+72
-125
lines changed

5 files changed

+72
-125
lines changed

common/testing/testhooks/noop_impl.go

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,28 @@
33
package testhooks
44

55
import (
6-
"context"
7-
6+
"go.temporal.io/server/common/log"
7+
"go.temporal.io/server/common/softassert"
88
"go.uber.org/fx"
99
)
1010

1111
var Module = fx.Options(
12-
fx.Provide(func() (_ TestHooks) { return }),
12+
fx.Provide(NewTestHooks),
1313
)
1414

15-
type (
16-
// TestHooks (in production mode) is an empty struct just so the build works.
17-
// See TestHooks in test_impl.go.
18-
//
19-
// TestHooks are an inherently unclean way of writing tests. They require mixing test-only
20-
// concerns into production code. In general you should prefer other ways of writing tests
21-
// wherever possible, and only use TestHooks sparingly, as a last resort.
22-
TestHooks struct{}
23-
)
15+
// TestHooks (in production mode) is an empty struct just so the build works.
16+
// See TestHooks in test_impl.go.
17+
//
18+
// TestHooks are an inherently unclean way of writing tests. They require mixing test-only
19+
// concerns into production code. In general you should prefer other ways of writing tests
20+
// wherever possible, and only use TestHooks sparingly, as a last resort.
21+
type TestHooks struct {
22+
logger log.Logger
23+
}
24+
25+
func NewTestHooks(logger log.Logger) TestHooks {
26+
return TestHooks{logger: logger}
27+
}
2428

2529
// Get gets the value of a test hook. In production mode it always returns the zero value and
2630
// false, which hopefully the compiler will inline and remove the hook as dead code.
@@ -31,23 +35,14 @@ func Get[T any](_ TestHooks, key Key) (T, bool) {
3135
return zero, false
3236
}
3337

34-
// GetCtx gets the value of a test hook from the registry embedded in the
35-
// context chain.
36-
//
37-
// TestHooks should be used sparingly, see comment on TestHooks.
38-
func GetCtx[T any](ctx context.Context, key Key) (T, bool) {
39-
var zero T
40-
return zero, false
41-
}
42-
4338
// Call calls a func() hook if present.
4439
//
4540
// TestHooks should be used very sparingly, see comment on TestHooks.
4641
func Call(_ TestHooks, key Key) {
4742
}
4843

49-
// CallCtx calls a func(context.Context) hook if present.
50-
//
51-
// TestHooks should be used very sparingly, see comment on TestHooks.
52-
func CallCtx(_ context.Context, key Key) {
44+
// Set is only to be used by test code together with the test_dep build tag.
45+
func Set[T any](th TestHooks, key Key, val T) func() {
46+
softassert.Fail(th.logger, "testhooks.Set called without test_dep build tag")
47+
return func() {}
5348
}

common/testing/testhooks/test_impl.go

Lines changed: 6 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
package testhooks
44

55
import (
6-
"context"
76
"sync"
87

8+
"go.temporal.io/server/common/log"
99
"go.uber.org/fx"
1010
)
1111

1212
var Module = fx.Options(
13-
fx.Provide(NewTestHooksImpl),
13+
fx.Provide(NewTestHooks),
1414
)
1515

1616
type (
@@ -31,10 +31,12 @@ type (
3131
testHooksImpl struct {
3232
m sync.Map
3333
}
34-
35-
contextKey struct{}
3634
)
3735

36+
func NewTestHooks(_ log.Logger) TestHooks {
37+
return &testHooksImpl{}
38+
}
39+
3840
// Get gets the value of a test hook from the registry.
3941
//
4042
// TestHooks should be used sparingly, see comment on TestHooks.
@@ -50,19 +52,6 @@ func Get[T any](th TestHooks, key Key) (T, bool) {
5052
return zero, false
5153
}
5254

53-
// GetCtx gets the value of a test hook from the registry embedded in the
54-
// context chain.
55-
//
56-
// TestHooks should be used sparingly, see comment on TestHooks.
57-
func GetCtx[T any](ctx context.Context, key Key) (T, bool) {
58-
hooks := ctx.Value(contextKey{})
59-
if hooks, ok := hooks.(TestHooks); ok {
60-
return Get[T](hooks, key)
61-
}
62-
var zero T
63-
return zero, false
64-
}
65-
6655
// Call calls a func() hook if present.
6756
//
6857
// TestHooks should be used sparingly, see comment on TestHooks.
@@ -72,49 +61,13 @@ func Call(th TestHooks, key Key) {
7261
}
7362
}
7463

75-
// CallCtx calls a func() hook if present and a TestHooks implementation
76-
// exists in the context chain.
77-
//
78-
// TestHooks should be used sparingly, see comment on TestHooks.
79-
func CallCtx(ctx context.Context, key Key) {
80-
hooks := ctx.Value(contextKey{})
81-
if hooks, ok := hooks.(TestHooks); ok {
82-
Call(hooks, key)
83-
}
84-
}
85-
8664
// Set sets a test hook to a value and returns a cleanup function to unset it.
8765
// Calls to Set and the cleanup function should form a stack.
8866
func Set[T any](th TestHooks, key Key, val T) func() {
8967
th.set(key, val)
9068
return func() { th.del(key) }
9169
}
9270

93-
// SetCtx sets a test hook to a value on the provided context and returns a
94-
// cleanup function to unset it. Calls to SetCtx and the cleanup function
95-
// should form a stack.
96-
func SetCtx[T any](ctx context.Context, key Key, val T) func() {
97-
hooks := ctx.Value(contextKey{})
98-
if hooks, ok := hooks.(TestHooks); ok {
99-
return Set(hooks, key, val)
100-
}
101-
return func() {}
102-
}
103-
104-
// NewTestHooksImpl returns a new instance of a test hook registry. This is provided and used
105-
// in the main "resource" module as a default, but in functional tests, it's overridden by an
106-
// explicitly constructed instance.
107-
func NewTestHooksImpl() TestHooks {
108-
return &testHooksImpl{}
109-
}
110-
111-
// NewInjectedTestHooksImpl returns a new instance of a test hook registry and a context with the
112-
// registry injected.
113-
func NewInjectedTestHooksImpl(parent context.Context) (context.Context, TestHooks) {
114-
hooks := NewTestHooksImpl()
115-
return context.WithValue(parent, contextKey{}, hooks), hooks
116-
}
117-
11871
func (th *testHooksImpl) get(key Key) (any, bool) {
11972
return th.m.Load(key)
12073
}

common/testing/testhooks/test_impl_test.go

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,51 @@
22

33
package testhooks
44

5-
import "testing"
6-
7-
// Ensure that testhook functionality that operates on contexts functions as
8-
// expected.
9-
func TestTestHooks_Context(t *testing.T) {
10-
t.Run("Values can be set and get on the context directly", func(t *testing.T) {
11-
ctx, _ := NewInjectedTestHooksImpl(t.Context())
12-
cleanup := SetCtx(ctx, UpdateWithStartInBetweenLockAndStart, 33)
13-
defer cleanup()
5+
import (
6+
"testing"
7+
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestGet(t *testing.T) {
12+
th := NewTestHooks(nil)
1413

15-
v, ok := GetCtx[int](ctx, UpdateWithStartInBetweenLockAndStart)
16-
if !ok {
17-
t.Fatal("Expected TestHooksImpl to contain value")
18-
}
19-
if v != 33 {
20-
t.Fatal("Expected value to be 33")
21-
}
14+
t.Run("returns false for unset key", func(t *testing.T) {
15+
_, ok := Get[int](th, MatchingDisableSyncMatch)
16+
require.False(t, ok)
2217
})
2318

24-
t.Run("Values set directly on the registry are visible through the context", func(t *testing.T) {
25-
ctx, reg := NewInjectedTestHooksImpl(t.Context())
26-
cleanup := Set(reg, UpdateWithStartInBetweenLockAndStart, 44)
19+
t.Run("returns set value", func(t *testing.T) {
20+
cleanup := Set(th, MatchingDisableSyncMatch, 42)
2721
defer cleanup()
2822

29-
v, ok := GetCtx[int](ctx, UpdateWithStartInBetweenLockAndStart)
30-
if !ok {
31-
t.Fatal("Expected TestHooksImpl to contain value")
32-
}
33-
if v != 44 {
34-
t.Fatal("Expected value to be 44")
35-
}
23+
v, ok := Get[int](th, MatchingDisableSyncMatch)
24+
require.True(t, ok)
25+
require.Equal(t, 42, v)
26+
})
27+
28+
t.Run("cleanup removes value", func(t *testing.T) {
29+
cleanup := Set(th, MatchingDisableSyncMatch, 42)
30+
cleanup()
31+
32+
_, ok := Get[int](th, MatchingDisableSyncMatch)
33+
require.False(t, ok)
34+
})
35+
}
36+
37+
func TestCall(t *testing.T) {
38+
th := NewTestHooks(nil)
39+
40+
t.Run("does nothing for unset key", func(t *testing.T) {
41+
Call(th, MatchingDisableSyncMatch) // should not panic
3642
})
3743

38-
t.Run("CallCtx uses the registry injected into the context", func(t *testing.T) {
39-
ctx, reg := NewInjectedTestHooksImpl(t.Context())
40-
var value int
41-
callback := func() {
42-
value = 55
43-
}
44-
cleanup := Set(reg, UpdateWithStartInBetweenLockAndStart, callback)
44+
t.Run("calls set function", func(t *testing.T) {
45+
called := false
46+
cleanup := Set(th, MatchingDisableSyncMatch, func() { called = true })
4547
defer cleanup()
4648

47-
CallCtx(ctx, UpdateWithStartInBetweenLockAndStart)
48-
if value != 55 {
49-
t.Fatal("Expected value to be 55")
50-
}
49+
Call(th, MatchingDisableSyncMatch)
50+
require.True(t, called)
5151
})
5252
}

docs/development/testing.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ This document describes the project's testing setup, utilities and best practice
55
## Setup
66

77
### Build tags
8-
- `test_dep` (required): This Go build tag is required for running functional tests.
8+
- `test_dep`: This Go build tag enables the test hooks implementation. Only very few tests require it; they will fail if not enabled.
99
- `TEMPORAL_DEBUG`: Extends functional test timeouts to allow sufficient time for debugging sessions.
1010
- `disable_grpc_modules`: Disables gRPC modules for faster compilation during unit tests.
1111

@@ -136,4 +136,4 @@ See [tracing.md](../../docs/development/tracing.md) for more details.
136136
You'll find the code coverage reporting in Codecov: https://app.codecov.io/gh/temporalio/temporal.
137137

138138
Consider installing the [Codecov Browser Extension](https://docs.codecov.com/docs/the-codecov-browser-extension)
139-
to see code coverage directly in GitHub PRs.
139+
to see code coverage directly in GitHub PRs.

tests/testcore/onebox.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -214,14 +214,13 @@ func newTemporal(t *testing.T, params *TemporalParams) *TemporalImpl {
214214
tlsConfigProvider: params.TLSConfigProvider,
215215
captureMetricsHandler: params.CaptureMetricsHandler,
216216
dcClient: dynamicconfig.NewMemoryClient(),
217-
// If this doesn't build, make sure you're building with tags 'test_dep':
218-
testHooks: testhooks.NewTestHooksImpl(),
219-
serviceFxOptions: params.ServiceFxOptions,
220-
taskCategoryRegistry: params.TaskCategoryRegistry,
221-
hostsByProtocolByService: params.HostsByProtocolByService,
222-
grpcClientInterceptor: grpcinject.NewInterceptor(),
223-
replicationStreamRecorder: NewReplicationStreamRecorder(),
224-
spanExporters: params.SpanExporters,
217+
testHooks: testhooks.NewTestHooks(params.Logger),
218+
serviceFxOptions: params.ServiceFxOptions,
219+
taskCategoryRegistry: params.TaskCategoryRegistry,
220+
hostsByProtocolByService: params.HostsByProtocolByService,
221+
grpcClientInterceptor: grpcinject.NewInterceptor(),
222+
replicationStreamRecorder: NewReplicationStreamRecorder(),
223+
spanExporters: params.SpanExporters,
225224
}
226225

227226
// Configure output file path for on-demand logging (call WriteToLog() to write)

0 commit comments

Comments
 (0)