UNPKG

@intuit/judo

Version:

Test command line interfaces.

329 lines (278 loc) 12.8 kB
#!/usr/bin/env node "use strict";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(); }