aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Ashcon Partovi <ashcon@partovi.net> 2023-03-07 17:02:34 -0800
committerGravatar Ashcon Partovi <ashcon@partovi.net> 2023-03-07 17:02:43 -0800
commit1d2b0bbc98a388cdb7d3655a56333cac09c5a280 (patch)
tree9dff51f1ac32662b3ea25f911e65255043d1b3ed
parent45ddf321b187ab1597e9e086655f71f1b36af4cb (diff)
downloadbun-1d2b0bbc98a388cdb7d3655a56333cac09c5a280.tar.gz
bun-1d2b0bbc98a388cdb7d3655a56333cac09c5a280.tar.zst
bun-1d2b0bbc98a388cdb7d3655a56333cac09c5a280.zip
Add more test harness
-rw-r--r--test/js/deno/abort/abort-controller.test.ts66
-rw-r--r--test/js/deno/harness.ts226
-rw-r--r--test/js/deno/html/blob.test.ts117
-rw-r--r--test/js/deno/resources/imports.json4
-rw-r--r--test/js/deno/resources/tests.json10
-rw-r--r--test/js/deno/scripts/postinstall.ts31
-rw-r--r--test/tsconfig.json4
7 files changed, 457 insertions, 1 deletions
diff --git a/test/js/deno/abort/abort-controller.test.ts b/test/js/deno/abort/abort-controller.test.ts
new file mode 100644
index 000000000..ba386d021
--- /dev/null
+++ b/test/js/deno/abort/abort-controller.test.ts
@@ -0,0 +1,66 @@
+// Updated: Wed, 08 Mar 2023 00:55:15 GMT
+// URL: https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/abort_controller_test.ts
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+import { assert, assertEquals } from "deno:harness";
+
+Deno.test(function basicAbortController() {
+ const controller = new AbortController();
+ assert(controller);
+ const { signal } = controller;
+ assert(signal);
+ assertEquals(signal.aborted, false);
+ controller.abort();
+ assertEquals(signal.aborted, true);
+});
+
+Deno.test(function signalCallsOnabort() {
+ const controller = new AbortController();
+ const { signal } = controller;
+ let called = false;
+ signal.onabort = (evt) => {
+ assert(evt);
+ assertEquals(evt.type, "abort");
+ called = true;
+ };
+ controller.abort();
+ assert(called);
+});
+
+Deno.test(function signalEventListener() {
+ const controller = new AbortController();
+ const { signal } = controller;
+ let called = false;
+ signal.addEventListener("abort", function (ev) {
+ assert(this === signal);
+ assertEquals(ev.type, "abort");
+ called = true;
+ });
+ controller.abort();
+ assert(called);
+});
+
+Deno.test(function onlyAbortsOnce() {
+ const controller = new AbortController();
+ const { signal } = controller;
+ let called = 0;
+ signal.addEventListener("abort", () => called++);
+ signal.onabort = () => {
+ called++;
+ };
+ controller.abort();
+ assertEquals(called, 2);
+ controller.abort();
+ assertEquals(called, 2);
+});
+
+Deno.test(function controllerHasProperToString() {
+ const actual = Object.prototype.toString.call(new AbortController());
+ assertEquals(actual, "[object AbortController]");
+});
+
+Deno.test(function abortReason() {
+ const signal = AbortSignal.abort("hey!");
+ assertEquals(signal.aborted, true);
+ assertEquals(signal.reason, "hey!");
+});
diff --git a/test/js/deno/harness.ts b/test/js/deno/harness.ts
new file mode 100644
index 000000000..167860d1f
--- /dev/null
+++ b/test/js/deno/harness.ts
@@ -0,0 +1,226 @@
+// Deno's test utilities implemented using expect().
+// https://github.com/denoland/deno/blob/main/cli/tests/unit/test_util.ts
+
+import { concatArrayBuffers } from "bun";
+import { it, expect } from "bun:test";
+
+export function test(fn: () => void): void {
+ it(fn.name, fn);
+}
+
+export function assert(condition: unknown, message?: string): asserts condition is true {
+ if (message) {
+ it(message, () => assert(condition));
+ } else {
+ expect(condition).toBeTruthy();
+ }
+}
+
+export function assertFalse(condition: unknown, message?: string): asserts condition is false {
+ if (message) {
+ it(message, () => assertFalse(condition));
+ } else {
+ expect(condition).toBeFalsy();
+ }
+}
+
+export function assertEquals(actual: unknown, expected: unknown, message?: string): void {
+ if (message) {
+ it(message, () => assertEquals(actual, expected));
+ } else {
+ expect(actual).toEqual(expected);
+ }
+}
+
+export function assertExists(value: unknown, message?: string): void {
+ if (message) {
+ it(message, () => assertExists(value));
+ } else {
+ expect(value).toBeDefined();
+ }
+}
+
+export function assertNotEquals(actual: unknown, expected: unknown, message?: string): void {
+ if (message) {
+ it(message, () => assertNotEquals(actual, expected));
+ } else {
+ expect(actual).not.toEqual(expected);
+ }
+}
+
+export function assertStrictEquals(actual: unknown, expected: unknown, message?: string): void {
+ if (message) {
+ it(message, () => assertStrictEquals(actual, expected));
+ } else {
+ expect(actual).toStrictEqual(expected);
+ }
+}
+
+export function assertNotStrictEquals(actual: unknown, expected: unknown, message?: string): void {
+ if (message) {
+ it(message, () => assertNotStrictEquals(actual, expected));
+ } else {
+ expect(actual).not.toStrictEqual(expected);
+ }
+}
+
+export function assertAlmostEquals(actual: unknown, expected: number, epsilon: number = 1e-7, message?: string): void {
+ if (message) {
+ it(message, () => assertAlmostEquals(actual, expected));
+ } else if (typeof actual === "number") {
+ // TODO: toBeCloseTo()
+ expect(Math.abs(actual - expected)).toBeLessThanOrEqual(epsilon);
+ } else {
+ expect(typeof actual).toBe("number");
+ }
+}
+
+export function assertInstanceOf(actual: unknown, expected: unknown, message?: string): void {
+ if (message) {
+ it(message, () => assertInstanceOf(actual, expected));
+ } else if (typeof actual === "object") {
+ if (actual !== null) {
+ expect(actual).toHaveProperty("constructor", expected);
+ } else {
+ expect(actual).not.toBeNull();
+ }
+ } else {
+ expect(typeof actual).toBe("object");
+ }
+}
+
+export function assertNotInstanceOf(actual: unknown, expected: unknown, message?: string): void {
+ if (message) {
+ it(message, () => assertNotInstanceOf(actual, expected));
+ } else if (typeof actual === "object") {
+ if (actual !== null) {
+ expect(actual).not.toHaveProperty("constructor", expected);
+ } else {
+ expect(actual).not.toBeNull();
+ }
+ } else {
+ expect(typeof actual).toBe("object");
+ }
+}
+
+export function assertStringIncludes(actual: unknown, expected: string, message?: string): void {
+ if (message) {
+ it(message, () => assertStringIncludes(actual, expected));
+ } else if (typeof actual === "string") {
+ expect(actual).toContain(expected);
+ } else {
+ expect(typeof actual).toBe("string");
+ }
+}
+
+export function assertArrayIncludes(actual: unknown, expected: unknown[], message?: string): void {
+ if (message) {
+ it(message, () => assertArrayIncludes(actual, expected));
+ } else if (Array.isArray(actual)) {
+ for (const value of expected) {
+ expect(actual).toContain(value);
+ }
+ } else {
+ expect(Array.isArray(actual)).toBe(true);
+ }
+}
+
+export function assertMatch(actual: unknown, expected: RegExp, message?: string): void {
+ if (message) {
+ it(message, () => assertMatch(actual, expected));
+ } else if (typeof actual === "string") {
+ expect(expected.test(actual)).toBe(true);
+ } else {
+ expect(typeof actual).toBe("string");
+ }
+}
+
+export function assertNotMatch(actual: unknown, expected: RegExp, message?: string): void {
+ if (message) {
+ it(message, () => assertNotMatch(actual, expected));
+ } else if (typeof actual === "string") {
+ expect(expected.test(actual)).toBe(false);
+ } else {
+ expect(typeof actual).toBe("string");
+ }
+}
+
+export function assertObjectMatch(actual: unknown, expected: Record<PropertyKey, unknown>, message?: string): void {
+ if (message) {
+ it(message, () => assertObjectMatch(actual, expected));
+ } else if (typeof actual === "object") {
+ // TODO: toMatchObject()
+ if (actual !== null) {
+ const expectedKeys = Object.keys(expected);
+ for (const key of Object.keys(actual)) {
+ if (!expectedKeys.includes(key)) {
+ // @ts-ignore
+ delete actual[key];
+ }
+ }
+ expect(actual).toEqual(expected);
+ } else {
+ expect(actual).not.toBeNull();
+ }
+ } else {
+ expect(typeof actual).toBe("object");
+ }
+}
+
+export function assertThrows(fn: () => void, message?: string): void {
+ if (message) {
+ it(message, () => assertThrows(fn));
+ } else {
+ try {
+ fn();
+ } catch (error) {
+ expect(error).toBeDefined();
+ return;
+ }
+ throw new Error("Expected an error to be thrown");
+ }
+}
+
+export async function assertRejects(fn: () => Promise<unknown>, message?: string): Promise<void> {
+ if (message) {
+ it(message, () => assertRejects(fn));
+ } else {
+ try {
+ await fn();
+ } catch (error) {
+ expect(error).toBeDefined();
+ return;
+ }
+ throw new Error("Expected an error to be thrown");
+ }
+}
+
+export function equal(a: unknown, b: unknown): boolean {
+ return Bun.deepEquals(a, b);
+}
+
+export function fail(message: string): never {
+ throw new Error(message);
+}
+
+export function unimplemented(message: string): never {
+ throw new Error(`Unimplemented: ${message}`);
+}
+
+export function unreachable(): never {
+ throw new Error("Unreachable");
+}
+
+export function concat(...buffers: Uint8Array[]): Uint8Array {
+ return new Uint8Array(concatArrayBuffers(buffers));
+}
+
+export function inspect(...args: unknown[]): string {
+ return Bun.inspect(...args);
+}
+
+// @ts-expect-error
+globalThis["Deno"] = {
+ test,
+ inspect,
+};
diff --git a/test/js/deno/html/blob.test.ts b/test/js/deno/html/blob.test.ts
new file mode 100644
index 000000000..0b50c8452
--- /dev/null
+++ b/test/js/deno/html/blob.test.ts
@@ -0,0 +1,117 @@
+// Updated: Wed, 08 Mar 2023 00:55:15 GMT
+// URL: https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/blob_test.ts
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { assert, assertEquals, assertStringIncludes } from "deno:harness";
+import { concat } from "deno:harness";
+
+Deno.test(function blobString() {
+ const b1 = new Blob(["Hello World"]);
+ const str = "Test";
+ const b2 = new Blob([b1, str]);
+ assertEquals(b2.size, b1.size + str.length);
+});
+
+Deno.test(function blobBuffer() {
+ const buffer = new ArrayBuffer(12);
+ const u8 = new Uint8Array(buffer);
+ const f1 = new Float32Array(buffer);
+ const b1 = new Blob([buffer, u8]);
+ assertEquals(b1.size, 2 * u8.length);
+ const b2 = new Blob([b1, f1]);
+ assertEquals(b2.size, 3 * u8.length);
+});
+
+Deno.test(function blobSlice() {
+ const blob = new Blob(["Deno", "Foo"]);
+ const b1 = blob.slice(0, 3, "Text/HTML");
+ assert(b1 instanceof Blob);
+ assertEquals(b1.size, 3);
+ assertEquals(b1.type, "text/html");
+ const b2 = blob.slice(-1, 3);
+ assertEquals(b2.size, 0);
+ const b3 = blob.slice(100, 3);
+ assertEquals(b3.size, 0);
+ const b4 = blob.slice(0, 10);
+ assertEquals(b4.size, blob.size);
+});
+
+Deno.test(function blobInvalidType() {
+ const blob = new Blob(["foo"], {
+ type: "\u0521",
+ });
+
+ assertEquals(blob.type, "");
+});
+
+Deno.test(function blobShouldNotThrowError() {
+ let hasThrown = false;
+
+ try {
+ // deno-lint-ignore no-explicit-any
+ const options1: any = {
+ ending: "utf8",
+ hasOwnProperty: "hasOwnProperty",
+ };
+ const options2 = Object.create(null);
+ new Blob(["Hello World"], options1);
+ new Blob(["Hello World"], options2);
+ } catch {
+ hasThrown = true;
+ }
+
+ assertEquals(hasThrown, false);
+});
+
+/* TODO https://github.com/denoland/deno/issues/7540
+Deno.test(function nativeEndLine() {
+ const options = {
+ ending: "native",
+ } as const;
+ const blob = new Blob(["Hello\nWorld"], options);
+
+ assertEquals(blob.size, Deno.build.os === "windows" ? 12 : 11);
+});
+*/
+
+Deno.test(async function blobText() {
+ const blob = new Blob(["Hello World"]);
+ assertEquals(await blob.text(), "Hello World");
+});
+
+Deno.test(async function blobStream() {
+ const blob = new Blob(["Hello World"]);
+ const stream = blob.stream();
+ assert(stream instanceof ReadableStream);
+ const reader = stream.getReader();
+ let bytes = new Uint8Array();
+ const read = async (): Promise<void> => {
+ const { done, value } = await reader.read();
+ if (!done && value) {
+ bytes = concat(bytes, value);
+ return read();
+ }
+ };
+ await read();
+ const decoder = new TextDecoder();
+ assertEquals(decoder.decode(bytes), "Hello World");
+});
+
+Deno.test(async function blobArrayBuffer() {
+ const uint = new Uint8Array([102, 111, 111]);
+ const blob = new Blob([uint]);
+ assertEquals(await blob.arrayBuffer(), uint.buffer);
+});
+
+Deno.test(function blobConstructorNameIsBlob() {
+ const blob = new Blob();
+ assertEquals(blob.constructor.name, "Blob");
+});
+
+Deno.test(function blobCustomInspectFunction() {
+ const blob = new Blob();
+ assertEquals(
+ Deno.inspect(blob),
+ `Blob { size: 0, type: "" }`,
+ );
+ assertStringIncludes(Deno.inspect(Blob.prototype), "Blob");
+});
diff --git a/test/js/deno/resources/imports.json b/test/js/deno/resources/imports.json
new file mode 100644
index 000000000..0c75b5739
--- /dev/null
+++ b/test/js/deno/resources/imports.json
@@ -0,0 +1,4 @@
+[
+ "test_util.ts",
+ "test_util/std/bytes/concat.ts"
+]
diff --git a/test/js/deno/resources/tests.json b/test/js/deno/resources/tests.json
new file mode 100644
index 000000000..1d7e0f52a
--- /dev/null
+++ b/test/js/deno/resources/tests.json
@@ -0,0 +1,10 @@
+[
+ {
+ "path": "abort/abort-controller.test.ts",
+ "remotePath": "unit/abort_controller_test.ts"
+ },
+ {
+ "path": "html/blob.test.ts",
+ "remotePath": "unit/blob_test.ts"
+ }
+] \ No newline at end of file
diff --git a/test/js/deno/scripts/postinstall.ts b/test/js/deno/scripts/postinstall.ts
new file mode 100644
index 000000000..4031b29df
--- /dev/null
+++ b/test/js/deno/scripts/postinstall.ts
@@ -0,0 +1,31 @@
+import { mkdirSync } from "node:fs";
+import { join, dirname } from "node:path";
+import imports from "../resources/imports.json";
+import tests from "../resources/tests.json";
+
+for (const test of tests) {
+ const path = join(import.meta.dir, "..", test.path);
+ const url = new URL(
+ test.remotePath,
+ "https://raw.githubusercontent.com/denoland/deno/main/cli/tests/"
+ );
+ const response = await fetch(url);
+ console.log(response.status, url.toString(), "->", test.path);
+ if (!response.ok) {
+ throw new Error(
+ `Failed to download from GitHub: ${url} [status: ${response.status}]`
+ );
+ }
+ let body = await response.text();
+ for (const query of imports) {
+ const pattern = new RegExp(`"(.*${query})"`, "gmi");
+ body = body.replace(pattern, "\"deno:harness\"");
+ }
+ const src = `// Updated: ${response.headers.get("Date")}
+// URL: ${url}
+${body}`;
+ try {
+ mkdirSync(dirname(path));
+ } catch {}
+ await Bun.write(path, src);
+}
diff --git a/test/tsconfig.json b/test/tsconfig.json
index e879cd5f5..12ad19dc7 100644
--- a/test/tsconfig.json
+++ b/test/tsconfig.json
@@ -12,12 +12,14 @@
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"allowJs": true,
+ "resolveJsonModule": true,
"types": ["bun-types"],
"baseUrl": ".",
"paths": {
"harness": ["harness.ts"],
"mkfifo": ["mkfifo.ts"],
- "node-harness": ["js/node/harness.ts"]
+ "node-harness": ["js/node/harness.ts"],
+ "deno:harness": ["js/deno/harness.ts"]
}
}
}