From c365e612107b4c8a9b77d2b4dbefbdacc5e0cee6 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Fri, 6 Sep 2024 14:31:01 +0100 Subject: [PATCH] test: remove unwanted logging from Wrangler tests (#6447) --- .../wrangler/src/__tests__/api-dev.test.ts | 2 + .../src/__tests__/api-devregistry.test.ts | 30 +- .../startDevWorker/BundleController.test.ts | 486 +++--- .../startDevWorker/ConfigController.test.ts | 3 + .../LocalRuntimeController.test.ts | 1425 +++++++++-------- .../src/__tests__/helpers/teardown.ts | 58 +- .../wrangler/src/__tests__/rollback.test.ts | 2 + 7 files changed, 1000 insertions(+), 1006 deletions(-) diff --git a/packages/wrangler/src/__tests__/api-dev.test.ts b/packages/wrangler/src/__tests__/api-dev.test.ts index 303138274c30..8884e19e993f 100644 --- a/packages/wrangler/src/__tests__/api-dev.test.ts +++ b/packages/wrangler/src/__tests__/api-dev.test.ts @@ -2,12 +2,14 @@ import * as fs from "node:fs"; import { Request } from "undici"; import { vi } from "vitest"; import { unstable_dev } from "../api"; +import { mockConsoleMethods } from "./helpers/mock-console"; import { runInTempDir } from "./helpers/run-in-tmp"; vi.unmock("child_process"); vi.unmock("undici"); describe("unstable_dev", () => { + mockConsoleMethods(); it("should return Hello World", async () => { const worker = await unstable_dev( "src/__tests__/helpers/worker-scripts/hello-world-worker.js", diff --git a/packages/wrangler/src/__tests__/api-devregistry.test.ts b/packages/wrangler/src/__tests__/api-devregistry.test.ts index 642e1cd74fa6..f0e7e7430f51 100644 --- a/packages/wrangler/src/__tests__/api-devregistry.test.ts +++ b/packages/wrangler/src/__tests__/api-devregistry.test.ts @@ -6,12 +6,15 @@ import { mockConsoleMethods } from "./helpers/mock-console"; vi.unmock("child_process"); vi.unmock("undici"); +// We need this outside the describe block to make sure the console spies are not +// torn down before the workers. +const std = mockConsoleMethods(); + /** * a huge caveat to how testing multi-worker scripts works: * you can't shutdown the first worker you spun up, or it'll kill the devRegistry */ describe("multi-worker testing", () => { - mockConsoleMethods(); // eslint-disable-next-line @typescript-eslint/no-explicit-any let childWorker: any; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -80,25 +83,6 @@ describe("multi-worker testing", () => { }); it("should be able to stop and start the server with no warning logs", async () => { - // Spy on all the console methods - let logs = ""; - // Resolve when we see `[mf:inf] GET / 200 OK` message. This log is sent in - // a `waitUntil()`, which may execute after tests complete. To stop Vitest - // complaining about logging after a test, wait for this log. - let requestResolve: () => void; - const requestPromise = new Promise( - (resolve) => (requestResolve = resolve) - ); - (["debug", "info", "log", "warn", "error"] as const).forEach((method) => - vi.spyOn(console, method).mockImplementation((...args: unknown[]) => { - logs += `\n${args}`; - // Regexp ignores colour codes - if (/\[wrangler.*:inf].+GET.+\/.+200.+OK/.test(String(args))) { - requestResolve(); - } - }) - ); - async function startWorker() { const worker = await unstable_dev( "src/__tests__/helpers/worker-scripts/hello-world-worker.js", @@ -131,9 +115,11 @@ describe("multi-worker testing", () => { const resp = await worker.fetch(); expect(resp).not.toBe(undefined); - await requestPromise; + await vi.waitFor(() => + /\[wrangler.*:inf].+GET.+\/.+200.+OK/.test(std.debug) + ); - expect(logs).not.toMatch( + expect(std.debug).not.toMatch( /Failed to register worker in local service registry/ ); }, 10000); diff --git a/packages/wrangler/src/__tests__/api/startDevWorker/BundleController.test.ts b/packages/wrangler/src/__tests__/api/startDevWorker/BundleController.test.ts index 39aa4ab223e0..35fc2c6088cb 100644 --- a/packages/wrangler/src/__tests__/api/startDevWorker/BundleController.test.ts +++ b/packages/wrangler/src/__tests__/api/startDevWorker/BundleController.test.ts @@ -3,6 +3,7 @@ import path from "path"; import dedent from "ts-dedent"; import { test as base, describe } from "vitest"; import { BundlerController } from "../../../api/startDevWorker/BundlerController"; +import { mockConsoleMethods } from "../../helpers/mock-console"; import { runInTempDir } from "../../helpers/run-in-tmp"; import { seed } from "../../helpers/seed"; import { unusable } from "../../helpers/unusable"; @@ -46,11 +47,15 @@ function configDefaults( ...config, }; } -describe("happy path bundle + watch", () => { + +describe("BundleController", () => { + mockConsoleMethods(); runInTempDir(); - test("single ts source file", async ({ controller }) => { - await seed({ - "src/index.ts": dedent/* javascript */ ` + + describe("happy path bundle + watch", () => { + test("single ts source file", async ({ controller }) => { + await seed({ + "src/index.ts": dedent/* javascript */ ` export default { fetch(request, env, ctx) { //comment @@ -58,33 +63,33 @@ describe("happy path bundle + watch", () => { } } satisfies ExportedHandler `, - }); - const config: Partial = { - legacy: {}, - name: "worker", - entrypoint: path.resolve("src/index.ts"), - directory: path.resolve("src"), - build: { - additionalModules: [], - processEntrypoint: false, - nodejsCompatMode: null, - bundle: true, - moduleRules: [], - custom: {}, - define: {}, - format: "modules", - moduleRoot: path.resolve("src"), - }, - }; + }); + const config: Partial = { + legacy: {}, + name: "worker", + entrypoint: path.resolve("src/index.ts"), + directory: path.resolve("src"), + build: { + additionalModules: [], + processEntrypoint: false, + nodejsCompatMode: null, + bundle: true, + moduleRules: [], + custom: {}, + define: {}, + format: "modules", + moduleRoot: path.resolve("src"), + }, + }; - await controller.onConfigUpdate({ - type: "configUpdate", - config: configDefaults(config), - }); + await controller.onConfigUpdate({ + type: "configUpdate", + config: configDefaults(config), + }); - let ev = await waitForBundleComplete(controller); - expect(findSourceFile(ev.bundle.entrypointSource, "index.ts")) - .toMatchInlineSnapshot(` + let ev = await waitForBundleComplete(controller); + expect(findSourceFile(ev.bundle.entrypointSource, "index.ts")) + .toMatchInlineSnapshot(` "// index.ts var src_default = { fetch(request, env, ctx) { @@ -93,8 +98,8 @@ describe("happy path bundle + watch", () => { }; " `); - await seed({ - "src/index.ts": dedent/* javascript */ ` + await seed({ + "src/index.ts": dedent/* javascript */ ` export default { fetch(request, env, ctx) { //comment @@ -102,10 +107,10 @@ describe("happy path bundle + watch", () => { } } satisfies ExportedHandler `, - }); - ev = await waitForBundleComplete(controller); - expect(findSourceFile(ev.bundle.entrypointSource, "index.ts")) - .toMatchInlineSnapshot(` + }); + ev = await waitForBundleComplete(controller); + expect(findSourceFile(ev.bundle.entrypointSource, "index.ts")) + .toMatchInlineSnapshot(` "// index.ts var src_default = { fetch(request, env, ctx) { @@ -114,10 +119,11 @@ describe("happy path bundle + watch", () => { }; " `); - }); - test("multiple ts source files", async ({ controller }) => { - await seed({ - "src/index.ts": dedent/* javascript */ ` + }); + + test("multiple ts source files", async ({ controller }) => { + await seed({ + "src/index.ts": dedent/* javascript */ ` import name from "./other" export default { fetch(request, env, ctx) { @@ -126,42 +132,42 @@ describe("happy path bundle + watch", () => { } } satisfies ExportedHandler `, - "src/other.ts": dedent/* javascript */ ` + "src/other.ts": dedent/* javascript */ ` export default "someone" `, - }); - const config: Partial = { - legacy: {}, - name: "worker", - entrypoint: path.resolve("src/index.ts"), - directory: path.resolve("src"), - build: { - additionalModules: [], - processEntrypoint: false, - nodejsCompatMode: null, - bundle: true, - moduleRules: [], - custom: {}, - define: {}, - format: "modules", - moduleRoot: path.resolve("src"), - }, - }; + }); + const config: Partial = { + legacy: {}, + name: "worker", + entrypoint: path.resolve("src/index.ts"), + directory: path.resolve("src"), + build: { + additionalModules: [], + processEntrypoint: false, + nodejsCompatMode: null, + bundle: true, + moduleRules: [], + custom: {}, + define: {}, + format: "modules", + moduleRoot: path.resolve("src"), + }, + }; - await controller.onConfigUpdate({ - type: "configUpdate", - config: configDefaults(config), - }); + await controller.onConfigUpdate({ + type: "configUpdate", + config: configDefaults(config), + }); - let ev = await waitForBundleComplete(controller); - expect(findSourceFile(ev.bundle.entrypointSource, "other.ts")) - .toMatchInlineSnapshot(` + let ev = await waitForBundleComplete(controller); + expect(findSourceFile(ev.bundle.entrypointSource, "other.ts")) + .toMatchInlineSnapshot(` "// other.ts var other_default = \\"someone\\"; " `); - expect(findSourceFile(ev.bundle.entrypointSource, "index.ts")) - .toMatchInlineSnapshot(` + expect(findSourceFile(ev.bundle.entrypointSource, "index.ts")) + .toMatchInlineSnapshot(` "// index.ts var src_default = { fetch(request, env, ctx) { @@ -170,23 +176,23 @@ describe("happy path bundle + watch", () => { }; " `); - await seed({ - "src/other.ts": dedent/* javascript */ ` + await seed({ + "src/other.ts": dedent/* javascript */ ` export default "someone else" `, - }); - ev = await waitForBundleComplete(controller); - expect(findSourceFile(ev.bundle.entrypointSource, "other.ts")) - .toMatchInlineSnapshot(` + }); + ev = await waitForBundleComplete(controller); + expect(findSourceFile(ev.bundle.entrypointSource, "other.ts")) + .toMatchInlineSnapshot(` "// other.ts var other_default = \\"someone else\\"; " `); - }); + }); - test("custom build", async ({ controller }) => { - await seed({ - "random_dir/index.ts": dedent/* javascript */ ` + test("custom build", async ({ controller }) => { + await seed({ + "random_dir/index.ts": dedent/* javascript */ ` export default { fetch(request, env, ctx) { //comment @@ -194,36 +200,36 @@ describe("happy path bundle + watch", () => { } } satisfies ExportedHandler `, - }); - const config: Partial = { - legacy: {}, - name: "worker", - entrypoint: path.resolve("out.ts"), - directory: path.resolve("."), - build: { - additionalModules: [], - processEntrypoint: false, - nodejsCompatMode: null, - bundle: true, - moduleRules: [], - custom: { - command: "cp random_dir/index.ts out.ts", - watch: "random_dir", + }); + const config: Partial = { + legacy: {}, + name: "worker", + entrypoint: path.resolve("out.ts"), + directory: path.resolve("."), + build: { + additionalModules: [], + processEntrypoint: false, + nodejsCompatMode: null, + bundle: true, + moduleRules: [], + custom: { + command: "cp random_dir/index.ts out.ts", + watch: "random_dir", + }, + define: {}, + format: "modules", + moduleRoot: path.resolve("."), }, - define: {}, - format: "modules", - moduleRoot: path.resolve("."), - }, - }; + }; - await controller.onConfigUpdate({ - type: "configUpdate", - config: configDefaults(config), - }); + await controller.onConfigUpdate({ + type: "configUpdate", + config: configDefaults(config), + }); - let ev = await waitForBundleComplete(controller); - expect(findSourceFile(ev.bundle.entrypointSource, "out.ts")) - .toMatchInlineSnapshot(` + let ev = await waitForBundleComplete(controller); + expect(findSourceFile(ev.bundle.entrypointSource, "out.ts")) + .toMatchInlineSnapshot(` "// out.ts var out_default = { fetch(request, env, ctx) { @@ -232,8 +238,8 @@ describe("happy path bundle + watch", () => { }; " `); - await seed({ - "random_dir/index.ts": dedent/* javascript */ ` + await seed({ + "random_dir/index.ts": dedent/* javascript */ ` export default { fetch(request, env, ctx) { //comment @@ -241,10 +247,10 @@ describe("happy path bundle + watch", () => { } } `, - }); - ev = await waitForBundleComplete(controller); - expect(findSourceFile(ev.bundle.entrypointSource, "out.ts")) - .toMatchInlineSnapshot(` + }); + ev = await waitForBundleComplete(controller); + expect(findSourceFile(ev.bundle.entrypointSource, "out.ts")) + .toMatchInlineSnapshot(` "// out.ts var out_default = { fetch(request, env, ctx) { @@ -253,6 +259,7 @@ describe("happy path bundle + watch", () => { }; " `); + }); }); test("module aliasing", async ({ controller }) => { @@ -317,13 +324,11 @@ describe("happy path bundle + watch", () => { var bar_default = "bar" `); }); -}); -describe("switching", () => { - runInTempDir(); - test("esbuild -> custom builds", async ({ controller }) => { - await seed({ - "src/index.ts": dedent/* javascript */ ` + describe("switching", () => { + test("esbuild -> custom builds", async ({ controller }) => { + await seed({ + "src/index.ts": dedent/* javascript */ ` export default { fetch(request, env, ctx) { //comment @@ -331,34 +336,34 @@ describe("switching", () => { } } satisfies ExportedHandler `, - }); - const config: Partial = { - legacy: {}, - name: "worker", - entrypoint: path.resolve("src/index.ts"), - directory: path.resolve("src"), + }); + const config: Partial = { + legacy: {}, + name: "worker", + entrypoint: path.resolve("src/index.ts"), + directory: path.resolve("src"), - build: { - additionalModules: [], - processEntrypoint: false, - nodejsCompatMode: null, - bundle: true, - moduleRules: [], - custom: {}, - define: {}, - format: "modules", - moduleRoot: path.resolve("src"), - }, - }; + build: { + additionalModules: [], + processEntrypoint: false, + nodejsCompatMode: null, + bundle: true, + moduleRules: [], + custom: {}, + define: {}, + format: "modules", + moduleRoot: path.resolve("src"), + }, + }; - await controller.onConfigUpdate({ - type: "configUpdate", - config: configDefaults(config), - }); + await controller.onConfigUpdate({ + type: "configUpdate", + config: configDefaults(config), + }); - const ev = await waitForBundleComplete(controller); - expect(findSourceFile(ev.bundle.entrypointSource, "index.ts")) - .toMatchInlineSnapshot(` + const ev = await waitForBundleComplete(controller); + expect(findSourceFile(ev.bundle.entrypointSource, "index.ts")) + .toMatchInlineSnapshot(` "// index.ts var src_default = { fetch(request, env, ctx) { @@ -368,8 +373,8 @@ describe("switching", () => { " `); - await seed({ - "random_dir/index.ts": dedent/* javascript */ ` + await seed({ + "random_dir/index.ts": dedent/* javascript */ ` export default { fetch(request, env, ctx) { //comment @@ -377,36 +382,36 @@ describe("switching", () => { } } satisfies ExportedHandler `, - }); - const configCustom: Partial = { - name: "worker", - entrypoint: path.resolve("out.ts"), - directory: process.cwd(), - build: { - additionalModules: [], - processEntrypoint: false, - nodejsCompatMode: null, - bundle: true, - moduleRules: [], - custom: { - command: "cp random_dir/index.ts out.ts", - watch: "random_dir", + }); + const configCustom: Partial = { + name: "worker", + entrypoint: path.resolve("out.ts"), + directory: process.cwd(), + build: { + additionalModules: [], + processEntrypoint: false, + nodejsCompatMode: null, + bundle: true, + moduleRules: [], + custom: { + command: "cp random_dir/index.ts out.ts", + watch: "random_dir", + }, + define: {}, + format: "modules", + moduleRoot: process.cwd(), }, - define: {}, - format: "modules", - moduleRoot: process.cwd(), - }, - legacy: {}, - }; + legacy: {}, + }; - await controller.onConfigUpdate({ - type: "configUpdate", - config: configDefaults(configCustom), - }); + await controller.onConfigUpdate({ + type: "configUpdate", + config: configDefaults(configCustom), + }); - let evCustom = await waitForBundleComplete(controller); - expect(findSourceFile(evCustom.bundle.entrypointSource, "out.ts")) - .toMatchInlineSnapshot(` + let evCustom = await waitForBundleComplete(controller); + expect(findSourceFile(evCustom.bundle.entrypointSource, "out.ts")) + .toMatchInlineSnapshot(` "// out.ts var out_default = { fetch(request, env, ctx) { @@ -415,9 +420,9 @@ describe("switching", () => { }; " `); - // Make sure custom builds can reload after switching to them - await seed({ - "random_dir/index.ts": dedent/* javascript */ ` + // Make sure custom builds can reload after switching to them + await seed({ + "random_dir/index.ts": dedent/* javascript */ ` export default { fetch(request, env, ctx) { //comment @@ -425,10 +430,10 @@ describe("switching", () => { } } `, - }); - evCustom = await waitForBundleComplete(controller); - expect(findSourceFile(evCustom.bundle.entrypointSource, "out.ts")) - .toMatchInlineSnapshot(` + }); + evCustom = await waitForBundleComplete(controller); + expect(findSourceFile(evCustom.bundle.entrypointSource, "out.ts")) + .toMatchInlineSnapshot(` "// out.ts var out_default = { fetch(request, env, ctx) { @@ -437,11 +442,11 @@ describe("switching", () => { }; " `); - }); + }); - test("custom builds -> esbuild", async ({ controller }) => { - await seed({ - "random_dir/index.ts": dedent/* javascript */ ` + test("custom builds -> esbuild", async ({ controller }) => { + await seed({ + "random_dir/index.ts": dedent/* javascript */ ` export default { fetch(request, env, ctx) { //comment @@ -449,36 +454,36 @@ describe("switching", () => { } } satisfies ExportedHandler `, - }); - const configCustom: Partial = { - name: "worker", - entrypoint: path.resolve("out.ts"), - directory: process.cwd(), + }); + const configCustom: Partial = { + name: "worker", + entrypoint: path.resolve("out.ts"), + directory: process.cwd(), - build: { - additionalModules: [], - processEntrypoint: false, - nodejsCompatMode: null, - bundle: true, - moduleRules: [], - custom: { - command: "cp random_dir/index.ts out.ts", - watch: "random_dir", + build: { + additionalModules: [], + processEntrypoint: false, + nodejsCompatMode: null, + bundle: true, + moduleRules: [], + custom: { + command: "cp random_dir/index.ts out.ts", + watch: "random_dir", + }, + define: {}, + format: "modules", + moduleRoot: process.cwd(), }, - define: {}, - format: "modules", - moduleRoot: process.cwd(), - }, - }; + }; - await controller.onConfigUpdate({ - type: "configUpdate", - config: configDefaults(configCustom), - }); + await controller.onConfigUpdate({ + type: "configUpdate", + config: configDefaults(configCustom), + }); - const evCustom = await waitForBundleComplete(controller); - expect(findSourceFile(evCustom.bundle.entrypointSource, "out.ts")) - .toMatchInlineSnapshot(` + const evCustom = await waitForBundleComplete(controller); + expect(findSourceFile(evCustom.bundle.entrypointSource, "out.ts")) + .toMatchInlineSnapshot(` "// out.ts var out_default = { fetch(request, env, ctx) { @@ -487,8 +492,8 @@ describe("switching", () => { }; " `); - await seed({ - "src/index.ts": dedent/* javascript */ ` + await seed({ + "src/index.ts": dedent/* javascript */ ` export default { fetch(request, env, ctx) { //comment @@ -496,34 +501,34 @@ describe("switching", () => { } } satisfies ExportedHandler `, - }); - const config: Partial = { - legacy: {}, - name: "worker", - entrypoint: path.resolve("src/index.ts"), - directory: path.resolve("src"), + }); + const config: Partial = { + legacy: {}, + name: "worker", + entrypoint: path.resolve("src/index.ts"), + directory: path.resolve("src"), - build: { - additionalModules: [], - processEntrypoint: false, - nodejsCompatMode: null, - bundle: true, - moduleRules: [], - custom: {}, - define: {}, - format: "modules", - moduleRoot: path.resolve("src"), - }, - }; + build: { + additionalModules: [], + processEntrypoint: false, + nodejsCompatMode: null, + bundle: true, + moduleRules: [], + custom: {}, + define: {}, + format: "modules", + moduleRoot: path.resolve("src"), + }, + }; - await controller.onConfigUpdate({ - type: "configUpdate", - config: configDefaults(config), - }); + await controller.onConfigUpdate({ + type: "configUpdate", + config: configDefaults(config), + }); - let ev = await waitForBundleComplete(controller); - expect(findSourceFile(ev.bundle.entrypointSource, "index.ts")) - .toMatchInlineSnapshot(` + let ev = await waitForBundleComplete(controller); + expect(findSourceFile(ev.bundle.entrypointSource, "index.ts")) + .toMatchInlineSnapshot(` "// index.ts var src_default = { fetch(request, env, ctx) { @@ -532,8 +537,8 @@ describe("switching", () => { }; " `); - await seed({ - "src/index.ts": dedent/* javascript */ ` + await seed({ + "src/index.ts": dedent/* javascript */ ` export default { fetch(request, env, ctx) { //comment @@ -541,10 +546,10 @@ describe("switching", () => { } } satisfies ExportedHandler `, - }); - ev = await waitForBundleComplete(controller); - expect(findSourceFile(ev.bundle.entrypointSource, "index.ts")) - .toMatchInlineSnapshot(` + }); + ev = await waitForBundleComplete(controller); + expect(findSourceFile(ev.bundle.entrypointSource, "index.ts")) + .toMatchInlineSnapshot(` "// index.ts var src_default = { fetch(request, env, ctx) { @@ -553,5 +558,6 @@ describe("switching", () => { }; " `); + }); }); }); diff --git a/packages/wrangler/src/__tests__/api/startDevWorker/ConfigController.test.ts b/packages/wrangler/src/__tests__/api/startDevWorker/ConfigController.test.ts index 3ede75567544..47fd9a8c7cc7 100644 --- a/packages/wrangler/src/__tests__/api/startDevWorker/ConfigController.test.ts +++ b/packages/wrangler/src/__tests__/api/startDevWorker/ConfigController.test.ts @@ -3,6 +3,7 @@ import path from "node:path"; import dedent from "ts-dedent"; import { describe, it } from "vitest"; import { ConfigController } from "../../../api/startDevWorker/ConfigController"; +import { mockConsoleMethods } from "../../helpers/mock-console"; import { runInTempDir } from "../../helpers/run-in-tmp"; import { seed } from "../../helpers/seed"; import type { ConfigUpdateEvent, StartDevWorkerInput } from "../../../api"; @@ -16,6 +17,8 @@ async function waitForConfigUpdate( describe("ConfigController", () => { runInTempDir(); + mockConsoleMethods(); + it("should emit configUpdate events with defaults applied", async () => { const controller = new ConfigController(); const event = waitForConfigUpdate(controller); diff --git a/packages/wrangler/src/__tests__/api/startDevWorker/LocalRuntimeController.test.ts b/packages/wrangler/src/__tests__/api/startDevWorker/LocalRuntimeController.test.ts index 2e52009cf3e7..45014975c055 100644 --- a/packages/wrangler/src/__tests__/api/startDevWorker/LocalRuntimeController.test.ts +++ b/packages/wrangler/src/__tests__/api/startDevWorker/LocalRuntimeController.test.ts @@ -12,7 +12,9 @@ import WebSocket from "ws"; import { LocalRuntimeController } from "../../../api/startDevWorker/LocalRuntimeController"; import { urlFromParts } from "../../../api/startDevWorker/utils"; import { RuleTypeToModuleType } from "../../../deployment-bundle/module-collection"; -import { teardown, useTmp } from "../../helpers/teardown"; +import { mockConsoleMethods } from "../../helpers/mock-console"; +import { runInTempDir } from "../../helpers/run-in-tmp"; +import { useTeardown } from "../../helpers/teardown"; import { unusable } from "../../helpers/unusable"; import type { Bundle, @@ -122,37 +124,40 @@ function makeEsbuildBundle(testBundle: TestBundle): Bundle { function configDefaults( config: Partial ): StartDevWorkerOptions { - const tmp = useTmp(); - const persist = path.join(tmp, "persist"); return { entrypoint: "NOT_REAL", directory: "NOT_REAL", build: unusable(), legacy: {}, - dev: { persist }, + dev: { persist: "./persist" }, ...config, }; } -describe("Core", () => { - it("should start Miniflare with module worker", async () => { - const controller = new LocalRuntimeController(); - teardown(() => controller.teardown()); +describe("LocalRuntimeController", () => { + const teardown = useTeardown(); + mockConsoleMethods(); + runInTempDir(); - const config = { - name: "worker", - entrypoint: "NOT_REAL", - compatibilityFlags: ["nodejs_compat"], - compatibilityDate: "2023-10-01", - }; - const bundle: Bundle = { - type: "esm", - modules: [ - { - type: "commonjs", - name: "add.cjs", - filePath: "/virtual/cjs/add.cjs", - content: ` + describe("Core", () => { + it("should start Miniflare with module worker", async () => { + const controller = new LocalRuntimeController(); + teardown(() => controller.teardown()); + + const config = { + name: "worker", + entrypoint: "NOT_REAL", + compatibilityFlags: ["nodejs_compat"], + compatibilityDate: "2023-10-01", + }; + const bundle: Bundle = { + type: "esm", + modules: [ + { + type: "commonjs", + name: "add.cjs", + filePath: "/virtual/cjs/add.cjs", + content: ` const addModule = require("./add.wasm"); const addInstance = new WebAssembly.Instance(addModule); module.exports = { @@ -162,12 +167,12 @@ describe("Core", () => { } } `, - }, - { - type: "nodejs-compat-module", - name: "base64.cjs", - filePath: "/virtual/node/base64.cjs", - content: `module.exports = { + }, + { + type: "nodejs-compat-module", + name: "base64.cjs", + filePath: "/virtual/node/base64.cjs", + content: `module.exports = { encode(value) { return Buffer.from(value).toString("base64"); }, @@ -178,29 +183,29 @@ describe("Core", () => { throw new Error("Oops!"); } }`, - }, - { - type: "text", - name: "data/wave.txt", - filePath: "/virtual/data/wave.txt", - content: "๐Ÿ‘‹", - }, - { - type: "buffer", - name: "data/wave.bin", - filePath: "/virtual/data/wave.bin", - content: "๐ŸŒŠ", - }, - { - type: "compiled-wasm", - name: "add.wasm", - filePath: "/virtual/add.wasm", - content: WASM_ADD_MODULE, - }, - ], - id: 0, - path: "/virtual/esm/index.mjs", - entrypointSource: dedent/*javascript*/ ` + }, + { + type: "text", + name: "data/wave.txt", + filePath: "/virtual/data/wave.txt", + content: "๐Ÿ‘‹", + }, + { + type: "buffer", + name: "data/wave.bin", + filePath: "/virtual/data/wave.bin", + content: "๐ŸŒŠ", + }, + { + type: "compiled-wasm", + name: "add.wasm", + filePath: "/virtual/add.wasm", + content: WASM_ADD_MODULE, + }, + ], + id: 0, + path: "/virtual/esm/index.mjs", + entrypointSource: dedent/*javascript*/ ` import add from "./add.cjs"; import base64 from "./base64.cjs"; import wave1 from "./data/wave.txt"; @@ -224,80 +229,80 @@ describe("Core", () => { } } `, - entry: { - file: "esm/index.mjs", - directory: "/virtual/", - format: "modules", - moduleRoot: "/virtual", - name: undefined, - }, - dependencies: {}, - sourceMapPath: undefined, - sourceMapMetadata: undefined, - }; - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, - }); - const event = await waitForReloadComplete(controller); - const url = urlFromParts(event.proxyData.userWorkerUrl); + entry: { + file: "esm/index.mjs", + directory: "/virtual/", + format: "modules", + moduleRoot: "/virtual", + name: undefined, + }, + dependencies: {}, + sourceMapPath: undefined, + sourceMapMetadata: undefined, + }; + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); + const event = await waitForReloadComplete(controller); + const url = urlFromParts(event.proxyData.userWorkerUrl); - // Check all module types - let res = await fetch(url); - expect(res.status).toBe(200); - expect(await res.json()).toEqual({ message: "๐Ÿ‘‹๐ŸŒŠ", sum: 3 }); + // Check all module types + let res = await fetch(url); + expect(res.status).toBe(200); + expect(await res.json()).toEqual({ message: "๐Ÿ‘‹๐ŸŒŠ", sum: 3 }); - // Check stack traces from ESModule and CommonJS modules include file path - res = await fetch(new URL("/throw-commonjs", url)); - expect(res.status).toBe(200); - if (isWindows) { - expect(await res.text()).toMatchInlineSnapshot(` + // Check stack traces from ESModule and CommonJS modules include file path + res = await fetch(new URL("/throw-commonjs", url)); + expect(res.status).toBe(200); + if (isWindows) { + expect(normalizeDrive(await res.text())).toMatchInlineSnapshot(` "Error: Oops! at Object.throw (file:///D:/virtual/cjs/add.cjs:7:14) at Object.fetch (file:///D:/virtual/esm/index.mjs:15:19)" `); - // Check stack traces from NodeJsCompatModule modules include file path - res = await fetch(new URL("/throw-nodejs-compat-module", url)); - expect(res.status).toBe(200); - expect(await res.text()).toMatchInlineSnapshot(` + // Check stack traces from NodeJsCompatModule modules include file path + res = await fetch(new URL("/throw-nodejs-compat-module", url)); + expect(res.status).toBe(200); + expect(normalizeDrive(await res.text())).toMatchInlineSnapshot(` "Error: Oops! at Object.throw (file:///D:/virtual/esm/base64.cjs:9:14) at Object.fetch (file:///D:/virtual/esm/index.mjs:17:22)" `); - } else { - expect(await res.text()).toMatchInlineSnapshot(` + } else { + expect(await res.text()).toMatchInlineSnapshot(` "Error: Oops! at Object.throw (file:///virtual/cjs/add.cjs:7:14) at Object.fetch (file:///virtual/esm/index.mjs:15:19)" `); - // Check stack traces from NodeJsCompatModule modules include file path - res = await fetch(new URL("/throw-nodejs-compat-module", url)); - expect(res.status).toBe(200); - expect(await res.text()).toMatchInlineSnapshot(` + // Check stack traces from NodeJsCompatModule modules include file path + res = await fetch(new URL("/throw-nodejs-compat-module", url)); + expect(res.status).toBe(200); + expect(await res.text()).toMatchInlineSnapshot(` "Error: Oops! at Object.throw (file:///virtual/esm/base64.cjs:9:14) at Object.fetch (file:///virtual/esm/index.mjs:17:22)" `); - } - }); - it("should start Miniflare with service worker", async () => { - const controller = new LocalRuntimeController(); - teardown(() => controller.teardown()); + } + }); + it("should start Miniflare with service worker", async () => { + const controller = new LocalRuntimeController(); + teardown(() => controller.teardown()); - const config = { - name: "worker", - entrypoint: "NOT_REAL", - }; - const bundle: Bundle = { - type: "commonjs", - entrypointSource: dedent/*javascript*/ ` + const config = { + name: "worker", + entrypoint: "NOT_REAL", + }; + const bundle: Bundle = { + type: "commonjs", + entrypointSource: dedent/*javascript*/ ` addEventListener("fetch", (event) => { const { pathname } = new URL(event.request.url); if (pathname === "/") { @@ -315,195 +320,195 @@ describe("Core", () => { } }); `, - modules: [ - { - type: "text", - name: "data/one.txt", - content: "one", - filePath: "/virtual/data/one.txt", - }, - { - type: "buffer", - name: "data/two.bin", - content: "two", - filePath: "/virtual/data/two.bin", - }, - { - type: "compiled-wasm", - name: "add.wasm", - content: WASM_ADD_MODULE, - filePath: "/virtual/add.wasm", + modules: [ + { + type: "text", + name: "data/one.txt", + content: "one", + filePath: "/virtual/data/one.txt", + }, + { + type: "buffer", + name: "data/two.bin", + content: "two", + filePath: "/virtual/data/two.bin", + }, + { + type: "compiled-wasm", + name: "add.wasm", + content: WASM_ADD_MODULE, + filePath: "/virtual/add.wasm", + }, + ], + id: 0, + path: "/virtual/index.js", + entry: { + file: "index.js", + directory: "/virtual/", + format: "service-worker", + moduleRoot: "/virtual", + name: undefined, }, - ], - id: 0, - path: "/virtual/index.js", - entry: { - file: "index.js", - directory: "/virtual/", - format: "service-worker", - moduleRoot: "/virtual", - name: undefined, - }, - dependencies: {}, - sourceMapPath: undefined, - sourceMapMetadata: undefined, - }; - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, - }); - const event = await waitForReloadComplete(controller); - const url = urlFromParts(event.proxyData.userWorkerUrl); + dependencies: {}, + sourceMapPath: undefined, + sourceMapMetadata: undefined, + }; + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); + const event = await waitForReloadComplete(controller); + const url = urlFromParts(event.proxyData.userWorkerUrl); - // Check additional modules added to global scope - let res = await fetch(url); - expect(res.status).toBe(200); - expect(await res.json()).toEqual({ one: "one", two: "two", three: 3 }); + // Check additional modules added to global scope + let res = await fetch(url); + expect(res.status).toBe(200); + expect(await res.json()).toEqual({ one: "one", two: "two", three: 3 }); - // Check stack traces include file path - res = await fetch(new URL("/throw", url)); - expect(res.status).toBe(200); - if (isWindows) { - expect(await res.text()).toMatchInlineSnapshot(` + // Check stack traces include file path + res = await fetch(new URL("/throw", url)); + expect(res.status).toBe(200); + if (isWindows) { + expect(normalizeDrive(await res.text())).toMatchInlineSnapshot(` "Error: Oops! at file:///D:/virtual/index.js:12:15" `); - } else { - expect(await res.text()).toMatchInlineSnapshot(` + } else { + expect(normalizeDrive(await res.text())).toMatchInlineSnapshot(` "Error: Oops! at file:///virtual/index.js:12:15" `); - } - }); - it("should update the running Miniflare instance", async () => { - const controller = new LocalRuntimeController(); - teardown(() => controller.teardown()); + } + }); + it("should update the running Miniflare instance", async () => { + const controller = new LocalRuntimeController(); + teardown(() => controller.teardown()); - function update(version: number) { - const config = { - name: "worker", - entrypoint: "NOT_REAL", - bindings: { - VERSION: { type: "json", value: version }, - }, - } satisfies Partial; - const bundle = makeEsbuildBundle(dedent/*javascript*/ ` + function update(version: number) { + const config = { + name: "worker", + entrypoint: "NOT_REAL", + bindings: { + VERSION: { type: "json", value: version }, + }, + } satisfies Partial; + const bundle = makeEsbuildBundle(dedent/*javascript*/ ` export default { fetch(request, env, ctx) { return Response.json({ binding: env.VERSION, bundle: ${version} }); } } `); - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, - }); - } + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); + } - // Start worker - update(1); - let event = await waitForReloadComplete(controller); - let res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); - expect(await res.json()).toEqual({ binding: 1, bundle: 1 }); + // Start worker + update(1); + let event = await waitForReloadComplete(controller); + let res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); + expect(await res.json()).toEqual({ binding: 1, bundle: 1 }); - // Update worker and check config/bundle updated - update(2); - event = await waitForReloadComplete(controller); - res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); - expect(await res.json()).toEqual({ binding: 2, bundle: 2 }); + // Update worker and check config/bundle updated + update(2); + event = await waitForReloadComplete(controller); + res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); + expect(await res.json()).toEqual({ binding: 2, bundle: 2 }); - // Update worker multiple times and check only latest config/bundle used - const eventPromise = waitForReloadComplete(controller); - update(3); - update(4); - update(5); - event = await eventPromise; - res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); - expect(await res.json()).toEqual({ binding: 5, bundle: 5 }); - }); - it("should start Miniflare with configured compatibility settings", async () => { - const controller = new LocalRuntimeController(); - teardown(() => controller.teardown()); + // Update worker multiple times and check only latest config/bundle used + const eventPromise = waitForReloadComplete(controller); + update(3); + update(4); + update(5); + event = await eventPromise; + res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); + expect(await res.json()).toEqual({ binding: 5, bundle: 5 }); + }); + it("should start Miniflare with configured compatibility settings", async () => { + const controller = new LocalRuntimeController(); + teardown(() => controller.teardown()); - // `global_navigator` was enabled by default on `2022-03-21`: - // https://developers.cloudflare.com/workers/configuration/compatibility-dates/#global-navigator - const disabledDate = "2022-03-20"; - const enabledDate = "2022-03-21"; + // `global_navigator` was enabled by default on `2022-03-21`: + // https://developers.cloudflare.com/workers/configuration/compatibility-dates/#global-navigator + const disabledDate = "2022-03-20"; + const enabledDate = "2022-03-21"; - const config: Partial = { - name: "worker", - entrypoint: "NOT_REAL", - compatibilityDate: disabledDate, - }; - const bundle = makeEsbuildBundle(dedent/*javascript*/ ` + const config: Partial = { + name: "worker", + entrypoint: "NOT_REAL", + compatibilityDate: disabledDate, + }; + const bundle = makeEsbuildBundle(dedent/*javascript*/ ` export default { fetch(request, env, ctx) { return new Response(typeof navigator); } } `); - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, - }); - let event = await waitForReloadComplete(controller); - let res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); - expect(await res.text()).toBe("undefined"); + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); + let event = await waitForReloadComplete(controller); + let res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); + expect(await res.text()).toBe("undefined"); - // Check respects compatibility date - config.compatibilityDate = enabledDate; - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, - }); - event = await waitForReloadComplete(controller); - res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); - expect(await res.text()).toBe("object"); + // Check respects compatibility date + config.compatibilityDate = enabledDate; + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); + event = await waitForReloadComplete(controller); + res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); + expect(await res.text()).toBe("object"); - // Check respects compatibility flags - config.compatibilityDate = disabledDate; - config.compatibilityFlags = ["global_navigator"]; - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, + // Check respects compatibility flags + config.compatibilityDate = disabledDate; + config.compatibilityFlags = ["global_navigator"]; + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); + event = await waitForReloadComplete(controller); + res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); + expect(await res.text()).toBe("object"); }); - event = await waitForReloadComplete(controller); - res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); - expect(await res.text()).toBe("object"); - }); - it("should start inspector on random port and allow debugging", async () => { - const controller = new LocalRuntimeController(); - teardown(() => controller.teardown()); + it("should start inspector on random port and allow debugging", async () => { + const controller = new LocalRuntimeController(); + teardown(() => controller.teardown()); - const config: Partial = { - name: "worker", - entrypoint: "NOT_REAL", - }; - const bundle = makeEsbuildBundle(dedent/*javascript*/ ` + const config: Partial = { + name: "worker", + entrypoint: "NOT_REAL", + }; + const bundle = makeEsbuildBundle(dedent/*javascript*/ ` export default { fetch(request, env, ctx) { debugger; @@ -511,73 +516,75 @@ describe("Core", () => { } } `); - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, - }); - const event = await waitForReloadComplete(controller); - const url = urlFromParts(event.proxyData.userWorkerUrl); - const inspectorUrl = urlFromParts(event.proxyData.userWorkerInspectorUrl); - - // Connect inspector WebSocket - const ws = new WebSocket(inspectorUrl); - const messages = events.on(ws, "message"); - async function nextMessage() { - const messageEvent = (await messages.next()).value; - return JSON.parse(messageEvent[0].toString()); - } - - // Enable `Debugger` domain - await events.once(ws, "open"); - ws.send(JSON.stringify({ id: 0, method: "Debugger.enable" })); - if (isWindows) { - expect(await nextMessage()).toMatchObject({ - method: "Debugger.scriptParsed", - params: { url: "file:///D:/virtual/index.mjs" }, + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), }); - } else { - expect(await nextMessage()).toMatchObject({ - method: "Debugger.scriptParsed", - params: { url: "file:///virtual/index.mjs" }, + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, }); - } - expect(await nextMessage()).toMatchObject({ id: 0 }); + const event = await waitForReloadComplete(controller); + const url = urlFromParts(event.proxyData.userWorkerUrl); + const inspectorUrl = urlFromParts(event.proxyData.userWorkerInspectorUrl); - // Send request and hit `debugger;` statement - const resPromise = fetch(url); - expect(await nextMessage()).toMatchObject({ method: "Debugger.paused" }); + // Connect inspector WebSocket + const ws = new WebSocket(inspectorUrl); + const messages = events.on(ws, "message"); + async function nextMessage() { + const messageEvent = (await messages.next()).value; + return JSON.parse(messageEvent[0].toString()); + } - // Resume execution - ws.send(JSON.stringify({ id: 1, method: "Debugger.resume" })); - expect(await nextMessage()).toMatchObject({ id: 1 }); - const res = await resPromise; - expect(await res.text()).toBe("body"); + // Enable `Debugger` domain + await events.once(ws, "open"); + ws.send(JSON.stringify({ id: 0, method: "Debugger.enable" })); + if (isWindows) { + expect(await nextMessage()).toMatchObject({ + method: "Debugger.scriptParsed", + params: { + url: expect.stringMatching(/file:\/\/\/[A-Z]:\/virtual\/index.mjs/), + }, + }); + } else { + expect(await nextMessage()).toMatchObject({ + method: "Debugger.scriptParsed", + params: { url: "file:///virtual/index.mjs" }, + }); + } + expect(await nextMessage()).toMatchObject({ id: 0 }); + + // Send request and hit `debugger;` statement + const resPromise = fetch(url); + expect(await nextMessage()).toMatchObject({ method: "Debugger.paused" }); + + // Resume execution + ws.send(JSON.stringify({ id: 1, method: "Debugger.resume" })); + expect(await nextMessage()).toMatchObject({ id: 1 }); + const res = await resPromise; + expect(await res.text()).toBe("body"); + }); }); -}); -describe("Bindings", () => { - it("should expose basic bindings", async () => { - const controller = new LocalRuntimeController(); - teardown(() => controller.teardown()); + describe("Bindings", () => { + it("should expose basic bindings", async () => { + const controller = new LocalRuntimeController(); + teardown(() => controller.teardown()); - const config: Partial = { - name: "worker", - entrypoint: "NOT_REAL", - bindings: { - TEXT: { type: "plain_text", value: "text" }, - OBJECT: { type: "json", value: { a: { b: 1 } } }, - DATA: { - type: "data_blob", - source: { contents: new Uint8Array([1, 2, 3]) }, + const config: Partial = { + name: "worker", + entrypoint: "NOT_REAL", + bindings: { + TEXT: { type: "plain_text", value: "text" }, + OBJECT: { type: "json", value: { a: { b: 1 } } }, + DATA: { + type: "data_blob", + source: { contents: new Uint8Array([1, 2, 3]) }, + }, }, - }, - }; - const bundle = makeEsbuildBundle(dedent/*javascript*/ ` + }; + const bundle = makeEsbuildBundle(dedent/*javascript*/ ` export default { fetch(request, env, ctx) { const body = JSON.stringify(env, (key, value) => { @@ -590,72 +597,70 @@ describe("Bindings", () => { } } `); - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, - }); + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); - const event = await waitForReloadComplete(controller); - const res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); - expect(await res.json()).toEqual({ - TEXT: "text", - OBJECT: { a: { b: 1 } }, - DATA: { $type: "ArrayBuffer", value: [1, 2, 3] }, + const event = await waitForReloadComplete(controller); + const res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); + expect(await res.json()).toEqual({ + TEXT: "text", + OBJECT: { a: { b: 1 } }, + DATA: { $type: "ArrayBuffer", value: [1, 2, 3] }, + }); }); - }); - it("should expose WebAssembly module bindings in service workers", async () => { - const controller = new LocalRuntimeController(); - teardown(() => controller.teardown()); + it("should expose WebAssembly module bindings in service workers", async () => { + const controller = new LocalRuntimeController(); + teardown(() => controller.teardown()); - const config: Partial = { - name: "worker", - entrypoint: "NOT_REAL", - bindings: { - // `wasm-module` bindings aren't allowed in modules workers - WASM: { type: "wasm_module", source: { contents: WASM_ADD_MODULE } }, - }, - }; - const bundle: Bundle = makeEsbuildBundle({ - type: "service-worker", - serviceWorker: { - contents: `addEventListener("fetch", (event) => { + const config: Partial = { + name: "worker", + entrypoint: "NOT_REAL", + bindings: { + // `wasm-module` bindings aren't allowed in modules workers + WASM: { type: "wasm_module", source: { contents: WASM_ADD_MODULE } }, + }, + }; + const bundle: Bundle = makeEsbuildBundle({ + type: "service-worker", + serviceWorker: { + contents: `addEventListener("fetch", (event) => { const addInstance = new WebAssembly.Instance(WASM); event.respondWith(new Response(addInstance.exports.add(1, 2))); });`, - }, - }); - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, - }); + }, + }); + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); - const event = await waitForReloadComplete(controller); - const res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); - expect(await res.text()).toBe("3"); - }); - it("should persist cached data", async () => { - const tmp = useTmp(); - const persist = path.join(tmp, "persist"); - const controller = new LocalRuntimeController(); - teardown(() => controller.teardown()); + const event = await waitForReloadComplete(controller); + const res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); + expect(await res.text()).toBe("3"); + }); + it("should persist cached data", async () => { + const controller = new LocalRuntimeController(); + teardown(() => controller.teardown()); - const config: Partial = { - name: "worker", - entrypoint: "NOT_REAL", - dev: { persist }, - }; + const config: Partial = { + name: "worker", + entrypoint: "NOT_REAL", + dev: { persist: "./persist" }, + }; - const bundle = makeEsbuildBundle(`export default { + const bundle = makeEsbuildBundle(`export default { async fetch(request, env, ctx) { const key = "http://localhost/"; if (request.method === "POST") { @@ -668,132 +673,129 @@ describe("Bindings", () => { } }`); - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, - }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); - let event = await waitForReloadComplete(controller); - let res = await fetch(urlFromParts(event.proxyData.userWorkerUrl), { - method: "POST", - }); - expect(await res.text()).toBe("cached"); + let event = await waitForReloadComplete(controller); + let res = await fetch(urlFromParts(event.proxyData.userWorkerUrl), { + method: "POST", + }); + expect(await res.text()).toBe("cached"); - // Check restarting uses persisted data - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, - }); - event = await waitForReloadComplete(controller); - res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); - expect(await res.text()).toBe("cached"); + // Check restarting uses persisted data + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); + event = await waitForReloadComplete(controller); + res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); + expect(await res.text()).toBe("cached"); - // Check deleting persistence directory removes data - await controller.teardown(); - fs.rmSync(persist, { recursive: true }); - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, + // Check deleting persistence directory removes data + await controller.teardown(); + fs.rmSync("./persist", { recursive: true }); + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); + event = await waitForReloadComplete(controller); + res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); + expect(await res.text()).toBe("miss"); }); - event = await waitForReloadComplete(controller); - res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); - expect(await res.text()).toBe("miss"); - }); - it("should expose KV namespace bindings", async () => { - const tmp = useTmp(); - const persist = path.join(tmp, "persist"); - const controller = new LocalRuntimeController(); - teardown(() => controller.teardown()); + it("should expose KV namespace bindings", async () => { + const controller = new LocalRuntimeController(); + teardown(() => controller.teardown()); - const config: Partial = { - name: "worker", - entrypoint: "NOT_REAL", - bindings: { NAMESPACE: { type: "kv_namespace", id: "ns" } }, - dev: { persist }, - }; - const bundle = makeEsbuildBundle(`export default { + const config: Partial = { + name: "worker", + entrypoint: "NOT_REAL", + bindings: { NAMESPACE: { type: "kv_namespace", id: "ns" } }, + dev: { persist: "./persist" }, + }; + const bundle = makeEsbuildBundle(`export default { async fetch(request, env, ctx) { if (request.method === "POST") await env.NAMESPACE.put("key", "value"); return new Response(await env.NAMESPACE.get("key")); } }`); - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, - }); + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); - let event = await waitForReloadComplete(controller); - let res = await fetch(urlFromParts(event.proxyData.userWorkerUrl), { - method: "POST", - }); - expect(await res.text()).toBe("value"); + let event = await waitForReloadComplete(controller); + let res = await fetch(urlFromParts(event.proxyData.userWorkerUrl), { + method: "POST", + }); + expect(await res.text()).toBe("value"); - // Check restarting uses persisted data - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, - }); - event = await waitForReloadComplete(controller); - res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); - expect(await res.text()).toBe("value"); + // Check restarting uses persisted data + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); + event = await waitForReloadComplete(controller); + res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); + expect(await res.text()).toBe("value"); - // Check deleting persistence directory removes data - await controller.teardown(); - fs.rmSync(persist, { recursive: true }); - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, + // Check deleting persistence directory removes data + await controller.teardown(); + fs.rmSync("./persist", { recursive: true }); + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); + event = await waitForReloadComplete(controller); + res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); + expect(await res.text()).toBe(""); }); - event = await waitForReloadComplete(controller); - res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); - expect(await res.text()).toBe(""); - }); - it("should support Workers Sites bindings", async () => { - const tmp = useTmp(); - const controller = new LocalRuntimeController(); - teardown(() => controller.teardown()); + it("should support Workers Sites bindings", async () => { + const controller = new LocalRuntimeController(); + teardown(() => controller.teardown()); - fs.writeFileSync(path.join(tmp, "company.txt"), "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ"); - fs.writeFileSync(path.join(tmp, "charts.xlsx"), "๐Ÿ“Š"); - fs.writeFileSync(path.join(tmp, "secrets.txt"), "๐Ÿ”"); + fs.writeFileSync("company.txt", "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ"); + fs.writeFileSync("charts.xlsx", "๐Ÿ“Š"); + fs.writeFileSync("secrets.txt", "๐Ÿ”"); - let config: Partial = { - name: "worker", - entrypoint: "NOT_REAL", - legacy: { site: { bucket: tmp, include: ["*.txt"] } }, - }; - const bundle = makeEsbuildBundle(` + let config: Partial = { + name: "worker", + entrypoint: "NOT_REAL", + legacy: { site: { bucket: ".", include: ["*.txt"] } }, + }; + const bundle = makeEsbuildBundle(` import manifestJSON from "__STATIC_CONTENT_MANIFEST"; const manifest = JSON.parse(manifestJSON); export default { @@ -808,129 +810,125 @@ describe("Bindings", () => { } }`); - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, - }); - let event = await waitForReloadComplete(controller); - let url = urlFromParts(event.proxyData.userWorkerUrl); - let res = await fetch(new URL("/company.txt", url)); - expect(res.status).toBe(200); - expect(await res.text()).toBe("๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ"); - res = await fetch(new URL("/charts.xlsx", url)); - expect(res.status).toBe(404); - res = await fetch(new URL("/secrets.txt", url)); - expect(res.status).toBe(200); - config = { - ...config, - legacy: { - ...config.legacy, - site: { bucket: tmp, exclude: ["secrets.txt"] }, - }, - }; - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); + let event = await waitForReloadComplete(controller); + let url = urlFromParts(event.proxyData.userWorkerUrl); + let res = await fetch(new URL("/company.txt", url)); + expect(res.status).toBe(200); + expect(await res.text()).toBe("๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ"); + res = await fetch(new URL("/charts.xlsx", url)); + expect(res.status).toBe(404); + res = await fetch(new URL("/secrets.txt", url)); + expect(res.status).toBe(200); + config = { + ...config, + legacy: { + ...config.legacy, + site: { bucket: ".", exclude: ["secrets.txt"] }, + }, + }; + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); + event = await waitForReloadComplete(controller); + url = urlFromParts(event.proxyData.userWorkerUrl); + res = await fetch(new URL("/company.txt", url)); + expect(res.status).toBe(200); + res = await fetch(new URL("/charts.xlsx", url)); + expect(res.status).toBe(200); + res = await fetch(new URL("/secrets.txt", url)); + expect(res.status).toBe(404); }); - event = await waitForReloadComplete(controller); - url = urlFromParts(event.proxyData.userWorkerUrl); - res = await fetch(new URL("/company.txt", url)); - expect(res.status).toBe(200); - res = await fetch(new URL("/charts.xlsx", url)); - expect(res.status).toBe(200); - res = await fetch(new URL("/secrets.txt", url)); - expect(res.status).toBe(404); - }); - it("should expose R2 bucket bindings", async () => { - const tmp = useTmp(); - const persist = path.join(tmp, "persist"); - const controller = new LocalRuntimeController(); - teardown(() => controller.teardown()); + it("should expose R2 bucket bindings", async () => { + const controller = new LocalRuntimeController(); + teardown(() => controller.teardown()); - const config: Partial = { - name: "worker", - entrypoint: "NOT_REAL", - bindings: { BUCKET: { type: "r2_bucket", bucket_name: "bucket" } }, - dev: { persist }, - }; - const bundle = makeEsbuildBundle(`export default { + const config: Partial = { + name: "worker", + entrypoint: "NOT_REAL", + bindings: { BUCKET: { type: "r2_bucket", bucket_name: "bucket" } }, + dev: { persist: "./persist" }, + }; + const bundle = makeEsbuildBundle(`export default { async fetch(request, env, ctx) { if (request.method === "POST") await env.BUCKET.put("key", "value"); const object = await env.BUCKET.get("key"); return new Response(object?.body); } }`); - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, - }); + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); - let event = await waitForReloadComplete(controller); - let res = await fetch(urlFromParts(event.proxyData.userWorkerUrl), { - method: "POST", - }); - expect(await res.text()).toBe("value"); + let event = await waitForReloadComplete(controller); + let res = await fetch(urlFromParts(event.proxyData.userWorkerUrl), { + method: "POST", + }); + expect(await res.text()).toBe("value"); - // Check restarting uses persisted data - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, - }); - event = await waitForReloadComplete(controller); - res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); - expect(await res.text()).toBe("value"); + // Check restarting uses persisted data + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); + event = await waitForReloadComplete(controller); + res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); + expect(await res.text()).toBe("value"); - // Check deleting persistence directory removes data - await controller.teardown(); - fs.rmSync(persist, { recursive: true }); - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, + // Check deleting persistence directory removes data + await controller.teardown(); + fs.rmSync("./persist", { recursive: true }); + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); + event = await waitForReloadComplete(controller); + res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); + expect(await res.text()).toBe(""); }); - event = await waitForReloadComplete(controller); - res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); - expect(await res.text()).toBe(""); - }); - it("should expose D1 database bindings", async () => { - const tmp = useTmp(); - const persist = path.join(tmp, "persist"); - const controller = new LocalRuntimeController(); - teardown(() => controller.teardown()); + it("should expose D1 database bindings", async () => { + const controller = new LocalRuntimeController(); + teardown(() => controller.teardown()); - const config: Partial = { - name: "worker", - entrypoint: "NOT_REAL", - bindings: { - DB: { type: "d1", database_name: "db-name", database_id: "db" }, - }, - dev: { persist }, - }; - const bundle = makeEsbuildBundle(`export default { + const config: Partial = { + name: "worker", + entrypoint: "NOT_REAL", + bindings: { + DB: { type: "d1", database_name: "db-name", database_id: "db" }, + }, + dev: { persist: "./persist" }, + }; + const bundle = makeEsbuildBundle(`export default { async fetch(request, env, ctx) { await env.DB.exec("CREATE TABLE IF NOT EXISTS entries (key text PRIMARY KEY, value text)"); if (request.method === "POST") { @@ -940,78 +938,76 @@ describe("Bindings", () => { return Response.json(result.results); } }`); - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, - }); + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); - let event = await waitForReloadComplete(controller); - let res = await fetch(urlFromParts(event.proxyData.userWorkerUrl), { - method: "POST", - }); - expect(await res.json()).toEqual([{ key: "key", value: "value" }]); + let event = await waitForReloadComplete(controller); + let res = await fetch(urlFromParts(event.proxyData.userWorkerUrl), { + method: "POST", + }); + expect(await res.json()).toEqual([{ key: "key", value: "value" }]); - // Check restarting uses persisted data - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, - }); - event = await waitForReloadComplete(controller); - res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); - expect(await res.json()).toEqual([{ key: "key", value: "value" }]); + // Check restarting uses persisted data + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); + event = await waitForReloadComplete(controller); + res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); + expect(await res.json()).toEqual([{ key: "key", value: "value" }]); - // Check deleting persistence directory removes data - await controller.teardown(); - fs.rmSync(persist, { recursive: true }); - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, + // Check deleting persistence directory removes data + await controller.teardown(); + fs.rmSync("./persist", { recursive: true }); + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); + event = await waitForReloadComplete(controller); + res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); + expect(await res.json()).toEqual([]); }); - event = await waitForReloadComplete(controller); - res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); - expect(await res.json()).toEqual([]); - }); - it("should expose queue producer bindings and consume queue messages", async () => { - const tmp = useTmp(); - const persist = path.join(tmp, "persist"); - const controller = new LocalRuntimeController(); - teardown(() => controller.teardown()); + it("should expose queue producer bindings and consume queue messages", async () => { + const controller = new LocalRuntimeController(); + teardown(() => controller.teardown()); - const reportPromise = new DeferredPromise(); - const config: Partial = { - name: "worker", - entrypoint: "NOT_REAL", - bindings: { - QUEUE: { type: "queue", queue_name: "queue" }, - BATCH_REPORT: { - type: "fetcher", - async fetcher(request) { - reportPromise.resolve(await request.json()); - return new Response(null, { status: 204 }); + const reportPromise = new DeferredPromise(); + const config: Partial = { + name: "worker", + entrypoint: "NOT_REAL", + bindings: { + QUEUE: { type: "queue", queue_name: "queue" }, + BATCH_REPORT: { + type: "fetcher", + async fetcher(request) { + reportPromise.resolve(await request.json()); + return new Response(null, { status: 204 }); + }, }, }, - }, - triggers: [ - { type: "queue-consumer", queue: "queue", max_batch_timeout: 0 }, - ], - dev: { persist }, - }; - const bundle = makeEsbuildBundle(`export default { + triggers: [ + { type: "queue-consumer", queue: "queue", max_batch_timeout: 0 }, + ], + dev: { persist: "./persist" }, + }; + const bundle = makeEsbuildBundle(`export default { async fetch(request, env, ctx) { await env.QUEUE.send("message"); return new Response(null, { status: 204 }); @@ -1023,45 +1019,47 @@ describe("Bindings", () => { }); } }`); - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, - }); + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); - const event = await waitForReloadComplete(controller); - const res = await fetch(urlFromParts(event.proxyData.userWorkerUrl), { - method: "POST", + const event = await waitForReloadComplete(controller); + const res = await fetch(urlFromParts(event.proxyData.userWorkerUrl), { + method: "POST", + }); + expect(res.status).toBe(204); + expect(await reportPromise).toEqual(["message"]); }); - expect(res.status).toBe(204); - expect(await reportPromise).toEqual(["message"]); - }); - it("should expose hyperdrive bindings", async () => { - // Start echo TCP server - const server = net.createServer((socket) => socket.pipe(socket)); - const listeningPromise = events.once(server, "listening"); - server.listen(0, "127.0.0.1"); - teardown(() => util.promisify(server.close.bind(server))()); - await listeningPromise; - const address = server.address(); - assert(typeof address === "object" && address !== null); - const port = address.port; + it("should expose hyperdrive bindings", async () => { + // Start echo TCP server + const server = net.createServer((socket) => socket.pipe(socket)); + const listeningPromise = events.once(server, "listening"); + server.listen(0, "127.0.0.1"); + teardown(() => util.promisify(server.close.bind(server))()); + await listeningPromise; + const address = server.address(); + assert(typeof address === "object" && address !== null); + const port = address.port; - // Start runtime with hyperdrive binding - const controller = new LocalRuntimeController(); - teardown(() => controller.teardown()); + // Start runtime with hyperdrive binding + const controller = new LocalRuntimeController(); + teardown(() => controller.teardown()); - const localConnectionString = `postgres://username:password@127.0.0.1:${port}/db`; - const config: Partial = { - name: "worker", - entrypoint: "NOT_REAL", - bindings: { DB: { type: "hyperdrive", id: "db", localConnectionString } }, - }; - const bundle = makeEsbuildBundle(`export default { + const localConnectionString = `postgres://username:password@127.0.0.1:${port}/db`; + const config: Partial = { + name: "worker", + entrypoint: "NOT_REAL", + bindings: { + DB: { type: "hyperdrive", id: "db", localConnectionString }, + }, + }; + const bundle = makeEsbuildBundle(`export default { async fetch(request, env, ctx) { const socket = env.DB.connect(); const writer = socket.writable.getWriter(); @@ -1070,19 +1068,24 @@ describe("Bindings", () => { return new Response(socket.readable); } }`); - controller.onBundleStart({ - type: "bundleStart", - config: configDefaults(config), - }); - controller.onBundleComplete({ - type: "bundleComplete", - config: configDefaults(config), - bundle, - }); + controller.onBundleStart({ + type: "bundleStart", + config: configDefaults(config), + }); + controller.onBundleComplete({ + type: "bundleComplete", + config: configDefaults(config), + bundle, + }); - const event = await waitForReloadComplete(controller); - const res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); - expect(res.status).toBe(200); - expect(await res.text()).toBe("๐Ÿ‘‹"); + const event = await waitForReloadComplete(controller); + const res = await fetch(urlFromParts(event.proxyData.userWorkerUrl)); + expect(res.status).toBe(200); + expect(await res.text()).toBe("๐Ÿ‘‹"); + }); }); }); + +function normalizeDrive(p: string): string { + return p.replaceAll(/file:\/\/\/[A-Z]:/g, "file:///D:"); +} diff --git a/packages/wrangler/src/__tests__/helpers/teardown.ts b/packages/wrangler/src/__tests__/helpers/teardown.ts index fd8d4542a0cd..e35fd2e116dc 100644 --- a/packages/wrangler/src/__tests__/helpers/teardown.ts +++ b/packages/wrangler/src/__tests__/helpers/teardown.ts @@ -1,39 +1,31 @@ -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; import { afterEach } from "vitest"; -const teardownCallbacks: (() => void | Promise)[] = []; -// TODO: Switch to vitest.onTestFinished() -export function teardown(callback: () => void | Promise) { - // `unshift()` so teardown callbacks executed in reverse - teardownCallbacks.unshift(callback); -} - -afterEach(async () => { - const errors: unknown[] = []; - for (const callback of teardownCallbacks.splice(0)) { - try { - await callback(); - } catch (error) { - errors.push(error); - } +export function useTeardown(): typeof teardown { + const teardownCallbacks: (() => void | Promise)[] = []; + // TODO: Switch to vitest.onTestFinished() + // We can't really use `onTestFinished()` because it always runs the callback after all the `afterEach()` blocks have run. + // And so, for example, all the spies have been removed by the time it is called. + // This leads to unwanted console logs in tests for example. + function teardown(callback: () => void | Promise) { + // `unshift()` so teardown callbacks executed in reverse + teardownCallbacks.unshift(callback); } - if (errors.length > 0) { - throw new AggregateError( - errors, - ["Unable to teardown:", ...errors.map(String)].join("\n") - ); - } -}); -export function useTmp() { - const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "wrangler-vitest-")); - teardown(() => { - try { - // This sometimes fails with EBUSY on Windows - fs.rmSync(tmp, { recursive: true, force: true }); - } catch {} + afterEach(async () => { + const errors: unknown[] = []; + for (const callback of teardownCallbacks.splice(0)) { + try { + await callback(); + } catch (error) { + errors.push(error); + } + } + if (errors.length > 0) { + throw new AggregateError( + errors, + ["Unable to teardown:", ...errors.map(String)].join("\n") + ); + } }); - return tmp; + return teardown; } diff --git a/packages/wrangler/src/__tests__/rollback.test.ts b/packages/wrangler/src/__tests__/rollback.test.ts index 0933042044f5..f3702c098770 100644 --- a/packages/wrangler/src/__tests__/rollback.test.ts +++ b/packages/wrangler/src/__tests__/rollback.test.ts @@ -3,6 +3,7 @@ import { describe, expect, test } from "vitest"; import { CANNOT_ROLLBACK_WITH_MODIFIED_SECERT_CODE } from "../versions/rollback"; import { collectCLIOutput } from "./helpers/collect-cli-output"; import { mockAccountId, mockApiToken } from "./helpers/mock-account-id"; +import { mockConsoleMethods } from "./helpers/mock-console"; import { mockConfirm, mockPrompt } from "./helpers/mock-dialogs"; import { useMockIsTTY } from "./helpers/mock-istty"; import { createFetchResult, msw } from "./helpers/msw"; @@ -10,6 +11,7 @@ import { runWrangler } from "./helpers/run-wrangler"; import type { ApiDeployment } from "../versions/types"; describe("rollback", () => { + mockConsoleMethods(); const std = collectCLIOutput(); const { setIsTTY } = useMockIsTTY(); mockAccountId();