mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-04 02:04:03 +00:00
## What changed? Add a default 90s timeout to individual tests using the `testcore.NewEnv` for setup. Tests should use the `context.Context` returned from `testcore.NewEnv(...)` to fail early when a test exceeds the timeout. ## Why? Go only allows to set a timeout for an entire test run via `go test`, but a single test case can make the entire run be stuck for the full test timeout time. This change will make those tests exit early speeding up the dev process. ## How did you test it? - [ ] built - [X] run locally and tested manually - [X] covered by existing tests - [ ] added new unit test(s) - [ ] added new functional test(s) ## Potential risks Test change only, could introduce flakes if tests did not have previous context timeouts. --- > [!NOTE] > **Medium Risk** > Touches shared test infrastructure and changes how contexts/timeouts are created and propagated across functional tests, which could introduce new test flakes or premature cancellations if timeouts are too aggressive. > > **Overview** > Functional tests created via `testcore.NewEnv` now get a *test-scoped* timeout context (default **90s**, configurable via `TEMPORAL_TEST_TIMEOUT` or per-test `testcore.WithTimeout`) that is canceled on timeout and fails the test during cleanup. > > `testcore.NewContext` is updated to optionally derive from a parent context, and key functional tests are migrated to pass `s.Context()`/`env.Context()` into RPC calls and `context.WithTimeout(...)` to respect cancellations. Linting/docs are updated to discourage `context.Background()` in tests and document the new timeout knob. > > <sup>Written by [Cursor Bugbot]() for commit 98852a5e43d62aec769af4caae7de9783f314065. This will update automatically on new commits. Configure [here]().</sup>
187 lines
7.0 KiB
Markdown
187 lines
7.0 KiB
Markdown
# Testing
|
|
|
|
This document describes the project's testing setup, utilities and best practices.
|
|
|
|
## Setup
|
|
|
|
### Build tags
|
|
- `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.
|
|
|
|
### Environment variables
|
|
- `CGO_ENABLED`: Set to `0` to disable CGO, which can significantly speed up compilation time.
|
|
- `TEMPORAL_TEST_LOG_FORMAT`: Controls the output format for test logs. Available options: `json` or `console`
|
|
- `TEMPORAL_TEST_LOG_LEVEL`: Sets the verbosity level for test logging. Available levels: `debug`, `info`, `warn`, `error`, `fatal`
|
|
- `TEMPORAL_TEST_OTEL_OUTPUT`: Enables OpenTelemetry (OTEL) trace output for failed tests to the provided file path.
|
|
- `TEMPORAL_TEST_SHARED_CLUSTERS`: Number of shared clusters in the pool. Each can be used by multiple tests simultaneously.
|
|
- `TEMPORAL_TEST_DEDICATED_CLUSTERS`: Number of dedicated clusters in the pool. Each can be used by one test only at a time.
|
|
- `TEMPORAL_TEST_TIMEOUT`: Sets the duration timeout per test (e.g., `90s` for 90 seconds). This can be overridden per-test using `testcore.WithTimeout()`. The timeout is multiplied by `debug.TimeoutMultiplier` when debugging.
|
|
- `TEMPORAL_TEST_DATA_ENCODING`: Controls the encoding used for persistence DataBlobs. Available options: `proto3` (default) or `json`.
|
|
|
|
### Debugging via IDE
|
|
|
|
#### GoLand
|
|
|
|
For general instructions, see [GoLand Debugging](https://www.jetbrains.com/help/go/debugging-code.html).
|
|
To pass in the required build tags, add them to the "Go tool arguments" field in the Run/Debug configuration:
|
|
|
|
```
|
|
-tags disable_grpc_modules,test_dep
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### Parallelization
|
|
|
|
All tests (and subtests!) should use `t.Parallel()` to be run concurrently;
|
|
unless there is a reason not to.
|
|
|
|
`make parallelize-tests` can be used to automatically add `t.Parallel()`.
|
|
Use `//parallelize:ignore` to opt your test out of it.
|
|
|
|
Functional tests in `tests/` using `testcore.NewEnv(t)` will always use `t.Parallel()`;
|
|
unless the `MustRunSequential` option is passed.
|
|
|
|
## Test helpers
|
|
|
|
Test helpers can be found in the [common/testing](../../common/testing) package.
|
|
|
|
### testvars package
|
|
|
|
Instead of creating identifiers like task queue name, namespace or worker identity by hand,
|
|
use the `testvars` package.
|
|
|
|
Example:
|
|
|
|
```go
|
|
func TestFoo(t *testing.T) {
|
|
tv := testvars.New(t)
|
|
|
|
req := &workflowservice.SignalWithStartWorkflowExecutionRequest{
|
|
RequestId: tv.Any().String(),
|
|
Namespace: tv.NamespaceName().String(),
|
|
WorkflowId: tv.WorkflowID(),
|
|
WorkflowType: tv.WorkflowType(),
|
|
TaskQueue: tv.TaskQueue(),
|
|
SignalName: tv.SignalName(),
|
|
}
|
|
}
|
|
```
|
|
Later you can assert on the generated values. `testvars` guarantees to provide the same value every time you call the same method.
|
|
|
|
```go
|
|
assert.Equal(t, tv.WorkflowID(), startedWorkflow.WorkflowId)
|
|
```
|
|
|
|
If you need more than one value for the same entity in one test, you can use `WithEntityNumber()` method to
|
|
get a new instance of `testvars` with a different value.
|
|
|
|
```go
|
|
func TestFoo(t *testing.T) {
|
|
|
|
tv := testvars.New(t)
|
|
tv1 := tv.WithUpdateIDNumber(1)
|
|
tv2 := tv.WithUpdateIDNumber(2)
|
|
|
|
req1 := &workflowservice.UpdateWorkflowExecutionRequest{
|
|
Namespace: tv1.NamespaceName().String(),
|
|
WorkflowExecution: tv1.WorkflowExecution(),
|
|
Request: &updatepb.Request{
|
|
Meta: &updatepb.Meta{UpdateId: tv1.UpdateID()},
|
|
Input: &updatepb.Input{
|
|
Name: tv1.HandlerName(),
|
|
Args: payloads.EncodeString("args-value-of-" + tv1.UpdateID()),
|
|
},
|
|
},
|
|
}
|
|
|
|
req2 := &workflowservice.UpdateWorkflowExecutionRequest{
|
|
Namespace: tv2.NamespaceName().String(),
|
|
WorkflowExecution: tv2.WorkflowExecution(),
|
|
Request: &updatepb.Request{
|
|
Meta: &updatepb.Meta{UpdateId: tv2.UpdateID()},
|
|
Input: &updatepb.Input{
|
|
Name: tv2.HandlerName(),
|
|
Args: payloads.EncodeString("args-value-of-" + tv2.UpdateID()),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
```
|
|
|
|
If you don't care about specific value, you can use `Any()` method to generate a random value.
|
|
It indicates that value doesn't matter for this test and will never be asserted on (but required for API, for example).
|
|
|
|
### taskpoller package
|
|
|
|
For end-to-end testing, consider using `taskpoller.TaskPoller` to handle workflow tasks. This is
|
|
useful when you need full control over the worker behavior in a way that the SDK cannot provide;
|
|
or if there's no SDK support for that API available yet.
|
|
|
|
You'll find a fully initialized task poller in any functional test suite, look for `s.TaskPoller`.
|
|
|
|
_NOTE: The previous `testcore.TaskPoller` has been deprecated and should not be used in new code._
|
|
|
|
### testhooks package
|
|
|
|
The `testhooks` package injects test-specific behavior into production code paths that are otherwise
|
|
difficult to test. This is a **last resort** - prefer mocking and dependency injection when possible.
|
|
|
|
**Example:**
|
|
|
|
The UpdateWithStart API has a race window between releasing a lock and starting a workflow where
|
|
another request could create the same workflow first. The `UpdateWithStartInBetweenLockAndStart`
|
|
hook lets tests inject a callback at this exact point, making it possible to reliably test
|
|
conflict handling.
|
|
|
|
_NOTE: Tests using testhooks must be run with `-tags=test_dep`._
|
|
|
|
### softassert package
|
|
|
|
`softassert.That` is a "soft" assertion that logs an error if the given condition is false.
|
|
|
|
It is useful to highlight invariant violations in production code.
|
|
It is *not* a substitute for regular error handling, validation, or control flow.
|
|
|
|
In functional tests, a failed soft assertion will not stop the test execution immediately, but it
|
|
will ultimately fail the test.
|
|
|
|
### Test Cluster
|
|
|
|
Use `testcore.NewEnv(t)` to create a test environment with access to a Temporal cluster for end-to-end testing.
|
|
|
|
```go
|
|
func TestMyFeatureSuite(t *testing.T) {
|
|
t.Run("scenario one", func(t *testing.T) {
|
|
s := testcore.NewEnv(t)
|
|
// ...
|
|
})}
|
|
```
|
|
|
|
Note that each test has its own namespace (`s.Namespace()`) for isolation.
|
|
|
|
> **Note:** The legacy `FunctionalTestBase` (using testify's `suite`) has been deprecated for new tests.
|
|
|
|
## OpenTelemetry (OTEL)
|
|
|
|
To debug your test by analysing observability traces, set the following environment variables:
|
|
|
|
```bash
|
|
export OTEL_BSP_SCHEDULE_DELAY=100
|
|
export OTEL_EXPORTER_OTLP_TRACES_INSECURE=true
|
|
export OTEL_TRACES_EXPORTER=otlp
|
|
export TEMPORAL_OTEL_DEBUG=true
|
|
```
|
|
|
|
And have an OTEL collector running, such as Grafana Tempo (`make start-dependencies`).
|
|
|
|
See [tracing.md](../../docs/development/tracing.md) for more details.
|
|
|
|
## Code coverage
|
|
|
|
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.
|