396 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			396 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| Object.defineProperty(exports, '__esModule', {
 | |
|   value: true
 | |
| });
 | |
| Object.defineProperty(exports, 'Frame', {
 | |
|   enumerable: true,
 | |
|   get: function get() {
 | |
|     return _types.Frame;
 | |
|   }
 | |
| });
 | |
| exports.separateMessageFromStack = exports.formatResultsErrors = exports.formatStackTrace = exports.getTopFrame = exports.getStackTraceLines = exports.formatExecError = void 0;
 | |
| 
 | |
| var _fs = _interopRequireDefault(require('fs'));
 | |
| 
 | |
| var _path = _interopRequireDefault(require('path'));
 | |
| 
 | |
| var _chalk = _interopRequireDefault(require('chalk'));
 | |
| 
 | |
| var _micromatch = _interopRequireDefault(require('micromatch'));
 | |
| 
 | |
| var _slash = _interopRequireDefault(require('slash'));
 | |
| 
 | |
| var _codeFrame = require('@babel/code-frame');
 | |
| 
 | |
| var _stackUtils = _interopRequireDefault(require('stack-utils'));
 | |
| 
 | |
| var _types = require('./types');
 | |
| 
 | |
| function _interopRequireDefault(obj) {
 | |
|   return obj && obj.__esModule ? obj : {default: obj};
 | |
| }
 | |
| 
 | |
| var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
 | |
| 
 | |
| var jestReadFile =
 | |
|   global[Symbol.for('jest-native-read-file')] || _fs.default.readFileSync;
 | |
| 
 | |
| var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
 | |
| // stack utils tries to create pretty stack by making paths relative.
 | |
| const stackUtils = new _stackUtils.default({
 | |
|   cwd: 'something which does not exist'
 | |
| });
 | |
| let nodeInternals = [];
 | |
| 
 | |
| try {
 | |
|   nodeInternals = _stackUtils.default.nodeInternals();
 | |
| } catch (e) {
 | |
|   // `StackUtils.nodeInternals()` fails in browsers. We don't need to remove
 | |
|   // node internals in the browser though, so no issue.
 | |
| }
 | |
| 
 | |
| const PATH_NODE_MODULES = `${_path.default.sep}node_modules${
 | |
|   _path.default.sep
 | |
| }`;
 | |
| const PATH_JEST_PACKAGES = `${_path.default.sep}jest${
 | |
|   _path.default.sep
 | |
| }packages${_path.default.sep}`; // filter for noisy stack trace lines
 | |
| 
 | |
| const JASMINE_IGNORE = /^\s+at(?:(?:.jasmine\-)|\s+jasmine\.buildExpectationResult)/;
 | |
| const JEST_INTERNALS_IGNORE = /^\s+at.*?jest(-.*?)?(\/|\\)(build|node_modules|packages)(\/|\\)/;
 | |
| const ANONYMOUS_FN_IGNORE = /^\s+at <anonymous>.*$/;
 | |
| const ANONYMOUS_PROMISE_IGNORE = /^\s+at (new )?Promise \(<anonymous>\).*$/;
 | |
| const ANONYMOUS_GENERATOR_IGNORE = /^\s+at Generator.next \(<anonymous>\).*$/;
 | |
| const NATIVE_NEXT_IGNORE = /^\s+at next \(native\).*$/;
 | |
| const TITLE_INDENT = '  ';
 | |
| const MESSAGE_INDENT = '    ';
 | |
| const STACK_INDENT = '      ';
 | |
| const ANCESTRY_SEPARATOR = ' \u203A ';
 | |
| 
 | |
| const TITLE_BULLET = _chalk.default.bold('\u25cf ');
 | |
| 
 | |
| const STACK_TRACE_COLOR = _chalk.default.dim;
 | |
| const STACK_PATH_REGEXP = /\s*at.*\(?(\:\d*\:\d*|native)\)?/;
 | |
| const EXEC_ERROR_MESSAGE = 'Test suite failed to run';
 | |
| const NOT_EMPTY_LINE_REGEXP = /^(?!$)/gm;
 | |
