Skip to content

Durabletesting2#2428

Draft
GarrettBeatty wants to merge 9 commits into
devfrom
durabletesting2
Draft

Durabletesting2#2428
GarrettBeatty wants to merge 9 commits into
devfrom
durabletesting2

Conversation

@GarrettBeatty

Copy link
Copy Markdown
Contributor

Summary

  • Introduces Amazon.Lambda.DurableExecution.Testing — a new NuGet package for testing durable workflows locally and against deployed functions
  • Local runner (DurableTestRunner<TIn,TOut>): drives workflows to completion in-process using the real runtime engine with an in-memory service backend. Supports time skipping, sibling function registration, and the full callback flow
  • Cloud runner (CloudDurableTestRunner<TIn,TOut>): invokes deployed Lambda functions, polls for results, and provides the same TestResult<TOutput> inspection API
  • Both runners implement IDurableTestRunner<TIn,TOut> so tests can swap backends without code changes
  • Adds IDurableServiceClient internal interface to the runtime package as the injection seam (no public API change)

Key types

  • DurableTestRunner<TIn,TOut> / CloudDurableTestRunner<TIn,TOut> — the runners
  • TestResult<TOutput> / TestStep — step-level inspection (GetStep, GetResult, Children)
  • TestRunnerOptions / CloudTestRunnerOptions — configuration
  • Exception types: TestExecutionFailedException, TestExecutionLimitException, UnregisteredSiblingFunctionException, CloudTestException

Commits (each self-contained with tests)

  1. IDurableServiceClient interface seam
  2. Project scaffolding + public data types (53 tests)
  3. InMemoryOperationStore + CheckpointProcessor (23 tests)
  4. FunctionRegistry for sibling routing (9 tests)
  5. DurableTestRunner + RunAsync (9 tests)
  6. Callback support (6 tests)
  7. CloudDurableTestRunner (6 tests)

Test plan

  • 106 unit/integration tests passing on net8.0 and net10.0
  • Existing runtime tests unaffected (pre-existing flaky test on net8.0 only)
  • Review public API surface matches design doc
  • Verify InternalsVisibleTo wiring correct for strong-named assemblies
  • Cloud runner E2E validation (deferred to integ-tests pipeline)

Introduce an internal IDurableServiceClient interface that
LambdaDurableServiceClient now implements. Refactor WrapAsyncCore to
accept this interface instead of IAmazonLambda, and add an internal
WrapAsync overload the testing package can call directly. Add
InternalsVisibleTo for Amazon.Lambda.DurableExecution.Testing.
Create Amazon.Lambda.DurableExecution.Testing package with the full
public API surface: IDurableTestRunner<TIn,TOut>, TestResult<TOut>,
TestStep, TestRunnerOptions, CloudTestRunnerOptions, OperationKind,
OperationStatus, and exception types (TestExecutionFailedException,
TestExecutionLimitException, UnregisteredSiblingFunctionException,
CloudTestException).

Add Amazon.Lambda.DurableExecution.Testing.Tests with unit tests for
TestStep (kind mapping, typed accessors, GetResult<T> deserialization),
TestResult (step lookup, parent-child linking, EnsureSucceeded),
options validation, and exception formatting.
…erviceClient

The core state machine for the local test runner:
- InMemoryOperationStore: per-execution operation storage with token tracking
- CheckpointProcessor: maps OperationUpdate actions (START/SUCCEED/FAIL/RETRY)
  to Operation status transitions, mints callback IDs, applies time skipping
- InMemoryDurableServiceClient: implements IDurableServiceClient by delegating
  to the processor and store

Includes unit tests for both store (isolation, ordering, upsert) and processor
(all action types, time skipping, callback ID minting, WaitForCondition retry).
Implements registration and invocation dispatch for InvokeAsync calls:
- RegisterPlain<TPayload, TResult>: plain Lambda handlers
- RegisterDurable<TPayload, TResult>: durable handlers (placeholder for
  nested runner, wired in commit 4)
