serverless/scripts/test-isolated.js

137 lines
4.0 KiB
JavaScript
Executable File

#!/usr/bin/env node
// Basic isolated tests runner
// Ensures each test file is run in distinct process and does not interfere with other test runs.
// To be used to confirm test files do not introduce and work by chance of side effects
// Temporary solution until we migrate to runner which provides that (reliably) on its own
'use strict';
process.on('unhandledRejection', err => {
throw err;
});
const spawn = require('child-process-ext/spawn');
const chalk = require('chalk');
const pLimit = require('p-limit');
const mochaCollectFiles = require('mocha/lib/cli/collect-files');
const inputOptions = {};
const filePatterns = process.argv.slice(2).filter(arg => {
if (!arg.startsWith('-')) return true;
switch (arg) {
case '--pass-through-aws-creds':
inputOptions.passThroughAwsCreds = true;
break;
case '--skip-fs-cleanup-check':
inputOptions.skipFsCleanupCheck = true;
break;
default:
process.stderr.write(chalk.red.bold(`Unrecognized option ${arg}\n\n`));
process.exit(1);
}
return false;
});
if (!filePatterns.length) filePatterns.push('!(node_modules)/**/*.test.js');
const resolveGitStatus = () =>
spawn('git', ['status', '--porcelain']).then(
({ stdoutBuffer }) => String(stdoutBuffer),
error => {
process.stdout.write(error.stdoutBuffer);
process.stderr.write(error.stderrBuffer);
throw error;
}
);
const initialGitStatusDeferred = !inputOptions.skipFsCleanupCheck ? resolveGitStatus() : null;
const initialSetupDeferred = !inputOptions.skipFsCleanupCheck
? initialGitStatusDeferred
: Promise.resolve();
const cwdPathLength = process.cwd().length + 1;
const paths = mochaCollectFiles({
ignore: [],
extension: ['js'],
file: [],
recursive: process.argv.includes('--recursive'),
spec: filePatterns,
}).map(filename => filename.slice(cwdPathLength));
if (!paths.length) {
process.stderr.write(chalk.red.bold('No test files matched\n\n'));
process.exit(1);
}
const processesCount = !inputOptions.skipFsCleanupCheck
? 1
: Math.max(require('os').cpus().length - 1, 1);
const isMultiProcessRun = processesCount > 1;
const { ongoing, cliFooter } = (() => {
if (!isMultiProcessRun) return {};
return { ongoing: new Set(), cliFooter: require('cli-progress-footer')() };
})();
const run = path => {
if (isMultiProcessRun) {
ongoing.add(path);
cliFooter.updateProgress(Array.from(ongoing));
}
const onFinally = (() => {
if (isMultiProcessRun) {
return ({ stdoutBuffer, stderrBuffer }) => {
ongoing.delete(path);
cliFooter.updateProgress(Array.from(ongoing));
process.stdout.write(stdoutBuffer);
process.stderr.write(stderrBuffer);
return Promise.resolve();
};
}
if (inputOptions.skipFsCleanupCheck) return () => Promise.resolve();
return () =>
Promise.all([initialGitStatusDeferred, resolveGitStatus()]).then(
([initialStatus, currentStatus]) => {
if (initialStatus !== currentStatus) {
process.stderr.write(
chalk.red.bold(`${path} didn't clean created temporary files\n\n`)
);
process.exit(1);
}
}
);
})();
const env = {
APPDATA: process.env.APPDATA,
FORCE_COLOR: '1',
HOME: process.env.HOME,
PATH: process.env.PATH,
TMPDIR: process.env.TMPDIR,
USERPROFILE: process.env.USERPROFILE,
};
if (inputOptions.passThroughAwsCreds) {
for (const envVarName of Object.keys(process.env)) {
if (envVarName.startsWith('AWS_')) env[envVarName] = process.env[envVarName];
}
}
return spawn('npx', ['mocha', path], {
stdio: isMultiProcessRun ? null : 'inherit',
env,
}).then(onFinally, error => {
if (isMultiProcessRun) ongoing.clear();
return onFinally(error).then(() => {
process.stderr.write(chalk.red.bold(`${path} failed\n\n`));
if (error.code <= 2) process.exit(error.code);
throw error;
});
});
};
const limit = pLimit(processesCount);
return initialSetupDeferred.then(() => Promise.all(paths.map(path => limit(() => run(path)))));