Compare commits

...

4 Commits

Author SHA1 Message Date
Ashcon Partovi
57916d9426 More tests 2023-12-01 16:55:23 -08:00
Ashcon Partovi
cf9ebff4e3 Remove diff 2023-12-01 15:39:01 -08:00
Ashcon Partovi
2f1733b48f More test 2023-12-01 15:37:42 -08:00
Ashcon Partovi
bfe979946e Add ecosystem tests 2023-12-01 15:36:10 -08:00
49 changed files with 2374 additions and 0 deletions

View File

@@ -24,6 +24,8 @@ function* findTests(dir, query) {
for (const entry of readdirSync(resolve(dir), { encoding: "utf-8", withFileTypes: true })) {
const path = resolve(dir, entry.name);
if (entry.isDirectory()) {
// Do not run ecosystem tests here
if (entry.name === "ecosystem") continue;
yield* findTests(path, query);
} else if (entry.name.includes(".test.")) {
yield path;

81
test/ecosystem.ts Normal file
View File

@@ -0,0 +1,81 @@
// Since ecosystem tests can be flaky, this file is responsible for running the tests
// in the `ecosystem` directory and figuring out if things are broken or not.
import { spawnSync, Glob } from "bun";
import { join } from "node:path";
import { EOL } from "node:os";
import { bunEnv, bunExe } from "harness";
const cwd = join(import.meta.dir, "ecosystem");
const [...patterns] = process.argv.slice(2);
const globs = patterns.length ? patterns.map(pattern => new Glob(pattern)) : [new Glob("**/*.test.ts")];
const files = globs.flatMap(glob => [...glob.scanSync({ cwd })]);
if (!files.length) {
throw "No tests found";
}
for (const file of files) {
runTest(file);
}
function runTest(file: string) {
const { exitCode, stderr } = spawnSync({
cwd,
cmd: [bunExe(), "test", file],
env: bunEnv,
stdout: "ignore",
stderr: "pipe",
});
const buffer: string[] = [];
const names: Set<string> = new Set();
const results: { type: string; name: string; logs: string }[] = [];
for (const line of stderr.toString().split(EOL)) {
const text = stripAnsi(line);
let type: string | undefined;
if (text.startsWith("(pass)") || text.startsWith("✓")) {
type = "pass";
} else if (text.startsWith("(fail)") || text.startsWith("✖")) {
type = "fail";
} else if (text.startsWith("(todo)") || text.startsWith("✏")) {
type = "todo";
} else if (text.startsWith("(skip)") || text.startsWith("⏩")) {
type = "skip";
} else if (!text.startsWith("minimalloc:")) {
buffer.push(line);
}
if (type) {
const eol = text.lastIndexOf("[");
const name = text.substring(7, eol ? eol - 1 : undefined);
if (names.has(name)) {
continue;
}
names.add(name);
results.push({
type,
name,
logs: buffer.join("\n"),
});
buffer.length = 0;
}
}
if (results.length === 1 && results[0].type === "todo") {
return;
}
let summary = "";
for (const { type, name } of results.sort((a, b) => a.type.localeCompare(b.type))) {
summary += `(${type}) ${name}\n`;
}
console.log(file, summary);
}
function stripAnsi(text: string): string {
return text.replace(/\x1B\[\d+m/g, "");
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "astro",
repository: "https://github.com/withastro/astro",
ref: "astro@3.6.3",
paths: ["packages/astro/test/**/*.spec.js"],
runner: "jest",
todo: true, // error: workspace dependency "astro-benchmark" not found
});

View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "body-parser",
repository: "https://github.com/expressjs/body-parser",
ref: "1.20.2",
paths: ["test/*.js"],
runner: "mocha",
todo: true, // crashes
});

View File

@@ -0,0 +1,9 @@
import { runTests } from "./harness";
runTests({
package: "chalk",
repository: "https://github.com/chalk/chalk",
ref: "v5.3.0",
paths: ["test/*.js"],
runner: "ava",
});

View File

@@ -0,0 +1,9 @@
import { runTests } from "./harness";
runTests({
package: "classnames",
repository: "https://github.com/JedWatson/classnames",
ref: "v2.3.2",
paths: ["tests/*.js"],
runner: "mocha",
});

View File

@@ -0,0 +1,13 @@
import { runTests } from "./harness";
runTests({
package: "clickhouse-js",
repository: "https://github.com/ClickHouse/clickhouse-js",
ref: "0.2.6",
paths: ["packages/client-node/__tests__/unit/**/*.test.ts"],
runner: "jest",
todo: true,
// Cannot find module "@test/utils"
// expect.toThrowError is not a function
// TypeError: ES Modules cannot be stubbed (?)
});

View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "colors",
repository: "https://github.com/Marak/colors.js",
ref: "074a0f8ed0c31c35d13d28632bd8a049ff136fb6", // Jan 7 2022
paths: ["tests/*.js"],
runner: "script",
todo: true, // lockfile is too old
});

