Skip to content

Scopable InjectHook#9190

Merged
stephanos merged 8 commits intotemporalio:mainfrom
stephanos:injecthook-ns
Feb 26, 2026
Merged

Scopable InjectHook#9190
stephanos merged 8 commits intotemporalio:mainfrom
stephanos:injecthook-ns

Conversation

@stephanos
Copy link
Contributor

@stephanos stephanos commented Feb 1, 2026

What changed?

Make test hooks scopable to namespace.

Why?

Right now using InjectHook requires a dedicated cluster, but most hooks can be scoped to a namespace and allow using a shared cluster. This reduces resource consumption and increases test concurrency.

How did you test it?

  • built
  • run locally and tested manually
  • covered by existing tests
  • added new unit test(s)
  • added new functional test(s)


// simulate a race condition
s.InjectHook(testhooks.UpdateWithStartInBetweenLockAndStart, func() {
s.InjectHook(testhooks.NewHook(testhooks.UpdateWithStartInBetweenLockAndStart, func() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using testhooks.NewHook since InjectHook is method and Go doesn't allow type parameters there. This way it's a bit more verbose but preserves type checking.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, hmm. they are relaxing this constraint, but I guess it'll be in go 1.27

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh that'd be really neat

This comment was marked as resolved.

@temporalio temporalio deleted a comment from Copilot AI Feb 1, 2026
@temporalio temporalio deleted a comment from Copilot AI Feb 1, 2026
@temporalio temporalio deleted a comment from Copilot AI Feb 1, 2026
@stephanos stephanos force-pushed the injecthook-ns branch 8 times, most recently from aa65611 to 0d2edcd Compare February 2, 2026 03:36
if th.data == nil {
if th.lock == nil {
// This means TestHooks wasn't created via NewTestHooks. Ignore.
return zero, false
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This happened in service/worker/workerdeployment/workflow_test.go.

@stephanos stephanos marked this pull request as ready for review February 2, 2026 16:40
@stephanos stephanos requested review from a team as code owners February 2, 2026 16:40
@stephanos stephanos requested a review from dnr February 2, 2026 16:41
// Test hook keys with their return type and scope.
// Try to avoid global scope as it requires a dedicated test cluster.
var (
MatchingDisableSyncMatch = newKey[bool, namespace.ID]()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unfortunate to do a bunch of stuff at static init time just for tests (of course much better than doing it at runtime). What if we did something like define newKey differently in test/prod and have the prod version just return a struct{}{}?

return h.key.scope
}

func (h hook[T, S]) Apply(th TestHooks, scope any) func() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the new interface here? we can't roll this check into Set?

type TestHooks struct {
data *sync.Map
lock *sync.Mutex
hooks map[keyID]map[any]any // id -> scope -> value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you consider a single level map with a struct key? (then it could still be a sync.Map which might simplify the code?)

Comment on lines +74 to +78
scopes := th.hooks[key.id]
if scopes == nil {
scopes = make(map[any]any)
th.hooks[key.id] = scopes
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
scopes := th.hooks[key.id]
if scopes == nil {
scopes = make(map[any]any)
th.hooks[key.id] = scopes
}
scopes := util.GetOrSetMap(th.hooks, key.id)


// simulate a race condition
s.InjectHook(testhooks.UpdateWithStartInBetweenLockAndStart, func() {
s.InjectHook(testhooks.NewHook(testhooks.UpdateWithStartInBetweenLockAndStart, func() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, hmm. they are relaxing this constraint, but I guess it'll be in go 1.27

@stephanos
Copy link
Contributor Author

👍 Thank you for the feedback; it's shorter and cleaner now I think.

@stephanos stephanos requested a review from dnr February 12, 2026 03:51
}
}

type Key[T any, S any] struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you move this into test_impl/noop_impl and have it be defined as struct{} in noop_impl?

yeah, it works fine. I had to move Hook+NewHook also, and defined it as struct{} also. and defined Hook.Scope/Apply to do the same panic as Set()


type Key[T any, S any] struct {
id keyID
scope Scope
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got a little confused at first between the difference between Key and hookKey because they both have a "scope" but the meaning of the word is different. maybe:

Suggested change
scope Scope
scopeType ScopeType

@stephanos stephanos requested a review from dnr February 25, 2026 03:33
@stephanos stephanos enabled auto-merge (squash) February 26, 2026 01:28
@stephanos stephanos merged commit 0ab5275 into temporalio:main Feb 26, 2026
46 checks passed
@dnr dnr mentioned this pull request Feb 26, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants