From 5a84f21bb427c23177f4f5e7cbdff2ab14a32e4d Mon Sep 17 00:00:00 2001 From: cod1k Date: Thu, 25 Sep 2025 09:45:29 +0300 Subject: [PATCH 1/6] Add abort signals to test runners Updated test definitions across multiple suites to accept and pass abort signals (`signal`) for enhanced request handling. Adjusted the `start` method in the test runner to support optional abort signals. --- dev-packages/cloudflare-integration-tests/runner.ts | 4 ++-- .../cloudflare-integration-tests/suites/basic/test.ts | 4 ++-- .../suites/tracing/anthropic-ai/test.ts | 4 ++-- .../suites/tracing/durableobject/test.ts | 4 ++-- .../suites/tracing/openai/test.ts | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dev-packages/cloudflare-integration-tests/runner.ts b/dev-packages/cloudflare-integration-tests/runner.ts index 849b011250f9..b945bee2eeea 100644 --- a/dev-packages/cloudflare-integration-tests/runner.ts +++ b/dev-packages/cloudflare-integration-tests/runner.ts @@ -86,7 +86,7 @@ export function createRunner(...paths: string[]) { } return this; }, - start: function (): StartResult { + start: function (signal?: AbortSignal): StartResult { const { resolve, reject, promise: isComplete } = deferredPromise(cleanupChildProcesses); const expectedEnvelopeCount = expectedEnvelopes.length; @@ -155,7 +155,7 @@ export function createRunner(...paths: string[]) { '--var', `SENTRY_DSN:http://public@localhost:${mockServerPort}/1337`, ], - { stdio }, + { stdio, signal }, ); CLEANUP_STEPS.add(() => { diff --git a/dev-packages/cloudflare-integration-tests/suites/basic/test.ts b/dev-packages/cloudflare-integration-tests/suites/basic/test.ts index b785e6e37fd1..347c0d3530d8 100644 --- a/dev-packages/cloudflare-integration-tests/suites/basic/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/basic/test.ts @@ -2,7 +2,7 @@ import { expect, it } from 'vitest'; import { eventEnvelope } from '../../expect'; import { createRunner } from '../../runner'; -it('Basic error in fetch handler', async () => { +it('Basic error in fetch handler', async ({ signal }) => { const runner = createRunner(__dirname) .expect( eventEnvelope({ @@ -26,7 +26,7 @@ it('Basic error in fetch handler', async () => { }, }), ) - .start(); + .start(signal); await runner.makeRequest('get', '/', { expectError: true }); await runner.completed(); }); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/anthropic-ai/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/anthropic-ai/test.ts index 13966caaf460..c9e112b32241 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/anthropic-ai/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/anthropic-ai/test.ts @@ -6,7 +6,7 @@ import { createRunner } from '../../../runner'; // want to test that the instrumentation does not break in our // cloudflare SDK. -it('traces a basic message creation request', async () => { +it('traces a basic message creation request', async ({ signal }) => { const runner = createRunner(__dirname) .ignore('event') .expect(envelope => { @@ -35,7 +35,7 @@ it('traces a basic message creation request', async () => { ]), ); }) - .start(); + .start(signal); await runner.makeRequest('get', '/'); await runner.completed(); }); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/durableobject/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/durableobject/test.ts index a9daae21480f..e86508c0f101 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/durableobject/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/durableobject/test.ts @@ -1,7 +1,7 @@ import { expect, it } from 'vitest'; import { createRunner } from '../../../runner'; -it('traces a durable object method', async () => { +it('traces a durable object method', async ({ signal }) => { const runner = createRunner(__dirname) .expect(envelope => { const transactionEvent = envelope[1]?.[0]?.[1]; @@ -21,7 +21,7 @@ it('traces a durable object method', async () => { }), ); }) - .start(); + .start(signal); await runner.makeRequest('get', '/hello'); await runner.completed(); }); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/openai/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/openai/test.ts index c1aee24136a4..eb15fd80fc97 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/openai/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/openai/test.ts @@ -6,7 +6,7 @@ import { createRunner } from '../../../runner'; // want to test that the instrumentation does not break in our // cloudflare SDK. -it('traces a basic chat completion request', async () => { +it('traces a basic chat completion request', async ({ signal }) => { const runner = createRunner(__dirname) .ignore('event') .expect(envelope => { @@ -37,7 +37,7 @@ it('traces a basic chat completion request', async () => { ]), ); }) - .start(); + .start(signal); await runner.makeRequest('get', '/'); await runner.completed(); }); From d789edad6f420cc173744f69d7f6e9c4bd7172e7 Mon Sep 17 00:00:00 2001 From: cod1k Date: Thu, 25 Sep 2025 17:09:06 +0300 Subject: [PATCH 2/6] Refactor `copyExecutionContext` for improved flexibility Reworked `copyExecutionContext` to use a dynamic property descriptor approach, enabling method overrides without altering the original object. Expanded test cases to cover additional methods and verify override behavior. --- .../src/utils/copyExecutionContext.ts | 51 +++++++++++++++++ .../test/copy-execution-context.test.ts | 56 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 packages/cloudflare/src/utils/copyExecutionContext.ts create mode 100644 packages/cloudflare/test/copy-execution-context.test.ts diff --git a/packages/cloudflare/src/utils/copyExecutionContext.ts b/packages/cloudflare/src/utils/copyExecutionContext.ts new file mode 100644 index 000000000000..2ba9d2e36a2d --- /dev/null +++ b/packages/cloudflare/src/utils/copyExecutionContext.ts @@ -0,0 +1,51 @@ +import { type DurableObjectState, type ExecutionContext } from '@cloudflare/workers-types'; + +type ContextType = ExecutionContext | DurableObjectState; +type OverridesStore = Map unknown>; + +/** + * Creates a new copy of the given execution context, optionally overriding methods. + * + * @param {ContextType|void} ctx - The execution context to be copied. Can be of type `ContextType` or `void`. + * @return {ContextType|void} A new execution context with the same properties and overridden methods if applicable. + */ +export function copyExecutionContext(ctx: T): T { + if (!ctx) return ctx; + + const overrides: OverridesStore = new Map(); + const contextPrototype = Object.getPrototypeOf(ctx); + const descriptors = Object.getOwnPropertyNames(contextPrototype).reduce((prevDescriptors, methodName) => { + if (methodName === 'constructor') return prevDescriptors; + const pd = makeMethodDescriptor(overrides, ctx, methodName as keyof ContextType); + return { + ...prevDescriptors, + [methodName]: pd, + }; + }, {}); + + return Object.create(ctx, descriptors); +} + +/** + * Creates a property descriptor for a given method on a context object, enabling custom getter and setter behavior. + * + * @param store - The OverridesStore instance used to manage method overrides. + * @param ctx - The context object from which the method originates. + * @param method - The key of the method on the context object to create a descriptor for. + * @return A property descriptor with custom getter and setter functionalities for the specified method. + */ +function makeMethodDescriptor(store: OverridesStore, ctx: ContextType, method: keyof ContextType): PropertyDescriptor { + return { + configurable: true, + enumerable: true, + set: newValue => { + store.set(method, newValue); + return true; + }, + + get: () => { + if (store.has(method)) return store.get(method); + return Reflect.get(ctx, method).bind(ctx); + }, + }; +} diff --git a/packages/cloudflare/test/copy-execution-context.test.ts b/packages/cloudflare/test/copy-execution-context.test.ts new file mode 100644 index 000000000000..3ee71a10b695 --- /dev/null +++ b/packages/cloudflare/test/copy-execution-context.test.ts @@ -0,0 +1,56 @@ +import { type Mocked, describe, expect, it, vi } from 'vitest'; +import { copyExecutionContext } from '../src/utils/copyExecutionContext'; + +describe('Copy of the execution context', () => { + describe.for([ + 'waitUntil', + 'passThroughOnException', + 'acceptWebSocket', + 'blockConcurrencyWhile', + 'getWebSockets', + 'arbitraryMethod', + 'anythingElse', + ])('%s', method => { + it('Override without changing original', async () => { + const context = { + [method]: vi.fn(), + } as any; + const copy = copyExecutionContext(context); + copy[method] = vi.fn(); + expect(context[method]).not.toBe(copy[method]); + }); + + it('Overridden method was called', async () => { + const context = { + [method]: vi.fn(), + } as any; + const copy = copyExecutionContext(context); + const overridden = vi.fn(); + copy[method] = overridden; + copy[method](); + expect(overridden).toBeCalled(); + expect(context[method]).not.toBeCalled(); + }); + }); + + it('No side effects', async () => { + const context = makeExecutionContextMock(); + expect(() => copyExecutionContext(Object.freeze(context))).not.toThrow( + /Cannot define property \w+, object is not extensible/, + ); + }); + it('Respects symbols', async () => { + const s = Symbol('test'); + const context = makeExecutionContextMock(); + context[s] = {}; + const copy = copyExecutionContext(context); + expect(copy[s]).toBe(context[s]); + }); +}); + +function makeExecutionContextMock() { + return { + waitUntil: vi.fn(), + passThroughOnException: vi.fn(), + } as unknown as Mocked; +} From 4af573cab8ef6b9575c9f3b92e7b1988d83a7b5d Mon Sep 17 00:00:00 2001 From: cod1k Date: Thu, 25 Sep 2025 18:02:34 +0300 Subject: [PATCH 3/6] Improve `copyExecutionContext` method handling Enhanced the descriptor logic to better handle non-function properties and prevent invalid method overrides. Fixed potential issues with binding non-function properties to the context. --- packages/cloudflare/src/utils/copyExecutionContext.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/cloudflare/src/utils/copyExecutionContext.ts b/packages/cloudflare/src/utils/copyExecutionContext.ts index 2ba9d2e36a2d..bb49ce686a1e 100644 --- a/packages/cloudflare/src/utils/copyExecutionContext.ts +++ b/packages/cloudflare/src/utils/copyExecutionContext.ts @@ -39,13 +39,16 @@ function makeMethodDescriptor(store: OverridesStore, ctx: ContextType, method: k configurable: true, enumerable: true, set: newValue => { + if(typeof newValue !== 'function') throw new Error('Cannot override non-function') store.set(method, newValue); return true; }, get: () => { if (store.has(method)) return store.get(method); - return Reflect.get(ctx, method).bind(ctx); + const methodFunction = Reflect.get(ctx, method); + if (typeof methodFunction !== 'function') return methodFunction; + return methodFunction.bind(ctx); }, }; } From 6525e24d736b06c6903c2ce5437fee541e39e725 Mon Sep 17 00:00:00 2001 From: cod1k Date: Fri, 26 Sep 2025 09:17:09 +0300 Subject: [PATCH 4/6] Refactor `copyExecutionContext` for type safety and clarity Updated the `OverridesStore` type to strictly associate keys with context-specific methods, improving type safety. Renamed and enhanced the descriptor creation function to better handle method overriding and added checks for function-only properties. --- .../src/utils/copyExecutionContext.ts | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/packages/cloudflare/src/utils/copyExecutionContext.ts b/packages/cloudflare/src/utils/copyExecutionContext.ts index bb49ce686a1e..611841127251 100644 --- a/packages/cloudflare/src/utils/copyExecutionContext.ts +++ b/packages/cloudflare/src/utils/copyExecutionContext.ts @@ -1,7 +1,7 @@ import { type DurableObjectState, type ExecutionContext } from '@cloudflare/workers-types'; type ContextType = ExecutionContext | DurableObjectState; -type OverridesStore = Map unknown>; +type OverridesStore = Map unknown>; /** * Creates a new copy of the given execution context, optionally overriding methods. @@ -12,14 +12,16 @@ type OverridesStore = Map unknown>; export function copyExecutionContext(ctx: T): T { if (!ctx) return ctx; - const overrides: OverridesStore = new Map(); + const overrides: OverridesStore = new Map(); const contextPrototype = Object.getPrototypeOf(ctx); - const descriptors = Object.getOwnPropertyNames(contextPrototype).reduce((prevDescriptors, methodName) => { + const methodNames = Object.getOwnPropertyNames(contextPrototype) as unknown as (keyof T)[]; + const descriptors = methodNames.reduce((prevDescriptors, methodName) => { if (methodName === 'constructor') return prevDescriptors; - const pd = makeMethodDescriptor(overrides, ctx, methodName as keyof ContextType); + if (typeof ctx[methodName] !== 'function') return prevDescriptors; + const overridableDescriptor = makeOverridableDescriptor(overrides, ctx, methodName); return { ...prevDescriptors, - [methodName]: pd, + [methodName]: overridableDescriptor, }; }, {}); @@ -27,19 +29,26 @@ export function copyExecutionContext(ctx: T): T { } /** - * Creates a property descriptor for a given method on a context object, enabling custom getter and setter behavior. + * Creates a property descriptor that allows overriding of a method on the given context object. * - * @param store - The OverridesStore instance used to manage method overrides. - * @param ctx - The context object from which the method originates. - * @param method - The key of the method on the context object to create a descriptor for. - * @return A property descriptor with custom getter and setter functionalities for the specified method. + * This descriptor supports property overriding with functions only. It delegates method calls to + * the provided store if an override exists or to the original method on the context otherwise. + * + * @param {OverridesStore} store - The storage for overridden methods specific to the context type. + * @param {ContextType} ctx - The context object that contains the method to be overridden. + * @param {keyof ContextType} method - The method on the context object to create the overridable descriptor for. + * @return {PropertyDescriptor} A property descriptor enabling the overriding of the specified method. */ -function makeMethodDescriptor(store: OverridesStore, ctx: ContextType, method: keyof ContextType): PropertyDescriptor { +function makeOverridableDescriptor( + store: OverridesStore, + ctx: T, + method: keyof T, +): PropertyDescriptor { return { configurable: true, enumerable: true, set: newValue => { - if(typeof newValue !== 'function') throw new Error('Cannot override non-function') + if (typeof newValue !== 'function') throw new Error('Cannot override non-function'); store.set(method, newValue); return true; }, From ca281cc7eb953723f5bddb9dc8859edab22827ea Mon Sep 17 00:00:00 2001 From: cod1k Date: Tue, 30 Sep 2025 15:53:08 +0300 Subject: [PATCH 5/6] Added a notation why do we need to do "bind()" here --- packages/cloudflare/src/utils/copyExecutionContext.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cloudflare/src/utils/copyExecutionContext.ts b/packages/cloudflare/src/utils/copyExecutionContext.ts index 611841127251..1f1c09aa656d 100644 --- a/packages/cloudflare/src/utils/copyExecutionContext.ts +++ b/packages/cloudflare/src/utils/copyExecutionContext.ts @@ -57,6 +57,7 @@ function makeOverridableDescriptor( if (store.has(method)) return store.get(method); const methodFunction = Reflect.get(ctx, method); if (typeof methodFunction !== 'function') return methodFunction; + // We should do bind() to make sure that the method is bound to the context object - otherwise it will not work return methodFunction.bind(ctx); }, }; From cb97187046d8b9c618757aba0594dfd171c438eb Mon Sep 17 00:00:00 2001 From: cod1k Date: Tue, 30 Sep 2025 16:00:08 +0300 Subject: [PATCH 6/6] Ignore JUnit report files in version control Added `packages/**/*.junit.xml` to `.gitignore` to prevent JUnit report files from being tracked in the repository, keeping the working directory clean and avoiding unnecessary versioning of test artifacts. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index f381e7e6e24d..36f8a3f6b9fe 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,6 @@ packages/gatsby/gatsby-node.d.ts # intellij *.iml /**/.wrangler/* + +#junit reports +packages/**/*.junit.xml