View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "commander",
repository: "https://github.com/tj/commander.js",
ref: "v11.1.0",
paths: ["tests/*.test.js"],
runner: "jest",
todo: true, // ASSERTION FAILED: !m_errorInfoMaterialized
});

View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "cookie-parser",
repository: "https://github.com/expressjs/cookie-parser",
ref: "1.4.6",
paths: ["test/*.js"],
runner: "mocha",
todo: true, // times out
});

View File

@@ -0,0 +1,9 @@
import { runTests } from "./harness";
runTests({
package: "elysia",
repository: "https://github.com/elysiajs/elysia",
ref: "0.7",
paths: ["test/**/*.ts"],
runner: "jest",
});

View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "express",
repository: "https://github.com/expressjs/express",
ref: "2a00da2067b7017f769c9100205a2a5f267a884b", // June 4 2023
paths: ["test/acceptance/*.js"],
runner: "mocha",
todo: true, // Too many errors
});

View File

@@ -0,0 +1,12 @@
import { runTests } from "./harness";
runTests({
package: "glob",
repository: "https://github.com/isaacs/node-glob",
ref: "v10.3.10",
paths: ["test/*.ts"],
runner: "tap",
todo: true,
// t.pipe is not a function (test runner)
// expect() must be called in a test
});

View File

@@ -0,0 +1,12 @@
import { runTests } from "./harness";
runTests({
package: "got",
repository: "https://github.com/sindresorhus/got",
ref: "v14.0.0",
paths: ["test/*.ts"],
runner: "ava",
todo: true,
// need to implement more of ava runner
// also crashes
});

100
test/ecosystem/harness.ts Normal file
View File

@@ -0,0 +1,100 @@
import { spawnSync, Glob } from "bun";
import { join, resolve } from "node:path";
import { mkdtempSync, symlinkSync } from "node:fs";
import { tmpdir } from "node:os";
import { describe, test } from "bun:test";
import { bunEnv, bunExe } from "harness";
export type TestOptions = {
package: string;
repository: string;
ref: string | null;
paths: string[];
runner: "jest" | "vitest" | "ava" | "mocha" | "qunit" | "tap" | "utest" | "uvu" | "script";
skip?: boolean | string;
todo?: boolean | string;
cmds?: string[][];
};
export function runTests({ package: name, repository, ref, paths, runner, skip, todo, cmds }: TestOptions): void {
if (todo) {
test.todo(name, () => {});
return;
} else if (skip) {
test.skip(name, () => {});
return;
}
describe(name, () => {
const run = import(join(import.meta.dir, "runner", `${runner}.js`));
const tmp = mkdtempSync(join(tmpdir(), `${name.replace(/\//g, "-")}-`));
const cwd = join(tmp, "node_modules", name);
{
const target = ref ? `${repository}#${ref}` : repository;
const { exitCode } = spawnSync({
cwd: tmp,
cmd: [bunExe(), "install", target],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
if (exitCode !== 0) {
throw `bun install ${target}`;
}
}
{
const { exitCode } = spawnSync({
cwd,
cmd: [bunExe(), "install"],
env: bunEnv,
stdout: "inherit",
stderr: "inherit",
});
if (exitCode !== 0) {
throw "bun install";
}
}
for (const cmd of cmds ?? []) {
if (cmd[0] === "bun") {
cmd[0] = bunExe();
}
const { exitCode } = spawnSync({
cwd,
cmd,
env: bunEnv,
stdout: "inherit",
stderr: "inherit",
});
if (exitCode !== 0) {
throw cmd.join(" ");
}
}
for (const path of paths) {
const tests = [...new Glob(path).scanSync({ cwd })];
if (!tests.length) {
throw `No tests found: ${path}`;
}
for (const test of tests) {
const absolutePath = resolve(cwd, test);
if (!test.includes(".test.") && !test.includes(".spec.")) {
symlinkSync(absolutePath, absolutePath.replace(/\.(c|m)?(j|t)sx?$/, ".test.ts"));
}
describe(test, async () => {
const runner = await run;
await runner.run(absolutePath);
});
}
}
});
}

View File

@@ -0,0 +1,13 @@
import { runTests } from "./harness";
runTests({
package: "hono",
repository: "https://github.com/honojs/hono",
ref: "v3.10.3",
paths: ["src/**/*.test.ts"],
runner: "jest",
todo: true,
// expectTypeOf is not a function
// toThrowError is undefined
// Can't find variable: caches
});

View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "inquirer",
repository: "https://github.com/SBoudrias/Inquirer.js",
ref: "@inquirer/core@5.1.0",
paths: ["packages/**/*.test.mts"],
runner: "vitest",
todo: true, // hangs
});

