-
Notifications
You must be signed in to change notification settings - Fork 21
fix(tests): make payload-size integration suite deterministic, move backend-delivery checks to non-blocking suite #1353
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
Open
lucaspimentel
wants to merge
9
commits into
main
Choose a base branch
from
lpimentel/payload-size-indexing-retry
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
a8feb89
Poll for trace indexing in payload-size tests
lucaspimentel 8c2f828
Split payload-size backend-delivery checks into a non-blocking suite
lucaspimentel fa81f5b
Poll for searchable payload-size logs before asserting
lucaspimentel e4988f8
Add diagnostics when payload-size log polling times out
lucaspimentel e36e211
Fix payload-size test retry and diagnostic gaps
lucaspimentel f102a19
Wait for trace send completion
lucaspimentel 3111181
Validate every Lambda invocation
lucaspimentel 3806729
Bound timeout log diagnostics
lucaspimentel 4308058
Stop repeated log pagination
lucaspimentel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,163 @@ | ||
| import { invokeAndCollectTelemetry, FunctionConfig } from './utils/default'; | ||
| import { DatadogTelemetry, getInvocationTracesLogsByRequestId, InvocationTracesLogs } from './utils/datadog'; | ||
| import { forceColdStart } from './utils/lambda'; | ||
| import { | ||
| SPAN_COUNT, | ||
| PAYLOAD_BYTES, | ||
| INVOCATION_COUNT, | ||
| DELAY_BETWEEN_INVOCATIONS_MS, | ||
| sleep, | ||
| } from './utils/payload-size'; | ||
| import { IDENTIFIER } from '../config'; | ||
|
|
||
| // Indexing of the ~10 MB trace can lag well past the default 5-minute wait in | ||
| // invokeAndCollectTelemetry, and it lands progressively: the root span becomes | ||
| // searchable long before the last of the SPAN_COUNT payload spans. The poll | ||
| // below therefore waits for the trace to be COMPLETE, not merely present. | ||
| const TRACE_INDEXING_TIMEOUT_MS = 10 * 60 * 1000; | ||
| const TRACE_INDEXING_POLL_INTERVAL_MS = 30 * 1000; | ||
|
|
||
| const stackName = `${IDENTIFIER}-payload-size-e2e`; | ||
|
|
||
| /** | ||
| * Backend-delivery checks for the large single-invocation trace. This suite is | ||
| * `allow_failure: true` in CI: the extension reliably sends the ~10 MB payload | ||
| * without a 413 (asserted in the blocking payload-size suite), but the backend | ||
| * intermittently drops or truncates the trace after intake. Failures here are | ||
| * kept visible as evidence for that backend issue, not treated as regressions. | ||
| */ | ||
| describe('Payload Size E2E Delivery Tests', () => { | ||
|
|
||
| describe('large single-invocation trace', () => { | ||
| let telemetry: Record<string, DatadogTelemetry>; | ||
|
|
||
| const functionName = `${stackName}-large-trace-lambda`; | ||
|
|
||
| beforeAll(async () => { | ||
| const functions: FunctionConfig[] = [ | ||
| { functionName, runtime: 'node' }, | ||
| ]; | ||
|
|
||
| await Promise.all(functions.map(fn => forceColdStart(fn.functionName))); | ||
|
|
||
| telemetry = await invokeAndCollectTelemetry( | ||
| functions, INVOCATION_COUNT, 1, DELAY_BETWEEN_INVOCATIONS_MS, | ||
| { spanCount: SPAN_COUNT, payloadBytes: PAYLOAD_BYTES }); | ||
|
|
||
| // The assertions below target the FIRST request's trace. Its ~10 MB of | ||
| // spans can take longer than the default indexing wait to become fully | ||
| // searchable, so poll for the complete trace before the assertions run. | ||
| const firstInvocation = telemetry.node?.threads[0]?.[0]; | ||
| if (firstInvocation) { | ||
| telemetry.node.threads[0][0] = await waitForCompleteTrace(functionName, firstInvocation); | ||
| } | ||
|
|
||
| console.log('Invocation and telemetry collection complete'); | ||
| }, 1800000); | ||
|
|
||
| // Assert on the FIRST request's trace. Its flush is deferred to a later | ||
| // invocation (cold-start race), which is why we invoke a few times, but the | ||
| // trace is tagged with the first request's id, so it's found here. The | ||
| // beforeAll hook polls for it if indexing lags past the default wait. | ||
| const getInvocation = () => telemetry.node?.threads[0]?.[0]; | ||
|
|
||
| it('should invoke Lambda successfully', () => { | ||
| const result = getInvocation(); | ||
| expect(result).toBeDefined(); | ||
| expect(result.statusCode).toBe(200); | ||
| }); | ||
|
|
||
| it('should deliver exactly one trace to Datadog', () => { | ||
| const result = getInvocation(); | ||
| expect(result).toBeDefined(); | ||
| expect(result.traces?.length).toBe(1); | ||
| }); | ||
|
|
||
| it('should have the aws.lambda root span', () => { | ||
| const result = getInvocation(); | ||
| expect(result).toBeDefined(); | ||
|
|
||
| const allSpans = result.traces!.flatMap(t => t.spans); | ||
| const awsLambdaSpan = allSpans.find( | ||
| (span: any) => span.attributes.operation_name === 'aws.lambda' | ||
| ); | ||
| expect(awsLambdaSpan).toBeDefined(); | ||
| }); | ||
|
|
||
| it('should contain all the payload-carrying spans from the large trace', () => { | ||
| // Exactly the SPAN_COUNT order.process spans we emitted should come back | ||
| // (SPAN_COUNT < the 1000-span API page limit, so none are truncated). | ||
| const result = getInvocation(); | ||
| expect(result).toBeDefined(); | ||
|
|
||
| const orderSpans = result | ||
| .traces!.flatMap(t => t.spans) | ||
| .filter((span: any) => span.attributes.operation_name === 'order.process'); | ||
| expect(orderSpans.length).toBe(SPAN_COUNT); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| /** | ||
| * Number of payload-carrying spans currently indexed for an invocation. Used as | ||
| * the poll's completeness signal: `traces.length > 0` goes true as soon as the | ||
| * root span is indexed, which is far earlier than the point the assertions | ||
| * below need (all SPAN_COUNT `order.process` spans searchable). | ||
| */ | ||
| function countOrderSpans(invocation: InvocationTracesLogs): number { | ||
| return (invocation.traces ?? []) | ||
| .flatMap(t => t.spans) | ||
| .filter((span: any) => span.attributes?.operation_name === 'order.process') | ||
| .length; | ||
| } | ||
|
|
||
| /** | ||
| * Polls until the invocation's trace is fully indexed in Datadog (all | ||
| * SPAN_COUNT payload spans searchable) or the timeout elapses. Returns the most | ||
| * complete result seen, so assertions fail with real data when it never | ||
| * completes. | ||
| */ | ||
| async function waitForCompleteTrace( | ||
| functionName: string, | ||
| invocation: InvocationTracesLogs, | ||
| ): Promise<InvocationTracesLogs> { | ||
| let best = invocation; | ||
| let bestCount = countOrderSpans(invocation); | ||
| if (bestCount >= SPAN_COUNT) { | ||
| return best; | ||
| } | ||
|
|
||
| const deadline = Date.now() + TRACE_INDEXING_TIMEOUT_MS; | ||
| let attempt = 0; | ||
| while (Date.now() < deadline) { | ||
| attempt += 1; | ||
| console.log( | ||
| `Trace for ${invocation.requestId} has ${bestCount}/${SPAN_COUNT} order.process spans ` + | ||
| `(attempt ${attempt}), retrying in ${TRACE_INDEXING_POLL_INTERVAL_MS / 1000}s...`); | ||
| await sleep(TRACE_INDEXING_POLL_INTERVAL_MS); | ||
|
|
||
| let latest: InvocationTracesLogs; | ||
| try { | ||
| latest = await getInvocationTracesLogsByRequestId(functionName, invocation.requestId); | ||
| } catch (err) { | ||
| console.error(`Failed to query traces for ${invocation.requestId}:`, err); | ||
| continue; | ||
| } | ||
| latest.statusCode = invocation.statusCode; | ||
|
|
||
| const count = countOrderSpans(latest); | ||
| if (count >= bestCount) { | ||
| best = latest; | ||
| bestCount = count; | ||
| } | ||
| if (bestCount >= SPAN_COUNT) { | ||
| console.log(`Complete trace indexed for ${invocation.requestId} after ${attempt} poll(s)`); | ||
| return best; | ||
| } | ||
| } | ||
|
|
||
| console.warn( | ||
| `Trace for ${invocation.requestId} still incomplete ` + | ||
| `(${bestCount}/${SPAN_COUNT} order.process spans) after ${TRACE_INDEXING_TIMEOUT_MS / 1000}s`); | ||
| return best; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
As the failure could be due to setup, deployment and flaky tests, allowing failure may unintentionally open merge gate if not due to flaky tests. Would it be better to split this job into 2 steps: one for setup/deployment/invocation with allow failure=false and one for only known flaky tests with allow failure=true. What do you think?
Uh oh!
There was an error while loading. Please reload this page.
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.
That is a valid concern, thanks! This PR splits the existing flaky
payload-sizesuite into two parts:payload-size, which remains blocking, andpayload-size-e2e, which hasallow_failure: truewhile we investigate the downstream flakiness.The blocking
payload-sizesuite already deploys the samePayloadSizestack type, invokes the same workload, requires successful responses, and verifies that the large payload is flushed without a 413. The behavior unique topayload-size-e2eis querying the Datadog API and asserting backend delivery and indexing, which is the known-flaky portion.Splitting it further into separate jobs would require passing request IDs between jobs, moving the suite outside the existing integration-test matrix, and adding teardown and retry handling. Since deployment and invocation are already protected by the blocking
payload-sizesuite, I don’t think the additional complexity is justified. What do you think? Does that address your concern?