@intuit/judo
Version:
Test command line interfaces.
329 lines (278 loc) • 12.8 kB
JavaScript
;Object.defineProperty(exports, "__esModule", { value: true });exports.run = exports.handleResults = void 0;
var _parseSpawnArgs = require("parse-spawn-args");
var _assert = _interopRequireDefault(require("assert"));
var _jsYaml = _interopRequireDefault(require("js-yaml"));
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
var _logger = require("./logger");
var _executor = require("./executor");
var _fileUtil = require("./common/file-util");
var _numberUtil = require("./common/number-util");
var _StepResult = require("./models/StepResult");
var _getReporter = require("./reporters/get-reporter");
var _stringToRegexp = _interopRequireDefault(require("string-to-regexp"));
var _mustache = _interopRequireDefault(require("mustache"));
var _jsonRefs = _interopRequireDefault(require("json-refs"));function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {try {var info = gen[key](arg);var value = info.value;} catch (error) {reject(error);return;}if (info.done) {resolve(value);} else {Promise.resolve(value).then(_next, _throw);}}function _asyncToGenerator(fn) {return function () {var self = this,args = arguments;return new Promise(function (resolve, reject) {var gen = fn.apply(self, args);function _next(value) {asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);}function _throw(err) {asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);}_next(undefined);});};}
/**
* Main run function to initiate Judo. Discovers all files from provided CLI option,
* runs tests in each file, and handles the results.
*/
const run = /*#__PURE__*/function () {var _ref = _asyncToGenerator(function* () {
const yamlFilePath = process.argv[2];
const optionsFlags = process.argv.slice(3);
let options = {
timeout: 120000,
junitReport: false };
// gather options flags
optionsFlags.forEach((flag, i) => {
if (flag === '--timeout' || flag === '-t') {
options.timeout = optionsFlags[i + 1];
}
if (flag === '--junitreport' || flag === '-j') {
options.junitReport = true;
}
if (flag === '--includejsonfiles' || flag === '-ij') {
options.includeJSONFiles = true;
}
});
if (!yamlFilePath) {
_logger.logger.error(
'yaml file or directory path required as argument (ex: judo tests/)');
process.exit(1);
}
if ((0, _fileUtil.isFile)(yamlFilePath)) {
// if argument is a file, just run that file
try {const _yield$runStepFile = yield (
runStepFile(yamlFilePath, options)),stepResults = _yield$runStepFile.stepResults;
return handleResults({ stepResults });
} catch (e) {
_logger.logger.error(new Error(`Failed to run step file: ${e}`));
process.exit(1);
}
} else if ((0, _fileUtil.isDirectory)(yamlFilePath)) {
// if argument is a directory, run against all files in that dir and subdirs recursively
const allStepFiles = (0, _fileUtil.listFilesRecursively)(yamlFilePath).filter(
file => file.indexOf('.yml') !== -1 || options.includeJSONFiles && file.indexOf('.json') !== -1);
const numStepFiles = allStepFiles.length;
_logger.logger.info(`Found ${numStepFiles} tests in ${yamlFilePath}`);
let numStepFilesComplete = 0;
let currentStepFileIndex = 0;
let allStepResults = [];
const recurse = /*#__PURE__*/function () {var _ref2 = _asyncToGenerator(function* (thisStepFilePath) {
try {const _yield$runStepFile2 = yield (
runStepFile(thisStepFilePath, options)),stepResults = _yield$runStepFile2.stepResults;
allStepResults = allStepResults.concat(stepResults);
numStepFilesComplete++;
currentStepFileIndex++;
// check if done with all files
if (numStepFilesComplete === numStepFiles) {
const reporter = (0, _getReporter.getReporter)({ options: options, stepResults: allStepResults });
if (reporter) {
_fs.default.writeFileSync(reporter.outputFile, reporter.generateReport());
}
return handleResults({ stepResults: allStepResults });
}
return recurse(allStepFiles[currentStepFileIndex]);
} catch (e) {
throw new Error(`Failed to run step file ${thisStepFilePath}: ${e}`);
}
});return function recurse(_x) {return _ref2.apply(this, arguments);};}();
try {
yield recurse(allStepFiles[currentStepFileIndex]);
} catch (e) {
_logger.logger.error(e);
process.exit(1);
}
} else {
_logger.logger.error('Unrecognized file/directory path provided');
process.exit(1);
}
});return function run() {return _ref.apply(this, arguments);};}();
/**
* Runs all of the run steps in a single YAML step file ("test scenario").
* @param {String} yamlFilePath
* @param {Object} options options object. contains "timeout"
*/exports.run = run;
const runStepFile = /*#__PURE__*/function () {var _ref3 = _asyncToGenerator(function* (yamlFilePath, options) {
const fileContents = _fs.default.readFileSync(yamlFilePath);
let runSteps = {};
// first step for every file is gonna be parse itself
let runStepNames = [];
try {
const yamlFile = _jsYaml.default.safeLoad(fileContents, 'utf8');const _yield$refResolver$re = yield (
_jsonRefs.default.resolveRefs(yamlFile, {
location: _path.default.dirname(yamlFilePath) })),resolved = _yield$refResolver$re.resolved;
// logger.info('Running step file: ' + yamlFilePath);
runSteps = resolved.run;
runStepNames = Object.keys(runSteps);
if (resolved.vars) {
const view = resolved.vars;
_mustache.default.escape = text => text;
const resolveVars = obj => {
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'string') {
obj[key] = _mustache.default.render(obj[key], view);
} else if (typeof obj[key] === 'object') {
resolveVars(obj[key]);
}
});
};
resolveVars(runSteps);
}
} catch (e) {
_logger.logger.error(`YAML PARSER FAILED on file ${yamlFilePath}: ${e.message}`);
process.exit(1);
}
const numTotal = runStepNames.length;
const thisStepFileResults = [];
let currentStepIndex = 0;
let numComplete = 0;
const recurse = /*#__PURE__*/function () {var _ref4 = _asyncToGenerator(function* (runStep) {
const stepName = runStepNames[currentStepIndex];
const thisStepResult = new _StepResult.StepResult({
stepFilePath: yamlFilePath,
stepName });
_logger.logger.info(`Executing run step: ${stepName}`);
const startTime = new Date();
try {
yield executeRunStep(stepName, runSteps[stepName], options);
const endTime = new Date();
thisStepResult.setDuration(endTime - startTime);
thisStepResult.setPassed(true);
const time = (0, _numberUtil.truncateAfterDecimal)(thisStepResult.getDuration(), 5);
_logger.logger.success(`PASSED "${stepName}" (${time}ms)`);
} catch (e) {
const endTime = new Date();
thisStepResult.setDuration(endTime - startTime);
const time = (0, _numberUtil.truncateAfterDecimal)(thisStepResult.getDuration(), 5);
_logger.logger.error(`FAILED "${stepName}": ${e.message} (${time}ms)`);
thisStepResult.setPassed(false);
thisStepResult.setErrorMessage(e.message);
}
thisStepFileResults.push(thisStepResult);
numComplete++;
currentStepIndex++;
if (numComplete < numTotal) {
return recurse(runSteps[currentStepIndex]);
} else {
return { stepResults: thisStepFileResults };
}
});return function recurse(_x4) {return _ref4.apply(this, arguments);};}();
return recurse(runSteps[currentStepIndex]);
});return function runStepFile(_x2, _x3) {return _ref3.apply(this, arguments);};}();
/**
* Runs a single run step, along with its prerequisites.
* @param {String} stepName the name of this step, as defined in the step file ("test scenario")
* @param {Object} runStep run step JSON object from parsing the YAML step file ("test scenario")
* @param {Object} options options object
*/
const executeRunStep = /*#__PURE__*/function () {var _ref5 = _asyncToGenerator(function* (stepName, runStep, { timeout }) {
const commandString = runStep.command;
const commandSplit = commandString.split(' ');
const command = commandSplit[0];
const args = commandSplit.slice(1);
const argsString = args.join(' ');
const parsedAndCleanArgs = (0, _parseSpawnArgs.parse)(argsString);
const cwd = runStep.cwd;
const prerequisiteCwd = runStep.prerequisiteCwd;
const prerequisites = runStep.prerequisites || [];
const when = runStep.when || [];
const expectCode = runStep.expectCode || 0;
const outputContains = runStep.outputContains || [];
const outputDoesntContain = runStep.outputDoesntContain || [];
if (prerequisites && prerequisites.length) {
yield (0, _executor.executePrerequisites)({ prerequisites, cwd: prerequisiteCwd });
}
try {const _yield$execute = yield (
(0, _executor.execute)(command, parsedAndCleanArgs || [], {
cwd,
argsString,
timeout,
when: when.map(thisStep => {
const output = Object.keys(thisStep)[0];
const response = thisStep[output];
return {
output,
response };
}) })),output = _yield$execute.output,code = _yield$execute.code;
_assert.default.equal(
code,
expectCode,
`Expected exit code: ${expectCode}, received: ${code}`);
outputContains.forEach(expectedOutput => {
const isRegex = expectedOutput.indexOf('/') === 0;
if (isRegex) {
_assert.default.equal(
true,
(0, _stringToRegexp.default)(expectedOutput).test(output),
`Expected output to match regex: ${expectedOutput}`);
} else {
_assert.default.equal(
true,
output.indexOf(expectedOutput) !== -1,
`Expected output to contain: ${expectedOutput}`);
}
});
outputDoesntContain.forEach(notExpectedOutput => {
const isRegex = notExpectedOutput.indexOf('/') === 0;
if (isRegex) {
_assert.default.equal(
false,
(0, _stringToRegexp.default)(notExpectedOutput).test(output),
`Expected output to not match regex: ${notExpectedOutput}`);
} else {
_assert.default.equal(
true,
output.indexOf(notExpectedOutput) === -1,
`Expected output to NOT contain: ${notExpectedOutput}`);
}
});
} catch (e) {
throw new Error(e.message || 'No error message, check [OUTPUT] logs.');
}
});return function executeRunStep(_x5, _x6, _x7) {return _ref5.apply(this, arguments);};}();
/**
* Handles the results of all test suites and scenarios after Judo is done running. Logs results
* to console and exits with the correct code.
* @param {*} param0
*/
const handleResults = ({ stepResults }) => {
_logger.logger.lineBreak();
_logger.logger.summary('============= JUDO TESTS COMPLETE =============');
let numPassed = 0;
let numFailed = 0;
let totalDurationMs = 0;
let previousStepFilePath = '';
stepResults.forEach(stepResult => {
if (stepResult.getStepFilePath() !== previousStepFilePath) {
_logger.logger.summary(stepResult.getStepFilePath());
previousStepFilePath = stepResult.getStepFilePath();
}
const name = stepResult.getStepName();
const time = (0, _numberUtil.truncateAfterDecimal)(stepResult.getDuration() / 1000, 5);
if (stepResult.getPassed()) {
_logger.logger.success(`PASSED: ${name} (${time}s)`);
numPassed++;
} else {
_logger.logger.error(`FAILED: ${name} (${time}s)`);
numFailed++;
}
totalDurationMs += stepResult.getDuration();
});
_logger.logger.summary(
`Total tests: ${stepResults.length} (${(0, _numberUtil.truncateAfterDecimal)(
totalDurationMs / 1000,
5)
}s)`);
_logger.logger.summary(`Passed: ${numPassed}`);
_logger.logger.summary(`Failed: ${numFailed}`);
if (numFailed !== 0) {
process.exit(1);
} else {
process.exit(0);
}
};exports.handleResults = handleResults;
if (process.env.NODE_ENV !== 'test') {
run();
}