View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "lodash",
repository: "https://github.com/lodash/lodash",
ref: "aa18212085c52fc106d075319637b8729e0f179f", // Sep 27 2023
paths: ["test/*.spec.js"],
runner: "jest",
todo: true,
});

View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "moment",
repository: "https://github.com/moment/moment",
ref: "v2.29.4",
paths: ["src/test/moment/*.js"],
runner: "qunit",
todo: true, // Implement `qunit` runner
});

View File

@@ -0,0 +1,13 @@
import { runTests } from "./harness";
runTests({
package: "mongodb",
repository: "https://github.com/mongodb/node-mongodb-native",
ref: "v6.3.0",
paths: ["test/unit/**/*.test.ts"],
runner: "mocha",
todo: true,
// SyntaxError: export 'Document' not found in 'bson' (module/import issue?)
// ReferenceError: Can't find variable: context (test runner issue)
// error: /bin/sh: ./node_modules/.bin/ts-node: No such file or directory (bun install issue?)
});

View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "mysql",
repository: "https://github.com/mysqljs/mysql",
ref: "dc9c152a87ec51a1f647447268917243d2eab1fd", // Mar 13 2022
paths: ["test/unit/**/*.js"],
runner: "utest",
todo: true, // TypeError: Module is not a function (near '...test...')
});

View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "mysql2",
repository: "https://github.com/sidorares/node-mysql2",
ref: "v3.6.5",
paths: ["test/unit/**/*.js"],
runner: "utest",
todo: true, // TypeError: Module is not a function (near '...test...')
});

View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "nextjs-project",
repository: "https://github.com/vercel/next.js",
ref: "v14.0.3",
paths: ["test/unit/**/*.test.ts", "test/e2e/**/*.test.ts"],
runner: "jest",
todo: true, // fatal: not in a git directory
});

View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "nuxt-framework",
repository: "https://github.com/nuxt/nuxt",
ref: "v3.8.2",
paths: ["test/**/*.test.ts"],
runner: "jest",
todo: true, // error: workspace dependency "@nuxt/webpack-builder" not found
});

10
test/ecosystem/pg.test.ts Normal file
View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "node-postgres",
repository: "https://github.com/brianc/node-postgres",
ref: "pg@8.11.3",
paths: ["packages/pg/test/unit/connection-parameters/*.js"],
runner: "mocha",
todo: true, // https://github.com/oven-sh/bun/issues/7360
});

View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "postgres",
repository: "https://github.com/porsager/postgres",
ref: "v3.4.3",
paths: ["tests/index.js"],
runner: "mocha",
todo: true,
});

View File

@@ -0,0 +1,9 @@
import { runTests } from "./harness";
runTests({
package: "prettier",
repository: "https://github.com/prettier/prettier",
ref: "3.1.0",
paths: ["tests/unit/*.js"],
runner: "jest",
});

View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "redis",
repository: "https://github.com/redis/node-redis",
ref: "redis@4.6.11",
paths: ["packages/client/lib/**/*.spec.ts"],
runner: "mocha",
todo: true, // https://github.com/oven-sh/bun/issues/7360
});

View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "rimraf",
repository: "https://github.com/isaacs/rimraf",
ref: "v5.0.5",
paths: ["test/**/*.ts"],
runner: "tap",
todo: true, // Too many errors
});

View File