| 
 | |
| const indentAllLines = (lines, indent) =>
 | |
|   lines.replace(NOT_EMPTY_LINE_REGEXP, indent);
 | |
| 
 | |
| const trim = string => (string || '').trim(); // Some errors contain not only line numbers in stack traces
 | |
| // e.g. SyntaxErrors can contain snippets of code, and we don't
 | |
| // want to trim those, because they may have pointers to the column/character
 | |
| // which will get misaligned.
 | |
| 
 | |
| const trimPaths = string =>
 | |
|   string.match(STACK_PATH_REGEXP) ? trim(string) : string;
 | |
| 
 | |
| const getRenderedCallsite = (fileContent, line, column) => {
 | |
|   let renderedCallsite = (0, _codeFrame.codeFrameColumns)(
 | |
|     fileContent,
 | |
|     {
 | |
|       start: {
 | |
|         column,
 | |
|         line
 | |
|       }
 | |
|     },
 | |
|     {
 | |
|       highlightCode: true
 | |
|     }
 | |
|   );
 | |
|   renderedCallsite = indentAllLines(renderedCallsite, MESSAGE_INDENT);
 | |
|   renderedCallsite = `\n${renderedCallsite}\n`;
 | |
|   return renderedCallsite;
 | |
| }; // ExecError is an error thrown outside of the test suite (not inside an `it` or
 | |
| // `before/after each` hooks). If it's thrown, none of the tests in the file
 | |
| // are executed.
 | |
| 
 | |
| const formatExecError = (error, config, options, testPath, reuseMessage) => {
 | |
|   if (!error || typeof error === 'number') {
 | |
|     error = new Error(`Expected an Error, but "${String(error)}" was thrown`);
 | |
|     error.stack = '';
 | |
|   }
 | |
| 
 | |
|   let message, stack;
 | |
| 
 | |
|   if (typeof error === 'string' || !error) {
 | |
|     error || (error = 'EMPTY ERROR');
 | |
|     message = '';
 | |
|     stack = error;
 | |
|   } else {
 | |
|     message = error.message;
 | |
|     stack = error.stack;
 | |
|   }
 | |
| 
 | |
|   const separated = separateMessageFromStack(stack || '');
 | |
|   stack = separated.stack;
 | |
| 
 | |
|   if (separated.message.indexOf(trim(message)) !== -1) {
 | |
|     // Often stack trace already contains the duplicate of the message
 | |
|     message = separated.message;
 | |
|   }
 | |
| 
 | |
|   message = indentAllLines(message, MESSAGE_INDENT);
 | |
|   stack =
 | |
|     stack && !options.noStackTrace
 | |
|       ? '\n' + formatStackTrace(stack, config, options, testPath)
 | |
|       : '';
 | |
| 
 | |
|   if (message.match(/^\s*$/) && stack.match(/^\s*$/)) {
 | |
|     // this can happen if an empty object is thrown.
 | |
|     message = MESSAGE_INDENT + 'Error: No message was provided';
 | |
|   }
 | |
| 
 | |
|   let messageToUse;
 | |
| 
 | |
|   if (reuseMessage) {
 | |
|     messageToUse = ` ${message.trim()}`;
 | |
|   } else {
 | |
|     messageToUse = `${EXEC_ERROR_MESSAGE}\n\n${message}`;
 | |
|   }
 | |
| 
 | |
|   return TITLE_INDENT + TITLE_BULLET + messageToUse + stack + '\n';
 | |
| };
 | |
| 
 | |
| exports.formatExecError = formatExecError;
 | |
| 
 | |