- Name matching: exact match first, then ARN-extracted name fallback
- Errors from handlers returned as ErrorObject (not re-thrown)
- UnregisteredSiblingFunctionException for unknown functions
The core local test runner that drives workflows to completion:
- ExecutionOrchestrator: seeds the EXECUTION op with serialized input,
  then loops DurableFunction.WrapAsync (internal overload) with the
  in-memory service client until terminal or MaxInvocations exceeded
- DurableTestRunner<TInput, TOutput>: public entry point with RunAsync,
  RegisterFunction, RegisterDurableFunction, IAsyncDisposable

Integration tests verify: single step, multi-step inspection, workflow
failure, EnsureSucceeded, WaitAsync with time skipping, MaxInvocations
limit, timeout, null results, and custom ARN propagation.
…, WaitForResultAsync

Implements the two-call pattern for callback workflows:
- StartAsync: drives workflow until it suspends on a callback, returns ARN
- WaitForCallbackAsync: finds the pending callback ID in the store
  (matches by name or "{name}-callback" convention from the runtime)
- SendCallbackSuccessAsync/FailureAsync: mutates the callback operation
- SendCallbackHeartbeatAsync: validates callback exists (no-op locally)
- WaitForResultAsync: re-drives the workflow to terminal after callback
  resolution, caches completed results

Also adds DriveUntilSuspendedAsync to ExecutionOrchestrator for the
Start→Suspend pattern.
…unctions

Implements the cloud test runner that invokes real Lambda functions:
- StartAsync: invokes the function, extracts DurableExecutionArn from response
- WaitForResultAsync: polls GetDurableExecutionState until terminal, builds TestResult
- WaitForCallbackAsync: polls until a CALLBACK STARTED operation appears
- SendCallbackSuccessAsync/FailureAsync/HeartbeatAsync: calls real Lambda APIs
- BuildTestResult: reconstructs TestResult<TOutput> from polled operations

Tests use a mock AmazonLambdaClient to verify ARN extraction, polling
behavior, timeout handling, callback discovery, and API delegation.
Add the testing package to .autover/autover.json and set version to
0.0.1. Preview label will be added manually on first release.
…itForCondition

Wire up and correct the Amazon.Lambda.DurableExecution.Testing package so its
headline features work end-to-end, with integration coverage for each.

Local runner:
- Wire RegisterFunction/RegisterDurableFunction into execution: CheckpointProcessor
  captures CHAINED_INVOKE STARTs and the orchestrator resolves them through the
  FunctionRegistry between invocations, stamping ChainedInvokeDetails.Result/Error.
  Unregistered siblings throw UnregisteredSiblingFunctionException instead of
  looping to TestExecutionLimitException.
- Implement durable siblings via a nested DurableTestRunner (was NotImplementedException).
- Persist WaitForCondition poll state (RETRY Payload -> StepDetails.Result) and advance
  its attempt counter on RETRY (it emits START only once); remove the dead Type==Wait
  WaitForCondition branches (runtime emits Type=STEP).
- Accumulate InvocationCount across StartAsync + WaitForResultAsync; make
  SeedExecutionOperation idempotent so re-drives don't reset the EXECUTION op.
- RunAsync throws an actionable error when a workflow suspends on a callback.

Cloud runner:
- Determine terminal state and the typed result/error from GetDurableExecution
  (Status/Result/Error) instead of scanning the EXECUTION op in the state stream,
  which never reaches a terminal status.
- Use the typed InvokeResponse.DurableExecutionArn; treat TIMED_OUT/STOPPED as
  terminal; map all four error fields on SendCallbackFailureAsync.

Hardening:
- InMemoryOperationStore is lock-guarded and returns snapshot copies.
- OperationStatus constants alias the runtime OperationStatuses (compile-time linked).
- TestResult gains IsSucceeded/IsFailed and GetStepsByStatus.
- Mark the package preview (0.0.1-preview + autover PrereleaseLabel) to match the runtime.

Tests: +15 (sibling invoke incl. durable + unregistered + failure, WaitForCondition
end-to-end, callback-via-RunAsync error, accumulated invocation count, step retry
attempt 1->2->3, cloud typed result/error/terminal-status, store snapshot/concurrency).
106 -> 121 passing on net8.0 and net10.0.
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.

1 participant