From d24512207872738f1d24c35c5a926b8efd668bde Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Mon, 23 Mar 2026 12:07:54 +0100 Subject: [PATCH 1/3] test(cloudflare): Enable multi-worker tests for CF integration tests --- .../cloudflare-integration-tests/runner.ts | 68 +++++++++++++++---- .../index-sub-worker.ts | 19 ++++++ .../tracing/worker-service-binding/index.ts | 36 ++++++++++ .../tracing/worker-service-binding/test.ts | 45 ++++++++++++ .../wrangler-sub-worker.jsonc | 15 ++++ .../worker-service-binding/wrangler.jsonc | 15 ++++ 6 files changed, 184 insertions(+), 14 deletions(-) create mode 100644 dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/index-sub-worker.ts create mode 100644 dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/index.ts create mode 100644 dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/test.ts create mode 100644 dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/wrangler-sub-worker.jsonc create mode 100644 dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/wrangler.jsonc diff --git a/dev-packages/cloudflare-integration-tests/runner.ts b/dev-packages/cloudflare-integration-tests/runner.ts index a9fb96b59505..940a2809fe6f 100644 --- a/dev-packages/cloudflare-integration-tests/runner.ts +++ b/dev-packages/cloudflare-integration-tests/runner.ts @@ -105,6 +105,7 @@ export function createRunner(...paths: string[]) { let envelopeCount = 0; const { resolve: setWorkerPort, promise: workerPortPromise } = deferredPromise(); let child: ReturnType | undefined; + let childSubWorker: ReturnType | undefined; /** Called after each expect callback to check if we're complete */ function expectCallbackCalled(): void { @@ -168,7 +169,7 @@ export function createRunner(...paths: string[]) { } createBasicSentryServer(newEnvelope) - .then(([mockServerPort, mockServerClose]) => { + .then(async ([mockServerPort, mockServerClose]) => { if (mockServerClose) { CLEANUP_STEPS.add(() => { mockServerClose(); @@ -181,6 +182,54 @@ export function createRunner(...paths: string[]) { ? ['inherit', 'inherit', 'inherit', 'ipc'] : ['ignore', 'ignore', 'ignore', 'ipc']; + const onChildError = (e: Error) => { + // eslint-disable-next-line no-console + console.error('Error starting child process:', e); + reject(e); + }; + + function onChildMessage( + message: string, + onReady?: (port: number) => void, + ): void { + const msg = JSON.parse(message) as { event: string; port?: number }; + if (msg.event === 'DEV_SERVER_READY' && typeof msg.port === 'number') { + if (process.env.DEBUG) log('worker ready on port', msg.port); + onReady?.(msg.port); + } + } + + if (existsSync(join(testPath, 'wrangler-sub-worker.jsonc'))) { + childSubWorker = spawn( + 'wrangler', + [ + 'dev', + '--config', + join(testPath, 'wrangler-sub-worker.jsonc'), + '--show-interactive-dev-session', + 'false', + '--var', + `SENTRY_DSN:http://public@localhost:${mockServerPort}/1337`, + '--port', + '0', + '--inspector-port', + '0', + ], + { stdio, signal }, + ); + + // Wait for the sub-worker to be ready before starting the main worker + await new Promise((resolveSubWorker, rejectSubWorker) => { + childSubWorker!.on('message', (msg: string) => + onChildMessage(msg, () => resolveSubWorker()), + ); + childSubWorker!.on('error', rejectSubWorker); + childSubWorker!.on('exit', code => { + rejectSubWorker(new Error(`Sub-worker exited with code ${code}`)); + }); + }); + } + child = spawn( 'wrangler', [ @@ -199,21 +248,12 @@ export function createRunner(...paths: string[]) { CLEANUP_STEPS.add(() => { child?.kill(); + childSubWorker?.kill(); }); - child.on('error', e => { - // eslint-disable-next-line no-console - console.error('Error starting child process:', e); - reject(e); - }); - - child.on('message', (message: string) => { - const msg = JSON.parse(message) as { event: string; port?: number }; - if (msg.event === 'DEV_SERVER_READY' && typeof msg.port === 'number') { - setWorkerPort(msg.port); - if (process.env.DEBUG) log('worker ready on port', msg.port); - } - }); + childSubWorker?.on('error', onChildError); + child.on('error', onChildError); + child.on('message', (msg: string) => onChildMessage(msg, setWorkerPort)); }) .catch(e => reject(e)); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/index-sub-worker.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/index-sub-worker.ts new file mode 100644 index 000000000000..06c79931b880 --- /dev/null +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/index-sub-worker.ts @@ -0,0 +1,19 @@ +import * as Sentry from '@sentry/cloudflare'; + +interface Env { + SENTRY_DSN: string; +} + +const myWorker = { + async fetch(request: Request) { + return new Response('Hello from another worker!'); + }, +}; + +export default Sentry.withSentry( + (env: Env) => ({ + dsn: env.SENTRY_DSN, + tracesSampleRate: 1.0, + }), + myWorker, +); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/index.ts new file mode 100644 index 000000000000..5eb0a36f21b2 --- /dev/null +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/index.ts @@ -0,0 +1,36 @@ +import * as Sentry from '@sentry/cloudflare'; +import { DurableObject } from 'cloudflare:workers'; + +interface Env { + SENTRY_DSN: string; + MY_DURABLE_OBJECT: DurableObjectNamespace; + ANOTHER_WORKER: Fetcher; +} + +class MyDurableObjectBase extends DurableObject { + async fetch(request: Request) { + return new Response('DO is fine'); + } +} + +export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( + (env: Env) => ({ + dsn: env.SENTRY_DSN, + tracesSampleRate: 1.0, + }), + MyDurableObjectBase, +); + +export default Sentry.withSentry( + (env: Env) => ({ + dsn: env.SENTRY_DSN, + tracesSampleRate: 1.0, + }), + { + async fetch(request, env) { + const response = await env.ANOTHER_WORKER.fetch(new Request('http://fake-host/hello')); + const text = await response.text(); + return new Response(text); + }, + } satisfies ExportedHandler, +); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/test.ts new file mode 100644 index 000000000000..3b8d9e7519e9 --- /dev/null +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/test.ts @@ -0,0 +1,45 @@ +import { expect, it } from 'vitest'; +import type { Event } from '@sentry/core'; +import { createRunner } from '../../../runner'; + +it('propagates trace from worker to worker via service binding', async ({ signal }) => { + const runner = createRunner(__dirname) + .expect(envelope => { + const transactionEvent = envelope[1]?.[0]?.[1] as Event; + expect(transactionEvent).toEqual( + expect.objectContaining({ + contexts: expect.objectContaining({ + trace: expect.objectContaining({ + op: 'http.server', + data: expect.objectContaining({ + 'sentry.origin': 'auto.http.cloudflare', + }), + origin: 'auto.http.cloudflare', + }), + }), + transaction: 'GET /', + }), + ); + }) + .expect(envelope => { + const transactionEvent = envelope[1]?.[0]?.[1] as Event; + expect(transactionEvent).toEqual( + expect.objectContaining({ + contexts: expect.objectContaining({ + trace: expect.objectContaining({ + op: 'http.server', + data: expect.objectContaining({ + 'sentry.origin': 'auto.http.cloudflare', + }), + origin: 'auto.http.cloudflare', + }), + }), + transaction: 'GET /hello', + }), + ); + }) + .unordered() + .start(signal); + await runner.makeRequest('get', '/'); + await runner.completed(); +}); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/wrangler-sub-worker.jsonc b/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/wrangler-sub-worker.jsonc new file mode 100644 index 000000000000..70cef772c165 --- /dev/null +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/wrangler-sub-worker.jsonc @@ -0,0 +1,15 @@ +{ + "name": "cloudflare-service-binding-sub-worker", + "main": "index-sub-worker.ts", + "compatibility_date": "2025-06-17", + "compatibility_flags": ["nodejs_als"], + "migrations": [ + { + "new_sqlite_classes": ["MyServiceBinding"], + "tag": "v1", + }, + ], + "vars": { + "SENTRY_DSN": "https://932e620ee3921c3b4a61c72558ad88ce@o447951.ingest.us.sentry.io/4509553159831552", + }, +} diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/wrangler.jsonc b/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/wrangler.jsonc new file mode 100644 index 000000000000..23ea0de547ad --- /dev/null +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/wrangler.jsonc @@ -0,0 +1,15 @@ +{ + "name": "cloudflare-durable-objects", + "main": "index.ts", + "compatibility_date": "2025-06-17", + "compatibility_flags": ["nodejs_als"], + "vars": { + "SENTRY_DSN": "https://932e620ee3921c3b4a61c72558ad88ce@o447951.ingest.us.sentry.io/4509553159831552", + }, + "services": [ + { + "binding": "ANOTHER_WORKER", + "service": "cloudflare-service-binding-sub-worker", + }, + ], +} From 25e255778bf43fd351d7a115ed6b2fb3605b8733 Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Tue, 24 Mar 2026 08:41:46 +0100 Subject: [PATCH 2/3] fixup! test(cloudflare): Enable multi-worker tests for CF integration tests --- .../cloudflare-integration-tests/runner.ts | 9 ++------- .../tracing/worker-service-binding/index.ts | 16 ---------------- .../wrangler-sub-worker.jsonc | 6 ------ 3 files changed, 2 insertions(+), 29 deletions(-) diff --git a/dev-packages/cloudflare-integration-tests/runner.ts b/dev-packages/cloudflare-integration-tests/runner.ts index 940a2809fe6f..b0b439eb122a 100644 --- a/dev-packages/cloudflare-integration-tests/runner.ts +++ b/dev-packages/cloudflare-integration-tests/runner.ts @@ -188,10 +188,7 @@ export function createRunner(...paths: string[]) { reject(e); }; - function onChildMessage( - message: string, - onReady?: (port: number) => void, - ): void { + function onChildMessage(message: string, onReady?: (port: number) => void): void { const msg = JSON.parse(message) as { event: string; port?: number }; if (msg.event === 'DEV_SERVER_READY' && typeof msg.port === 'number') { if (process.env.DEBUG) log('worker ready on port', msg.port); @@ -220,9 +217,7 @@ export function createRunner(...paths: string[]) { // Wait for the sub-worker to be ready before starting the main worker await new Promise((resolveSubWorker, rejectSubWorker) => { - childSubWorker!.on('message', (msg: string) => - onChildMessage(msg, () => resolveSubWorker()), - ); + childSubWorker!.on('message', (msg: string) => onChildMessage(msg, () => resolveSubWorker())); childSubWorker!.on('error', rejectSubWorker); childSubWorker!.on('exit', code => { rejectSubWorker(new Error(`Sub-worker exited with code ${code}`)); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/index.ts index 5eb0a36f21b2..dc178759f51d 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/index.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/index.ts @@ -1,26 +1,10 @@ import * as Sentry from '@sentry/cloudflare'; -import { DurableObject } from 'cloudflare:workers'; interface Env { SENTRY_DSN: string; - MY_DURABLE_OBJECT: DurableObjectNamespace; ANOTHER_WORKER: Fetcher; } -class MyDurableObjectBase extends DurableObject { - async fetch(request: Request) { - return new Response('DO is fine'); - } -} - -export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( - (env: Env) => ({ - dsn: env.SENTRY_DSN, - tracesSampleRate: 1.0, - }), - MyDurableObjectBase, -); - export default Sentry.withSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/wrangler-sub-worker.jsonc b/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/wrangler-sub-worker.jsonc index 70cef772c165..30bd95322560 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/wrangler-sub-worker.jsonc +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/wrangler-sub-worker.jsonc @@ -3,12 +3,6 @@ "main": "index-sub-worker.ts", "compatibility_date": "2025-06-17", "compatibility_flags": ["nodejs_als"], - "migrations": [ - { - "new_sqlite_classes": ["MyServiceBinding"], - "tag": "v1", - }, - ], "vars": { "SENTRY_DSN": "https://932e620ee3921c3b4a61c72558ad88ce@o447951.ingest.us.sentry.io/4509553159831552", }, From b472de2fb0d079ea6bd10613b308bbb28bbf58ad Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Tue, 24 Mar 2026 19:55:33 +0100 Subject: [PATCH 3/3] fixup! test(cloudflare): Enable multi-worker tests for CF integration tests --- .../suites/tracing/worker-service-binding/test.ts | 2 +- .../suites/tracing/worker-service-binding/wrangler.jsonc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/test.ts index 3b8d9e7519e9..fd64c0d31d27 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/test.ts @@ -2,7 +2,7 @@ import { expect, it } from 'vitest'; import type { Event } from '@sentry/core'; import { createRunner } from '../../../runner'; -it('propagates trace from worker to worker via service binding', async ({ signal }) => { +it('adds a trace to a worker via service binding', async ({ signal }) => { const runner = createRunner(__dirname) .expect(envelope => { const transactionEvent = envelope[1]?.[0]?.[1] as Event; diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/wrangler.jsonc b/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/wrangler.jsonc index 23ea0de547ad..69bc9f6e6e99 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/wrangler.jsonc +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/worker-service-binding/wrangler.jsonc @@ -1,5 +1,5 @@ { - "name": "cloudflare-durable-objects", + "name": "cloudflare-worker-service-binding", "main": "index.ts", "compatibility_date": "2025-06-17", "compatibility_flags": ["nodejs_als"],