File
Blob: src/workerd/api/tests/tail-worker-test.js
| 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 |
| 4 | import * as assert from 'node:assert'; |
| 5 | import 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. |
| 9 | let allInvocations = []; |
| 10 | |
| 11 | // JSON.stringify() does not support BigInt by default - manually add that here. |
| 12 | BigInt.prototype.toJSON = function () { |
| 13 | return this.toString(); |
| 14 | }; |
| 15 | |
| 16 | export 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. |
| 88 | function 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. |
| 141 | function 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. |
| 155 | function n(events, children = []) { |
| 156 | return { events, children }; |
| 157 | } |
| 158 | |
| 159 | // Event strings shared between both expected trees (sorted alphabetically within each group). |
| 160 | const 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. |
| 244 | const 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. |
| 283 | const 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. |
| 342 | function 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 | } |
| 348 | sortTreeChildren(expectedFlat); |
| 349 | sortTreeChildren(expectedWithPropagation); |
| 350 | |
| 351 | export 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 | }; |