Skip to content

Commit abe27ab

Browse files
authored
test(tracer): streamline e2e test structure (#3031)
1 parent 9677258 commit abe27ab

21 files changed

+791
-1633
lines changed

package-lock.json

+1-50
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/testing/src/types.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,13 @@ type XRayTraceDocumentParsed = {
108108
request_id: string;
109109
};
110110
http?: {
111-
response: {
111+
request: {
112+
url: string;
113+
method: string;
114+
};
115+
response?: {
112116
status: number;
117+
content_length?: number;
113118
};
114119
};
115120
origin?: string;
@@ -142,6 +147,7 @@ type XRayTraceDocumentParsed = {
142147
message: string;
143148
};
144149
error?: boolean;
150+
namespace?: string;
145151
};
146152

147153
type XRaySegmentParsed = {
@@ -163,6 +169,10 @@ type GetXRayTraceDetailsOptions = {
163169
* The expected number of segments in each trace
164170
*/
165171
expectedSegmentsCount: number;
172+
/**
173+
* The name of the function that the trace is expected to be associated with
174+
*/
175+
functionName: string;
166176
};
167177

168178
/**

packages/testing/src/xray-traces-utils.ts

+86-76
Original file line numberDiff line numberDiff line change
@@ -83,18 +83,66 @@ const retriableGetTraceIds = (options: GetXRayTraceIdsOptions) =>
8383
endTime.getTime() / 1000
8484
)} --filter-expression 'resource.arn ENDSWITH ":function:${options.resourceName}"'`
8585
);
86+
87+
throw new Error(
88+
`Failed to get trace IDs after ${retryOptions.retries} retries`,
89+
{ cause: error }
90+
);
8691
}
8792
retry(error);
8893
}
89-
});
94+
}, retryOptions);
95+
96+
/**
97+
* Find the main Powertools subsegment in the trace
98+
*
99+
* A main Powertools subsegment is identified by the `## index.` suffix. Depending on the
100+
* runtime, it may also be identified by the `Invocation` name.
101+
*
102+
* @param trace - The trace to find the main Powertools subsegment
103+
* @param functionName - The function name to find the main Powertools subsegment
104+
*/
105+
const findMainPowertoolsSubsegment = (
106+
trace: XRayTraceDocumentParsed,
107+
functionName: string
108+
) => {
109+
const maybePowertoolsSubsegment = trace.subsegments?.find(
110+
(subsegment) =>
111+
subsegment.name.startsWith('## index.') ||
112+
subsegment.name === 'Invocation'
113+
);
114+
115+
if (!maybePowertoolsSubsegment) {
116+
throw new Error(`Main subsegment not found for ${functionName} segment`);
117+
}
118+
119+
if (maybePowertoolsSubsegment.name === 'Invocation') {
120+
const powertoolsSubsegment = maybePowertoolsSubsegment.subsegments?.find(
121+
(subsegment) => subsegment.name.startsWith('## index.')
122+
);
123+
124+
if (!powertoolsSubsegment) {
125+
throw new Error(`Main subsegment not found for ${functionName} segment`);
126+
}
127+
128+
return powertoolsSubsegment;
129+
}
130+
131+
return maybePowertoolsSubsegment;
132+
};
90133

91134
/**
92135
* Parse and sort the trace segments by start time
93136
*
94137
* @param trace - The trace to parse and sort
95138
* @param expectedSegmentsCount - The expected segments count for the trace
139+
* @param functionName - The function name to find the main Powertools subsegment
96140
*/
97-
const parseAndSortTrace = (trace: Trace, expectedSegmentsCount: number) => {
141+
const parseAndSortTrace = (
142+
trace: Trace,
143+
expectedSegmentsCount: number,
144+
functionName: string
145+
) => {
98146
const { Id: id, Segments: segments } = trace;
99147
if (segments === undefined || segments.length !== expectedSegmentsCount) {
100148
throw new Error(
@@ -111,9 +159,14 @@ const parseAndSortTrace = (trace: Trace, expectedSegmentsCount: number) => {
111159
);
112160
}
113161

162+
const parsedDocument = JSON.parse(Document) as XRayTraceDocumentParsed;
163+
if (parsedDocument.origin === 'AWS::Lambda::Function') {
164+
findMainPowertoolsSubsegment(parsedDocument, functionName);
165+
}
166+
114167
parsedSegments.push({
115168
Id,
116-
Document: JSON.parse(Document) as XRayTraceDocumentParsed,
169+
Document: parsedDocument,
117170
});
118171
}
119172

@@ -136,15 +189,14 @@ const parseAndSortTrace = (trace: Trace, expectedSegmentsCount: number) => {
136189
const getTraceDetails = async (
137190
options: GetXRayTraceDetailsOptions
138191
): Promise<XRayTraceParsed[]> => {
139-
const { traceIds, expectedSegmentsCount } = options;
192+
const { traceIds, expectedSegmentsCount, functionName } = options;
140193
const response = await xrayClient.send(
141194
new BatchGetTracesCommand({
142195
TraceIds: traceIds,
143196
})
144197
);
145198

146-
const traces = response.Traces;
147-
199+
const { Traces: traces } = response;
148200
if (traces === undefined || traces.length !== traceIds.length) {
149201
throw new Error(
150202
`Expected ${traceIds.length} traces, got ${traces ? traces.length : 0}`
@@ -153,7 +205,9 @@ const getTraceDetails = async (
153205

154206
const parsedAndSortedTraces: XRayTraceParsed[] = [];
155207
for (const trace of traces) {
156-
parsedAndSortedTraces.push(parseAndSortTrace(trace, expectedSegmentsCount));
208+
parsedAndSortedTraces.push(
209+
parseAndSortTrace(trace, expectedSegmentsCount, functionName)
210+
);
157211
}
158212

159213
return parsedAndSortedTraces.sort(
@@ -168,16 +222,28 @@ const getTraceDetails = async (
168222
* @param options - The options to get trace details, including the trace IDs and expected segments count
169223
*/
170224
const retriableGetTraceDetails = (options: GetXRayTraceDetailsOptions) =>
171-
promiseRetry(async (retry) => {
225+
promiseRetry(async (retry, attempt) => {
172226
try {
173227
return await getTraceDetails(options);
174228
} catch (error) {
229+
if (attempt === retryOptions.retries) {
230+
console.log(
231+
`Manual query: aws xray batch-get-traces --trace-ids ${
232+
options.traceIds
233+
}`
234+
);
235+
236+
throw new Error(
237+
`Failed to get trace details after ${retryOptions.retries} retries`,
238+
{ cause: error }
239+
);
240+
}
175241
retry(error);
176242
}
177-
});
243+
}, retryOptions);
178244

179245
/**
180-
* Find the main function segment in the trace identified by the `## index.` suffix
246+
* Find the main function segment within the `AWS::Lambda::Function` segment
181247
*/
182248
const findPowertoolsFunctionSegment = (
183249
trace: XRayTraceParsed,
@@ -194,30 +260,7 @@ const findPowertoolsFunctionSegment = (
194260
}
195261

196262
const document = functionSegment.Document;
197-
198-
const maybePowertoolsSubsegment = document.subsegments?.find(
199-
(subsegment) =>
200-
subsegment.name.startsWith('## index.') ||
201-
subsegment.name === 'Invocation'
202-
);
203-
204-
if (!maybePowertoolsSubsegment) {
205-
throw new Error(`Main subsegment not found for ${functionName} segment`);
206-
}
207-
208-
if (maybePowertoolsSubsegment.name === 'Invocation') {
209-
const powertoolsSubsegment = maybePowertoolsSubsegment.subsegments?.find(
210-
(subsegment) => subsegment.name.startsWith('## index.')
211-
);
212-
213-
if (!powertoolsSubsegment) {
214-
throw new Error(`Main subsegment not found for ${functionName} segment`);
215-
}
216-
217-
return powertoolsSubsegment;
218-
}
219-
220-
return maybePowertoolsSubsegment;
263+
return findMainPowertoolsSubsegment(document, functionName);
221264
};
222265

223266
/**
@@ -271,6 +314,7 @@ const getXRayTraceData = async (
271314
const traces = await retriableGetTraceDetails({
272315
traceIds,
273316
expectedSegmentsCount,
317+
functionName: resourceName,
274318
});
275319

276320
if (!traces) {
@@ -286,9 +330,15 @@ const getXRayTraceData = async (
286330
* @param options - The options to get the X-Ray trace data, including the start time, resource name, expected traces count, and expected segments count
287331
*/
288332
const getTraces = async (
289-
options: GetXRayTraceIdsOptions & Omit<GetXRayTraceDetailsOptions, 'traceIds'>
333+
options: GetXRayTraceIdsOptions &
334+
Omit<GetXRayTraceDetailsOptions, 'traceIds' | 'functionName'> & {
335+
resourceName: string;
336+
}
290337
): Promise<EnrichedXRayTraceDocumentParsed[]> => {
291-
const traces = await getXRayTraceData(options);
338+
const traces = await getXRayTraceData({
339+
...options,
340+
functionName: options.resourceName,
341+
});
292342

293343
const { resourceName } = options;
294344

@@ -305,45 +355,6 @@ const getTraces = async (
305355
return mainSubsegments;
306356
};
307357

308-
/**
309-
* Get the X-Ray trace data for a given resource name without the main subsegments.
310-
*
311-
* This is useful when we are testing cases where Active Tracing is disabled and we don't have the main subsegments.
312-
*
313-
* @param options - The options to get the X-Ray trace data, including the start time, resource name, expected traces count, and expected segments count
314-
*/
315-
const getTracesWithoutMainSubsegments = async (
316-
options: GetXRayTraceIdsOptions & Omit<GetXRayTraceDetailsOptions, 'traceIds'>
317-
): Promise<EnrichedXRayTraceDocumentParsed[]> => {
318-
const traces = await getXRayTraceData(options);
319-
320-
const { resourceName } = options;
321-
322-
const lambdaFunctionSegments = [];
323-
for (const trace of traces) {
324-
const functionSegment = trace.Segments.find(
325-
(segment) => segment.Document.origin === 'AWS::Lambda::Function'
326-
);
327-
328-
if (!functionSegment) {
329-
throw new Error(
330-
`AWS::Lambda::Function segment not found for ${resourceName}`
331-
);
332-
}
333-
334-
const lambdaFunctionSegment = functionSegment.Document;
335-
const enrichedSubsegment = {
336-
...lambdaFunctionSegment,
337-
subsegments: parseSubsegmentsByName(
338-
lambdaFunctionSegment.subsegments ?? []
339-
),
340-
};
341-
lambdaFunctionSegments.push(enrichedSubsegment);
342-
}
343-
344-
return lambdaFunctionSegments;
345-
};
346-
347358
export {
348359
getTraceIds,
349360
retriableGetTraceIds,
@@ -352,5 +363,4 @@ export {
352363
findPowertoolsFunctionSegment,
353364
getTraces,
354365
parseSubsegmentsByName,
355-
getTracesWithoutMainSubsegments,
356366
};

packages/tracer/package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@
2929
"devDependencies": {
3030
"@aws-lambda-powertools/testing-utils": "file:../testing",
3131
"@aws-sdk/client-dynamodb": "^3.637.0",
32-
"@aws-sdk/client-xray": "^3.637.0",
33-
"aws-sdk": "^2.1688.0"
32+
"@aws-sdk/client-xray": "^3.637.0"
3433
},
3534
"peerDependencies": {
3635
"@middy/core": "4.x || 5.x"

0 commit comments

Comments
 (0)