@@ -0,0 +1,49 @@
export async function run(path) {
const jest = Bun.jest(path);
const { expect, mock } = jest;
// https://github.com/avajs/ava/blob/main/docs/03-assertions.md
const t = {
is(actual, expected) {
expect(actual).toBe(expected);
},
not(actual, expected) {
expect(actual).not.toBe(expected);
},
deepEqual(actual, expected) {
expect(actual).toEqual(expected);
},
throws(fn, expected) {
if (expected.message) {
expect(fn).toThrow(expected.message);
} else {
expect(fn).toThrow(expected);
}
},
};
for (const fn of Object.values(t)) {
hideFromStack(fn);
}
mock.module("ava", () => {
return {
default: (title, fn) => {
jest.test(title, async () => {
await fn(t);
});
},
};
});
await import(path);
}
function hideFromStack(fn) {
Object.defineProperty(fn, "name", {
value: "::bunternal::",
configurable: true,
enumerable: true,
writable: true,
});
}

View File

@@ -0,0 +1,6 @@
export async function run(path) {
for (const [key, value] of Object.entries(Bun.jest(path))) {
globalThis[key] = value;
}
await import(path);
}

View File

@@ -0,0 +1,9 @@
export async function run(path) {
const jest = Bun.jest(path);
for (const [key, value] of Object.entries(jest)) {
globalThis[key] = value;
}
globalThis.before = jest.beforeAll;
globalThis.after = jest.afterAll;
await import(path);
}

View File

@@ -0,0 +1,124 @@
export async function run(path) {
const jest = Bun.jest(path);
const { expect, mock } = jest;
// https://api.qunitjs.com/config/
const config = {};
// https://api.qunitjs.com/assert/
const assert = {
async(count) {
throw new Error("Not implemented: async");
},
deepEqual(actual, expected) {
expect(actual).toStrictEqual(expected);
},
equal(actual, expected) {
expect(actual).toEqual(expected);
},
expect(amount) {
// TODO
},
false(actual) {
expect(actual).toBeFalse();
},
notDeepEqual(actual, expected) {
expect(actual).not.toStrictEqual(expected);
},
notEqual(actual, expected) {
expect(actual).not.toEqual(expected);
},
notOk(actual) {
expect(actual).toBeFalsy();
},
notPropContains(actual, expected) {
throw new Error("Not implemented: notPropContains");
},
notPropEqual(actual, expected) {
throw new Error("Not implemented: notPropEqual");
},
notStrictEqual(actual, expected) {
expect(actual).not.toStrictEqual(expected);
},
ok(actual) {
expect(actual).toBeTruthy();
},
propContains(actual, expected) {
throw new Error("Not implemented: propContains");
},
propEqual(actual, expected) {
throw new Error("Not implemented: propEqual");
},
pushResult(resultInfo) {
throw new Error("Not implemented: pushResult");
},
rejects(actual, expected) {
expect(actual).rejects.toThrow(expected);
},
step(label) {
throw new Error("Not implemented: step");
},
strictEqual(actual, expected) {
expect(actual).toStrictEqual(expected);
},
throws(actual, expected) {
expect(actual).toThrow(expected);
},
raises(actual, expected) {
expect(actual).toThrow(expected);
},
timeout() {
// TODO
},
true(actual) {
expect(actual).toBeTrue();
},
verifySteps(steps) {
throw new Error("Not implemented: verifySteps");
},
};
let module = "";
const QUnit = {
config,
test(name, fn) {
if (module) {
name = `${module} > ${name}`;
}
jest.test(name, () => fn(assert));
},
module(name, fn) {
if (fn) {
module = "";
jest.describe(name, () => fn(assert));
} else {
module = name;
}
},
};
for (const fn of Object.values(QUnit)) {
hideFromStack(fn);
}
globalThis.QUnit = QUnit;
mock.module("qunit", () => {
const module = {
default: QUnit,
...QUnit,
};
return module;
});
await import(path);
}
function hideFromStack(fn) {
Object.defineProperty(fn, "name", {
value: "::bunternal::",
configurable: true,
enumerable: true,
writable: true,
});
}

View File

@@ -0,0 +1,9 @@
import { basename } from "node:path";
export async function run(path) {
const { test, expect } = Bun.jest(path);
test(basename(path), async () => {
expect(import(path)).resolves.not.toThrow();
});
}

View File

