Skip to content
File

Blob: src/workerd/api/tests/tail-worker-test.js

javascript366 lines
1// Copyright (c) 2017-2023 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4import * as assert from 'node:assert';
5import unsafe from 'workerd:unsafe';
6 
7// Flat array of all invocations observed by the tail handler.
8// Each entry captures trace metadata and concatenated event JSON.
9let allInvocations = [];
10 
11// JSON.stringify() does not support BigInt by default - manually add that here.
12BigInt.prototype.toJSON = function () {
13 return this.toString();
14};
15 
16export default {
17 // https://developers.cloudflare.com/workers/observability/logs/tail-workers/
18 tailStream(event, env, ctx) {
19 // Onset event, must be singleton
20 let spanIdSet = new Set();
21 
22 // For scheduled and alarm tests, override scheduledTime to make this test deterministic.
23 if (
24 event.event.info.type == 'scheduled' ||
25 event.event.info.type == 'alarm'
26 ) {
27 event.event.info.scheduledTime = '1970-01-01T00:00:00.000Z';
28 }
29 
30 const topLevelSpanId = event.event.spanId;
31 // Set inner spanId to zero to have deterministic output, but save it first.
32 event.event.spanId = '0000000000000000';
33 
34 const entry = {
35 traceId: event.spanContext.traceId,
36 parentSpanId: event.spanContext.spanId, // undefined for top-level, caller's spanId for subrequests
37 spanId: topLevelSpanId, // this invocation's own root span
38 // All span IDs owned by this invocation (root + children from spanOpen events).
39 // Used by buildTree to match a child invocation's parentSpanId to this invocation.
40 allSpanIds: new Set([topLevelSpanId]),
41 events: JSON.stringify(event.event),
42 };
43 allInvocations.push(entry);
44 
45 return (event) => {
46 // The spanContext always has a non-zero spanId available for events other than Onset.
47 assert.notStrictEqual(event.spanContext.spanId, undefined);
48 assert.notStrictEqual(event.spanContext.spanId, '0000000000000000');
49 
50 // For outcome events (and all other tail events except for nested spans) the reported spanId
51 // must match the span opened by the Onset event.
52 if (event.event.type == 'outcome') {
53 assert.strictEqual(event.spanContext.spanId, topLevelSpanId);
54 } else if (event.event.type == 'spanOpen') {
55 // Every SpanOpen event must have another span or the Onset as its parent, as described by
56 // the SpanContext spanId.
57 assert.ok(
58 event.spanContext.spanId == topLevelSpanId ||
59 spanIdSet.has(event.spanContext.spanId)
60 );
61 // Add the newly opened span to the set of spanIds.
62 spanIdSet.add(event.event.spanId);
63 entry.allSpanIds.add(event.event.spanId);
64 } else if (
65 event.event.type == 'attributes' ||
66 event.event.type == 'spanClose'
67 ) {
68 // Every SpanClose/attributes event must point to the spanId of a previously opened span
69 // OR the top-level span created by the onset event.
70 assert.ok(
71 spanIdSet.has(event.spanContext.spanId) ||
72 event.spanContext.spanId === topLevelSpanId
73 );
74 }
75 
76 entry.events += JSON.stringify(event.event);
77 };
78 },
79};
80 
81// Build a tree from the flat invocations array.
82// Each invocation tracks allSpanIds (its root span + all child spans from spanOpen events).
83// A child invocation's parentSpanId is matched against allSpanIds from invocations in the
84// same trace (same traceId) to find its parent. This handles both:
85// - Subrequests: parentSpanId is the caller's user span ID (sequential, from spanOpen)
86// - Hibernation: parentSpanId is the caller's invocation root span ID (from fromEntropy)
87// Scoping by traceId avoids false matches from sequential span IDs that reset per invocation.
88function buildTree(invocations) {
89 // First pass: create nodes and group by traceId.
90 const byTraceId = new Map();
91 const nodes = [];
92 for (const inv of invocations) {
93 const node = { events: inv.events, children: [] };
94 nodes.push({ inv, node });
95 if (!byTraceId.has(inv.traceId)) {
96 byTraceId.set(inv.traceId, []);
97 }
98 byTraceId.get(inv.traceId).push({ inv, node });
99 }
100 
101 // Build per-trace span ID index: maps spanId -> node within the same trace.
102 const traceSpanIndex = new Map(); // traceId -> Map(spanId -> node)
103 for (const [traceId, group] of byTraceId) {
104 const index = new Map();
105 for (const { inv, node } of group) {
106 for (const sid of inv.allSpanIds) {
107 index.set(sid, node);
108 }
109 }
110 traceSpanIndex.set(traceId, index);
111 }
112 
113 // Second pass: link children to parents within the same trace.
114 const roots = [];
115 for (const { inv, node } of nodes) {
116 if (inv.parentSpanId === undefined) {
117 roots.push(node);
118 } else {
119 const index = traceSpanIndex.get(inv.traceId);
120 const parent = index.get(inv.parentSpanId);
121 if (parent && parent !== node) {
122 parent.children.push(node);
123 } else {
124 roots.push(node);
125 }
126 }
127 }
128 
129 // Sort recursively for deterministic comparison.
130 function sortTree(node) {
131 node.children.sort((a, b) => a.events.localeCompare(b.events));
132 for (const child of node.children) sortTree(child);
133 }
134 for (const root of roots) sortTree(root);
135 roots.sort((a, b) => a.events.localeCompare(b.events));
136 
137 return roots;
138}
139 
140// Verify that invocations with a parentSpanId have a non-zero value.
141function verifyTraceIds(invocations) {
142 for (const inv of invocations) {
143 if (inv.parentSpanId !== undefined) {
144 // parentSpanId must be non-zero.
145 assert.notStrictEqual(
146 inv.parentSpanId,
147 '0000000000000000',
148 'parentSpanId must be non-zero'
149 );
150 }
151 }
152}
153 
154// Helper to create a tree node.
155function n(events, children = []) {
156 return { events, children };
157}
158 
159// Event strings shared between both expected trees (sorted alphabetically within each group).
160const E = {
161 // actor-alarms-test.js
162 alarm:
163 '{"type":"onset","executionModel":"durableObject","spanId":"0000000000000000","entrypoint":"DurableObjectExample","scriptTags":[],"info":{"type":"alarm","scheduledTime":"1970-01-01T00:00:00.000Z"}}{"type":"spanOpen","name":"durable_object_storage_getAlarm","spanId":"0000000000000001"}{"type":"spanClose","outcome":"ok"}{"type":"return"}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
164 doFetch:
165 '{"type":"onset","executionModel":"durableObject","spanId":"0000000000000000","entrypoint":"DurableObjectExample","scriptTags":[],"info":{"type":"fetch","method":"GET","url":"http://foo/test","headers":[]}}{"type":"spanOpen","name":"durable_object_storage_setAlarm","spanId":"0000000000000001"}{"type":"spanClose","outcome":"ok"}{"type":"spanOpen","name":"durable_object_storage_getAlarm","spanId":"0000000000000002"}{"type":"spanClose","outcome":"ok"}{"type":"spanOpen","name":"durable_object_storage_getAlarm","spanId":"0000000000000003"}{"type":"spanClose","outcome":"ok"}{"type":"return","info":{"type":"fetch","statusCode":200}}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
166 
167 // websocket/hibernation
168 wsUpgrade:
169 '{"type":"onset","executionModel":"durableObject","spanId":"0000000000000000","entrypoint":"DurableObjectExample","scriptTags":[],"info":{"type":"fetch","method":"GET","url":"http://example.com/","headers":[{"name":"upgrade","value":"websocket"}]}}{"type":"return"}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
170 wsHibernation:
171 '{"type":"onset","executionModel":"durableObject","spanId":"0000000000000000","entrypoint":"DurableObjectExample","scriptTags":[],"info":{"type":"fetch","method":"GET","url":"http://example.com/hibernation","headers":[{"name":"upgrade","value":"websocket"}]}}{"type":"return"}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
172 wsMessage:
173 '{"type":"onset","executionModel":"durableObject","spanId":"0000000000000000","entrypoint":"DurableObjectExample","scriptTags":[],"info":{"type":"hibernatableWebSocket","info":{"type":"message"}}}{"type":"return"}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
174 wsClose:
175 '{"type":"onset","executionModel":"durableObject","spanId":"0000000000000000","entrypoint":"DurableObjectExample","scriptTags":[],"info":{"type":"hibernatableWebSocket","info":{"type":"close","code":1000,"wasClean":true}}}{"type":"return"}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
176 
177 // jsrpc
178 myActorJsrpc:
179 '{"type":"onset","executionModel":"durableObject","spanId":"0000000000000000","entrypoint":"MyActor","scriptTags":[],"info":{"type":"jsrpc"}}{"type":"log","level":"log","message":["baz"]}{"type":"attributes","info":[{"name":"jsrpc.method","value":"functionProperty"}]}{"type":"return"}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
180 jsrpcNonFunction:
181 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","entrypoint":"MyService","scriptTags":[],"info":{"type":"jsrpc"}}{"type":"attributes","info":[{"name":"jsrpc.method","value":"nonFunctionProperty"}]}{"type":"log","level":"log","message":["bar"]}{"type":"log","level":"log","message":["foo"]}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
182 jsrpcGetCounter:
183 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","entrypoint":"MyService","scriptTags":[],"info":{"type":"jsrpc"}}{"type":"attributes","info":[{"name":"jsrpc.method","value":"getCounter"}]}{"type":"log","level":"log","message":["bar"]}{"type":"log","level":"log","message":["getCounter called"]}{"type":"return"}{"type":"log","level":"log","message":["increment called on transient"]}{"type":"log","level":"log","message":["getValue called on transient"]}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
184 jsrpcDoSubrequest:
185 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","scriptTags":[],"info":{"type":"custom"}}{"type":"spanOpen","name":"jsRpcSession","spanId":"0000000000000001"}{"type":"spanOpen","name":"durable_object_subrequest","spanId":"0000000000000002"}{"type":"spanOpen","name":"jsRpcSession","spanId":"0000000000000003"}{"type":"attributes","info":[{"name":"objectId","value":"af6dd8b6678e07bac992dae1bbbb3f385af19ebae7e5ea8c66d6341b246d3328"}]}{"type":"spanClose","outcome":"ok"}{"type":"spanClose","outcome":"ok"}{"type":"spanClose","outcome":"ok"}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
186 
187 // cacheMode
188 cacheMode:
189 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","entrypoint":"cacheMode","scriptTags":[],"info":{"type":"custom"}}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
190 
191 // connect
192 connectHandler:
193 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","entrypoint":"connectHandler","scriptTags":[],"info":{"type":"custom"}}{"type":"spanOpen","name":"connect","spanId":"0000000000000001"}{"type":"spanClose","outcome":"ok"}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
194 connectHandlerProxy:
195 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","entrypoint":"connectHandlerProxy","scriptTags":[],"info":{"type":"custom"}}{"type":"spanOpen","name":"connect","spanId":"0000000000000001"}{"type":"spanClose","outcome":"ok"}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
196 localAddressViaServiceBinding:
197 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","entrypoint":"localAddressViaServiceBinding","scriptTags":[],"info":{"type":"custom"}}{"type":"spanOpen","name":"connect","spanId":"0000000000000001"}{"type":"spanClose","outcome":"ok"}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
198 connectTarget:
199 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","scriptTags":[],"info":{"type":"connect"}}{"type":"return"}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
200 
201 // http-test.js: main test() handler with fetches + scheduled
202 httpTest:
203 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","scriptTags":[],"info":{"type":"custom"}}{"type":"spanOpen","name":"fetch","spanId":"0000000000000001"}{"type":"attributes","info":[{"name":"network.protocol.name","value":"http"},{"name":"network.protocol.version","value":"HTTP/1.1"},{"name":"http.request.method","value":"POST"},{"name":"url.full","value":"http://placeholder/body-length"},{"name":"http.request.body.size","value":"3"},{"name":"http.response.status_code","value":"200"},{"name":"http.response.body.size","value":"22"}]}{"type":"spanClose","outcome":"ok"}{"type":"spanOpen","name":"fetch","spanId":"0000000000000002"}{"type":"attributes","info":[{"name":"network.protocol.name","value":"http"},{"name":"network.protocol.version","value":"HTTP/1.1"},{"name":"http.request.method","value":"POST"},{"name":"url.full","value":"http://placeholder/body-length"},{"name":"http.response.status_code","value":"200"},{"name":"http.response.body.size","value":"31"}]}{"type":"spanClose","outcome":"ok"}{"type":"spanOpen","name":"fetch","spanId":"0000000000000003"}{"type":"spanOpen","name":"scheduled","spanId":"0000000000000004"}{"type":"attributes","info":[{"name":"network.protocol.name","value":"http"},{"name":"network.protocol.version","value":"HTTP/1.1"},{"name":"http.request.method","value":"GET"},{"name":"url.full","value":"http://placeholder/ray-id"},{"name":"http.response.status_code","value":"200"},{"name":"http.response.body.size","value":"0"},{"name":"cloudflare.ray_id","value":"test-ray-id-123"}]}{"type":"spanClose","outcome":"ok"}{"type":"spanClose","outcome":"ok"}{"type":"spanOpen","name":"scheduled","spanId":"0000000000000005"}{"type":"spanClose","outcome":"ok"}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
204 
205 // http-test subrequest handlers
206 fetchBodyLength:
207 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","scriptTags":[],"info":{"type":"fetch","method":"POST","url":"http://placeholder/body-length","headers":[]}}{"type":"return","info":{"type":"fetch","statusCode":200}}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
208 fetchRayId:
209 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","scriptTags":[],"info":{"type":"fetch","method":"GET","url":"http://placeholder/ray-id","headers":[]}}{"type":"return","info":{"type":"fetch","statusCode":200}}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
210 fetchNotFound:
211 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","scriptTags":[],"info":{"type":"fetch","method":"GET","url":"http://placeholder/not-found","headers":[]}}{"type":"return","info":{"type":"fetch","statusCode":404}}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
212 fetchWebSocket:
213 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","scriptTags":[],"info":{"type":"fetch","method":"GET","url":"http://placeholder/web-socket","headers":[{"name":"upgrade","value":"websocket"}]}}{"type":"return"}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
214 fetchEmptyUrl:
215 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","scriptTags":[],"info":{"type":"fetch","method":"GET","url":"","headers":[]}}{"type":"return","info":{"type":"fetch","statusCode":404}}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
216 scheduledEmpty:
217 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","scriptTags":[],"info":{"type":"scheduled","scheduledTime":"1970-01-01T00:00:00.000Z","cron":""}}{"type":"return"}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
218 scheduledCron:
219 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","scriptTags":[],"info":{"type":"scheduled","scheduledTime":"1970-01-01T00:00:00.000Z","cron":"* * * * 30"}}{"type":"return"}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
220 
221 // queue-test.js
222 queueTest:
223 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","scriptTags":[],"info":{"type":"custom"}}{"type":"spanOpen","name":"queue_send","spanId":"0000000000000001"}{"type":"spanClose","outcome":"ok"}{"type":"spanOpen","name":"queue_send","spanId":"0000000000000002"}{"type":"spanClose","outcome":"ok"}{"type":"spanOpen","name":"queue_send","spanId":"0000000000000003"}{"type":"spanClose","outcome":"ok"}{"type":"spanOpen","name":"queue_send","spanId":"0000000000000004"}{"type":"spanClose","outcome":"ok"}{"type":"spanOpen","name":"queue_send","spanId":"0000000000000005"}{"type":"spanClose","outcome":"ok"}{"type":"spanOpen","name":"queue","spanId":"0000000000000006"}{"type":"spanClose","outcome":"ok"}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
224 fetchMsgText:
225 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","scriptTags":[],"info":{"type":"fetch","method":"POST","url":"https://fake-host/message","headers":[{"name":"content-type","value":"application/octet-stream"},{"name":"x-msg-delay-secs","value":"2"},{"name":"x-msg-fmt","value":"text"}]}}{"type":"return","info":{"type":"fetch","statusCode":200}}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
226 fetchMsgBytes:
227 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","scriptTags":[],"info":{"type":"fetch","method":"POST","url":"https://fake-host/message","headers":[{"name":"content-type","value":"application/octet-stream"},{"name":"x-msg-fmt","value":"bytes"}]}}{"type":"return","info":{"type":"fetch","statusCode":200}}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
228 fetchMsgJson:
229 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","scriptTags":[],"info":{"type":"fetch","method":"POST","url":"https://fake-host/message","headers":[{"name":"content-type","value":"application/octet-stream"},{"name":"x-msg-fmt","value":"json"}]}}{"type":"return","info":{"type":"fetch","statusCode":200}}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
230 fetchMsgV8:
231 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","scriptTags":[],"info":{"type":"fetch","method":"POST","url":"https://fake-host/message","headers":[{"name":"content-type","value":"application/octet-stream"},{"name":"x-msg-fmt","value":"v8"}]}}{"type":"return","info":{"type":"fetch","statusCode":200}}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
232 fetchBatch:
233 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","scriptTags":[],"info":{"type":"fetch","method":"POST","url":"https://fake-host/batch","headers":[{"name":"cf-queue-batch-bytes","value":"31"},{"name":"cf-queue-batch-count","value":"4"},{"name":"cf-queue-largest-msg","value":"13"},{"name":"content-type","value":"application/json"},{"name":"x-msg-delay-secs","value":"2"}]}}{"type":"return","info":{"type":"fetch","statusCode":200}}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
234 queueConsumer:
235 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","scriptTags":[],"info":{"type":"queue","queueName":"test-queue","batchSize":5}}{"type":"return"}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
236 
237 // buffered tail worker traces
238 trace:
239 '{"type":"onset","executionModel":"stateless","spanId":"0000000000000000","scriptTags":[],"info":{"type":"trace","traces":[""]}}{"type":"outcome","outcome":"ok","cpuTime":0,"wallTime":0}',
240};
241 
242// Expected tree without propagation — every invocation is a root with no children.
243// This is the same set of events as the old flat expected array, just wrapped in tree nodes.
244const expectedFlat = [
245 n(E.alarm),
246 n(E.wsUpgrade),
247 n(E.wsHibernation),
248 n(E.doFetch),
249 n(E.wsClose),
250 n(E.wsMessage),
251 n(E.myActorJsrpc),
252 n(E.cacheMode),
253 n(E.connectHandler),
254 n(E.connectHandlerProxy),
255 n(E.localAddressViaServiceBinding),
256 n(E.jsrpcGetCounter),
257 n(E.jsrpcNonFunction),
258 n(E.connectTarget),
259 n(E.jsrpcDoSubrequest),
260 n(E.httpTest),
261 n(E.queueTest),
262 n(E.fetchEmptyUrl),
263 n(E.fetchNotFound),
264 n(E.fetchRayId),
265 n(E.fetchWebSocket),
266 n(E.fetchBodyLength),
267 n(E.fetchBodyLength),
268 n(E.fetchBatch),
269 n(E.fetchMsgText),
270 n(E.fetchMsgBytes),
271 n(E.fetchMsgJson),
272 n(E.fetchMsgV8),
273 n(E.queueConsumer),
274 n(E.scheduledEmpty),
275 n(E.scheduledCron),
276 n(E.trace),
277 n(E.trace),
278].sort((a, b) => a.events.localeCompare(b.events));
279 
280// Expected tree with propagation — subrequest callees are children of their callers.
281// DOs that are called via subrequests inherit the caller's traceId and become children.
282// DOs triggered by system events (alarms, hibernation wakeups) remain standalone roots.
283const expectedWithPropagation = [
284 // actor-alarms-test: DO fetch and alarm are independent roots (own traceId)
285 n(E.alarm),
286 n(E.doFetch),
287 
288 // websocket/hibernation: independent roots
289 n(E.wsUpgrade),
290 n(E.wsHibernation),
291 n(E.wsMessage),
292 n(E.wsClose),
293 
294 // cacheMode: standalone
295 n(E.cacheMode),
296 
297 // connect: handler, proxy, and service-binding test are separate top-level tests, target is
298 // independent
299 n(E.connectHandler),
300 n(E.connectHandlerProxy),
301 n(E.localAddressViaServiceBinding),
302 n(E.connectTarget),
303 
304 // jsrpc DO subrequest test: caller has children (MyService + MyActor DO calls)
305 n(E.jsrpcDoSubrequest, [
306 n(E.myActorJsrpc),
307 n(E.jsrpcGetCounter),
308 n(E.jsrpcNonFunction),
309 ]),
310 
311 // http-test: main test handler with subrequest children
312 n(E.httpTest, [
313 n(E.fetchRayId),
314 n(E.fetchBodyLength),
315 n(E.fetchBodyLength),
316 n(E.scheduledEmpty),
317 n(E.scheduledCron),
318 ]),
319 
320 // http-test: standalone fetch handlers that are NOT subrequests of the main test
321 // (not-found, web-socket, empty-url are separate test invocations)
322 n(E.fetchNotFound),
323 n(E.fetchWebSocket),
324 n(E.fetchEmptyUrl),
325 
326 // queue-test: caller has subrequest children
327 n(E.queueTest, [
328 n(E.fetchBatch),
329 n(E.fetchMsgText),
330 n(E.fetchMsgBytes),
331 n(E.fetchMsgJson),
332 n(E.fetchMsgV8),
333 n(E.queueConsumer),
334 ]),
335 
336 // buffered tail worker traces
337 n(E.trace),
338 n(E.trace),
339].sort((a, b) => a.events.localeCompare(b.events));
340 
341// Sort children in expected trees to match buildTree's sort order.
342function sortTreeChildren(nodes) {
343 for (const node of nodes) {
344 node.children.sort((a, b) => a.events.localeCompare(b.events));
345 sortTreeChildren(node.children);
346 }
347}
348sortTreeChildren(expectedFlat);
349sortTreeChildren(expectedWithPropagation);
350 
351export const test = {
352 async test() {
353 // HACK: The prior tests terminates once the scheduled() invocation has returned a response, but
354 // propagating the outcome of the invocation may take longer. Wait briefly so this can go ahead.
355 await scheduler.wait(50);
356 
357 // @all-autogates enables USER_SPAN_CONTEXT_PROPAGATION.
358 const expected = unsafe.isTestAutogateEnabled()
359 ? expectedWithPropagation
360 : expectedFlat;
361 
362 verifyTraceIds(allInvocations);
363 assert.deepStrictEqual(buildTree(allInvocations), expected);
364 },
365};