mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 10:58:56 +00:00
369 lines
8.8 KiB
JavaScript
369 lines
8.8 KiB
JavaScript
import { spawnSync } from "bun";
|
|
import { heapStats } from "bun:jsc";
|
|
import { expect, it } from "bun:test";
|
|
import { bunEnv, bunExe, isWindows } from "harness";
|
|
import path from "node:path";
|
|
|
|
it("setTimeout", async () => {
|
|
var lastID = -1;
|
|
const result = await new Promise((resolve, reject) => {
|
|
var numbers = [];
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
const id = setTimeout(
|
|
(...args) => {
|
|
numbers.push(i);
|
|
if (i === 9) {
|
|
resolve(numbers);
|
|
}
|
|
try {
|
|
expect(args).toStrictEqual(["foo"]);
|
|
} catch (err) {
|
|
reject(err);
|
|
}
|
|
},
|
|
i,
|
|
"foo",
|
|
);
|
|
expect(+id > lastID).toBe(true);
|
|
lastID = id;
|
|
}
|
|
});
|
|
|
|
for (let j = 0; j < result.length; j++) {
|
|
expect(result[j]).toBe(j);
|
|
}
|
|
expect(result.length).toBe(10);
|
|
});
|
|
|
|
it("clearTimeout", async () => {
|
|
var called = false;
|
|
|
|
// as object
|
|
{
|
|
const id = setTimeout(() => {
|
|
called = true;
|
|
expect.unreachable();
|
|
}, 0);
|
|
clearTimeout(id);
|
|
|
|
// assert it doesn't crash if you call clearTimeout twice
|
|
clearTimeout(id);
|
|
}
|
|
|
|
// as number
|
|
{
|
|
const id = setTimeout(() => {
|
|
called = true;
|
|
expect.unreachable();
|
|
}, 0);
|
|
clearTimeout(+id);
|
|
|
|
// assert it doesn't crash if you call clearTimeout twice
|
|
clearTimeout(+id);
|
|
}
|
|
|
|
await new Promise((resolve, reject) => {
|
|
setTimeout(resolve, 10);
|
|
});
|
|
expect(called).toBe(false);
|
|
});
|
|
|
|
it.todo("setImmediate runs after setTimeout cb", async () => {
|
|
var ranFirst = -1;
|
|
setTimeout(() => {
|
|
if (ranFirst === -1) ranFirst = 1;
|
|
}, 0);
|
|
setImmediate(() => {
|
|
if (ranFirst === -1) ranFirst = 0;
|
|
});
|
|
|
|
await Bun.sleep(5);
|
|
|
|
expect(ranFirst).toBe(1);
|
|
});
|
|
|
|
it("setTimeout(() => {}, 0)", async () => {
|
|
var called = false;
|
|
setTimeout(() => {
|
|
called = true;
|
|
}, 0);
|
|
await new Promise((resolve, reject) => {
|
|
setTimeout(() => {
|
|
resolve();
|
|
}, 10);
|
|
});
|
|
expect(called).toBe(true);
|
|
var ranFirst = -1;
|
|
setTimeout(() => {
|
|
if (ranFirst === -1) ranFirst = 0;
|
|
}, 1);
|
|
setTimeout(() => {
|
|
if (ranFirst === -1) ranFirst = 1;
|
|
}, 0);
|
|
|
|
await new Promise((resolve, reject) => {
|
|
setTimeout(() => {
|
|
resolve();
|
|
}, 10);
|
|
});
|
|
expect(ranFirst).toBe(0);
|
|
|
|
ranFirst = -1;
|
|
|
|
const id = setTimeout(() => {
|
|
ranFirst = 0;
|
|
}, 0);
|
|
clearTimeout(id);
|
|
await new Promise((resolve, reject) => {
|
|
setTimeout(() => {
|
|
resolve();
|
|
}, 10);
|
|
});
|
|
expect(ranFirst).toBe(-1);
|
|
});
|
|
|
|
it("Bun.sleep", async () => {
|
|
var sleeps = 0;
|
|
await Bun.sleep(0);
|
|
const start = performance.now();
|
|
sleeps++;
|
|
await Bun.sleep(1);
|
|
sleeps++;
|
|
await Bun.sleep(2);
|
|
sleeps++;
|
|
const end = performance.now();
|
|
expect((end - start) * 1000).toBeGreaterThan(2);
|
|
|
|
expect(sleeps).toBe(3);
|
|
});
|
|
|
|
it("Bun.sleep propagates exceptions", async () => {
|
|
try {
|
|
await Bun.sleep(1).then(a => {
|
|
throw new Error("TestPassed");
|
|
});
|
|
throw "Should not reach here";
|
|
} catch (err) {
|
|
expect(err.message).toBe("TestPassed");
|
|
}
|
|
});
|
|
|
|
const tolerance = 8;
|
|
it("Bun.sleep works with a Date object", async () => {
|
|
const offset = isWindows ? 100 : 10;
|
|
const init = performance.now();
|
|
var ten_ms = new Date();
|
|
ten_ms.setMilliseconds(ten_ms.getMilliseconds() + offset);
|
|
await Bun.sleep(ten_ms);
|
|
expect(Math.ceil(performance.now() - init + tolerance)).toBeGreaterThanOrEqual(offset);
|
|
});
|
|
|
|
it("Bun.sleep(Date) fulfills after Date", async () => {
|
|
const offset = isWindows ? 100 : 50;
|
|
let ten_ms = new Date();
|
|
const init = performance.now();
|
|
ten_ms.setMilliseconds(ten_ms.getMilliseconds() + offset);
|
|
await Bun.sleep(ten_ms);
|
|
expect(Math.ceil(performance.now() - init + tolerance)).toBeGreaterThanOrEqual(offset);
|
|
});
|
|
|
|
it("node.js timers/promises setTimeout propagates exceptions", async () => {
|
|
const { setTimeout } = require("timers/promises");
|
|
try {
|
|
await setTimeout(1).then(a => {
|
|
throw new Error("TestPassed");
|
|
});
|
|
throw "Should not reach here";
|
|
} catch (err) {
|
|
expect(err.message).toBe("TestPassed");
|
|
}
|
|
});
|
|
|
|
it("order of setTimeouts", done => {
|
|
var nums = [];
|
|
var maybeDone = cb => {
|
|
return () => {
|
|
cb();
|
|
if (nums.length === 4) {
|
|
try {
|
|
expect(nums).toEqual([1, 2, 3, 4]);
|
|
done();
|
|
} catch (e) {
|
|
done(e);
|
|
}
|
|
}
|
|
};
|
|
};
|
|
setTimeout(maybeDone(() => nums.push(2)));
|
|
setTimeout(maybeDone(() => nums.push(3), 0));
|
|
setTimeout(maybeDone(() => nums.push(4), 1));
|
|
Promise.resolve().then(maybeDone(() => nums.push(1)));
|
|
});
|
|
|
|
it("setTimeout -> refresh", () => {
|
|
const { exitCode, stdout } = spawnSync({
|
|
cmd: [bunExe(), path.join(import.meta.dir, "setTimeout-unref-fixture.js")],
|
|
env: bunEnv,
|
|
});
|
|
expect(exitCode).toBe(0);
|
|
expect(stdout.toString()).toBe("SUCCESS\n");
|
|
});
|
|
|
|
it("setTimeout -> unref -> ref works", () => {
|
|
const { exitCode, stdout } = spawnSync({
|
|
cmd: [bunExe(), path.join(import.meta.dir, "setTimeout-unref-fixture-4.js")],
|
|
env: bunEnv,
|
|
});
|
|
expect(exitCode).toBe(0);
|
|
expect(stdout.toString()).toBe("TEST PASSED!\n");
|
|
});
|
|
|
|
it("setTimeout -> ref -> unref works, even if there is another timer", () => {
|
|
const { exitCode, stdout } = spawnSync({
|
|
cmd: [bunExe(), path.join(import.meta.dir, "setTimeout-unref-fixture-2.js")],
|
|
env: bunEnv,
|
|
});
|
|
expect(exitCode).toBe(0);
|
|
expect(stdout.toString()).toBe("");
|
|
});
|
|
|
|
it("setTimeout -> ref -> unref works", () => {
|
|
const { exitCode, stdout } = spawnSync({
|
|
cmd: [bunExe(), path.join(import.meta.dir, "setTimeout-unref-fixture-5.js")],
|
|
env: bunEnv,
|
|
});
|
|
expect(exitCode).toBe(0);
|
|
expect(stdout.toString()).toBe("");
|
|
});
|
|
|
|
it("setTimeout -> unref doesn't keep event loop alive forever", () => {
|
|
const { exitCode, stdout } = spawnSync({
|
|
cmd: [bunExe(), path.join(import.meta.dir, "setTimeout-unref-fixture-3.js")],
|
|
env: bunEnv,
|
|
});
|
|
expect(exitCode).toBe(0);
|
|
expect(stdout.toString()).toBe("");
|
|
});
|
|
|
|
it("setTimeout should refresh N times", done => {
|
|
let count = 0;
|
|
let timer = setTimeout(() => {
|
|
count++;
|
|
expect(timer.refresh()).toBe(timer);
|
|
}, 50);
|
|
|
|
setTimeout(() => {
|
|
clearTimeout(timer);
|
|
try {
|
|
expect(count).toBeGreaterThanOrEqual(isWindows ? 4 : 5);
|
|
} finally {
|
|
done();
|
|
}
|
|
}, 300);
|
|
});
|
|
|
|
it("setTimeout if refreshed before run, should reschedule to run later", done => {
|
|
let start = Date.now();
|
|
let timer = setTimeout(() => {
|
|
let end = Date.now();
|
|
expect(end - start).toBeGreaterThan(120);
|
|
done();
|
|
}, 100);
|
|
|
|
setTimeout(() => {
|
|
timer.refresh();
|
|
}, 50);
|
|
});
|
|
|
|
it("setTimeout should refresh after already been run", done => {
|
|
let count = 0;
|
|
let timer = setTimeout(() => {
|
|
count++;
|
|
}, 50);
|
|
|
|
setTimeout(() => {
|
|
timer.refresh();
|
|
}, 100);
|
|
|
|
setTimeout(() => {
|
|
expect(count).toBe(2);
|
|
done();
|
|
}, 300);
|
|
});
|
|
|
|
it("setTimeout should not refresh after clearTimeout", done => {
|
|
let count = 0;
|
|
let timer = setTimeout(() => {
|
|
count++;
|
|
}, 50);
|
|
|
|
clearTimeout(timer);
|
|
|
|
timer.refresh();
|
|
|
|
setTimeout(() => {
|
|
expect(count).toBe(0);
|
|
done();
|
|
}, 100);
|
|
});
|
|
|
|
it("setTimeout Timeout objects are unprotected after called", async () => {
|
|
let { promise, resolve } = Promise.withResolvers();
|
|
|
|
const initial = heapStats().protectedObjectTypeCounts;
|
|
let remaining = 2;
|
|
setTimeout(() => {
|
|
remaining--;
|
|
if (remaining === 0) resolve();
|
|
}, 0);
|
|
setTimeout(() => {
|
|
remaining--;
|
|
if (remaining === 0) resolve();
|
|
}, 0);
|
|
expect(heapStats().protectedObjectTypeCounts.Timeout || 0).toEqual((initial.Timeout || 0) + 2);
|
|
|
|
// Assert it's unprotected.
|
|
await promise;
|
|
|
|
expect(heapStats().protectedObjectTypeCounts.Timeout || 0).toEqual(initial.Timeout || 0);
|
|
|
|
Bun.gc(true);
|
|
remaining = 5;
|
|
({ promise, resolve } = Promise.withResolvers());
|
|
setInterval(function () {
|
|
remaining--;
|
|
if (remaining === 0) {
|
|
clearInterval(this);
|
|
queueMicrotask(resolve);
|
|
}
|
|
});
|
|
Bun.gc(true);
|
|
await promise;
|
|
expect(heapStats().protectedObjectTypeCounts.Timeout || 0).toEqual(initial.Timeout || 0);
|
|
});
|
|
|
|
it("setTimeout CPU usage #7790", async () => {
|
|
const process = Bun.spawn({
|
|
cmd: [bunExe(), "run", path.join(import.meta.dir, "setTimeout-cpu-fixture.js")],
|
|
env: bunEnv,
|
|
stdout: "inherit",
|
|
});
|
|
const code = await process.exited;
|
|
expect(code).toBe(0);
|
|
const stats = process.resourceUsage();
|
|
expect(stats.cpuTime.total / BigInt(1e6)).toBeLessThan(1);
|
|
});
|
|
|
|
it("Returning a Promise in setTimeout doesnt keep the event loop alive forever", async () => {
|
|
expect([path.join(import.meta.dir, "setTimeout-unref-fixture-6.js")]).toRun();
|
|
});
|
|
|
|
it("Returning a Promise in setTimeout (unref'd) doesnt keep the event loop alive forever", async () => {
|
|
expect([path.join(import.meta.dir, "setTimeout-unref-fixture-7.js")]).toRun();
|
|
});
|
|
|
|
it("setTimeout canceling with unref, close, _idleTimeout, and _onTimeout", () => {
|
|
expect([path.join(import.meta.dir, "timers-fixture-unref.js"), "setTimeout"]).toRun();
|
|
});
|