@@ -0,0 +1,282 @@
import { spawnSync } from "bun";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { linkSync, mkdirSync, mkdtempSync, symlinkSync, writeFileSync } from "node:fs";
export async function run(path) {
const jest = Bun.jest(path);
const { expect, mock } = jest;
// https://node-tap.org/api/
// https://github.com/tapjs/tapjs/blob/511019b2ac0fa014370154c3a341a0e632f50b19/src/asserts/src/index.ts
const t = {
plan(count) {
// expect.assertions(count);
},
equal(actual, expected) {
expect(actual).toBe(expected);
},
notEqual(actual, expected) {
expect(actual).not.toBe(expected);
},
deepEqual(actual, expected) {
expect(actual).toEqual(expected);
},
notDeepEqual(actual, expected) {
expect(actual).not.toEqual(expected);
},
fail(message) {
expect.fail(message);
},
pass(message) {
// ...
},
// https://github.com/tapjs/tapjs/blob/511019b2ac0fa014370154c3a341a0e632f50b19/src/asserts/src/index.ts
ok(value) {
expect(value).toBeTruthy();
},
notOk(value) {
expect(value).toBeFalsy();
},
equal(actual, expected) {
expect(actual).toBe(expected);
},
not(actual, expected) {
expect(actual).not.toBe(expected);
},
type(actual, expected) {
if (actual in ["undefined", "boolean", "number", "string", "symbol", "function", "object"]) {
expect(typeof actual).toBe(expected);
} else {
expect(actual).toBeInstanceOf(expected);
}
},
same(actual, expected) {
expect(actual).toEqual(expected);
},
notSame(actual, expected) {
expect(actual).not.toEqual(expected);
},
strictSame(actual, expected) {
expect(actual).toStrictEqual(expected);
},
strictNotSame(actual, expected) {
expect(actual).not.toStrictEqual(expected);
},
has(actual, expected) {
throw new Error("Not implemented: has");
},
notHas(actual, expected) {
throw new Error("Not implemented: notHas");
},
hasStrict(actual, expected) {
throw new Error("Not implemented: hasStrict");
},
notHasStrict(actual, expected) {
throw new Error("Not implemented: notHasStrict");
},
match(actual, expected) {
if (typeof expected === "string" || expected instanceof RegExp) {
expect(actual).toMatch(expected);
} else {
expect(actual).toMatchObject(expected);
}
},
notMatch(actual, expected) {
if (typeof expected === "string" || expected instanceof RegExp) {
expect(actual).not.toMatch(expected);
} else {
expect(actual).not.toMatchObject(expected);
}
},
matchOnly(actual, expected) {
throw new Error("Not implemented: matchOnly");
},
notMatchOnly(actual, expected) {
throw new Error("Not implemented: notMatchOnly");
},
matchOnlyStrict(actual, expected) {
throw new Error("Not implemented: matchOnlyStrict");
},
notMatchOnlyStrict(actual, expected) {
throw new Error("Not implemented: notMatchOnlyStrict");
},
matchStrict(actual, expected) {
throw new Error("Not implemented: matchStrict");
},
notMatchStrict(actual, expected) {
throw new Error("Not implemented: notMatchStrict");
},
hasProp(actual, expected) {
throw new Error("Not implemented: hasProp");
},
hasOwnProp(actual, expected) {
throw new Error("Not implemented: hasOwnProp");
},
hasProps(actual, expected) {
throw new Error("Not implemented: hasProps");
},
hasOwnProps(actual, expected) {
throw new Error("Not implemented: hasOwnProps");
},
hasOwnPropsOnly(actual, expected) {
throw new Error("Not implemented: hasOwnPropsOnly");
},
throws(fn, expected) {
expect(fn).toThrow(expected);
},
doesNotThrow(fn, expected) {
expect(fn).not.toThrow(expected);
},
rejects(fnOrPromise, expected) {
const promise = typeof fnOrPromise === "function" ? fnOrPromise() : fnOrPromise;
expect(promise).rejects.toThrow(expected);
},
resolves(fnOrPromise, expected) {
const promise = typeof fnOrPromise === "function" ? fnOrPromise() : fnOrPromise;
expect(promise).resolves.toEqual(expected);
},
resolveMatch(fnOrPromise, expected) {
const promise = typeof fnOrPromise === "function" ? fnOrPromise() : fnOrPromise;
expect(promise).resolves.toMatch(expected);
},
emits(emitter, event) {
throw new Error("Not implemented: emits");
},
error(err) {
expect(err).toBeInstanceOf(Error);
},
// https://github.com/tapjs/tapjs/blob/511019b2ac0fa014370154c3a341a0e632f50b19/src/mock/src/index.ts
mock(name, mocks) {
jest.mock(name, mocks);
return require(name);
},
mockImport(name, mocks) {
jest.mock(name, mocks);
return import(name);
},
mockRequire(name, mocks) {
jest.mock(name, mocks);
return require(name);
},
mockAll(name, mocks) {
jest.mock(name, mocks);
},
// https://github.com/tapjs/tapjs/blob/main/src/snapshot/src/index.ts
matchSnapshot(expected) {
expect(expected).toMatchSnapshot();
},
resolveMatchSnapshot(fnOrPromise) {
const promise = typeof fnOrPromise === "function" ? fnOrPromise() : fnOrPromise;
expect(promise).resolves.toMatchSnapshot();
},
// https://github.com/tapjs/tapjs/tree/511019b2ac0fa014370154c3a341a0e632f50b19/src/fixture
fixture(type, content) {
return {
type,
content,
};
},
testdir(content) {
const cwd = mkdtempSync(join(tmpdir(), "tap-testdir-"));
const write = (path, content) => {
if (typeof content === "string") {
writeFileSync(join(cwd, path), content);
} else if (typeof content === "object") {
if ("type" in content && "content" in content) {
const { type, content: value } = content;
if (type === "file") {
writeFileSync(join(cwd, path), content);
} else if (type === "dir") {
mkdirSync(join(cwd, path));
} else if (type === "symlink") {
symlinkSync(join(cwd, path), value);
} else if (type === "link") {
linkSync(join(cwd, path), value);
} else {
throw new Error(`Not implemented fixture: ${type}`);
}
} else {
if (path) {
mkdirSync(join(cwd, path));
}
for (const [filename, entry] of Object.entries(content)) {
write(join(path, filename), entry);
}
}
}
};
write("", content);
},
// https://github.com/tapjs/tapjs/tree/511019b2ac0fa014370154c3a341a0e632f50b19/src/spawn
spawn(cmd, args, { cwd, env }) {
spawnSync({
cmd: [cmd, ...args],
cwd,
env,
});
},
// ...
comment(message) {
// ...
},
test(name, fn, options) {
// if (typeof fn !== "function") {
// if (fn.skip) {
// jest.skip(name, () => {});
// return;
// }
// fn = options;
// }
jest.test(name, () => {
fn(t);
});
},
before(fn) {
jest.beforeAll(() => fn(t));
},
beforeEach(fn) {
jest.beforeEach(() => fn(t));
},
after(fn) {
jest.afterAll(() => fn(t));
},
afterEach(fn) {
jest.afterEach(() => fn(t));
},
teardown(fn) {
jest.afterAll(() => fn(t));
},
end() {
// ...
},
};
for (const fn of Object.values(t)) {
hideFromStack(fn);
}
mock.module("tap", () => {
const module = {
default: t,
...t,
};
for (const fn of Object.values(module)) {
hideFromStack(fn);
}
return module;
});
await import(path);
}
function hideFromStack(fn) {
Object.defineProperty(fn, "name", {
value: "::bunternal::",
configurable: true,
enumerable: true,
writable: true,
});
}

