@power-doctest/tester
Version:
A Test Runner for A power-doctest.
168 lines (165 loc) • 6.62 kB
JavaScript
import * as vm from "node:vm";
import { convertCode } from "@power-doctest/core";
// @ts-expect-error: no types
import assert from "power-assert";
const CALLBACK_FUNCTION_NAME = "__power_doctest_runner_callback__";
export function test(parsedCode, oprions) {
if (parsedCode.state === "disabled") {
return Promise.resolve();
}
if (oprions?.disableRunning && parsedCode.state === "none") {
return Promise.resolve();
}
return run(parsedCode.code, {
...(oprions?.defaultDoctestRunnerOptions ? oprions.defaultDoctestRunnerOptions : {}),
...parsedCode.doctestOptions,
}).catch((error) => {
// if it is expected error, resolve it
if (parsedCode.expectedError && error.name === parsedCode.expectedError) {
return Promise.resolve();
}
const doctestOptions = parsedCode.doctestOptions;
error.fileName = doctestOptions?.filePath ? doctestOptions.filePath : error.fileName;
error.lineNumber = parsedCode.location.start.line;
error.columnNumber = parsedCode.location.start.column;
const metadata = parsedCode.metadata;
if (metadata) {
error.meta = metadata;
}
return Promise.reject(error);
});
}
export function run(code, options = {}) {
const filePath = options.filePath || "default.js";
const runMode = options.runMode || "all";
const timeout = options.timeout !== undefined ? options.timeout : 2000;
const postCallbackName = options.powerDoctestCallbackFunctionName || CALLBACK_FUNCTION_NAME;
const context = options.context || {};
// total count of assert
let totalAssertionCount = 0;
// current count of assert
let countOfExecutedAssertion = 0;
return new Promise((resolve, reject) => {
let isSettled = false;
const timeoutId = setTimeout(() => {
if (isSettled) {
return;
}
restoreListener();
reject(new Error(`Timeout error
${runMode === "all"
? `If you use { "runMode": "all" }, you should check all condition flow is passed.
Total Assertion: ${totalAssertionCount}
Executed Assertion: ${countOfExecutedAssertion}
Also, you should consider to use { "runMode": "any" }`
: ""}`));
}, timeout);
// Test Runner like mocha listen unhandledRejection and uncaughtException
// Disable these listener before running code
const originalUnhandledRejection = process.listeners("unhandledRejection");
const originalUncaughtException = process.listeners("uncaughtException");
process.removeAllListeners("uncaughtException");
process.removeAllListeners("unhandledRejection");
const unhandledRejection = (reason, _promise) => {
restoreListener();
reject(reason);
};
const uncaughtException = (error) => {
restoreListener();
reject(error);
};
const restoreListener = () => {
isSettled = true;
process.removeListener("uncaughtException", uncaughtException);
process.removeListener("unhandledRejection", unhandledRejection);
// restore
const currentUncaughtException = process.listeners("uncaughtException");
const currentUnhandledRejection = process.listeners("unhandledRejection");
originalUncaughtException
.filter((listener) => {
// remove duplicated
return !currentUncaughtException.includes(listener);
})
.forEach((listener) => {
process.addListener("uncaughtException", listener);
});
originalUnhandledRejection
.filter((listener) => {
// remove duplicated
return !currentUnhandledRejection.includes(listener);
})
.forEach((listener) => {
process.addListener("unhandledRejection", listener);
});
// clearTimeout
clearTimeout(timeoutId);
};
process.on("uncaughtException", uncaughtException);
process.on("unhandledRejection", unhandledRejection);
const poweredCode = convertCode(code, {
assertAfterCallbackName: postCallbackName,
filePath: filePath,
});
// total count of assert
totalAssertionCount = poweredCode.split(CALLBACK_FUNCTION_NAME).length - 1;
// current count of assert
countOfExecutedAssertion = 0;
try {
const HostBuildIns = Object.freeze({
__proto__: null,
version: parseInt(process.versions.node.split(".")[0]),
process,
console,
setTimeout,
setInterval,
setImmediate,
clearTimeout,
clearInterval,
clearImmediate,
});
const script = new vm.Script(poweredCode, {
filename: options.filePath,
});
const vmContext = vm.createContext({
require: (moduleName) => {
if (moduleName === "power-assert") {
return assert;
}
// options.requireMock
if (options.requireMock && Object.hasOwn(options.requireMock, moduleName)) {
return options.requireMock[moduleName];
}
return require(moduleName);
},
[postCallbackName]: (_id) => {
countOfExecutedAssertion++;
if (runMode === "all" && countOfExecutedAssertion === totalAssertionCount) {
// when all finish
restoreListener();
resolve();
}
else if (runMode === "any") {
// when anyone finish
restoreListener();
resolve();
}
},
...HostBuildIns,
...context,
});
script.runInNewContext(vmContext, {
timeout,
});
}
catch (error) {
restoreListener();
reject(error);
}
// No assertion code
if (totalAssertionCount === 0) {
restoreListener();
resolve();
}
});
}
//# sourceMappingURL=index.js.map