// Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. // Tests adapted from https://github.com/nodejs/node/blob/main/test/parallel/test-util-promisify.js import { describe, it } from "bun:test"; import fs from "node:fs"; // TODO: vm module not implemented by bun yet // import vm from 'node:vm'; import { promisify } from "util"; import assert from "assert"; const stat = promisify(fs.stat); // A helper function to simplify checking for ERR_INVALID_ARG_TYPE output. function invalidArgTypeHelper(input) { if (input == null) { return ` Received ${input}`; } if (typeof input === "function" && input.name) { return ` Received function ${input.name}`; } if (typeof input === "object") { if (input.constructor?.name) { return ` Received an instance of ${input.constructor.name}`; } return ` Received ${inspect(input, { depth: -1 })}`; } let inspected = inspect(input, { colors: false }); if (inspected.length > 28) { inspected = `${inspected.slice(inspected, 0, 25)}...`; } return ` Received type ${typeof input} (${inspected})`; } describe("util.promisify", () => { describe("promisify fs calls", () => { // TODO: common.mustCall is not implemented here yet // https://github.com/nodejs/node/blob/main/test/common/index.js#L398 it.skip("all cases", () => { const promise = stat(__filename); assert.equal(promise instanceof Promise, true); promise.then( common.mustCall((value) => { assert.deepStrictEqual(value, fs.statSync(__filename)); }), ); const promiseFileDontExist = stat("/dontexist"); promiseFileDontExist.catch( common.mustCall((error) => { assert( error.message.includes("ENOENT: no such file or directory, stat"), ); }), ); }); }); describe("promisify.custom", () => { it("double promisify", () => { function fn() {} function promisifedFn() {} fn[promisify.custom] = promisifedFn; assert.strictEqual(promisify(fn), promisifedFn); assert.strictEqual(promisify(promisify(fn)), promisifedFn); }); it.skip("should register shared promisify symbol", () => { function fn() {} function promisifiedFn() {} // TODO: register shared symbol promisify.custom // util.promisify.custom is a shared symbol which can be accessed // as `Symbol.for("nodejs.util.promisify.custom")`. const kCustomPromisifiedSymbol = Symbol.for( "nodejs.util.promisify.custom", ); fn[kCustomPromisifiedSymbol] = promisifiedFn; assert.strictEqual(kCustomPromisifiedSymbol, promisify.custom); assert.strictEqual(promisify(fn), promisifiedFn); assert.strictEqual(promisify(promisify(fn)), promisifiedFn); }); }); it("should fail when type is not a function", () => { function fn() {} fn[promisify.custom] = 42; assert.throws( () => promisify(fn), // TODO: error code is not the same as node's. // { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' } { name: "TypeError" }, ); }); it("should call custom promised promised function with proper args", () => { const firstValue = 5; const secondValue = 17; var called = false; function fn(callback) { called = true; callback(null, firstValue, secondValue); } fn[Symbol("customPromisifyArgs")] = ["first", "second"]; promisify(fn)().then((firstValue, secondValue) => { assert.strictEqual(called, true); assert.strictEqual(firstValue, 5); assert.strictEqual(secondValue, 17); }); }); // TODO: unable to test since vm module not implemented // it("should run in new vm context", () => { // const fn = vm.runInNewContext('(function() {})'); // assert.notStrictEqual(Object.getPrototypeOf(promisify(fn)),Function.prototype); // }); describe("callback cases", () => { it("should run basic callback", async () => { var called = false; function fn(callback) { called = true; callback(null, "foo", "bar"); } await promisify(fn)().then((value) => { assert.strictEqual(value, "foo"); assert.strictEqual(called, true); }); }); it("should not require value to be returned in callback", async () => { var called = false; function fn(callback) { called = true; callback(null); } await promisify(fn)().then((value) => { assert.strictEqual(value, undefined); assert.strictEqual(called, true); }); }); it("should not require error to be passed", async () => { var called = false; function fn(callback) { called = true; callback(); } await promisify(fn)().then((value) => { assert.strictEqual(value, undefined); assert.strictEqual(called, true); }); }); it("custom callback", async () => { var called = false; function fn(err, val, callback) { called = true; callback(err, val); } await promisify(fn)(null, 42).then((value) => { assert.strictEqual(value, 42); assert.strictEqual(called, true); }); }); it("should catch error", async () => { var called = false; function fn(err, val, callback) { called = true; callback(err, val); } await promisify(fn)(new Error("oops"), null).catch((err) => { assert.strictEqual(err.message, "oops"); assert.strictEqual(called, true); }); }); it("should call promisify properly inside async block", async () => { var called = false; function fn(err, val, callback) { called = true; callback(err, val); } await (async () => { const value = await promisify(fn)(null, 42); assert.strictEqual(value, 42); })().then(() => { assert.strictEqual(called, true); }); }); it("should not break this reference", async () => { const o = {}; var called = false; const fn = promisify(function (cb) { called = true; cb(null, this === o); }); o.fn = fn; await o.fn().then((val) => { assert.strictEqual(called, true); assert.strictEqual(val, true); }); }); it("should not have called callback with error", async () => { const err = new Error( "Should not have called the callback with the error.", ); const stack = err.stack; var called = false; const fn = promisify(function (cb) { called = true; cb(null); cb(err); }); await (async () => { await fn(); await Promise.resolve(); return assert.strictEqual(stack, err.stack); })().then(() => { assert.strictEqual(called, true); }); }); it("should compare promised objects properly", () => { function c() {} const a = promisify(function () {}); const b = promisify(a); assert.notStrictEqual(c, a); assert.strictEqual(a, b); }); it("should throw error", async () => { let errToThrow; const thrower = promisify(function (a, b, c, cb) { errToThrow = new Error(); throw errToThrow; }); await thrower(1, 2, 3) .then(assert.fail) .then(assert.fail, (e) => assert.strictEqual(e, errToThrow)); }); it("should also throw error inside Promise.all", async () => { const err = new Error(); const a = promisify((cb) => cb(err))(); const b = promisify(() => { throw err; })(); await Promise.all([ a.then(assert.fail, function (e) { assert.strictEqual(err, e); }), b.then(assert.fail, function (e) { assert.strictEqual(err, e); }), ]); }); }); describe("invalid input", () => { // This test is failing because 'code' property // is not thrown in the error. does it have different // throw error implementation in bun? it("should throw on invalid inputs for promisify", () => { [undefined, null, true, 0, "str", {}, [], Symbol()].forEach((input) => { assert.throws(() => promisify(input), { code: "ERR_INVALID_ARG_TYPE", name: "TypeError", message: 'The "original" argument must be of type Function', }); }); }); }); });