Repository navigation
test(otel): cover replay and chained execution #796
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b31739b
444199a
6484e57
974b38a
f56cf00
522d3fc
a8baa73
dc736d5
1d27b7c
fb0226f
3ec2f08
49af62d
2cb44c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,7 +12,7 @@ language-agnostic OTel conformance runner in | |
| which invokes each function, collects its spans from the configured backend, and | ||
| asserts they match the shared requirement specification. That repository owns the | ||
| runner, the requirement specifications, and the orchestration (backend matrix, | ||
| ADOT layer resolution, collector build, long-running cycle); this package owns | ||
| ADOT layer resolution, collector build); this package owns | ||
| the Python handlers and SAM templates. | ||
|
|
||
| This mirrors the non-OTel | ||
|
|
@@ -33,7 +33,7 @@ template-long-running.yaml # otel-long-running suite | |
| tests/ # contract tests for the templates and handlers | ||
| ``` | ||
|
|
||
| The 20 invocation and 20 execution requirements reuse the same scenario | ||
| The invocation and execution requirements reuse the same scenario | ||
| handlers; the view is selected per function through the `OTEL_PLUGIN_MODE` | ||
| environment variable, which `common.otel_plugin_factory()` reads to pick | ||
| `InvocationOtelPluginFactory` or `ExecutionOtelPluginFactory`. `template.yaml` | ||
|
|
@@ -63,6 +63,13 @@ deploys only the view named by its `OtelSuite` parameter. | |
| | `otel-invocation-18` | `otel_18_chained_invoke_failure.handler` | Verifies failed chained-invoke telemetry. | | ||
| | `otel-invocation-19` | `otel_19_execution_failure.handler` | Verifies telemetry for a direct handler failure. | | ||
| | `otel-invocation-20` | `otel_20_virtual_context.handler` | Verifies a virtual child-context span without context checkpoints. | | ||
| | `otel-invocation-21` | `otel_21_completed_step_replay.handler` | Local coverage verifies completed steps are skipped after wait/resume; cloud coverage is deferred. | | ||
| | `otel-invocation-22` | `otel_22_user_function_context.handler` | Creates ordinary user spans under the active handler, attempt, child, branch, and iteration contexts. | | ||
| | `otel-invocation-23` | `otel_23_callback_function_context.handler` | Verifies retry/check attempts, callback submitter, wrapped retry helper, and virtual-child callback parents. | | ||
| | `otel-invocation-24` | `otel_24_invocation_retry_status.handler` | Raises a retryable invocation error after a completed step, then resumes with its saved result. | | ||
| | `otel-invocation-25` | `otel_25_caught_callback_failure_replay.handler` | Catches a callback failure without diagnostic details, then replays it through two callback barriers. | | ||
| | `otel-invocation-26` | `otel_26_external_callback_completion_replay.handler` | Exports an external callback completion before the observed step and preserves its export count across two later barriers. | | ||
| | `otel-invocation-27` | `otel_27_chained_invoke_suspend.handler` | Verifies caller and child invocation spans when the child suspends for a durable wait and resumes. | | ||
| | `otel-execution-1` | `otel_1_success.handler` | Verifies the execution-view workflow, step, and attempt hierarchy. | | ||
| | `otel-execution-2` | `otel_2_wait_resume.handler` | Verifies the execution view across a resumed invocation. | | ||
| | `otel-execution-3` | `otel_3_retry.handler` | Verifies the execution view across retry attempts. | | ||
|
|
@@ -83,13 +90,91 @@ deploys only the view named by its `OtelSuite` parameter. | |
| | `otel-execution-18` | `otel_18_chained_invoke_failure.handler` | Verifies source and target failed workflow roots. | | ||
| | `otel-execution-19` | `otel_19_execution_failure.handler` | Verifies a failed invocation without a completed workflow. | | ||
| | `otel-execution-20` | `otel_20_virtual_context.handler` | Verifies a virtual child-context span under the workflow root. | | ||
| | `otel-execution-21` | `otel_21_completed_step_replay.handler` | Local coverage verifies completed-operation spans are exported once across replay; cloud coverage is deferred. | | ||
| | `otel-execution-22` | `otel_22_user_function_context.handler` | Observes active execution-view callback contexts without supplying or repairing parents. | | ||
| | `otel-execution-23` | `otel_23_callback_function_context.handler` | Verifies the same SDK-owned callback lifecycle parents in the execution view. | | ||
| | `otel-execution-24` | `otel_24_invocation_retry_status.handler` | Verifies invocation retry status independently of step retry and recovery re-exports. | | ||
| | `otel-execution-25` | `otel_25_caught_callback_failure_replay.handler` | Verifies one failed callback export with ERROR and no exception details while the caught execution succeeds. | | ||
| | `otel-execution-26` | `otel_26_external_callback_completion_replay.handler` | Verifies first-completion export timing and no duplicate export across later callback resumes. | | ||
| | `otel-execution-27` | `otel_27_chained_invoke_suspend.handler` | Verifies caller/child workflow parentage and continuation links across child suspension. | | ||
| | `otel-long-running-1` | `otel_long_running_1_wait.handler` | Verifies wait and resume telemetry across a long durable suspension. | | ||
| | `otel-long-running-2` | `otel_long_running_2_retry.handler` | Verifies retry telemetry across a long durable backoff. | | ||
| | `otel-long-running-3` | `otel_long_running_3_callback.handler` | Verifies callback telemetry when completion arrives after a long delay. | | ||
| | `otel-long-running-4` | `otel_long_running_4_chained_invoke.handler` | Verifies chained-invoke telemetry while the target stays suspended. | | ||
|
|
||
| The runner discovers each mapping from `TestingMetadata.TestDescription` on the | ||
| functions in the templates. | ||
| The runner discovers each active cloud mapping from | ||
| `TestingMetadata.TestDescription` on the functions in the templates. | ||
|
|
||
| ## Deferred cloud coverage | ||
|
|
||
| Case 21 is declared `NOT_IMPLEMENTED` in both views until the shared history | ||
| requirement accepts `WaitSucceeded` either before or after | ||
| `InvocationCompleted`. Both catalogs currently require invocation completion | ||
| first, but the wait timer runs independently of invocation cleanup. The | ||
| [execution-view cloud run](https://github.com/aws/aws-durable-execution-sdk-python/actions/runs/40588786563/job/122487525577) | ||
| observed the reverse order while the execution succeeded with `before-after`. | ||
| See the [review finding](https://github.com/aws/aws-durable-execution-sdk-python/pull/796#discussion_r4394770596). | ||
| The handler and local tests remain: they verify one execution of each step | ||
| body, `PENDING`/`SUCCEEDED` invocations, and one export per completed operation | ||
| in the execution view. Re-enable the SAM functions and build targets after the | ||
| shared requirement accepts both history orderings without relaxing those replay | ||
| assertions. | ||
|
|
||
| ## Callback regressions awaiting fixes | ||
|
|
||
| Cases 25 and 26 are active in both views, including their SAM functions, build | ||
| targets, and local integration tests. These tests intentionally keep CI failing | ||
| until the required SDK and testing-library fixes merge. They are ordinary | ||
| failing tests, without skips, expected-failure markers, or failure suppression. | ||
|
|
||
| - Case 25 needs terminal failure status without diagnostic details | ||
| ([#853](https://github.com/aws/aws-durable-execution-sdk-python/issues/853)) | ||
| and consistent empty callback error history | ||
| ([#862](https://github.com/aws/aws-durable-execution-sdk-python/issues/862)). | ||
| - Case 26 needs completion discovery and export | ||
| ([#854](https://github.com/aws/aws-durable-execution-sdk-python/issues/854)), | ||
| callback update production | ||
| ([#860](https://github.com/aws/aws-durable-execution-sdk-python/issues/860)), | ||
| and invocation-input acknowledgment | ||
| ([#861](https://github.com/aws/aws-durable-execution-sdk-python/issues/861)). | ||
| - The additional case 17 callback-error regression also needs snapshot isolation | ||
| ([#852](https://github.com/aws/aws-durable-execution-sdk-python/issues/852)). | ||
| The existing case 17 handler and supplied-error coverage remain active. | ||
|
Comment on lines
+130
to
+142
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This list no longer matches what the tests need. Two of the listed issues are closed, and the tests pass without the fixes for two others. Can it say that case 25 waits on Python SDK #853: report terminal failures and keep exception events within span bounds and case 26 waits on Python SDK #860: record callback terminal completions as updated operations, and drop the other entries? Two things I checked support this:
The description lists the same prerequisites, so it may need the same change. |
||
|
|
||
| Case 25 asserts the local-runner contract proposed in #862: detailed failure | ||
| history includes an empty `Payload` with `Truncated: false`, while metadata-only | ||
| history omits the payload and sets `Truncated: true`. The exact empty history | ||
| `Payload` shape remains **unverified** against deployed AWS Lambda and requires | ||
| service evidence before claiming service parity. The test inspects raw exported | ||
| spans and ordinary pytest diagnostics; it has no configurable proof-file output. | ||
|
|
||
| Case 26 asserts inside the observed step that the terminal callback has already | ||
| reached the exporter, then checks that its raw export count remains one at both | ||
| later barriers and after completion. Recorded span timestamps alone are not | ||
| evidence of export order. | ||
|
|
||
| The additional case 17 test remains available for follow-up in | ||
| [commit `6484e577`](https://github.com/aws/aws-durable-execution-sdk-python/commit/6484e577b478aeb6345e346515b40bd516cacb1f). | ||
| Before restoring it, correct its data-inclusion expectations: detailed errors | ||
| are untruncated; metadata-only history omits payloads and cannot reconstruct | ||
| diagnostic details. | ||
|
|
||
| Case 24 remains active and covers invocation `RETRY` becoming `RETRYING`/`UNSET`. | ||
|
|
||
| ## Callback coverage boundary | ||
|
|
||
| Cases 22 and 23 create normal user spans from the active context, without an | ||
| explicit parent or a copied execution ARN. They cover handler context, step and | ||
| condition attempts, child and branch bodies, callback submission, wrapped | ||
| `with_retry` body/strategy callbacks, and a virtual child. Case 23 places the | ||
| retry helper and virtual child after callback completion so ordinary successful | ||
| replay does not repeat their probes. | ||
|
|
||
| This does not promise an operation/attempt parent for every arbitrary callback. | ||
| General retry/wait policies, serializers, summary generators, and item naming | ||
| have phase-specific caller scopes outside the wrapped user-function lifecycle; | ||
| these cases do not impose a new ownership policy on them. Instrumentation | ||
| extensions and user-created threads are outside this business-callback contract. | ||
|
|
||
| ## How a handler maps to a requirement | ||
|
|
||
|
|
@@ -197,6 +282,16 @@ write access; the runner identity needs list, read, and cleanup access. | |
| `.github/workflows/opentelemetry-conformance-tests.yml` calls the shared | ||
| orchestrator in the conformance repository and points it at this package with | ||
| `examples_dir`, so orchestration stays centralized while the handlers run from | ||
| the commit under test. Pull requests run the invocation and execution suites plus | ||
| a short (60-second) long-running cycle; the full multi-hour long-running cycle is | ||
| driven by `workflow_dispatch` with `phase: launch` and `phase: check`. | ||
| the commit under test. The shared workflow tracks the conformance repository's | ||
| `main` branch, and the test catalog defaults to `main`. Manual runs can override | ||
| the catalog with the `conformance_test_ref` input. Pull requests run the | ||
| invocation and execution suites, with only case 21 reported as | ||
| `NOT_IMPLEMENTED`. Cases 25 and 26 remain enabled while their prerequisite fixes | ||
| are pending. The orchestrator no longer runs the legacy long-running | ||
| suite; its templates and local contract tests remain in this package. | ||
|
|
||
| Case 27 (`chained-invoke-suspend`) invokes a child that performs a one-second | ||
| durable wait and returns the original payload after resuming. Both views | ||
| check two caller invocations, two child invocations and the child wait's | ||
| parentage and continuation links. It replaces the suspended-child scenario | ||
| formerly exercised by the short mode of `otel-long-running-4`. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # SPDX-FileCopyrightText: 2026-present Amazon.com, Inc. or its affiliates. | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| """Normal successful replay of a completed step for OTel case 21.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import Any | ||
|
|
||
| from aws_durable_execution_sdk_python import ( | ||
| DurableContext, | ||
| StepContext, | ||
| durable_execution, | ||
| durable_step, | ||
| ) | ||
| from aws_durable_execution_sdk_python.config import Duration | ||
| from common import otel_plugin_factory, require_scenario | ||
|
|
||
|
|
||
| @durable_step | ||
| def before_wait(_step_context: StepContext) -> str: | ||
| return "before" | ||
|
|
||
|
|
||
| @durable_step | ||
| def after_wait(_step_context: StepContext) -> str: | ||
| return "after" | ||
|
|
||
|
|
||
| @durable_execution(plugins=[otel_plugin_factory()]) | ||
| def handler(event: dict[str, Any], context: DurableContext) -> str: | ||
| require_scenario(event, "completed-step-replay") | ||
| before = context.step(before_wait(), name="otel-before-wait") | ||
| context.wait(Duration.from_seconds(1), name="otel-replay-wait") | ||
| after = context.step(after_wait(), name="otel-after-wait") | ||
| return f"{before}-{after}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With
@main, every later push to the conformance repository'smainbranch changes the code that runs in this job. This job hasid-token: writeandactions: write, and it passes seven secrets to that code. A pinned commit keeps each such change behind a reviewed pull request in this repository. Can this pin the conformance repository's currentmaincommit instead of@main?Three things I checked support this:
case_countinput, which defaults to 27, and the removal of the long-running jobs. The current pin still hard-codescase_count: 20.telemetry-conformance-tests.ymlpins the same conformance commit as the current pin here. With@mainhere, the two callers of the same shared repository would follow different rules.The workflow test would then check for a full commit SHA, and lines 285 and 286 of the package README would change with it:
One related cleanup, which is optional. At the commit above, the orchestrator runs neither suite for a manual run unless
phaseisshort, and it ignoresdelay_seconds. So a manual run withlaunchorcheckruns no tests, and the header comment on line 7 still mentions the long-running cycle. Can thephaseanddelay_secondsinputs (lines 26 to 39) and the lines that pass them (87 and 88) go in this PR too?