mo/run-spec.js

259 lines
6.5 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
2023-04-08 16:52:22 +00:00
const exec = require("child_process").exec;
const fsPromises = require("fs").promises;
function specFileToName(file) {
return file
.replace(/.*\//, "")
.replace(".json", "")
.replace("~", "")
.replace(/(^|-)[a-z]/g, function (match) {
return match.toUpperCase();
});
}
function processArraySequentially(array, callback) {
function processCopy() {
if (arrayCopy.length) {
const item = arrayCopy.shift();
return Promise.resolve(item)
.then(callback)
.then((singleResult) => {
result.push(singleResult);
return processCopy();
});
} else {
return Promise.resolve(result);
}
}
const result = [];
const arrayCopy = array.slice();
return processCopy();
}
function debug(...args) {
if (process.env.DEBUG) {
console.debug(...args);
}
}
function makeShellString(value) {
2023-04-08 16:52:22 +00:00
if (typeof value === "string") {
// Newlines are tricky
2023-04-08 16:52:22 +00:00
return value
.split(/\n/)
.map(function (chunk) {
return JSON.stringify(chunk);
})
.join('"\n"');
}
2023-04-08 16:52:22 +00:00
if (typeof value === "number") {
return value;
}
2023-04-08 16:52:22 +00:00
return "ERR_CONVERTING";
}
2023-04-08 16:52:22 +00:00
function addToEnvironmentArray(name, value) {
const result = ["("];
value.forEach(function (subValue) {
result.push(makeShellString(subValue));
});
result.push(")");
2023-04-08 16:52:22 +00:00
return name + "=" + result.join(" ");
}
2023-04-08 16:52:22 +00:00
function addToEnvironmentObject(name, value) {
// Sometimes the __tag__ property of the code in the lambdas may
// be missing. :-(
2023-04-08 16:52:22 +00:00
if (
(value && value.__tag__ === "code") ||
(value.ruby && value.php && value.perl)
) {
if (value.bash) {
return `${name}() { ${value.bash}; }`;
}
2023-04-08 16:52:22 +00:00
return `${name}() { perl -e 'print ((${value.perl})->("'"$1"'"))'; }`;
}
2023-04-08 16:52:22 +00:00
if (value) {
return `#${name} is an object and will not work in Bash`;
}
2023-04-08 16:52:22 +00:00
// null
return `#${name} is null`;
}
2023-04-08 16:52:22 +00:00
function addToEnvironment(name, value) {
if (Array.isArray(value)) {
return addToEnvironmentArray(name, value);
}
2023-04-08 16:52:22 +00:00
if (typeof value === "object" && value) {
return addToEnvironmentObject(name, value);
}
2023-04-08 16:52:22 +00:00
if (typeof value === "boolean") {
return `${name}="${value ? "true" : ""}"`;
}
2023-04-08 16:52:22 +00:00
return `${name}=${makeShellString(value)}`;
}
2023-04-08 16:52:22 +00:00
function buildScript(test) {
const script = ["#!/usr/bin/env bash"];
Object.keys(test.data).forEach(function (name) {
script.push(addToEnvironment(name, test.data[name]));
});
2023-04-08 16:52:22 +00:00
script.push(". ./mo");
script.push("mo spec-runner/spec-template");
script.push("");
2023-04-08 16:52:22 +00:00
return script.join("\n");
}
2023-04-08 16:52:22 +00:00
function writePartials(test) {
return processArraySequentially(
Object.keys(test.partials),
(partialName) => {
debug("Writing partial:", partialName);
2023-04-08 16:52:22 +00:00
return fsPromises.writeFile(
"spec-runner/" + partialName,
test.partials[partialName]
);
}
2023-04-08 16:52:22 +00:00
);
}
2023-04-08 16:52:22 +00:00
function setupEnvironment(test) {
return cleanup()
.then(() => fsPromises.mkdir("spec-runner/"))
.then(() =>
fsPromises.writeFile("spec-runner/spec-script", test.script)
)
.then(() =>
fsPromises.writeFile("spec-runner/spec-template", test.template)
)
.then(() => writePartials(test));
}
function executeScript(test) {
return new Promise((resolve) => {
exec("bash spec-runner/spec-script 2>&1", {
timeout: 2000
}, (err, stdout) => {
if (err) {
test.scriptError = err.toString();
}
test.output = stdout;
resolve();
});
});
}
2023-04-08 16:52:22 +00:00
function cleanup() {
return fsPromises.rm("spec-runner/", { force: true, recursive: true });
}
2023-04-08 16:52:22 +00:00
function detectFailure(test) {
if (test.scriptError) {
return true;
}
2023-04-08 16:52:22 +00:00
if (test.output !== test.expected) {
return true;
}
return false;
}
function showFailureDetails(testSet, test) {
if (!test.isFailure) {
return;
}
console.log(`FAILURE: ${testSet.name} -> ${test.name}`)
console.log('');
console.log(test.desc);
console.log('');
console.log(test);
}
function runTest(testSet, test) {
test.script = buildScript(test);
test.partials = test.partials || {};
debug('Running test:', testSet.name, "->", test.name);
return setupEnvironment(test)
.then(() => executeScript(test))
.then(cleanup)
.then(() => test.isFailure = detectFailure(test))
.then(() => showFailureDetails(testSet, test));
}
function processSpecFile(filename) {
debug("Read spec file:", filename);
return fsPromises.readFile(filename, "utf8").then((fileContents) => {
const testSet = JSON.parse(fileContents);
testSet.name = specFileToName(filename);
return processArraySequentially(testSet.tests, (test) =>
runTest(testSet, test)
).then(() => {
testSet.pass = 0;
testSet.fail = 0;
for (const test of testSet.tests) {
if (test.isFailure) {
testSet.fail += 1;
} else {
testSet.pass += 1;
}
}
2023-04-08 16:52:22 +00:00
console.log(`### ${testSet.name} Results = ${testSet.pass} pass, ${testSet.fail} fail`);
return testSet;
});
});
}
// 0 = node, 1 = script, 2 = file
if (process.argv.length < 3) {
2023-04-08 16:52:22 +00:00
console.log("Specify one or more JSON spec files on the command line");
process.exit();
}
2023-04-08 16:52:22 +00:00
processArraySequentially(process.argv.slice(2), processSpecFile).then(
(result) => {
console.log('=========================================');
console.log('');
console.log('Failed Test Summary');
console.log('');
2023-04-08 16:52:22 +00:00
for (const testSet of result) {
console.log(`* ${testSet.name}: ${testSet.tests.length} total, ${testSet.pass} pass, ${testSet.fail} fail`);
2023-04-08 16:52:22 +00:00
for (const test of testSet.tests) {
if (test.isFailure) {
console.log(` * Failure: ${test.name}`);
}
}
}
},
(err) => {
console.error(err);
console.error("FAILURE RUNNING SCRIPT");
console.error("Testing artifacts are left in script-runner/ folder");
}
);