View File

@@ -0,0 +1,21 @@
// https://www.npmjs.com/package/utest
export async function run(path) {
const { mock, describe, test } = Bun.jest(path);
mock.module("utest", () => {
return {
default: (title, tests) => {
describe(title, () => {
for (const [name, fn] of Object.entries(tests)) {
test(name, async () => {
await fn();
});
}
});
},
};
});
await import(path);
}

View File

@@ -0,0 +1,201 @@
export async function run(path) {
const jest = Bun.jest(path);
const { expect, mock } = jest;
// https://github.com/lukeed/uvu/blob/master/docs/api.uvu.md
const suite = (name, context, fn) => {
if (typeof context === "function") {
fn = context;
context = {};
}
const tests = [];
const skips = [];
const onlys = [];
const beforeAlls = [];
const beforeEachs = [];
const afterAlls = [];
const afterEachs = [];
const result = {
name,
context,
test(name, fn) {
tests.push([name, fn]);
return result;
},
skip(name, fn) {
skips.push([name, fn]);
return result;
},
only(name, fn) {
onlys.push([name, fn]);
return result;
},
before: createCallable({
default: fn => beforeAlls.push(fn),
each: fn => beforeEachs.push(fn),
}),
after: createCallable({
default: fn => afterAlls.push(fn),
each: fn => afterEachs.push(fn),
}),
run() {
jest.describe(name, () => {
if (typeof fn === "function") {
fn(context);
}
for (const fn of beforeAlls) {
jest.beforeAll(() => fn(context));
}
for (const fn of beforeEachs) {
jest.beforeEach(() => fn(context));
}
for (const fn of afterAlls) {
jest.afterAll(() => fn(context));
}
for (const fn of afterEachs) {
jest.afterEach(() => fn(context));
}
for (const [name, fn] of tests) {
jest.test(name, () => fn(context));
}
for (const [name, fn] of skips) {
jest.test.skip(name, () => fn(context));
}
for (const [name, fn] of onlys) {
jest.test.only(name, () => fn(context));
}
});
},
};
for (const fn of Object.values(result)) {
if (typeof fn === "function") {
hideFromStack(fn);
}
}
return result;
};
// https://github.com/lukeed/uvu/blob/master/docs/api.assert.md
const assert = {
ok: value => {
expect(value).toBeTruthy();
},
is: createCallable({
default: (value, expected) => expect(value).toBe(expected),
not: (value, expected) => expect(value).not.toBe(expected),
}),
equal: (value, expected) => {
expect(value).toEqual(expected);
},
type: (value, expected) => {
expect(typeof value).toBe(expected);
},
instance: (value, expected) => {
expect(value).toBeInstanceOf(expected);
},
match: (value, expected) => {
expect(value).toMatch(expected);
},
snapshot: (value, expected) => {
expect(value).toBe(expected); // ?
},
fixture: (value, expected) => {
expect(value).toBe(expected); // ?
},
throws: (fn, expected) => {
if (!expected) {
expect(fn).toThrow();
} else if (typeof expected === "string" || expected instanceof RegExp) {
expect(fn).toThrow(expected);
} else if (typeof expected === "function") {
try {
fn();
} catch (error) {
if (!expected(error)) {
throw error;
}
}
} else {
expect.unreachable();
}
},
unreachable: () => {
expect.unreachable();
},
not: createCallable({
default: value => expect(value).toBeFalsy(),
ok: value => expect(value).toBeFalsy(),
equal: (value, expected) => expect(value).not.toEqual(expected),
type: (value, expected) => expect(typeof value).not.toBe(expected),
instance: (value, expected) => expect(value).not.toBeInstanceOf(expected),
match: (value, expected) => expect(value).not.toMatch(expected),
snapshot: (value, expected) => expect(value).not.toBe(expected), // ?
fixture: (value, expected) => expect(value).not.toBe(expected), // ?
throws: (fn, expected) => {
if (!expected) {
expect(fn).not.toThrow();
} else if (typeof expected === "string" || expected instanceof RegExp) {
expect(fn).not.toThrow(expected);
} else if (typeof expected === "function") {
try {
fn();
} catch (error) {
if (expected(error)) {
throw error;
}
}
} else {
expect.unreachable();
}
},
}),
};
for (const fn of Object.values(assert)) {
hideFromStack(fn);
}
mock.module("uvu", () => {
return {
suite: (name, fn) => {
const result = suite(name, fn);
return createCallable({
default: (name, fn) => result.test(name, fn),
...result,
});
},
test: (name, fn) => suite("").test(name, fn),
};
});
mock.module("uvu/assert", () => {
return {
default: assert,
...assert,
};
});
await import(path);
}
function createCallable(options) {
const { default: fn, ...fns } = options;
let result = (...args) => fn(...args);
for (const [name, fn] of Object.entries(fns)) {
if (typeof fn === "function") {
result[name] = fn;
}
}
return result;
}
function hideFromStack(fn) {
Object.defineProperty(fn, "name", {
value: "::bunternal::",
configurable: true,
enumerable: true,
writable: true,
});
}

