diff options
-rw-r--r-- | src/bun.js/child_process.exports.js | 19 | ||||
-rw-r--r-- | test/bun.js/bun-spawn-test.js | 21 | ||||
-rw-r--r-- | test/bun.js/child_process-node.test.js | 367 | ||||
-rw-r--r-- | test/bun.js/child_process.test.ts | 24 | ||||
-rw-r--r-- | test/bun.js/node-test-helpers.js | 145 | ||||
-rw-r--r-- | test/bun.js/node-test-helpers.test.js | 157 | ||||
-rw-r--r-- | test/bun.js/spawned-child.js | 1 |
7 files changed, 497 insertions, 237 deletions
diff --git a/src/bun.js/child_process.exports.js b/src/bun.js/child_process.exports.js index 9219e3ab6..eff7bfca0 100644 --- a/src/bun.js/child_process.exports.js +++ b/src/bun.js/child_process.exports.js @@ -9,6 +9,10 @@ const { const MAX_BUFFER = 1024 * 1024; const debug = process.env.DEBUG ? console.log : () => {}; +const platformTmpDir = `${process.platform === "darwin" ? "/private" : ""}${ + process.env.TMPDIR +}`.slice(0, -1); // remove trailing slash + // Sections: // 1. Exported child_process functions // 2. child_process helpers @@ -253,8 +257,7 @@ export function execFile(file, args, options, callback) { stderr = BufferConcat(_stderr); } - // TODO: Make this check code === 0 when Bun.spawn fixes exit code issue - if (!ex && code >= 0 && signal === null) { + if (!ex && code === 0 && signal === null) { callback(null, stdout, stderr); return; } @@ -843,6 +846,7 @@ function checkExecSyncError(ret, args, cmd) { //------------------------------------------------------------------------------ export class ChildProcess extends EventEmitter { #handle; + #handleExited; #exited = false; #closesNeeded = 1; #closesGot = 0; @@ -865,12 +869,12 @@ export class ChildProcess extends EventEmitter { // this.#handle[owner_symbol] = this; // } - #handleOnExit(exitCode, signalCode) { + async #handleOnExit(exitCode, signalCode) { if (this.#exited) return; if (signalCode) { this.signalCode = signalCode; } else { - this.exitCode = exitCode; + this.exitCode = this.#handle.exitCode; } if (this.stdin) { @@ -890,6 +894,12 @@ export class ChildProcess extends EventEmitter { err.spawnargs = ArrayPrototypeSlice.call(this.spawnargs, 1); this.emit("error", err); } else { + const maybeExited = Bun.peek(this.#handleExited); + if (maybeExited === this.#handleExited) { + this.exitCode = await this.#handleExited; + } else { + this.exitCode = maybeExited; + } this.emit("exit", this.exitCode, this.signalCode); } @@ -979,6 +989,7 @@ export class ChildProcess extends EventEmitter { env: options.envPairs || undefined, onExit: this.#handleOnExit.bind(this), }); + this.#handleExited = this.#handle.exited; this.stdio = this.#getBunSpawnIo(bunStdio, options); this.stdin = this.stdio[0]; diff --git a/test/bun.js/bun-spawn-test.js b/test/bun.js/bun-spawn-test.js new file mode 100644 index 000000000..1617a8588 --- /dev/null +++ b/test/bun.js/bun-spawn-test.js @@ -0,0 +1,21 @@ +const EventEmitter = import.meta.require("events"); +class TestClass extends EventEmitter { + #handle = null; + spawn() { + this.#handle = Bun.spawn(["pwd"], { + cwd: "/tmp", + onExit: this.#handleOnExit.bind(this), + }); + } + #handleOnExit(code) { + console.log(code); + this.emit("exit"); + } +} + +const testClass = new TestClass(); +testClass.spawn(); +testClass.on("exit", () => { + console.log("exiting"); + process.exit(0); +}); diff --git a/test/bun.js/child_process-node.test.js b/test/bun.js/child_process-node.test.js index 10135affa..3ac9b2fd1 100644 --- a/test/bun.js/child_process-node.test.js +++ b/test/bun.js/child_process-node.test.js @@ -1,10 +1,19 @@ -import { describe, expect, it } from "bun:test"; +import { beforeAll, describe, it } from "bun:test"; import { ChildProcess, spawn, exec } from "node:child_process"; -import { EOL } from "node:os"; -import assertNode from "node:assert"; -import { inspect } from "node:util"; +import { + strictEqual, + throws, + assert, + assertOk, + createCallCheckCtx, + createDoneDotAll, +} from "node-test-helpers"; -const debug = console.log; +const debug = process.env.DEBUG ? console.log : () => {}; + +const platformTmpDir = `${process.platform === "darwin" ? "/private" : ""}${ + process.env.TMPDIR +}`.slice(0, -1); // remove trailing slash // Copyright Joyent, Inc. and other Node contributors. // @@ -28,154 +37,9 @@ const debug = console.log; // USE OR OTHER DEALINGS IN THE SOFTWARE. const common = { - // // TODO: Fix the implementations of these functions, they may be ruining everything... - // mustCallAtLeast: function mustCallAtLeast(callback) { - // return (...args) => { - // callback(...args); - // expect(true).toBe(true); - // }; - // }, - // mustCall: function mustCall(callback) { - // return (...args) => { - // callback(...args); - // expect(true).toBe(true); - // }; - // }, pwdCommand: ["pwd", []], }; -const mustCallChecks = []; - -function runCallChecks(exitCode) { - if (exitCode !== 0) return; - - const failed = mustCallChecks.filter(function (context) { - if ("minimum" in context) { - context.messageSegment = `at least ${context.minimum}`; - return context.actual < context.minimum; - } - context.messageSegment = `exactly ${context.exact}`; - return context.actual !== context.exact; - }); - - failed.forEach(function (context) { - console.log( - "Mismatched %s function calls. Expected %s, actual %d.", - context.name, - context.messageSegment, - context.actual - ); - console.log(context.stack.split("\n").slice(2).join("\n")); - }); - - if (failed.length) process.exit(1); -} - -function mustCall(fn, exact) { - return _mustCallInner(fn, exact, "exact"); -} - -function mustSucceed(fn, exact) { - return mustCall(function (err, ...args) { - assert.ifError(err); - if (typeof fn === "function") return fn.apply(this, args); - }, exact); -} - -function mustCallAtLeast(fn, minimum) { - return _mustCallInner(fn, minimum, "minimum"); -} - -function _mustCallInner(fn, criteria = 1, field) { - if (process._exiting) - throw new Error("Cannot use common.mustCall*() in process exit handler"); - if (typeof fn === "number") { - criteria = fn; - fn = noop; - } else if (fn === undefined) { - fn = noop; - } - - if (typeof criteria !== "number") - throw new TypeError(`Invalid ${field} value: ${criteria}`); - - const context = { - [field]: criteria, - actual: 0, - stack: inspect(new Error()), - name: fn.name || "<anonymous>", - }; - - // Add the exit listener only once to avoid listener leak warnings - if (mustCallChecks.length === 0) process.on("exit", runCallChecks); - - mustCallChecks.push(context); - - const _return = function () { - // eslint-disable-line func-style - context.actual++; - return fn.apply(this, arguments); - }; - // Function instances have own properties that may be relevant. - // Let's replicate those properties to the returned function. - // Refs: https://tc39.es/ecma262/#sec-function-instances - Object.defineProperties(_return, { - name: { - value: fn.name, - writable: false, - enumerable: false, - configurable: true, - }, - length: { - value: fn.length, - writable: false, - enumerable: false, - configurable: true, - }, - }); - return _return; -} - -const strictEqual = (...args) => { - let error = null; - try { - assertNode.strictEqual(...args); - } catch (err) { - error = err; - } - expect(error).toBe(null); -}; - -const throws = (...args) => { - let error = null; - try { - assertNode.throws(...args); - } catch (err) { - error = err; - } - expect(error).toBe(null); -}; - -const assert = (...args) => { - let error = null; - try { - assertNode(...args); - } catch (err) { - error = err; - } - expect(error).toBe(null); -}; - -const assertOk = (...args) => { - let error = null; - try { - assertNode.ok(...args); - } catch (err) { - error = err; - } - expect(error).toBe(null); -}; - describe("ChildProcess.constructor", () => { it("should be a function", () => { strictEqual(typeof ChildProcess, "function"); @@ -302,62 +166,80 @@ describe("ChildProcess spawn bad stdio", () => { // Monkey patch spawn() to create a child process normally, but destroy the // stdout and stderr streams. This replicates the conditions where the streams // cannot be properly created. - const original = ChildProcess.prototype.spawn; - - ChildProcess.prototype.spawn = function () { - const err = original.apply(this, arguments); - - this.stdout.destroy(); - this.stderr.destroy(); - this.stdout = null; - this.stderr = null; - - return err; - }; - - function createChild(options, callback) { - const cmd = `"${process.execPath}" "${import.meta.path}" child`; - return exec(cmd, options, mustCall(callback)); + function createChild(options, callback, done) { + var __originalSpawn = ChildProcess.prototype.spawn; + ChildProcess.prototype.spawn = function () { + const err = __originalSpawn.apply(this, arguments); + + this.stdout.destroy(); + this.stderr.destroy(); + this.stdout = null; + this.stderr = null; + + return err; + }; + + const { mustCall } = createCallCheckCtx(done); + const cmd = `bun ${__dirname}/spawned-child.js`; + const child = exec(cmd, options, mustCall(callback)); + ChildProcess.prototype.spawn = __originalSpawn; + return child; } - it("should handle normal execution of child process", () => { - createChild({}, (err, stdout, stderr) => { - strictEqual(err, null); - strictEqual(stdout, ""); - strictEqual(stderr, ""); - }); + it("should handle normal execution of child process", (done) => { + createChild( + {}, + (err, stdout, stderr) => { + strictEqual(err, null); + strictEqual(stdout, ""); + strictEqual(stderr, ""); + }, + done + ); }); - it("should handle error event of child process", () => { + it("should handle error event of child process", (done) => { const error = new Error("foo"); - const child = createChild({}, (err, stdout, stderr) => { - strictEqual(err, error); - strictEqual(stdout, ""); - strictEqual(stderr, ""); - }); + const child = createChild( + {}, + (err, stdout, stderr) => { + strictEqual(err, error); + strictEqual(stdout, ""); + strictEqual(stderr, ""); + }, + done + ); child.emit("error", error); }); - it("should handle killed process", () => { - createChild({ timeout: 1 }, (err, stdout, stderr) => { - strictEqual(err.killed, true); - strictEqual(stdout, ""); - strictEqual(stderr, ""); - }); + it("should handle killed process", (done) => { + createChild( + { timeout: 1 }, + (err, stdout, stderr) => { + strictEqual(err.killed, true); + strictEqual(stdout, ""); + strictEqual(stderr, ""); + }, + done + ); }); - - ChildProcess.prototype.spawn = original; }); describe("child_process cwd", () => { - const tmpdir = { path: Bun.env.TMPDIR }; - // Spawns 'pwd' with given options, then test // - whether the child pid is undefined or number, // - whether the exit code equals expectCode, // - optionally whether the trimmed stdout result matches expectData - function testCwd(options, expectPidType, expectCode = 0, expectData) { + function testCwd( + options, + { expectPidType, expectCode = 0, expectData }, + done = () => {} + ) { + const createDone = createDoneDotAll(done); + const { mustCall } = createCallCheckCtx(createDone(1500)); + const exitDone = createDone(5000); + const child = spawn(...common.pwdCommand, options); strictEqual(typeof child.pid, expectPidType); @@ -366,19 +248,25 @@ describe("child_process cwd", () => { // No need to assert callback since `data` is asserted. let data = ""; - child.stdout.on("data", function (chunk) { + child.stdout.on("data", (chunk) => { data += chunk; }); - // Can't assert callback, as stayed in to API: - // _The 'exit' event may or may not fire after an error has occurred._ - child.on("exit", function (code, signal) { - strictEqual(code, expectCode).bind(this); + // TODO: Test exit events + // // Can't assert callback, as stayed in to API: + // // _The 'exit' event may or may not fire after an error has occurred._ + child.on("exit", (code, signal) => { + try { + strictEqual(code, expectCode); + exitDone(); + } catch (err) { + exitDone(err); + } }); child.on( "close", - mustCall(function () { + mustCall(() => { expectData && strictEqual(data.trim(), expectData); }) ); @@ -426,55 +314,82 @@ describe("child_process cwd", () => { // // } // }); - it("should work for valid given cwd", () => { + it("should work for valid given cwd", (done) => { + const tmpdir = { path: Bun.env.TMPDIR }; + const createDone = createDoneDotAll(done); + // Assume these exist, and 'pwd' gives us the right directory back - testCwd({ cwd: tmpdir.path }, "number", 0, tmpdir.path); + testCwd( + { cwd: tmpdir.path }, + { + expectPidType: "number", + expectCode: 0, + expectData: platformTmpDir, + }, + createDone(1500) + ); const shouldExistDir = "/dev"; - testCwd({ cwd: shouldExistDir }, "number", 0, shouldExistDir); - testCwd({ cwd: Bun.pathToFileURL(tmpdir.path) }, "number", 0, tmpdir.path); + testCwd( + { cwd: shouldExistDir }, + { + expectPidType: "number", + expectCode: 0, + expectData: shouldExistDir, + }, + createDone(1500) + ); + testCwd( + { cwd: Bun.pathToFileURL(tmpdir.path) }, + { + expectPidType: "number", + expectCode: 0, + expectData: platformTmpDir, + }, + createDone(1500) + ); }); - it("shouldn't try to chdir to an invalid cwd", () => { + it("shouldn't try to chdir to an invalid cwd", (done) => { + const createDone = createDoneDotAll(done); // Spawn() shouldn't try to chdir() to invalid arg, so this should just work - testCwd({ cwd: "" }, "number"); - testCwd({ cwd: undefined }, "number"); - testCwd({ cwd: null }, "number"); + testCwd({ cwd: "" }, { expectPidType: "number" }, createDone(1500)); + testCwd({ cwd: undefined }, { expectPidType: "number" }, createDone(1500)); + testCwd({ cwd: null }, { expectPidType: "number" }, createDone(1500)); }); }); describe("child_process default options", () => { - process.env.HELLO = "WORLD"; - - let child = spawn("/usr/bin/env", [], {}); - let response = ""; + it("should use process.env as default env", (done) => { + let child = spawn("printenv", [], {}); + let response = ""; - child.stdout.setEncoding("utf8"); - - it("should use process.env as default env", () => { - child.stdout.on("data", function (chunk) { + child.stdout.setEncoding("utf8"); + child.stdout.on("data", (chunk) => { debug(`stdout: ${chunk}`); response += chunk; }); - process.on("exit", function () { + // NOTE: Original test used child.on("exit"), but this is unreliable + // because the process can exit before the stream is closed and the data is read + child.stdout.on("close", () => { assertOk( - response.includes("HELLO=WORLD"), + response.includes(`TMPDIR=${process.env.TMPDIR}`), "spawn did not use process.env as default " + - `(process.env.HELLO = ${process.env.HELLO})` + `(process.env.TMPDIR = ${process.env.TMPDIR})` ); + done(); }); }); - - delete process.env.HELLO; }); describe("child_process double pipe", () => { - let grep, sed, echo; - grep = spawn("grep", ["o"]); - sed = spawn("sed", ["s/o/O/"]); - echo = spawn("echo", ["hello\nnode\nand\nworld\n"]); + it("should allow two pipes to be used at once", (done) => { + const { mustCallAtLeast, mustCall } = createCallCheckCtx(done); + let grep, sed, echo; + grep = spawn("grep", ["o"]); + sed = spawn("sed", ["s/o/O/"]); + echo = spawn("echo", ["hello\nnode\nand\nworld\n"]); - it("should allow two pipes to be used at once", () => { // pipe echo | grep echo.stdout.on( "data", @@ -497,7 +412,8 @@ describe("child_process double pipe", () => { // Propagate end from echo to grep echo.stdout.on( "end", - mustCall((code) => { + mustCall(() => { + debug("echo stdout end"); grep.stdin.end(); }) ); @@ -555,15 +471,16 @@ describe("child_process double pipe", () => { sed.stdout.on( "data", mustCallAtLeast((data) => { - result += data.toString("utf8", 0, data.length); + result += data.toString("utf8"); debug(data); }) ); sed.stdout.on( "end", - mustCall((code) => { - strictEqual(result, `hellO${EOL}nOde${EOL}wOrld${EOL}`); + mustCall(() => { + debug("result: " + result); + strictEqual(result, `hellO\nnOde\nwOrld\n`); }) ); }); diff --git a/test/bun.js/child_process.test.ts b/test/bun.js/child_process.test.ts index 6387589b0..536f4947b 100644 --- a/test/bun.js/child_process.test.ts +++ b/test/bun.js/child_process.test.ts @@ -10,6 +10,12 @@ import { execSync, } from "node:child_process"; +const debug = process.env.DEBUG ? console.log : () => {}; + +const platformTmpDir = `${process.platform === "darwin" ? "/private" : ""}${ + process.env.TMPDIR +}`.slice(0, -1); // remove trailing slash + // Semver regex: https://gist.github.com/jhorsman/62eeea161a13b80e39f5249281e17c39?permalink_comment_id=2896416#gistcomment-2896416 // Not 100% accurate, but good enough for this test const SEMVER_REGEX = @@ -65,11 +71,11 @@ describe("spawn()", () => { console.error(e); }); child.stdout.on("data", (data) => { - console.log(`stdout: ${data}`); + debug(`stdout: ${data}`); resolve(data.toString()); }); child.stderr.on("data", (data) => { - console.log(`stderr: ${data}`); + debug(`stderr: ${data}`); }); }); expect(SEMVER_REGEX.test(result.trim())).toBe(true); @@ -120,10 +126,7 @@ describe("spawn()", () => { resolve(data.toString()); }); }); - const platformTmpDir = `${process.platform === "darwin" ? "/private" : ""}${ - process.env.TMPDIR - }`; - expect(`${result.trim()}/`).toBe(platformTmpDir); + expect(result.trim()).toBe(platformTmpDir); }); it("should allow us to write to stdin", async () => { @@ -261,11 +264,16 @@ describe("execSync()", () => { describe("Bun.spawn()", () => { it("should return exit code 0 on successful execution", async () => { const result = await new Promise((resolve) => { - Bun.spawn({ + const proc = Bun.spawn({ cmd: ["echo", "hello"], - onExit: (code) => resolve(code), stdout: "inherit", }); + const maybeExited = Bun.peek(proc.exited); + if (maybeExited === proc.exited) { + proc.exited.then((code) => resolve(code)); + } else { + resolve(maybeExited); + } }); expect(result).toBe(0); }); diff --git a/test/bun.js/node-test-helpers.js b/test/bun.js/node-test-helpers.js new file mode 100644 index 000000000..e7f6c74f6 --- /dev/null +++ b/test/bun.js/node-test-helpers.js @@ -0,0 +1,145 @@ +import { expect } from "bun:test"; +import assertNode from "node:assert"; + +export const strictEqual = (...args) => { + let error = null; + try { + assertNode.strictEqual(...args); + } catch (err) { + error = err; + } + expect(error).toBe(null); +}; + +export const throws = (...args) => { + let error = null; + try { + assertNode.throws(...args); + } catch (err) { + error = err; + } + expect(error).toBe(null); +}; + +export const assert = (...args) => { + let error = null; + try { + assertNode(...args); + } catch (err) { + error = err; + } + expect(error).toBe(null); +}; + +export const assertOk = (...args) => { + let error = null; + try { + assertNode.ok(...args); + } catch (err) { + error = err; + } + expect(error).toBe(null); +}; + +export const createCallCheckCtx = (done, timeout = 1500) => { + const createDone = createDoneDotAll(done); + // const mustCallChecks = []; + + // failed.forEach(function (context) { + // console.log( + // "Mismatched %s function calls. Expected %s, actual %d.", + // context.name, + // context.messageSegment, + // context.actual + // ); + // console.log(context.stack.split("\n").slice(2).join("\n")); + // }); + + // TODO: Implement this to be exact only + function mustCall(fn, exact) { + return mustCallAtLeast(fn, exact); + } + + function mustSucceed(fn, exact) { + return mustCall(function (err, ...args) { + assert.ifError(err); + if (typeof fn === "function") return fn.apply(this, args); + }, exact); + } + + function mustCallAtLeast(fn, minimum) { + return _mustCallInner(fn, minimum, "minimum"); + } + + function _mustCallInner(fn, criteria = 1, field) { + if (process._exiting) + throw new Error("Cannot use common.mustCall*() in process exit handler"); + if (typeof fn === "number") { + criteria = fn; + fn = noop; + } else if (fn === undefined) { + fn = noop; + } + + if (typeof criteria !== "number") + throw new TypeError(`Invalid ${field} value: ${criteria}`); + + let actual = 0; + let expected = criteria; + + // mustCallChecks.push(context); + const done = createDone(timeout); + const _return = (...args) => { + const result = fn.apply(this, args); + actual++; + if (actual >= expected) { + done(); + } + return result; + }; + // Function instances have own properties that may be relevant. + // Let's replicate those properties to the returned function. + // Refs: https://tc39.es/ecma262/#sec-function-instances + Object.defineProperties(_return, { + name: { + value: fn.name, + writable: false, + enumerable: false, + configurable: true, + }, + length: { + value: fn.length, + writable: false, + enumerable: false, + configurable: true, + }, + }); + return _return; + } + return { + mustSucceed, + mustCall, + mustCallAtLeast, + }; +}; + +export function createDoneDotAll(done) { + let toComplete = 0; + let completed = 0; + function createDoneCb(timeout) { + toComplete += 1; + const timer = setTimeout(() => done(new Error("Timed out!")), timeout); + return (result) => { + clearTimeout(timer); + if (result instanceof Error) { + done(result); + return; + } + completed += 1; + if (completed === toComplete) { + done(); + } + }; + } + return createDoneCb; +} diff --git a/test/bun.js/node-test-helpers.test.js b/test/bun.js/node-test-helpers.test.js new file mode 100644 index 000000000..7fb2de320 --- /dev/null +++ b/test/bun.js/node-test-helpers.test.js @@ -0,0 +1,157 @@ +import { describe, it, expect } from "bun:test"; +import { + throws, + assert, + strictEqual, + createCallCheckCtx, + createDoneDotAll, +} from "node-test-helpers"; + +describe("OurAssert.throws()", () => { + it("should pass when the function throws", () => { + throws(() => { + throw new Error("THROWN!"); + }); + }); + + it("should fail when the function doesn't throw", () => { + let err; + try { + throws(() => {}, Error); + } catch (e) { + err = e; + } + + console.log(err.code); + expect(err instanceof Error).toBe(true); + }); +}); + +describe("OurAssert.assert()", () => { + it("should pass when the provided value is true", () => { + assert(true); + }); + + it("should fail when the provided value is false", () => { + let err; + try { + assert(false); + } catch (e) { + err = e; + } + expect(err instanceof Error).toBe(true); + }); +}); + +describe("OurAssert.strictEqual()", () => { + it("should pass when the provided values are deeply equal", () => { + strictEqual(1, 1); + strictEqual("hello", "hello"); + const testing = { hello: "world" }; + const testing2 = testing; + testing2.hello = "bla"; + strictEqual(testing, testing2); + strictEqual(NaN, NaN); + strictEqual(Infinity, Infinity); + strictEqual(-Infinity, -Infinity); + strictEqual(null, null); + strictEqual(undefined, undefined); + }); + + it("should fail when the provided values are not deeply equal", () => { + let err = null; + try { + strictEqual(1, 5); + } catch (e) { + err = e; + } + expect(err instanceof Error).toBe(true); + err = null; + try { + strictEqual({ foo: "bar" }, { foo: "bar" }); + } catch (e) { + err = e; + } + expect(err instanceof Error).toBe(true); + err = null; + try { + strictEqual("1", 1); + } catch (e) { + err = e; + } + expect(err instanceof Error).toBe(true); + err = null; + const obj1 = { foo: "bar" }; + const obj2 = JSON.parse(JSON.stringify(obj1)); + try { + strictEqual(obj1, obj2); + } catch (e) { + err = e; + } + expect(err instanceof Error).toBe(true); + }); +}); + +describe("OurAssert.createCallCheckCtx", () => { + it("should pass when all mustCall marked callbacks have been called", (done) => { + const { mustCall } = createCallCheckCtx(done); + const fn1 = mustCall(() => {}); + const fn2 = mustCall(() => {}); + fn1(); + fn2(); + }); + + it("should fail when all mustCall marked callbacks have NOT been called", (done) => { + const mockDone = (result) => { + expect(result instanceof Error).toBe(true); + done(); + }; + const { mustCall } = createCallCheckCtx(mockDone, 600); + const fn1 = mustCall(() => {}); + mustCall(() => {}); + fn1(); + }); + + it("should allow us to get the args of the wrapped callback from mustCall", (done) => { + const { mustCall } = createCallCheckCtx(done); + const fn1 = mustCall((arg1, arg2) => { + expect(arg1).toBe("hello"); + expect(arg2).toBe("world"); + }); + fn1("hello", "world"); + }); +}); + +describe("OurAssert.createDoneDotAll()", () => { + it("should pass when all dones have been called", (done) => { + const createDone = createDoneDotAll(done); + const done1 = createDone(600); + const done2 = createDone(600); + setTimeout(() => done1(), 300); + setTimeout(() => done2(), 450); + }); + + it("should fail when all dones have NOT been called before timeout", (done) => { + const mockDone = (result) => { + expect(result instanceof Error).toBe(true); + done(); + }; + const createDone = createDoneDotAll(mockDone); + const done1 = createDone(400); + createDone(400); + setTimeout(() => done1(), 200); + }); + + it("should allow us to combine mustCall and multiple dones", (done) => { + const createDone = createDoneDotAll(done); + const { mustCall } = createCallCheckCtx(createDone(600)); + const done1 = createDone(600); + const done2 = createDone(600); + const fn1 = mustCall(() => {}); + const fn2 = mustCall(() => {}); + setTimeout(() => done1(), 300); + setTimeout(() => done2(), 450); + setTimeout(() => fn1(), 200); + setTimeout(() => fn2(), 200); + }); +}); diff --git a/test/bun.js/spawned-child.js b/test/bun.js/spawned-child.js new file mode 100644 index 000000000..757aacd5c --- /dev/null +++ b/test/bun.js/spawned-child.js @@ -0,0 +1 @@ +setTimeout(() => console.log("hello"), 150); |