| const removeInternalStackEntries = (lines, options) => {
 | |
|   let pathCounter = 0;
 | |
|   return lines.filter(line => {
 | |
|     if (ANONYMOUS_FN_IGNORE.test(line)) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     if (ANONYMOUS_PROMISE_IGNORE.test(line)) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     if (ANONYMOUS_GENERATOR_IGNORE.test(line)) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     if (NATIVE_NEXT_IGNORE.test(line)) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     if (nodeInternals.some(internal => internal.test(line))) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     if (!STACK_PATH_REGEXP.test(line)) {
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     if (JASMINE_IGNORE.test(line)) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     if (++pathCounter === 1) {
 | |
|       return true; // always keep the first line even if it's from Jest
 | |
|     }
 | |
| 
 | |
|     if (options.noStackTrace) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     if (JEST_INTERNALS_IGNORE.test(line)) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     return true;
 | |
|   });
 | |
| };
 | |
| 
 | |
| const formatPaths = (config, relativeTestPath, line) => {
 | |
|   // Extract the file path from the trace line.
 | |
|   const match = line.match(/(^\s*at .*?\(?)([^()]+)(:[0-9]+:[0-9]+\)?.*$)/);
 | |
| 
 | |
|   if (!match) {
 | |
|     return line;
 | |
|   }
 | |
| 
 | |
|   let filePath = (0, _slash.default)(
 | |
|     _path.default.relative(config.rootDir, match[2])
 | |
|   ); // highlight paths from the current test file
 | |
| 
 | |
|   if (
 | |
|     (config.testMatch &&
 | |
|       config.testMatch.length &&
 | |
|       _micromatch.default.some(filePath, config.testMatch)) ||
 | |
|     filePath === relativeTestPath
 | |
|   ) {
 | |
|     filePath = _chalk.default.reset.cyan(filePath);
 | |
|   }
 | |
| 
 | |
|   return STACK_TRACE_COLOR(match[1]) + filePath + STACK_TRACE_COLOR(match[3]);
 | |
| };
 | |
| 
 | |
| const getStackTraceLines = (
 | |
|   stack,
 | |
|   options = {
 | |
|     noStackTrace: false
 | |
|   }
 | |
| ) => removeInternalStackEntries(stack.split(/\n/), options);
 | |
| 
 | |
| exports.getStackTraceLines = getStackTraceLines;
 | |
| 
 | |
| const getTopFrame = lines => {
 | |
|   var _iteratorNormalCompletion = true;
 | |
|   var _didIteratorError = false;
 | |
|   var _iteratorError = undefined;
 | |
| 
 | |
|   try {
 | |
|     for (
 | |
|       var _iterator = lines[Symbol.iterator](), _step;
 | |
|       !(_iteratorNormalCompletion = (_step = _iterator.next()).done);
 | |
|       _iteratorNormalCompletion = true
 | |
|     ) {
 | |
|       const line = _step.value;
 | |
| 
 | |
|       if (
 | |
|         line.includes(PATH_NODE_MODULES) ||
 | |
|         line.includes(PATH_JEST_PACKAGES)
 | |
|       ) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       const parsedFrame = stackUtils.parseLine(line.trim());
 | |
| 
 | |
|       if (parsedFrame && parsedFrame.file) {
 | |
|         return parsedFrame;
 | |
|       }
 | |
|     }
 | |
|   } catch (err) {
 | |
|     _didIteratorError = true;
 | |
|     _iteratorError = err;
 | |
|   } finally {
 | |
|     try {
 | |
|       if (!_iteratorNormalCompletion && _iterator.return != null) {
 | |
|         _iterator.return();
 | |
|       }
 | |
|     } finally {
 | |
|       if (_didIteratorError) {
 | |
|         throw _iteratorError;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return null;
 | |
| };
 | |
| 
 | |
| exports.getTopFrame = getTopFrame;
 | |
| 
 | |
| const formatStackTrace = (stack, config, options, testPath) => {
 | |
|   const lines = getStackTraceLines(stack, options);
 | |
|   const topFrame = getTopFrame(lines);
 | |
|   let renderedCallsite = '';
 | |
|   const relativeTestPath = testPath
 | |
|     ? (0, _slash.default)(_path.default.relative(config.rootDir, testPath))
 | |
|     : null;
 | |
| 
 | |
|   if (topFrame) {
 | |
|     const column = topFrame.column,
 | |
|       filename = topFrame.file,
 | |
|       line = topFrame.line;
 | |
| 
 | |
|     if (line && filename && _path.default.isAbsolute(filename)) {
 | |
|       let fileContent;
 | |
| 
 | |
|       try {
 | |
|         // TODO: check & read HasteFS instead of reading the filesystem:
 | |
|         // see: https://github.com/facebook/jest/pull/5405#discussion_r164281696
 | |
|         fileContent = jestReadFile(filename, 'utf8');
 | |
|         renderedCallsite = getRenderedCallsite(fileContent, line, column);
 | |
|       } catch (e) {
 | |
|         // the file does not exist or is inaccessible, we ignore
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   const stacktrace = lines
 | |
|     .filter(Boolean)
 | |
|     .map(
 | |
|       line =>
 | |
|         STACK_INDENT + formatPaths(config, relativeTestPath, trimPaths(line))
 | |
|     )
 | |
|     .join('\n');
 | |
|   return `${renderedCallsite}\n${stacktrace}`;
 | |
| };
 | |
| 
 | |
| exports.formatStackTrace = formatStackTrace;
 | |
| 
 | |
| const formatResultsErrors = (testResults, config, options, testPath) => {
 | |
|   const failedResults = testResults.reduce((errors, result) => {
 | |
|     result.failureMessages.forEach(content =>
 | |
|       errors.push({
 | |
|         content,
 | |
|         result
 | |
|       })
 | |
|     );
 | |
|     return errors;
 | |
|   }, []);
 | |
| 
 | |
|   if (!failedResults.length) {
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   return failedResults
 | |
|     .map(({result, content}) => {
 | |
|       let _separateMessageFromS = separateMessageFromStack(content),
 | |
|         message = _separateMessageFromS.message,
 | |
|         stack = _separateMessageFromS.stack;
 | |
| 
 | |
|       stack = options.noStackTrace
 | |
|         ? ''
 | |
|         : STACK_TRACE_COLOR(
 | |
|             formatStackTrace(stack, config, options, testPath)
 | |
|           ) + '\n';
 | |
|       message = indentAllLines(message, MESSAGE_INDENT);
 | |
|       const title =
 | |
|         _chalk.default.bold.red(
 | |
|           TITLE_INDENT +
 | |
|             TITLE_BULLET +
 | |
|             result.ancestorTitles.join(ANCESTRY_SEPARATOR) +
 | |
|             (result.ancestorTitles.length ? ANCESTRY_SEPARATOR : '') +
 | |
|             result.title
 | |
|         ) + '\n';
 | |
|       return title + '\n' + message + '\n' + stack;
 | |
|     })
 | |
|     .join('\n');
 | |
| }; // jasmine and worker farm sometimes don't give us access to the actual
 | |
| // Error object, so we have to regexp out the message from the stack string
 | |
| // to format it.
 | |
| 
 | |
| exports.formatResultsErrors = formatResultsErrors;
 | |
| 
 | |
| const separateMessageFromStack = content => {
 | |
|   if (!content) {
 | |
|     return {
 | |
|       message: '',
 | |
|       stack: ''
 | |
|     };
 | |
|   } // All lines up to what looks like a stack -- or if nothing looks like a stack
 | |
|   // (maybe it's a code frame instead), just the first non-empty line.
 | |
|   // If the error is a plain "Error:" instead of a SyntaxError or TypeError we
 | |
|   // remove the prefix from the message because it is generally not useful.
 | |
| 
 | |
|   const messageMatch = content.match(
 | |
|     /^(?:Error: )?([\s\S]*?(?=\n\s*at\s.*\:\d*\:\d*)|\s*.*)([\s\S]*)$/
 | |
|   );
 | |
| 
 | |
|   if (!messageMatch) {
 | |
|     // For flow
 | |
|     throw new Error('If you hit this error, the regex above is buggy.');
 | |
|   }
 | |
| 
 | |
|   const message = messageMatch[1];
 | |
|   const stack = messageMatch[2];
 | |
|   return {
 | |
|     message,
 | |
|     stack
 | |
|   };
 | |
| };
 | |
| 
 | |
| exports.separateMessageFromStack = separateMessageFromStack;
 |