mirror of
https://github.com/oven-sh/bun
synced 2026-02-18 14:51:52 +00:00
483 lines
13 KiB
TypeScript
483 lines
13 KiB
TypeScript
/**
|
|
*
|
|
* This file attempts to run practically every function in Bun with no
|
|
* arguments. This is sort of like a fuzzer.
|
|
*
|
|
* If you have a test failure pointing to this file, or if this file suddenly
|
|
* started becoming flaky, that usually means a JS bindings issue or a memory bug.
|
|
*
|
|
* What this does:
|
|
*
|
|
* Go through most of the methods & constructors in Bun:
|
|
* - Try to call them with no arguments - Foo()
|
|
* - Try to construct them with no arguments - new Foo()
|
|
*
|
|
* If your code panics or crashes with an uncatchable exception when no
|
|
* arguments are passed, that's a bug you should fix.
|
|
*
|
|
*/
|
|
|
|
const ENABLE_LOGGING = false;
|
|
|
|
import { afterAll, describe, test } from "bun:test";
|
|
import { EventEmitter } from "events";
|
|
import { isWindows } from "harness";
|
|
var calls = 0,
|
|
constructs = 0,
|
|
subclasses = 0;
|
|
afterAll(() => {
|
|
process.stdout.write(`\nStats: ${calls} calls, ${constructs} constructs, ${subclasses} subclasses\n`);
|
|
});
|
|
const Promise = globalThis.Promise;
|
|
globalThis.Promise = function (...args) {
|
|
if (args.length === 0) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
const { resolve, reject, promise } = Promise.withResolvers();
|
|
args[0](resolve, reject);
|
|
|
|
return promise?.catch?.(e => {
|
|
if (ENABLE_LOGGING) {
|
|
console.log(e);
|
|
}
|
|
});
|
|
};
|
|
globalThis.Promise.prototype = Promise.prototype;
|
|
Object.assign(globalThis.Promise, Promise);
|
|
|
|
function wrap(input) {
|
|
if (typeof input?.catch === "function") {
|
|
return input?.catch?.(e => {
|
|
if (ENABLE_LOGGING) {
|
|
console.error(e);
|
|
}
|
|
});
|
|
}
|
|
|
|
return input;
|
|
}
|
|
|
|
// Don't allow these to be called
|
|
delete process.exit;
|
|
delete process._reallyExit;
|
|
delete process.reallyExit;
|
|
delete process.abort;
|
|
delete process.kill;
|
|
delete process._kill;
|
|
delete process._destroy;
|
|
delete process._events;
|
|
delete process.openStdin;
|
|
delete process.emitWarning;
|
|
delete require("stream").Readable.prototype.destroy;
|
|
delete globalThis.Loader;
|
|
// ** Uncatchable errors in tests **
|
|
delete ReadableStreamDefaultReader.prototype["closed"];
|
|
delete ReadableStreamBYOBReader.prototype["closed"];
|
|
delete WritableStreamDefaultWriter.prototype["ready"];
|
|
delete WritableStreamDefaultWriter.prototype["closed"];
|
|
WebAssembly.compile = () => {};
|
|
WebAssembly.instantiate = () => {};
|
|
// ** Uncatchable errors in tests **
|
|
|
|
const banned = [
|
|
"alert",
|
|
"prompt",
|
|
"confirm",
|
|
"open",
|
|
"close",
|
|
"connect",
|
|
"listen",
|
|
"_start",
|
|
"wait",
|
|
"wait",
|
|
"sleep",
|
|
"exit",
|
|
"kill",
|
|
// "_read",
|
|
// "read",
|
|
// "_write",
|
|
// "resume",
|
|
];
|
|
const drainMicrotasks = require("bun:jsc").drainMicrotasks;
|
|
|
|
import.meta.require.cache["bun:jsc"] = {};
|
|
delete console.takeHeapSnapshot;
|
|
delete console.clear;
|
|
delete console.warn;
|
|
delete console.time;
|
|
delete console.timeEnd;
|
|
delete console.trace;
|
|
delete console.timeLog;
|
|
delete console.assert;
|
|
Bun.generateHeapSnapshot = () => {};
|
|
|
|
const ignoreList = [
|
|
Object.prototype,
|
|
Function.prototype,
|
|
Array.prototype,
|
|
async function () {}.prototype,
|
|
function* () {}.prototype,
|
|
async function* () {}.prototype,
|
|
function* () {}.prototype,
|
|
Uint8Array.prototype,
|
|
Uint16Array.prototype,
|
|
Uint32Array.prototype,
|
|
Int8Array.prototype,
|
|
Int16Array.prototype,
|
|
Int32Array.prototype,
|
|
Float16Array.prototype,
|
|
Float32Array.prototype,
|
|
Float64Array.prototype,
|
|
BigInt64Array.prototype,
|
|
BigUint64Array.prototype,
|
|
ArrayBuffer.prototype,
|
|
DataView.prototype,
|
|
Promise.prototype,
|
|
SharedArrayBuffer.prototype,
|
|
Error.prototype,
|
|
EvalError.prototype,
|
|
RangeError.prototype,
|
|
ReferenceError.prototype,
|
|
SyntaxError.prototype,
|
|
TypeError.prototype,
|
|
URIError.prototype,
|
|
RegExp.prototype,
|
|
Date.prototype,
|
|
String.prototype,
|
|
];
|
|
|
|
const constructBanned = banned;
|
|
const callBanned = [...banned];
|
|
|
|
function allThePropertyNames(object, banned) {
|
|
if (!object) {
|
|
return [];
|
|
}
|
|
const names = Object.getOwnPropertyNames(object);
|
|
var pro = Object.getPrototypeOf(object);
|
|
|
|
while (pro) {
|
|
if (ignoreList.includes(pro)) {
|
|
break;
|
|
}
|
|
|
|
names.push(...Object.getOwnPropertyNames(pro));
|
|
pro = Object.getPrototypeOf(pro);
|
|
}
|
|
|
|
for (const ban of banned) {
|
|
const index = names.indexOf(ban);
|
|
if (index !== -1) {
|
|
names.splice(index, 1);
|
|
}
|
|
}
|
|
|
|
return names;
|
|
}
|
|
|
|
if (ENABLE_LOGGING) {
|
|
{
|
|
const original = Reflect.construct;
|
|
Reflect.construct = function (...args) {
|
|
try {
|
|
console.log(args?.[0]?.name || args?.[1]?.name || args?.[0]?.name || args?.[0]?.[Symbol.toStringTag]);
|
|
} catch (e) {}
|
|
return original(...args);
|
|
};
|
|
}
|
|
{
|
|
const original = Reflect.apply;
|
|
Reflect.apply = function (...args) {
|
|
try {
|
|
console.log(args?.[0]?.name || args?.[1]?.name || args?.[0]?.name || args?.[0]?.[Symbol.toStringTag]);
|
|
} catch (e) {}
|
|
return original(...args);
|
|
};
|
|
}
|
|
}
|
|
|
|
const seenValues = new WeakSet();
|
|
var callAllMethodsCount = 0;
|
|
function callAllMethods(object) {
|
|
callAllMethodsCount++;
|
|
const queue = [];
|
|
const seen = new Set([object, object?.subarray]);
|
|
for (const methodName of allThePropertyNames(object, callBanned)) {
|
|
try {
|
|
try {
|
|
if (object instanceof EventEmitter) {
|
|
object?.on?.("error", () => {});
|
|
}
|
|
const returnValue = wrap(Reflect.apply(object?.[methodName], object, []));
|
|
Bun.inspect?.(returnValue), queue.push(returnValue);
|
|
calls++;
|
|
} catch (e) {
|
|
const returnValue = wrap(Reflect.apply(object.constructor?.[methodName], object?.constructor, []));
|
|
Bun.inspect?.(returnValue), queue.push(returnValue);
|
|
calls++;
|
|
}
|
|
} catch (e) {
|
|
const val = object?.[methodName];
|
|
if (val && (typeof val === "object" || typeof val === "function") && !seenValues.has(val)) {
|
|
seenValues.add(val);
|
|
queue.push(val);
|
|
}
|
|
} finally {
|
|
}
|
|
}
|
|
|
|
while (queue.length) {
|
|
const value = queue.shift();
|
|
if (value) {
|
|
for (const methodName of allThePropertyNames(value, callBanned)) {
|
|
try {
|
|
const method = value?.[methodName];
|
|
if (method && seen.has(method)) {
|
|
continue;
|
|
}
|
|
seen.add(method);
|
|
if (value instanceof EventEmitter) {
|
|
value?.on?.("error", () => {});
|
|
}
|
|
const returnValue = wrap(Reflect?.apply?.(method, value, []));
|
|
if (returnValue?.then) {
|
|
continue;
|
|
}
|
|
Bun.inspect?.(returnValue), queue.push(returnValue);
|
|
calls++;
|
|
} catch (e) {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function constructAllConstructors(object) {
|
|
const queue = [];
|
|
const seen = new Set([object?.subarray]);
|
|
for (const methodName of allThePropertyNames(object, constructBanned)) {
|
|
const method = object?.[methodName];
|
|
|
|
try {
|
|
try {
|
|
const returnValue = Reflect.construct(object, [], method);
|
|
Bun.inspect?.(returnValue), queue.push(returnValue);
|
|
constructs++;
|
|
} catch (e) {
|
|
const returnValue = Reflect.construct(object?.constructor, [], method);
|
|
Bun.inspect?.(returnValue), queue.push(returnValue);
|
|
constructs++;
|
|
}
|
|
} catch (e) {
|
|
try {
|
|
const returnValue = Reflect.construct(object?.prototype?.constructor, [], method);
|
|
Bun.inspect?.(returnValue), queue.push(returnValue);
|
|
constructs++;
|
|
} catch (e) {
|
|
Error.captureStackTrace(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
while (queue.length) {
|
|
const value = queue.shift();
|
|
for (const methodName of allThePropertyNames(value, constructBanned)) {
|
|
try {
|
|
const method = value?.[methodName];
|
|
if (method && seen.has(method)) {
|
|
continue;
|
|
}
|
|
|
|
const returnValue = Reflect.construct(value, [], method);
|
|
if (seen.has(returnValue)) {
|
|
continue;
|
|
}
|
|
|
|
Bun.inspect?.(returnValue), queue.push(returnValue);
|
|
seen.add(returnValue);
|
|
constructs++;
|
|
} catch (e) {}
|
|
}
|
|
}
|
|
}
|
|
|
|
function constructAllConstructorsWithSubclassing(object) {
|
|
const queue = [];
|
|
const seen = new Set([object?.subarray]);
|
|
for (const methodName of allThePropertyNames(object, constructBanned)) {
|
|
const method = object?.[methodName];
|
|
|
|
try {
|
|
try {
|
|
// Create a subclass of the constructor
|
|
class Subclass extends object {}
|
|
const returnValue = Reflect.construct(object, [], Subclass);
|
|
Bun.inspect?.(returnValue), queue.push(returnValue);
|
|
subclasses++;
|
|
} catch (e) {
|
|
try {
|
|
// Try with the constructor property
|
|
class Subclass extends object?.constructor {}
|
|
const returnValue = Reflect.construct(object?.constructor, [], Subclass);
|
|
Bun.inspect?.(returnValue), queue.push(returnValue);
|
|
subclasses++;
|
|
} catch (e) {
|
|
// Fallback to a more generic approach
|
|
const Subclass = function () {};
|
|
Object.setPrototypeOf(Subclass.prototype, object);
|
|
const returnValue = Reflect.construct(object, [], Subclass);
|
|
Bun.inspect?.(returnValue), queue.push(returnValue);
|
|
subclasses++;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
try {
|
|
// Try with prototype constructor
|
|
class Subclass extends object?.prototype?.constructor {}
|
|
const returnValue = Reflect.construct(object?.prototype?.constructor, [], Subclass);
|
|
Bun.inspect?.(returnValue), queue.push(returnValue);
|
|
subclasses++;
|
|
} catch (e) {
|
|
Error.captureStackTrace(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
while (queue.length) {
|
|
const value = queue.shift();
|
|
for (const methodName of allThePropertyNames(value, constructBanned)) {
|
|
try {
|
|
const method = value?.[methodName];
|
|
if (method && seen.has(method)) {
|
|
continue;
|
|
}
|
|
|
|
// Create a subclass of the value
|
|
try {
|
|
class Subclass extends value {}
|
|
const returnValue = Reflect.construct(value, [], Subclass);
|
|
if (seen.has(returnValue)) {
|
|
continue;
|
|
}
|
|
|
|
Bun.inspect?.(returnValue), queue.push(returnValue);
|
|
seen.add(returnValue);
|
|
subclasses++;
|
|
} catch (e) {
|
|
// Fallback to a more generic approach
|
|
const Subclass = function () {};
|
|
Object.setPrototypeOf(Subclass.prototype, value);
|
|
const returnValue = Reflect.construct(value, [], Subclass);
|
|
if (seen.has(returnValue)) {
|
|
continue;
|
|
}
|
|
|
|
Bun.inspect?.(returnValue), queue.push(returnValue);
|
|
seen.add(returnValue);
|
|
subclasses++;
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
}
|
|
}
|
|
|
|
const modules = [
|
|
"module",
|
|
"util",
|
|
"url",
|
|
"path",
|
|
"path/posix",
|
|
"path/win32",
|
|
"perf_hooks",
|
|
"os",
|
|
"dgram",
|
|
"domain",
|
|
"crypto",
|
|
"util/types",
|
|
"http",
|
|
"_http_agent",
|
|
"_http_client",
|
|
"_http_common",
|
|
"_http_incoming",
|
|
"_http_outgoing",
|
|
"_http_server",
|
|
"http2",
|
|
"process",
|
|
"undici",
|
|
"timers",
|
|
"punycode",
|
|
"trace_events",
|
|
"child_process",
|
|
"diagnostics_channel",
|
|
"http2",
|
|
"bun:ffi",
|
|
"string_decoder",
|
|
"bun:sqlite",
|
|
"fs/promises",
|
|
];
|
|
|
|
for (const mod of modules) {
|
|
describe(mod, () => {
|
|
test("call", () => callAllMethods(require(mod)));
|
|
test("construct", () => constructAllConstructors(require(mod)));
|
|
test("construct-subclass", () => constructAllConstructorsWithSubclassing(require(mod)));
|
|
});
|
|
}
|
|
|
|
for (const HardCodedClass of [
|
|
require("fs").ReadStream,
|
|
require("fs").WriteStream,
|
|
require("tty").ReadStream,
|
|
require("tty").WriteStream,
|
|
require("fs").Stats,
|
|
require("fs").Dirent,
|
|
Intl,
|
|
Intl.Collator,
|
|
Intl.DateTimeFormat,
|
|
Intl.ListFormat,
|
|
Intl.NumberFormat,
|
|
Intl.PluralRules,
|
|
Intl.RelativeTimeFormat,
|
|
Intl.Locale,
|
|
Intl.DisplayNames,
|
|
Intl.Segmenter,
|
|
|
|
// TODO: undefined is not an object
|
|
// require("fs").FSWatcher,
|
|
|
|
process,
|
|
]) {
|
|
test("call " + (HardCodedClass.name || HardCodedClass.toString()), () => constructAllConstructors(HardCodedClass));
|
|
test("construct " + (HardCodedClass.name || HardCodedClass.toString()), () => callAllMethods(HardCodedClass));
|
|
test("construct-subclass " + (HardCodedClass.name || HardCodedClass.toString()), () =>
|
|
constructAllConstructorsWithSubclassing(HardCodedClass),
|
|
);
|
|
}
|
|
|
|
const globals = [
|
|
[globalThis, "globalThis"],
|
|
[Bun, "Bun"],
|
|
] as const;
|
|
|
|
for (const [Global, name] of globals) {
|
|
describe(name, () => {
|
|
// TODO: hangs in CI on Windows.
|
|
test.skipIf(isWindows && Global === Bun)("call", async () => {
|
|
await Bun.sleep(1);
|
|
callAllMethods(Global);
|
|
await Bun.sleep(1);
|
|
});
|
|
// TODO: hangs in CI on Windows.
|
|
test.skipIf(isWindows && Global === Bun)("construct", async () => {
|
|
await Bun.sleep(1);
|
|
constructAllConstructors(Global);
|
|
await Bun.sleep(1);
|
|
});
|
|
test.skipIf(isWindows && Global === Bun)("construct-subclass", async () => {
|
|
await Bun.sleep(1);
|
|
constructAllConstructorsWithSubclassing(Global);
|
|
await Bun.sleep(1);
|
|
});
|
|
});
|
|
}
|