diff options
author | 2023-03-06 22:09:44 +0200 | |
---|---|---|
committer | 2023-03-06 12:09:44 -0800 | |
commit | 3e1d368a27529c5abe8aa4b96f4e2fc2c86fc3e5 (patch) | |
tree | 3199030c1ebba6c7cc7a6843286dda95bb6ccf86 | |
parent | 993fed51c113d4a502f23ef5fc39f82f35144d05 (diff) | |
download | bun-3e1d368a27529c5abe8aa4b96f4e2fc2c86fc3e5.tar.gz bun-3e1d368a27529c5abe8aa4b96f4e2fc2c86fc3e5.tar.zst bun-3e1d368a27529c5abe8aa4b96f4e2fc2c86fc3e5.zip |
fix & clean up tests (#2318)
- skip flaky tests when running as `root`
- use `expect().toThrow()`
- clean up temporary files after tests
-rw-r--r-- | packages/bun-types/globals.d.ts | 5 | ||||
-rw-r--r-- | test/bun.js/fetch.test.ts (renamed from test/bun.js/fetch.test.js) | 276 |
2 files changed, 133 insertions, 148 deletions
diff --git a/packages/bun-types/globals.d.ts b/packages/bun-types/globals.d.ts index e44bcf43d..2dd86f911 100644 --- a/packages/bun-types/globals.d.ts +++ b/packages/bun-types/globals.d.ts @@ -447,6 +447,7 @@ interface Headers { entries(): IterableIterator<[string, string]>; keys(): IterableIterator<string>; values(): IterableIterator<string>; + [Symbol.iterator](): IterableIterator<[string, string]>; forEach( callbackfn: (value: string, key: string, parent: Headers) => void, thisArg?: any, @@ -533,7 +534,7 @@ interface FormData { has(name: string): boolean; set(name: string, value: string | Blob, fileName?: string): void; keys(): IterableIterator<string>; - values(): IterableIterator<string>; + values(): IterableIterator<FormDataEntryValue>; entries(): IterableIterator<[string, FormDataEntryValue]>; [Symbol.iterator](): IterableIterator<[string, FormDataEntryValue]>; forEach( @@ -1494,7 +1495,7 @@ interface AbortController { /** * Invoking this method will set this object's AbortSignal's aborted flag and signal to any observers that the associated activity is to be aborted. */ - abort(): void; + abort(reason?: any): void; } /** EventTarget is a DOM interface implemented by objects that can receive events and may have listeners for them. */ diff --git a/test/bun.js/fetch.test.js b/test/bun.js/fetch.test.ts index be64a0109..f5f264dd7 100644 --- a/test/bun.js/fetch.test.js +++ b/test/bun.js/fetch.test.ts @@ -1,36 +1,38 @@ -import { afterAll, afterEach, beforeAll, describe, expect, it, test, beforeEach } from "bun:test"; -import fs, { chmodSync, unlinkSync } from "fs"; +import { serve, sleep } from "bun"; +import { afterAll, afterEach, beforeAll, describe, expect, it, beforeEach } from "bun:test"; +import { chmodSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "fs"; import { mkfifo } from "mkfifo"; +import { tmpdir } from "os"; +import { join } from "path"; import { gc, withoutAggressiveGC } from "./gc"; -const sleep = countdown => { - return Bun.sleep(countdown); -}; +const tmp_dir = mkdtempSync(join(realpathSync(tmpdir()), "fetch.test")); -const exampleFixture = fs.readFileSync( - import.meta.path.substring(0, import.meta.path.lastIndexOf("/")) + "/fetch.js.txt", - "utf8", -); +const fixture = readFileSync(join(import.meta.dir, "fetch.js.txt"), "utf8"); -var cachedServer; -function getServer({ port, ...rest }) { - return (cachedServer = Bun.serve({ - ...rest, +let server; +function startServer({ fetch, ...options }) { + server = serve({ + ...options, + fetch, port: 0, - })); + }); } afterEach(() => { - cachedServer?.stop?.(true); + server?.stop?.(true); +}); + +afterAll(() => { + rmSync(tmp_dir, { force: true, recursive: true }); }); const payload = new Uint8Array(1024 * 1024 * 2); crypto.getRandomValues(payload); describe("AbortSignal", () => { - var server; beforeEach(() => { - server = getServer({ + startServer({ async fetch(request) { if (request.url.endsWith("/nodelay")) { return new Response("Hello"); @@ -61,12 +63,15 @@ describe("AbortSignal", () => { }, }); }); + afterEach(() => { + server?.stop?.(true); + }); + it("AbortError", async () => { - let name; - try { - var controller = new AbortController(); - const signal = controller.signal; + const controller = new AbortController(); + const signal = controller.signal; + expect(async () => { async function manualAbort() { await sleep(1); controller.abort(); @@ -75,56 +80,47 @@ describe("AbortSignal", () => { fetch(`http://127.0.0.1:${server.port}`, { signal: signal }).then(res => res.text()), manualAbort(), ]); - } catch (error) { - name = error.name; - } - expect(name).toBe("AbortError"); + }).toThrow(new DOMException("The operation was aborted.")); }); it("AbortAfterFinish", async () => { - let error = undefined; - try { - var controller = new AbortController(); - const signal = controller.signal; + const controller = new AbortController(); + const signal = controller.signal; - await fetch(`http://127.0.0.1:${server.port}/nodelay`, { signal: signal }).then(res => res.text()); - controller.abort(); - } catch (ex) { - error = ex; - } - expect(error).toBeUndefined(); + await fetch(`http://127.0.0.1:${server.port}/nodelay`, { signal: signal }).then(async res => + expect(await res.text()).toBe("Hello"), + ); + controller.abort(); }); it("AbortErrorWithReason", async () => { - let reason; - try { - var controller = new AbortController(); - const signal = controller.signal; + const controller = new AbortController(); + const signal = controller.signal; + expect(async () => { async function manualAbort() { await sleep(10); - controller.abort("My Reason"); + controller.abort(new Error("My Reason")); } await Promise.all([ fetch(`http://127.0.0.1:${server.port}`, { signal: signal }).then(res => res.text()), manualAbort(), ]); - } catch (error) { - reason = error; - } - expect(reason).toBe("My Reason"); + }).toThrow("My Reason"); }); it("AbortErrorEventListener", async () => { - let name; - try { - var controller = new AbortController(); - const signal = controller.signal; - var eventSignal = undefined; - signal.addEventListener("abort", ev => { - eventSignal = ev.currentTarget; - }); + const controller = new AbortController(); + const signal = controller.signal; + signal.addEventListener("abort", ev => { + const target = ev.currentTarget; + expect(target).toBeDefined(); + expect(target.aborted).toBe(true); + expect(target.reason).toBeDefined(); + expect(target.reason.name).toBe("AbortError"); + }); + expect(async () => { async function manualAbort() { await sleep(10); controller.abort(); @@ -133,69 +129,59 @@ describe("AbortSignal", () => { fetch(`http://127.0.0.1:${server.port}`, { signal: signal }).then(res => res.text()), manualAbort(), ]); - } catch (error) { - name = error.name; - } - expect(eventSignal).toBeDefined(); - expect(eventSignal.reason.name).toBe(name); - expect(eventSignal.aborted).toBe(true); + }).toThrow(new DOMException("The operation was aborted.")); }); it("AbortErrorWhileUploading", async () => { - const abortController = new AbortController(); - let error; - try { + const controller = new AbortController(); + + expect(async () => { await fetch(`http://localhost:${server.port}`, { method: "POST", body: new ReadableStream({ - pull(controller) { - controller.enqueue(new Uint8Array([1, 2, 3, 4])); + pull(event_controller) { + event_controller.enqueue(new Uint8Array([1, 2, 3, 4])); //this will abort immediately should abort before connected - abortController.abort(); + controller.abort(); }, }), - signal: abortController.signal, + signal: controller.signal, }); - } catch (ex) { - error = ex; - } - - expect(error.name).toBe("AbortError"); - expect(error.message).toBe("The operation was aborted."); - expect(error instanceof DOMException).toBeTruthy(); + }).toThrow(new DOMException("The operation was aborted.")); }); it("TimeoutError", async () => { - let name; + const signal = AbortSignal.timeout(10); + try { - const signal = AbortSignal.timeout(10); await fetch(`http://127.0.0.1:${server.port}`, { signal: signal }).then(res => res.text()); - } catch (error) { - name = error.name; + expect(() => {}).toThrow(); + } catch (ex: any) { + expect(ex.name).toBe("TimeoutError"); } - expect(name).toBe("TimeoutError"); }); + it("Request", async () => { - let name; + const controller = new AbortController(); + const signal = controller.signal; + async function manualAbort() { + await sleep(10); + controller.abort(); + } + try { - var controller = new AbortController(); - const signal = controller.signal; const request = new Request(`http://127.0.0.1:${server.port}`, { signal }); - async function manualAbort() { - await sleep(10); - controller.abort(); - } await Promise.all([fetch(request).then(res => res.text()), manualAbort()]); - } catch (error) { - name = error.name; + expect(() => {}).toThrow(); + } catch (ex: any) { + expect(ex.name).toBe("AbortError"); } - expect(name).toBe("AbortError"); }); }); describe("Headers", () => { it(".toJSON", () => { - var headers = new Headers({ + const headers = new Headers({ "content-length": "123", "content-type": "text/plain", "x-another-custom-header": "Hello World", @@ -207,7 +193,7 @@ describe("Headers", () => { }); it(".getSetCookie() with object", () => { - var headers = new Headers({ + const headers = new Headers({ "content-length": "123", "content-type": "text/plain", "x-another-custom-header": "Hello World", @@ -219,7 +205,7 @@ describe("Headers", () => { }); it(".getSetCookie() with array", () => { - var headers = new Headers([ + const headers = new Headers([ ["content-length", "123"], ["content-type", "text/plain"], ["x-another-custom-header", "Hello World"], @@ -298,13 +284,15 @@ describe("fetch", () => { ]; for (let url of urls) { gc(); - let name = url; - if (name instanceof URL) { - name = "URL: " + name; - } else if (name instanceof Request) { - name = "Request: " + name.url; - } else if (name.hasOwnProperty("toString")) { - name = "Object: " + name.toString(); + let name; + if (url instanceof URL) { + name = "URL: " + url; + } else if (url instanceof Request) { + name = "Request: " + url.url; + } else if (url.hasOwnProperty("toString")) { + name = "Object: " + url.toString(); + } else { + name = url; } it(name, async () => { gc(); @@ -312,12 +300,12 @@ describe("fetch", () => { gc(); const text = await response.text(); gc(); - expect(exampleFixture).toBe(text); + expect(fixture).toBe(text); }); } - it(`"redirect: "manual"`, async () => { - const server = getServer({ + it('redirect: "manual"', async () => { + startServer({ fetch(req) { return new Response(null, { status: 302, @@ -335,8 +323,8 @@ describe("fetch", () => { expect(response.redirected).toBe(true); }); - it(`"redirect: "follow"`, async () => { - const server = getServer({ + it('redirect: "follow"', async () => { + startServer({ fetch(req) { return new Response(null, { status: 302, @@ -355,10 +343,11 @@ describe("fetch", () => { }); it("provide body", async () => { - const server = getServer({ + startServer({ fetch(req) { return new Response(req.body); }, + host: "localhost", }); // POST with body @@ -366,15 +355,16 @@ describe("fetch", () => { const response = await fetch(url, { method: "POST", body: "buntastic" }); expect(response.status).toBe(200); expect(await response.text()).toBe("buntastic"); - - // GET cannot have body - try { - await fetch(url, { body: "buntastic" }); - expect(false).toBe(true); - } catch (exception) { - expect(exception.name).toBe("TypeError"); - } }); + + ["GET", "HEAD", "OPTIONS"].forEach(method => + it(`fail on ${method} with body`, async () => { + const url = `http://${server.hostname}:${server.port}`; + expect(async () => { + await fetch(url, { body: "buntastic" }); + }).toThrow("fetch() request with GET/HEAD/OPTIONS method cannot have body."); + }), + ); }); it("simultaneous HTTPS fetch", async () => { @@ -388,7 +378,7 @@ it("simultaneous HTTPS fetch", async () => { expect(result.length).toBe(20); for (let i = 0; i < 20; i++) { expect(result[i].status).toBe(200); - expect(await result[i].text()).toBe(exampleFixture); + expect(await result[i].text()).toBe(fixture); } } }); @@ -398,7 +388,7 @@ it("website with tlsextname", async () => { await fetch("https://bun.sh", { method: "HEAD" }); }); -function testBlobInterface(blobbyConstructor, hasBlobFn) { +function testBlobInterface(blobbyConstructor, hasBlobFn?) { for (let withGC of [false, true]) { for (let jsonObject of [ { hello: true }, @@ -548,65 +538,59 @@ function testBlobInterface(blobbyConstructor, hasBlobFn) { } describe("Bun.file", () => { - const tempdir = require("os").tmpdir(); - const { join } = require("path"); - var callCount = 0; + let count = 0; testBlobInterface(data => { const blob = new Blob([data]); const buffer = Bun.peek(blob.arrayBuffer()); - const path = join(tempdir, "tmp-" + callCount++ + ".bytes"); - require("fs").writeFileSync(path, buffer); + const path = join(tmp_dir, `tmp-${count++}.bytes`); + writeFileSync(path, buffer); const file = Bun.file(path); expect(blob.size).toBe(file.size); return file; }); it("size is Infinity on a fifo", () => { - try { - unlinkSync("/tmp/test-fifo"); - } catch (e) {} - mkfifo("/tmp/test-fifo"); - - const { size } = Bun.file("/tmp/test-fifo"); + const path = join(tmp_dir, "test-fifo"); + mkfifo(path); + const { size } = Bun.file(path); expect(size).toBe(Infinity); }); - function forEachMethod(fn) { + function forEachMethod(fn, skip?) { const method = ["arrayBuffer", "text", "json"]; for (const m of method) { - test(m, fn(m)); + (skip ? it.skip : it)(m, fn(m)); } } describe("bad permissions throws", () => { + const path = join(tmp_dir, "my-new-file"); beforeAll(async () => { - try { - unlinkSync("/tmp/my-new-file"); - } catch {} - await Bun.write("/tmp/my-new-file", "hey"); - chmodSync("/tmp/my-new-file", 0o000); - }); - afterAll(() => { - try { - unlinkSync("/tmp/my-new-file"); - } catch {} + await Bun.write(path, "hey"); + chmodSync(path, 0o000); }); - forEachMethod(m => () => { - const file = Bun.file("/tmp/my-new-file"); - expect(async () => await file[m]()).toThrow("Permission denied"); - }); + forEachMethod( + m => () => { + const file = Bun.file(path); + expect(async () => await file[m]()).toThrow("Permission denied"); + }, + () => { + try { + readFileSync(path); + } catch { + return false; + } + return true; + }, + ); }); describe("non-existent file throws", () => { - beforeAll(() => { - try { - unlinkSync("/tmp/does-not-exist"); - } catch {} - }); + const path = join(tmp_dir, "does-not-exist"); forEachMethod(m => async () => { - const file = Bun.file("/tmp/does-not-exist"); + const file = Bun.file(path); expect(async () => await file[m]()).toThrow("No such file or directory"); }); }); |