mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-04 02:04:03 +00:00
## What changed?
Changed use of `testhooks`'s `Set` to be runtime error instead of
compile time error.
It still ensures that the default impl (used in `go build` artifacts
without `test_dep` tag) is the noop impl.
## Why?
When running a functional test via plain `go test` it will fail, as the
`test_dep` build flag is required. It is required to prevent
accidentally using the default/noop implementation of the `testhooks`
package which allows injecting custom behavior into production code from
tests.
But requiring `test_dep` to run any test adds friction: CLI invocations
need to specify it manually, editor setups need to be adjusted, AI
agents need to be instructed. That's a high price to pay for a test
helper that is used fairly sparsely.
## How did you test it?
- [ ] built
- [x] run locally and tested manually
- [x] covered by existing tests
- [x] added new unit test(s)
- [ ] added new functional test(s)
Functional test without testhook:
```
% go test ./tests -run "OwnershipLostErrorSuite" -count=1 .
ok go.temporal.io/server/tests 7.062s
```
Functional test with testhook and build tag:
```
% go test ./tests -run "TestUpdateWorkflowSuite" -tags test_dep -count=1 .
ok go.temporal.io/server/tests 36.975s
```
Functional test with testhook but no build tag:
```
% go test ./tests -run "TestUpdateWorkflowSuite" -count=1 .
...
noop_impl.go:39: test panicked: testhooks.Set called but TestHooks are not enabled: use -tags=test_dep when running `go test`
goroutine 33293 [running]:
runtime/debug.Stack()
...
FAIL
FAIL go.temporal.io/server/tests 57.128s
FAIL
```
59 lines
1.5 KiB
Go
59 lines
1.5 KiB
Go
//go:build test_dep
|
|
|
|
package testhooks
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"go.uber.org/fx"
|
|
)
|
|
|
|
var Module = fx.Options(
|
|
fx.Provide(NewTestHooks),
|
|
)
|
|
|
|
// 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 *sync.Map
|
|
}
|
|
|
|
func NewTestHooks() TestHooks {
|
|
return TestHooks{data: &sync.Map{}}
|
|
}
|
|
|
|
// 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.data == nil {
|
|
return zero, false
|
|
}
|
|
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
|
|
}
|
|
return zero, false
|
|
}
|
|
|
|
// Call calls a func() hook if present.
|
|
//
|
|
// TestHooks should be used sparingly, see comment on TestHooks.
|
|
func Call(th TestHooks, key Key) {
|
|
if hook, ok := Get[func()](th, key); ok {
|
|
hook()
|
|
}
|
|
}
|
|
|
|
// 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.Store(key, val)
|
|
return func() { th.data.Delete(key) }
|
|
}
|