## What changed? Migrated `TestWorkflowUpdateSuite` away from testify's `Suite`; enabling parallel test execution. **How it works** - a test invokes `testcore.NewEnv(t)` to obtain a new `TestEnv` - `TestEnv` sets `t.Parallel()` (_intentionally not giving a way to opt out!_) - `TestEnv` obtains a test cluster from `clusterPool` (_or blocks if all are in-use right now_) - env var `TEMPORAL_TEST_SHARED_CLUSTERS` controls size of the pool - if a test relies on APIs like InjectHook, a dedicated cluster is used to prevent overlap - env var `TEMPORAL_TEST_DEDICATED_CLUSTERS` controls number of dedicated clusters **testify suites** Existing test suites are limited by the same dedicated cluster pool to prevent creating too many clusters. **Database connections** SQLite setup for TestEnv-based func tests (ie only TestWorkflowUpdateSuite so far) has been changed to a file-based approach since that supports much better concurrency due to its WAL that an in-memory SQLite database does not support. Connection limits for other databases were also raised due to connection errors. **Planned follow-ups** - Migrating the other testify suites should be fairly straight-forward with the use of AI agents. - Reduce need for dedicated clusters by leveraging isolated namespace-per-test more. - Eliminate all `time.Sleep`s. - Tweak test cluster pool behavior. ## Why? 1. **Local speedup**: benchmarks show a ~50% speed increase (36.1s → 16.6s) for `TestWorkflowUpdateSuite`. 5. **Namespace isolation**: every test runs in its own namespace. This greatly reduces the risk of (accidental) collisions and also reduces the need to craft unique identifiers such as for task queues and workflow IDs. 6. **Deprecate testify suites**: Long-term strategy to remove use of testify suites in functional tests (one reason being their inability to run tests within a suite in parallel). ## How did you test it? - [ ] built - [ ] run locally and tested manually - [x] covered by existing tests - [ ] added new unit test(s) - [ ] added new functional test(s) ## Potential Issues 1. Logs become less useful since there is more interleaving of tests. 2. Higher resource consumption: it requires more concurrent connections to databases and shows higher memory consumption (see 3 and 4). This could cause some short-term instability on CI. Note that some other PRs were merged to add mechanics for monitoring memory usage much better; which will help here. 4. Until all functional tests are converted, there is an imbalance in test cluster creation: migrated tests use the shared pool while current tests create one cluster each. Especially given the fact that some tests don't allow for test cluster sharing as they use non-parallelizable actions such as `InjectHook` or dynamic config overrides. With some more effort the number of these can be reduced. 7. Setup of test clusters was designed around the idea of short-lived clusters, one per suite. But when re-using them for longer, some of the assumptions don't hold anymore and increase memory usage. There's a band aid in place to limit how often a test cluster can be used before it's torn down. A long-term solution requires some design changes to how test clusters are started/used/torn down. 8. If there are certain cross-namespace issues or bugs that affect multiple tests, it might be harder to identify the root cause now. However; the existing test re-runs should at least mitigate these short-term. --------- Co-authored-by: Dan Davison <dandavison7@gmail.com>
5.2 KiB
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 to0to disable CGO, which can significantly speed up compilation time.TEMPORAL_TEST_LOG_FORMAT: Controls the output format for test logs. Available options:jsonorconsoleTEMPORAL_TEST_LOG_LEVEL: Sets the verbosity level for test logging. Available levels:debug,info,warn,error,fatalTEMPORAL_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.
Debugging via IDE
GoLand
For general instructions, see GoLand Debugging. 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
Test helpers
Test helpers can be found in the common/testing package.
testvars package
Instead of creating identifiers like task queue name, namespace or worker identity by hand,
use the testvars package.
Example:
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.
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.
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.
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.
OpenTelemetry (OTEL)
To debug your test by analysing observability traces, set the following environment variables:
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 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 to see code coverage directly in GitHub PRs.