From b9433d3cd9d2a31febdd31d9a6aaf1c90ac140dd Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Mon, 27 Nov 2023 09:42:29 -0500 Subject: [PATCH] Enable Bun E2E tests (#971) --- .github/workflows/deployment-e2e.yml | 18 ++++++++- e2e/bun/package.json | 5 ++- e2e/bun/src/index.ts | 2 +- e2e/bun/tests/bun.spec.ts | 44 +++++++++++----------- packages/server/src/utils.ts | 55 ++++++++++++++++++++++++++-- 5 files changed, 93 insertions(+), 31 deletions(-) diff --git a/.github/workflows/deployment-e2e.yml b/.github/workflows/deployment-e2e.yml index 5bcc6c780e0..60ba10c7cda 100644 --- a/.github/workflows/deployment-e2e.yml +++ b/.github/workflows/deployment-e2e.yml @@ -9,7 +9,15 @@ jobs: strategy: fail-fast: false matrix: - plan: ['aws-lambda', 'azure-function', 'cloudflare-workers', 'cloudflare-modules', 'deno'] + plan: + [ + 'aws-lambda', + 'azure-function', + 'cloudflare-workers', + 'cloudflare-modules', + 'deno', + 'bun', + ] # TODO: Add vercel name: e2e / ${{ matrix.plan }} @@ -28,10 +36,16 @@ jobs: - name: Install Required Libraries run: sudo apt update && sudo apt install -y libcurl4-openssl-dev libssl-dev - - uses: denoland/setup-deno@v1 + - name: Use Deno + if: matrix.plan == 'deno' + uses: denoland/setup-deno@v1 with: deno-version: vx.x.x + - name: Use Bun + if: matrix.plan == 'bun' + uses: oven-sh/setup-bun@v1 + - name: Cache Node Modules uses: actions/cache@v3 id: node-modules-cache-deployment-e2e diff --git a/e2e/bun/package.json b/e2e/bun/package.json index 979a30c29ef..da8675beab9 100644 --- a/e2e/bun/package.json +++ b/e2e/bun/package.json @@ -3,12 +3,13 @@ "version": "0.0.74", "private": true, "scripts": { - "e2e": "bun wiptest", + "e2e": "bun test", "start": "bun src/index.ts" }, "dependencies": { "@e2e/shared-server": "0.0.74", - "bun-types": "^1.0.0" + "@types/node": "20.10.0", + "bun-types": "1.0.14" }, "devDependencies": { "typescript": "5.3.2" diff --git a/e2e/bun/src/index.ts b/e2e/bun/src/index.ts index 19b7b9ea933..c2bc3beafa8 100644 --- a/e2e/bun/src/index.ts +++ b/e2e/bun/src/index.ts @@ -1,3 +1,3 @@ -import { createTestServerAdapter } from '@e2e/shared-server'; +import { createTestServerAdapter } from '../../shared-server/src/index'; Bun.serve(createTestServerAdapter()); diff --git a/e2e/bun/tests/bun.spec.ts b/e2e/bun/tests/bun.spec.ts index 598ccc6e9d9..f222534e21a 100644 --- a/e2e/bun/tests/bun.spec.ts +++ b/e2e/bun/tests/bun.spec.ts @@ -1,29 +1,29 @@ -import { Server } from 'bun'; -import { describe, it } from 'bun:test'; -import { assertDeployedEndpoint } from '@e2e/shared-scripts'; -import { createTestServerAdapter } from '@e2e/shared-server'; - -let server: Server; -let url: string; -function beforeEach() { - server = Bun.serve({ - fetch: createTestServerAdapter(), - port: 3000, - }); - url = `http://${server.hostname}:${server.port}`; -} - -function afterEach() { - server.stop(); -} +import { createServer } from 'node:http'; +import { describe, expect, it } from 'bun:test'; +import { assertDeployedEndpoint } from '../../shared-scripts/src/index'; +import { createTestServerAdapter } from '../../shared-server/src/index'; describe('Bun', () => { it('works', async () => { - beforeEach(); + const server = Bun.serve({ + fetch: createTestServerAdapter(), + port: 3000, + }); + try { + await assertDeployedEndpoint(`http://localhost:3000/graphql`); + } catch (e) { + expect(e).toBeUndefined(); + } + server.stop(true); + }); + it('works with Node compat mode', async () => { + const server = createServer(createTestServerAdapter()); + await new Promise(resolve => server.listen(3000, resolve)); try { - await assertDeployedEndpoint(url); - } finally { - afterEach(); + await assertDeployedEndpoint(`http://localhost:3000/graphql`); + } catch (e) { + expect(e).toBeUndefined(); } + return new Promise(resolve => server.close(resolve)); }); }); diff --git a/packages/server/src/utils.ts b/packages/server/src/utils.ts index cc027b76662..69a80b5d355 100644 --- a/packages/server/src/utils.ts +++ b/packages/server/src/utils.ts @@ -111,6 +111,8 @@ export class ServerAdapterRequestAbortSignal extends EventTarget implements Abor } } +let bunNodeCompatModeWarned = false; + export function normalizeNodeRequest( nodeRequest: NodeRequest, RequestCtor: typeof Request, @@ -125,11 +127,24 @@ export function normalizeNodeRequest( fullUrl = url.toString(); } - const signal = new ServerAdapterRequestAbortSignal(); + let signal: AbortSignal; + + // If ponyfilled + if (RequestCtor !== globalThis.Request) { + signal = new ServerAdapterRequestAbortSignal(); + + if (rawRequest.once) { + rawRequest.once('end', () => (signal as ServerAdapterRequestAbortSignal).sendAbort()); + rawRequest.once('close', () => (signal as ServerAdapterRequestAbortSignal).sendAbort()); + } + } else { + const controller = new AbortController(); + signal = controller.signal; - if (rawRequest.once) { - rawRequest.once('end', () => signal.sendAbort()); - rawRequest.once('close', () => signal.sendAbort()); + if (rawRequest.once) { + rawRequest.once('end', () => controller.abort()); + rawRequest.once('close', () => controller.abort()); + } } if (nodeRequest.method === 'GET' || nodeRequest.method === 'HEAD') { @@ -178,6 +193,38 @@ export function normalizeNodeRequest( }); } + // Temporary workaround for a bug in Bun Node compat mode + if (globalThis.process?.versions?.bun && isReadable(rawRequest)) { + if (!bunNodeCompatModeWarned) { + bunNodeCompatModeWarned = true; + console.warn( + `You use Bun Node compatibility mode, which is not recommended! +It will affect your performance. Please check our Bun integration recipe, and avoid using 'node:http' for your server implementation.`, + ); + } + return new RequestCtor(fullUrl, { + method: nodeRequest.method, + headers: nodeRequest.headers, + body: new ReadableStream({ + start(controller) { + rawRequest.on('data', chunk => { + controller.enqueue(chunk); + }); + rawRequest.on('error', e => { + controller.error(e); + }); + rawRequest.on('end', () => { + controller.close(); + }); + }, + cancel(e) { + rawRequest.destroy(e); + }, + }), + signal, + }); + } + // perf: instead of spreading the object, we can just pass it as is and it performs better return new RequestCtor(fullUrl, { method: nodeRequest.method,