From 81df7937dbb265ea1f5302019fc6d9015ba9ac21 Mon Sep 17 00:00:00 2001 From: Stephan Behnke Date: Mon, 1 Dec 2025 11:42:47 -0800 Subject: [PATCH 1/4] softassert for test hooks --- common/testing/testhooks/noop_impl.go | 45 ++++++------- common/testing/testhooks/test_impl.go | 59 ++--------------- common/testing/testhooks/test_impl_test.go | 74 +++++++++++----------- docs/development/testing.md | 4 +- tests/testcore/onebox.go | 15 ++--- 5 files changed, 72 insertions(+), 125 deletions(-) diff --git a/common/testing/testhooks/noop_impl.go b/common/testing/testhooks/noop_impl.go index 44232be0088..e419e764e96 100644 --- a/common/testing/testhooks/noop_impl.go +++ b/common/testing/testhooks/noop_impl.go @@ -3,24 +3,28 @@ package testhooks import ( - "context" - + "go.temporal.io/server/common/log" + "go.temporal.io/server/common/softassert" "go.uber.org/fx" ) var Module = fx.Options( - fx.Provide(func() (_ TestHooks) { return }), + fx.Provide(NewTestHooks), ) -type ( - // TestHooks (in production mode) is an empty struct just so the build works. - // See TestHooks in test_impl.go. - // - // TestHooks are an inherently unclean way of writing tests. They require mixing test-only - // concerns into production code. In general you should prefer other ways of writing tests - // wherever possible, and only use TestHooks sparingly, as a last resort. - TestHooks struct{} -) +// TestHooks (in production mode) is an empty struct just so the build works. +// See TestHooks in test_impl.go. +// +// TestHooks are an inherently unclean way of writing tests. They require mixing test-only +// concerns into production code. In general you should prefer other ways of writing tests +// wherever possible, and only use TestHooks sparingly, as a last resort. +type TestHooks struct { + logger log.Logger +} + +func NewTestHooks(logger log.Logger) TestHooks { + return TestHooks{logger: logger} +} // Get gets the value of a test hook. In production mode it always returns the zero value and // 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) { return zero, false } -// GetCtx gets the value of a test hook from the registry embedded in the -// context chain. -// -// TestHooks should be used sparingly, see comment on TestHooks. -func GetCtx[T any](ctx context.Context, key Key) (T, bool) { - var zero T - return zero, false -} - // Call calls a func() hook if present. // // TestHooks should be used very sparingly, see comment on TestHooks. func Call(_ TestHooks, key Key) { } -// CallCtx calls a func(context.Context) hook if present. -// -// TestHooks should be used very sparingly, see comment on TestHooks. -func CallCtx(_ context.Context, key Key) { +// Set is only to be used by test code together with the test_dep build tag. +func Set[T any](th TestHooks, key Key, val T) func() { + softassert.Fail(th.logger, "testhooks.Set called without test_dep build tag") + return func() {} } diff --git a/common/testing/testhooks/test_impl.go b/common/testing/testhooks/test_impl.go index 8e08977a6b9..e83bc405a16 100644 --- a/common/testing/testhooks/test_impl.go +++ b/common/testing/testhooks/test_impl.go @@ -3,14 +3,14 @@ package testhooks import ( - "context" "sync" + "go.temporal.io/server/common/log" "go.uber.org/fx" ) var Module = fx.Options( - fx.Provide(NewTestHooksImpl), + fx.Provide(NewTestHooks), ) type ( @@ -31,10 +31,12 @@ type ( testHooksImpl struct { m sync.Map } - - contextKey struct{} ) +func NewTestHooks(_ log.Logger) TestHooks { + return &testHooksImpl{} +} + // Get gets the value of a test hook from the registry. // // TestHooks should be used sparingly, see comment on TestHooks. @@ -50,19 +52,6 @@ func Get[T any](th TestHooks, key Key) (T, bool) { return zero, false } -// GetCtx gets the value of a test hook from the registry embedded in the -// context chain. -// -// TestHooks should be used sparingly, see comment on TestHooks. -func GetCtx[T any](ctx context.Context, key Key) (T, bool) { - hooks := ctx.Value(contextKey{}) - if hooks, ok := hooks.(TestHooks); ok { - return Get[T](hooks, key) - } - var zero T - return zero, false -} - // Call calls a func() hook if present. // // TestHooks should be used sparingly, see comment on TestHooks. @@ -72,17 +61,6 @@ func Call(th TestHooks, key Key) { } } -// CallCtx calls a func() hook if present and a TestHooks implementation -// exists in the context chain. -// -// TestHooks should be used sparingly, see comment on TestHooks. -func CallCtx(ctx context.Context, key Key) { - hooks := ctx.Value(contextKey{}) - if hooks, ok := hooks.(TestHooks); ok { - Call(hooks, key) - } -} - // Set sets a test hook to a value and returns a cleanup function to unset it. // Calls to Set and the cleanup function should form a stack. func Set[T any](th TestHooks, key Key, val T) func() { @@ -90,31 +68,6 @@ func Set[T any](th TestHooks, key Key, val T) func() { return func() { th.del(key) } } -// SetCtx sets a test hook to a value on the provided context and returns a -// cleanup function to unset it. Calls to SetCtx and the cleanup function -// should form a stack. -func SetCtx[T any](ctx context.Context, key Key, val T) func() { - hooks := ctx.Value(contextKey{}) - if hooks, ok := hooks.(TestHooks); ok { - return Set(hooks, key, val) - } - return func() {} -} - -// NewTestHooksImpl returns a new instance of a test hook registry. This is provided and used -// in the main "resource" module as a default, but in functional tests, it's overridden by an -// explicitly constructed instance. -func NewTestHooksImpl() TestHooks { - return &testHooksImpl{} -} - -// NewInjectedTestHooksImpl returns a new instance of a test hook registry and a context with the -// registry injected. -func NewInjectedTestHooksImpl(parent context.Context) (context.Context, TestHooks) { - hooks := NewTestHooksImpl() - return context.WithValue(parent, contextKey{}, hooks), hooks -} - func (th *testHooksImpl) get(key Key) (any, bool) { return th.m.Load(key) } diff --git a/common/testing/testhooks/test_impl_test.go b/common/testing/testhooks/test_impl_test.go index c8c1c0cc0bf..c5eb77b2281 100644 --- a/common/testing/testhooks/test_impl_test.go +++ b/common/testing/testhooks/test_impl_test.go @@ -2,51 +2,51 @@ package testhooks -import "testing" - -// Ensure that testhook functionality that operates on contexts functions as -// expected. -func TestTestHooks_Context(t *testing.T) { - t.Run("Values can be set and get on the context directly", func(t *testing.T) { - ctx, _ := NewInjectedTestHooksImpl(t.Context()) - cleanup := SetCtx(ctx, UpdateWithStartInBetweenLockAndStart, 33) - defer cleanup() +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGet(t *testing.T) { + th := NewTestHooks(nil) - v, ok := GetCtx[int](ctx, UpdateWithStartInBetweenLockAndStart) - if !ok { - t.Fatal("Expected TestHooksImpl to contain value") - } - if v != 33 { - t.Fatal("Expected value to be 33") - } + t.Run("returns false for unset key", func(t *testing.T) { + _, ok := Get[int](th, MatchingDisableSyncMatch) + require.False(t, ok) }) - t.Run("Values set directly on the registry are visible through the context", func(t *testing.T) { - ctx, reg := NewInjectedTestHooksImpl(t.Context()) - cleanup := Set(reg, UpdateWithStartInBetweenLockAndStart, 44) + t.Run("returns set value", func(t *testing.T) { + cleanup := Set(th, MatchingDisableSyncMatch, 42) defer cleanup() - v, ok := GetCtx[int](ctx, UpdateWithStartInBetweenLockAndStart) - if !ok { - t.Fatal("Expected TestHooksImpl to contain value") - } - if v != 44 { - t.Fatal("Expected value to be 44") - } + v, ok := Get[int](th, MatchingDisableSyncMatch) + require.True(t, ok) + require.Equal(t, 42, v) + }) + + t.Run("cleanup removes value", func(t *testing.T) { + cleanup := Set(th, MatchingDisableSyncMatch, 42) + cleanup() + + _, ok := Get[int](th, MatchingDisableSyncMatch) + require.False(t, ok) + }) +} + +func TestCall(t *testing.T) { + th := NewTestHooks(nil) + + t.Run("does nothing for unset key", func(t *testing.T) { + Call(th, MatchingDisableSyncMatch) // should not panic }) - t.Run("CallCtx uses the registry injected into the context", func(t *testing.T) { - ctx, reg := NewInjectedTestHooksImpl(t.Context()) - var value int - callback := func() { - value = 55 - } - cleanup := Set(reg, UpdateWithStartInBetweenLockAndStart, callback) + t.Run("calls set function", func(t *testing.T) { + called := false + cleanup := Set(th, MatchingDisableSyncMatch, func() { called = true }) defer cleanup() - CallCtx(ctx, UpdateWithStartInBetweenLockAndStart) - if value != 55 { - t.Fatal("Expected value to be 55") - } + Call(th, MatchingDisableSyncMatch) + require.True(t, called) }) } diff --git a/docs/development/testing.md b/docs/development/testing.md index fe317bcf7c3..72b3d05780f 100644 --- a/docs/development/testing.md +++ b/docs/development/testing.md @@ -5,7 +5,7 @@ This document describes the project's testing setup, utilities and best practice ## Setup ### Build tags -- `test_dep` (required): This Go build tag is required for running functional tests. +- `test_dep`: This Go build tag enables the test hooks implementation. Only very few tests require it; they will fail if not enabled. - `TEMPORAL_DEBUG`: Extends functional test timeouts to allow sufficient time for debugging sessions. - `disable_grpc_modules`: Disables gRPC modules for faster compilation during unit tests. @@ -136,4 +136,4 @@ See [tracing.md](../../docs/development/tracing.md) for more details. You'll find the code coverage reporting in Codecov: https://app.codecov.io/gh/temporalio/temporal. Consider installing the [Codecov Browser Extension](https://docs.codecov.com/docs/the-codecov-browser-extension) -to see code coverage directly in GitHub PRs. \ No newline at end of file +to see code coverage directly in GitHub PRs. diff --git a/tests/testcore/onebox.go b/tests/testcore/onebox.go index 37708008043..c4ddd2e0be1 100644 --- a/tests/testcore/onebox.go +++ b/tests/testcore/onebox.go @@ -214,14 +214,13 @@ func newTemporal(t *testing.T, params *TemporalParams) *TemporalImpl { tlsConfigProvider: params.TLSConfigProvider, captureMetricsHandler: params.CaptureMetricsHandler, dcClient: dynamicconfig.NewMemoryClient(), - // If this doesn't build, make sure you're building with tags 'test_dep': - testHooks: testhooks.NewTestHooksImpl(), - serviceFxOptions: params.ServiceFxOptions, - taskCategoryRegistry: params.TaskCategoryRegistry, - hostsByProtocolByService: params.HostsByProtocolByService, - grpcClientInterceptor: grpcinject.NewInterceptor(), - replicationStreamRecorder: NewReplicationStreamRecorder(), - spanExporters: params.SpanExporters, + testHooks: testhooks.NewTestHooks(params.Logger), + serviceFxOptions: params.ServiceFxOptions, + taskCategoryRegistry: params.TaskCategoryRegistry, + hostsByProtocolByService: params.HostsByProtocolByService, + grpcClientInterceptor: grpcinject.NewInterceptor(), + replicationStreamRecorder: NewReplicationStreamRecorder(), + spanExporters: params.SpanExporters, } // Configure output file path for on-demand logging (call WriteToLog() to write) From 7274c8acefa0e0661db0433e313bdc37589ea603 Mon Sep 17 00:00:00 2001 From: Stephan Behnke Date: Fri, 5 Dec 2025 12:16:05 -0800 Subject: [PATCH 2/4] use panic --- common/testing/testhooks/noop_impl.go | 28 ++++-------- common/testing/testhooks/test_impl.go | 53 +++++++--------------- common/testing/testhooks/test_impl_test.go | 4 +- common/testing/testhooks/testhooks.go | 11 +++++ tests/testcore/onebox.go | 2 +- 5 files changed, 40 insertions(+), 58 deletions(-) create mode 100644 common/testing/testhooks/testhooks.go diff --git a/common/testing/testhooks/noop_impl.go b/common/testing/testhooks/noop_impl.go index e419e764e96..7b123ac3402 100644 --- a/common/testing/testhooks/noop_impl.go +++ b/common/testing/testhooks/noop_impl.go @@ -3,8 +3,6 @@ package testhooks import ( - "go.temporal.io/server/common/log" - "go.temporal.io/server/common/softassert" "go.uber.org/fx" ) @@ -12,25 +10,20 @@ var Module = fx.Options( fx.Provide(NewTestHooks), ) -// TestHooks (in production mode) is an empty struct just so the build works. -// See TestHooks in test_impl.go. -// -// TestHooks are an inherently unclean way of writing tests. They require mixing test-only -// concerns into production code. In general you should prefer other ways of writing tests -// wherever possible, and only use TestHooks sparingly, as a last resort. -type TestHooks struct { - logger log.Logger -} +// noopTestHooks is a noop implementation of TestHooks for production builds. +type noopTestHooks struct{} -func NewTestHooks(logger log.Logger) TestHooks { - return TestHooks{logger: logger} +func NewTestHooks() TestHooks { + return noopTestHooks{} } +func (noopTestHooks) testHooks() {} + // Get gets the value of a test hook. In production mode it always returns the zero value and // false, which hopefully the compiler will inline and remove the hook as dead code. // // TestHooks should be used very sparingly, see comment on TestHooks. -func Get[T any](_ TestHooks, key Key) (T, bool) { +func Get[T any](_ TestHooks, _ Key) (T, bool) { var zero T return zero, false } @@ -38,11 +31,10 @@ func Get[T any](_ TestHooks, key Key) (T, bool) { // Call calls a func() hook if present. // // TestHooks should be used very sparingly, see comment on TestHooks. -func Call(_ TestHooks, key Key) { +func Call(_ TestHooks, _ Key) { } // Set is only to be used by test code together with the test_dep build tag. -func Set[T any](th TestHooks, key Key, val T) func() { - softassert.Fail(th.logger, "testhooks.Set called without test_dep build tag") - return func() {} +func Set[T any](_ TestHooks, _ Key, _ T) func() { + panic("testhooks.Set called but TestHooks are not enabled: use -tags=test_dep when running `go test`") } diff --git a/common/testing/testhooks/test_impl.go b/common/testing/testhooks/test_impl.go index e83bc405a16..643088ee3e1 100644 --- a/common/testing/testhooks/test_impl.go +++ b/common/testing/testhooks/test_impl.go @@ -5,7 +5,6 @@ package testhooks import ( "sync" - "go.temporal.io/server/common/log" "go.uber.org/fx" ) @@ -13,30 +12,17 @@ var Module = fx.Options( fx.Provide(NewTestHooks), ) -type ( - // TestHooks holds a registry of active test hooks. It should be obtained through fx and - // used with Get and Set. - // - // TestHooks are an inherently unclean way of writing tests. They require mixing test-only - // concerns into production code. In general you should prefer other ways of writing tests - // wherever possible, and only use TestHooks sparingly, as a last resort. - TestHooks interface { - // private accessors; access must go through package-level Get/Set - get(Key) (any, bool) - set(Key, any) - del(Key) - } - - // testHooksImpl is an implementation of TestHooks. - testHooksImpl struct { - m sync.Map - } -) +// testHooksImpl is an implementation of TestHooks. +type testHooksImpl struct { + m sync.Map +} -func NewTestHooks(_ log.Logger) TestHooks { +func NewTestHooks() TestHooks { return &testHooksImpl{} } +func (*testHooksImpl) testHooks() {} + // Get gets the value of a test hook from the registry. // // TestHooks should be used sparingly, see comment on TestHooks. @@ -45,9 +31,13 @@ func Get[T any](th TestHooks, key Key) (T, bool) { if th == nil { return zero, false } - if val, ok := th.get(key); ok { + impl, ok := th.(*testHooksImpl) + if !ok { + return zero, false + } + if val, ok := impl.m.Load(key); ok { // this is only used in test so we want to panic on type mismatch: - return val.(T), ok // nolint:revive + return val.(T), ok //nolint:revive } return zero, false } @@ -64,18 +54,7 @@ func Call(th TestHooks, key Key) { // Set sets a test hook to a value and returns a cleanup function to unset it. // Calls to Set and the cleanup function should form a stack. func Set[T any](th TestHooks, key Key, val T) func() { - th.set(key, val) - return func() { th.del(key) } -} - -func (th *testHooksImpl) get(key Key) (any, bool) { - return th.m.Load(key) -} - -func (th *testHooksImpl) set(key Key, val any) { - th.m.Store(key, val) -} - -func (th *testHooksImpl) del(key Key) { - th.m.Delete(key) + impl := th.(*testHooksImpl) + impl.m.Store(key, val) + return func() { impl.m.Delete(key) } } diff --git a/common/testing/testhooks/test_impl_test.go b/common/testing/testhooks/test_impl_test.go index c5eb77b2281..f6d17309f9b 100644 --- a/common/testing/testhooks/test_impl_test.go +++ b/common/testing/testhooks/test_impl_test.go @@ -9,7 +9,7 @@ import ( ) func TestGet(t *testing.T) { - th := NewTestHooks(nil) + th := NewTestHooks() t.Run("returns false for unset key", func(t *testing.T) { _, ok := Get[int](th, MatchingDisableSyncMatch) @@ -35,7 +35,7 @@ func TestGet(t *testing.T) { } func TestCall(t *testing.T) { - th := NewTestHooks(nil) + th := NewTestHooks() t.Run("does nothing for unset key", func(t *testing.T) { Call(th, MatchingDisableSyncMatch) // should not panic diff --git a/common/testing/testhooks/testhooks.go b/common/testing/testhooks/testhooks.go new file mode 100644 index 00000000000..2583963e178 --- /dev/null +++ b/common/testing/testhooks/testhooks.go @@ -0,0 +1,11 @@ +package testhooks + +// TestHooks holds a registry of active test hooks. It should be obtained through fx and +// used with Get and Call. +// +// TestHooks are an inherently unclean way of writing tests. They require mixing test-only +// concerns into production code. In general you should prefer other ways of writing tests +// wherever possible, and only use TestHooks sparingly, as a last resort. +type TestHooks interface { + testHooks() +} diff --git a/tests/testcore/onebox.go b/tests/testcore/onebox.go index c4ddd2e0be1..880bee570f5 100644 --- a/tests/testcore/onebox.go +++ b/tests/testcore/onebox.go @@ -214,7 +214,7 @@ func newTemporal(t *testing.T, params *TemporalParams) *TemporalImpl { tlsConfigProvider: params.TLSConfigProvider, captureMetricsHandler: params.CaptureMetricsHandler, dcClient: dynamicconfig.NewMemoryClient(), - testHooks: testhooks.NewTestHooks(params.Logger), + testHooks: testhooks.NewTestHooks(), serviceFxOptions: params.ServiceFxOptions, taskCategoryRegistry: params.TaskCategoryRegistry, hostsByProtocolByService: params.HostsByProtocolByService, From e6887de8598b1f21533848f85a18ccd03045d8c2 Mon Sep 17 00:00:00 2001 From: Stephan Behnke Date: Fri, 5 Dec 2025 14:28:05 -0800 Subject: [PATCH 3/4] rm interfacce --- common/testing/testhooks/noop_impl.go | 13 +++++++----- common/testing/testhooks/test_impl.go | 30 ++++++++++++++------------- common/testing/testhooks/testhooks.go | 11 ---------- 3 files changed, 24 insertions(+), 30 deletions(-) delete mode 100644 common/testing/testhooks/testhooks.go diff --git a/common/testing/testhooks/noop_impl.go b/common/testing/testhooks/noop_impl.go index 7b123ac3402..a83cbf97ca4 100644 --- a/common/testing/testhooks/noop_impl.go +++ b/common/testing/testhooks/noop_impl.go @@ -10,15 +10,18 @@ var Module = fx.Options( fx.Provide(NewTestHooks), ) -// noopTestHooks is a noop implementation of TestHooks for production builds. -type noopTestHooks struct{} +// TestHooks (in production mode) is an empty struct just so the build works. +// See TestHooks in test_impl.go. +// +// TestHooks are an inherently unclean way of writing tests. They require mixing test-only +// concerns into production code. In general you should prefer other ways of writing tests +// wherever possible, and only use TestHooks sparingly, as a last resort. +type TestHooks struct{} func NewTestHooks() TestHooks { - return noopTestHooks{} + return TestHooks{} } -func (noopTestHooks) testHooks() {} - // Get gets the value of a test hook. In production mode it always returns the zero value and // false, which hopefully the compiler will inline and remove the hook as dead code. // diff --git a/common/testing/testhooks/test_impl.go b/common/testing/testhooks/test_impl.go index 643088ee3e1..91c614bb446 100644 --- a/common/testing/testhooks/test_impl.go +++ b/common/testing/testhooks/test_impl.go @@ -12,30 +12,33 @@ var Module = fx.Options( fx.Provide(NewTestHooks), ) -// testHooksImpl is an implementation of TestHooks. -type testHooksImpl struct { +type testHooksData struct { m sync.Map } -func NewTestHooks() TestHooks { - return &testHooksImpl{} +// TestHooks holds a registry of active test hooks. It should be obtained through fx and +// used with Get and Set. +// +// TestHooks are an inherently unclean way of writing tests. They require mixing test-only +// concerns into production code. In general you should prefer other ways of writing tests +// wherever possible, and only use TestHooks sparingly, as a last resort. +type TestHooks struct { + data *testHooksData } -func (*testHooksImpl) testHooks() {} +func NewTestHooks() TestHooks { + return TestHooks{data: &testHooksData{}} +} // Get gets the value of a test hook from the registry. // // TestHooks should be used sparingly, see comment on TestHooks. func Get[T any](th TestHooks, key Key) (T, bool) { var zero T - if th == nil { - return zero, false - } - impl, ok := th.(*testHooksImpl) - if !ok { + if th.data == nil { return zero, false } - if val, ok := impl.m.Load(key); ok { + if val, ok := th.data.m.Load(key); ok { // this is only used in test so we want to panic on type mismatch: return val.(T), ok //nolint:revive } @@ -54,7 +57,6 @@ func Call(th TestHooks, key Key) { // Set sets a test hook to a value and returns a cleanup function to unset it. // Calls to Set and the cleanup function should form a stack. func Set[T any](th TestHooks, key Key, val T) func() { - impl := th.(*testHooksImpl) - impl.m.Store(key, val) - return func() { impl.m.Delete(key) } + th.data.m.Store(key, val) + return func() { th.data.m.Delete(key) } } diff --git a/common/testing/testhooks/testhooks.go b/common/testing/testhooks/testhooks.go deleted file mode 100644 index 2583963e178..00000000000 --- a/common/testing/testhooks/testhooks.go +++ /dev/null @@ -1,11 +0,0 @@ -package testhooks - -// TestHooks holds a registry of active test hooks. It should be obtained through fx and -// used with Get and Call. -// -// TestHooks are an inherently unclean way of writing tests. They require mixing test-only -// concerns into production code. In general you should prefer other ways of writing tests -// wherever possible, and only use TestHooks sparingly, as a last resort. -type TestHooks interface { - testHooks() -} From 1c69d3380bfc64c82e9743f0041adc57ae0dbc02 Mon Sep 17 00:00:00 2001 From: Stephan Behnke Date: Tue, 9 Dec 2025 09:12:23 -0800 Subject: [PATCH 4/4] *sync.Map --- common/testing/testhooks/test_impl.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/common/testing/testhooks/test_impl.go b/common/testing/testhooks/test_impl.go index 91c614bb446..f5dac36a2c4 100644 --- a/common/testing/testhooks/test_impl.go +++ b/common/testing/testhooks/test_impl.go @@ -12,10 +12,6 @@ var Module = fx.Options( fx.Provide(NewTestHooks), ) -type testHooksData struct { - m sync.Map -} - // TestHooks holds a registry of active test hooks. It should be obtained through fx and // used with Get and Set. // @@ -23,11 +19,11 @@ type testHooksData struct { // concerns into production code. In general you should prefer other ways of writing tests // wherever possible, and only use TestHooks sparingly, as a last resort. type TestHooks struct { - data *testHooksData + data *sync.Map } func NewTestHooks() TestHooks { - return TestHooks{data: &testHooksData{}} + return TestHooks{data: &sync.Map{}} } // Get gets the value of a test hook from the registry. @@ -38,7 +34,7 @@ func Get[T any](th TestHooks, key Key) (T, bool) { if th.data == nil { return zero, false } - if val, ok := th.data.m.Load(key); ok { + if val, ok := th.data.Load(key); ok { // this is only used in test so we want to panic on type mismatch: return val.(T), ok //nolint:revive } @@ -57,6 +53,6 @@ func Call(th TestHooks, key Key) { // Set sets a test hook to a value and returns a cleanup function to unset it. // Calls to Set and the cleanup function should form a stack. func Set[T any](th TestHooks, key Key, val T) func() { - th.data.m.Store(key, val) - return func() { th.data.m.Delete(key) } + th.data.Store(key, val) + return func() { th.data.Delete(key) } }