View File

@@ -0,0 +1,6 @@
export async function run(path) {
for (const [key, value] of Object.entries(Bun.jest(path))) {
globalThis[key] = value;
}
await import(path);
}

View File

@@ -0,0 +1,9 @@
import { runTests } from "./harness";
runTests({
package: "semver",
repository: "https://github.com/npm/node-semver",
ref: "6240d75a7c620b0a222f05969a91fdc3dc2be0fb", // Nov 2023
paths: ["test/functions/*.js", "test/integration/*.js", "test/ranges/*.js", "test/bin/*.js"],
runner: "tap",
});

View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "superagent", // used by `express` for testing
repository: "https://github.com/ladjs/superagent",
ref: "v8.1.2",
paths: ["test/*.js"],
runner: "mocha",
todo: true,
});

View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "supertest", // used by `express` for testing
repository: "https://github.com/ladjs/supertest",
ref: "v6.3.3",
paths: ["test/*.js"],
runner: "mocha",
todo: true,
});

View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "underscore",
repository: "https://github.com/jashkenas/underscore",
ref: "1.13.6",
paths: ["test/*.js"],
runner: "qunit",
todo: true, // Too many errors
});

View File

@@ -0,0 +1,9 @@
import { runTests } from "./harness";
runTests({
package: "uuid",
repository: "https://github.com/uuidjs/uuid",
ref: "v9.0.1",
paths: ["test/unit/*.test.js"],
runner: "jest",
});

