Files
bun.sh/test/js/bun/util/fuzzy-wuzzy.test.ts
Jarred Sumner de7c947161 bump webkit (#22256)
### What does this PR do?

### How did you verify your code works?

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-01 16:20:13 -07:00

533 lines
16 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 = process.env.FUZZY_WUZZY_LOGGING === "1";
import { afterAll, describe, expect, 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`);
});
function log(from, ...message: any[]) {
if (!ENABLE_LOGGING) return;
console.log(`[ ${from} ] ${message.join(" ")}`);
}
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) {
log("unknown", "Uncaught error:");
console.log(e);
}
});
};
globalThis.Promise.prototype = Promise.prototype;
Object.assign(globalThis.Promise, Promise);
function wrap(input, from) {
if (typeof input?.catch === "function") {
return input?.catch?.(e => {
if (ENABLE_LOGGING) {
log(from, "Threw error:");
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;
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"];
Object.defineProperty(ReadableStreamDefaultReader.prototype, "closed", { value: false });
Object.defineProperty(ReadableStreamBYOBReader.prototype, "closed", { value: false });
Object.defineProperty(WritableStreamDefaultWriter.prototype, "ready", { value: Promise.resolve() });
Object.defineProperty(WritableStreamDefaultWriter.prototype, "closed", { value: false });
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, rootName) {
callAllMethodsCount++;
const queue: { value: unknown; from: string }[] = [];
const seen = new Set([object, object?.subarray]);
for (const methodName of allThePropertyNames(object, callBanned)) {
const fullName = rootName + "." + methodName;
try {
try {
if (object instanceof EventEmitter) {
object?.on?.("error", () => {});
}
log(fullName, "Calling...");
const returnValue = wrap(Reflect.apply(object?.[methodName], object, []), fullName + "()");
(Bun.inspect?.(returnValue), queue.push({ value: returnValue, from: fullName + "()" }));
calls++;
} catch (e) {
const returnValue = wrap(
Reflect.apply(object.constructor?.[methodName], object?.constructor, []),
"(new " + fullName + "())",
);
(Bun.inspect?.(returnValue), queue.push({ value: returnValue, from: "(new " + fullName + "())" }));
calls++;
}
} catch (e) {
const val = object?.[methodName];
if (val && (typeof val === "object" || typeof val === "function") && !seenValues.has(val)) {
seenValues.add(val);
queue.push({ value: val, from: fullName });
}
} finally {
}
}
while (queue.length) {
const { value, from } = queue.shift()!;
if (value) {
for (const methodName of allThePropertyNames(value, callBanned)) {
const fullName = from + "." + methodName;
try {
const method = value?.[methodName];
if (method && seen.has(method)) {
continue;
}
seen.add(method);
if (value instanceof EventEmitter) {
value?.on?.("error", () => {});
}
log(fullName, "Calling...");
const returnValue = wrap(Reflect?.apply?.(method, value, []), fullName + "()");
if (returnValue?.then) {
continue;
}
(Bun.inspect?.(returnValue), queue.push({ value: returnValue, from: fullName + "()" }));
calls++;
} catch (e) {}
}
}
}
}
function constructAllConstructors(object, rootName) {
const queue: { value: unknown; from: string }[] = [];
const seen = new Set([object?.subarray]);
for (const methodName of allThePropertyNames(object, constructBanned)) {
const fullName = rootName + "." + methodName;
const method = object?.[methodName];
try {
try {
log(fullName, "Constructing...");
const returnValue = Reflect.construct(object, [], method);
(Bun.inspect?.(returnValue), queue.push({ value: returnValue, from: "(new " + fullName + "())" }));
constructs++;
} catch (e) {
log(fullName, "Constructing...");
const returnValue = Reflect.construct(object?.constructor, [], method);
(Bun.inspect?.(returnValue), queue.push({ value: returnValue, from: "(new " + fullName + "())" }));
constructs++;
}
} catch (e) {
try {
log(fullName, "Constructing...");
const returnValue = Reflect.construct(object?.prototype?.constructor, [], method);
(Bun.inspect?.(returnValue), queue.push({ value: returnValue, from: "(new " + fullName + "())" }));
constructs++;
} catch (e) {
Error.captureStackTrace(e);
}
}
}
while (queue.length) {
const { value, from } = queue.shift()!;
for (const methodName of allThePropertyNames(value, constructBanned)) {
const fullName = from + "." + methodName;
try {
const method = value?.[methodName];
if (method && seen.has(method)) {
continue;
}
log(fullName, "Constructing...");
const returnValue = Reflect.construct(value, [], method);
if (seen.has(returnValue)) {
continue;
}
(Bun.inspect?.(returnValue), queue.push({ value: returnValue, from: "(new " + fullName + "())" }));
seen.add(returnValue);
constructs++;
} catch (e) {}
}
}
}
function constructAllConstructorsWithSubclassing(object, rootName) {
const queue: { value: unknown; from: string }[] = [];
const seen = new Set([object?.subarray]);
for (const methodName of allThePropertyNames(object, constructBanned)) {
const fullName = rootName + "." + methodName;
const method = object?.[methodName];
try {
try {
// Create a subclass of the constructor
class Subclass extends object {}
log(fullName, "Constructing with subclass...");
const returnValue = Reflect.construct(object, [], Subclass);
(Bun.inspect?.(returnValue), queue.push({ value: returnValue, from: "(newSubclass " + fullName + "())" }));
subclasses++;
} catch (e) {
try {
// Try with the constructor property
class Subclass extends object?.constructor {}
log(fullName, "Constructing with subclass...");
const returnValue = Reflect.construct(object?.constructor, [], Subclass);
(Bun.inspect?.(returnValue), queue.push({ value: returnValue, from: "(newSubclass " + fullName + "())" }));
subclasses++;
} catch (e) {
// Fallback to a more generic approach
const Subclass = function () {};
Object.setPrototypeOf(Subclass.prototype, object);
log(fullName, "Constructing with subclass...");
const returnValue = Reflect.construct(object, [], Subclass);
(Bun.inspect?.(returnValue), queue.push({ value: returnValue, from: "(newSubclass " + fullName + "())" }));
subclasses++;
}
}
} catch (e) {
try {
// Try with prototype constructor
class Subclass extends object?.prototype?.constructor {}
log(fullName, "Constructing with subclass...");
const returnValue = Reflect.construct(object?.prototype?.constructor, [], Subclass);
(Bun.inspect?.(returnValue), queue.push({ value: returnValue, from: "(newSubclass " + fullName + "())" }));
subclasses++;
} catch (e) {
Error.captureStackTrace(e);
}
}
}
while (queue.length) {
const { value, from } = queue.shift()!;
for (const methodName of allThePropertyNames(value, constructBanned)) {
const fullName = from + "." + methodName;
try {
const method = value?.[methodName];
if (method && seen.has(method)) {
continue;
}
// Create a subclass of the value
try {
class Subclass extends value {}
log(fullName, "Constructing with subclass...");
const returnValue = Reflect.construct(value, [], Subclass);
if (seen.has(returnValue)) {
continue;
}
(Bun.inspect?.(returnValue), queue.push({ value: returnValue, from: "(newSubclass " + fullName + "())" }));
seen.add(returnValue);
subclasses++;
} catch (e) {
// Fallback to a more generic approach
const Subclass = function () {};
Object.setPrototypeOf(Subclass.prototype, value);
log(fullName, "Constructing with subclass...");
const returnValue = Reflect.construct(value, [], Subclass);
if (seen.has(returnValue)) {
continue;
}
(Bun.inspect?.(returnValue), queue.push({ value: returnValue, from: "(newSubclass " + fullName + "())" }));
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", () => {
expect(async () => await callAllMethods(require(mod), `require("${mod}")`)).not.toThrow();
});
test("construct", () => {
expect(async () => await constructAllConstructors(require(mod), `require("${mod}")`)).not.toThrow();
});
test("construct-subclass", () => {
expect(
async () => await constructAllConstructorsWithSubclassing(require(mod), `require("${mod}")`),
).not.toThrow();
});
});
}
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, HardCodedClass.name || HardCodedClass.toString() || "HardCodedClass"),
);
test("construct " + (HardCodedClass.name || HardCodedClass.toString()), () =>
callAllMethods(HardCodedClass, HardCodedClass.name || HardCodedClass.toString() || "HardCodedClass"),
);
test("construct-subclass " + (HardCodedClass.name || HardCodedClass.toString()), () =>
constructAllConstructorsWithSubclassing(
HardCodedClass,
HardCodedClass.name || HardCodedClass.toString() || "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);
expect(async () => await callAllMethods(Global, Global === Bun ? "Bun" : "globalThis")).not.toThrow();
await Bun.sleep(1);
});
// TODO: hangs in CI on Windows.
test.skipIf(isWindows && Global === Bun)("construct", async () => {
await Bun.sleep(1);
expect(async () => await constructAllConstructors(Global, Global === Bun ? "Bun" : "globalThis")).not.toThrow();
await Bun.sleep(1);
});
test.skipIf(isWindows && Global === Bun)("construct-subclass", async () => {
await Bun.sleep(1);
expect(
async () => await constructAllConstructorsWithSubclassing(Global, Global === Bun ? "Bun" : "globalThis"),
).not.toThrow();
await Bun.sleep(1);
});
});
}