View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "@vitejs/vite-monorepo",
repository: "https://github.com/vitejs/vite",
ref: "v5.0.3",
paths: ["packages/create-vite/__tests__/**/*.spec.ts", "packages/vite/src/node/__tests__/**/*.spec.ts"],
runner: "jest",
todo: true, // error: workspace dependency "vite" not found
});

View File

@@ -0,0 +1,10 @@
import { runTests } from "./harness";
runTests({
package: "yargs",
repository: "https://github.com/yargs/yargs",
ref: "v17.7.2",
paths: ["test/*.cjs"],
runner: "mocha",
todo: true, // TypeError: path.charCodeAt is not a function
});

11
test/ecosystem/zx.test.ts Normal file
View File

@@ -0,0 +1,11 @@
import { runTests } from "./harness";
runTests({
package: "zx",
repository: "https://github.com/google/zx",
ref: "7.2.3",
paths: ["test/*.test.js"],
runner: "uvu",
cmds: [["bun", "run", "build"]],
todo: true, // Too many problems
});

View File

@@ -181,3 +181,7 @@ export function ignoreMimallocWarning({
Response.prototype.text = origResponseText;
});
}
export function randomPort() {
return Math.floor(Math.random() * (65535 - 1024) + 1024);
}

View File

@@ -0,0 +1,105 @@
import { describe, test, expect, afterEach } from "bun:test";
import { PipedSubprocess, Subprocess, spawn } from "bun";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { existsSync, mkdtempSync } from "node:fs";
import { bunExe, bunEnv, randomPort } from "harness";
describe.each(["vanilla", "vanilla-ts", "react", "react-ts"])("vite/%s", (template: string) => {
const tmp = mkdtempSync(join(tmpdir(), `vite-${template}-`));
const cwd = join(tmp, template);
let subprocess: Subprocess | undefined;
afterEach(() => {
subprocess?.kill();
});
test(
"bunx create-vite",
async () => {
subprocess = spawn({
cwd: tmp,
cmd: [bunExe(), "--bun", "x", "create-vite", template, "--template", template],
env: bunEnv,
stdout: "inherit",
stderr: "inherit",
});
expect(subprocess.exited).resolves.toBe(0);
expect(existsSync(cwd)).toBeTrue();
expect(existsSync(join(cwd, "package.json"))).toBeTrue();
},
{
timeout: 15_000,
},
);
test(
"bun install",
async () => {
subprocess = spawn({
cwd,
cmd: [bunExe(), "install"],
env: bunEnv,
stdout: "inherit",
stderr: "inherit",
});
expect(subprocess.exited).resolves.toBe(0);
expect(existsSync(join(cwd, "bun.lockb"))).toBeTrue();
expect(existsSync(join(cwd, "node_modules"))).toBeTrue();
},
{
timeout: 15_000,
},
);
test("bun run build", () => {
subprocess = spawn({
cwd,
cmd: [bunExe(), "--bun", "run", "build"],
env: bunEnv,
stdout: "inherit",
stderr: "inherit",
});
expect(subprocess.exited).resolves.toBe(0);
expect(existsSync(join(cwd, "dist"))).toBeTrue();
expect(existsSync(join(cwd, "dist", "index.html"))).toBeTrue();
});
test.each(["preview", "dev"])(
"bun run %s",
async subcommand => {
subprocess = spawn({
cwd,
cmd: [bunExe(), "--bun", "run", subcommand, "--port", `${randomPort()}`, "--strict-port", "true"],
env: bunEnv,
stdout: "pipe",
stderr: "inherit",
});
const { stdout } = subprocess as PipedSubprocess;
let url: string | undefined;
for await (const chunk of stdout) {
process.stdout.write(chunk);
const text = Buffer.from(chunk).toString();
const match = text.match(/(http:\/\/[^\s]+)/gim);
if (match?.length) {
url = match[0];
break;
}
}
if (!url) {
throw new Error("Failed to find server URL from stdout");
}
for (let i = 0; i < 100; i++) {
const response = await fetch(url);
expect(response.text()).resolves.toStartWith("<!doctype html>");
expect(response.status).toBe(200);
}
},
{
timeout: 60_000,
},
);
});