TestRunner.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. 'use strict';
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = undefined;
  6. var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
  7. var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
  8. var _extends2 = require('babel-runtime/helpers/extends');
  9. var _extends3 = _interopRequireDefault(_extends2);
  10. var _promise = require('babel-runtime/core-js/promise');
  11. var _promise2 = _interopRequireDefault(_promise);
  12. var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
  13. var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
  14. var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
  15. var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
  16. var _createClass2 = require('babel-runtime/helpers/createClass');
  17. var _createClass3 = _interopRequireDefault(_createClass2);
  18. var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
  19. var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
  20. var _inherits2 = require('babel-runtime/helpers/inherits');
  21. var _inherits3 = _interopRequireDefault(_inherits2);
  22. var _get2 = require('lodash/get');
  23. var _get3 = _interopRequireDefault(_get2);
  24. var _has2 = require('lodash/has');
  25. var _has3 = _interopRequireDefault(_has2);
  26. var _debounce2 = require('lodash/debounce');
  27. var _debounce3 = _interopRequireDefault(_debounce2);
  28. var _once2 = require('lodash/once');
  29. var _once3 = _interopRequireDefault(_once2);
  30. require('nodent-runtime');
  31. var _path = require('path');
  32. var _path2 = _interopRequireDefault(_path);
  33. var _events = require('events');
  34. var _events2 = _interopRequireDefault(_events);
  35. var _chokidar = require('chokidar');
  36. var _chokidar2 = _interopRequireDefault(_chokidar);
  37. var _glob = require('../util/glob');
  38. var _paths = require('../util/paths');
  39. var _createCompiler = require('../webpack/compiler/createCompiler');
  40. var _createCompiler2 = _interopRequireDefault(_createCompiler);
  41. var _createWatchCompiler = require('../webpack/compiler/createWatchCompiler');
  42. var _createWatchCompiler2 = _interopRequireDefault(_createWatchCompiler);
  43. var _registerInMemoryCompiler = require('../webpack/compiler/registerInMemoryCompiler');
  44. var _registerInMemoryCompiler2 = _interopRequireDefault(_registerInMemoryCompiler);
  45. var _registerReadyCallback = require('../webpack/compiler/registerReadyCallback');
  46. var _registerReadyCallback2 = _interopRequireDefault(_registerReadyCallback);
  47. var _entryLoader = require('../webpack/loader/entryLoader');
  48. var _configureMocha = require('./configureMocha');
  49. var _configureMocha2 = _interopRequireDefault(_configureMocha);
  50. var _getBuildStats = require('../webpack/util/getBuildStats');
  51. var _getBuildStats2 = _interopRequireDefault(_getBuildStats);
  52. var _buildProgressPlugin = require('../webpack/plugin/buildProgressPlugin');
  53. var _buildProgressPlugin2 = _interopRequireDefault(_buildProgressPlugin);
  54. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  55. // $FlowFixMe
  56. var entryPath = _path2.default.resolve(__dirname, '../entry.js');
  57. var entryLoaderPath = _path2.default.resolve(__dirname, '../webpack/loader/entryLoader.js');
  58. var includeLoaderPath = _path2.default.resolve(__dirname, '../webpack/loader/includeFilesLoader.js');
  59. var noop = function noop() {
  60. return undefined;
  61. };
  62. var TestRunner = function (_EventEmitter) {
  63. (0, _inherits3.default)(TestRunner, _EventEmitter);
  64. function TestRunner(entries, includes, options) {
  65. (0, _classCallCheck3.default)(this, TestRunner);
  66. var _this = (0, _possibleConstructorReturn3.default)(this, (TestRunner.__proto__ || (0, _getPrototypeOf2.default)(TestRunner)).call(this));
  67. _this.entries = entries;
  68. _this.includes = includes;
  69. _this.options = options;
  70. return _this;
  71. }
  72. (0, _createClass3.default)(TestRunner, [{
  73. key: 'prepareMocha',
  74. value: function prepareMocha(webpackConfig, stats) {
  75. var mocha = (0, _configureMocha2.default)(this.options);
  76. var outputPath = webpackConfig.output.path;
  77. var buildStats = (0, _getBuildStats2.default)(stats, outputPath);
  78. global.__webpackManifest__ = buildStats.affectedModules; // eslint-disable-line
  79. // clear up require cache for changed files to make sure that we get the latest changes
  80. buildStats.affectedFiles.forEach(function (filePath) {
  81. delete require.cache[filePath];
  82. });
  83. // pass webpack's entry files to mocha
  84. mocha.files = buildStats.entries;
  85. return mocha;
  86. }
  87. }, {
  88. key: 'run',
  89. value: function run() {
  90. return new Promise(function ($return, $error) {
  91. var $Try_1_Finally = function ($Try_1_Exit) {
  92. return function ($Try_1_Value) {
  93. // clean up single run
  94. dispose();
  95. return $Try_1_Exit && $Try_1_Exit.call(this, $Try_1_Value);
  96. }.$asyncbind(this, $error);
  97. }.$asyncbind(this);
  98. var _this2, _ref, config, failures, compiler, dispose;
  99. _this2 = this;
  100. return this.createWebpackConfig().then(function ($await_6) {
  101. _ref = $await_6, config = _ref.webpackConfig;
  102. failures = 0;
  103. compiler = (0, _createCompiler2.default)(config);
  104. compiler.hooks.run.tapAsync('mocha-webpack', function (c, cb) {
  105. _this2.emit('webpack:start');
  106. // $FlowFixMe
  107. cb();
  108. });
  109. dispose = (0, _registerInMemoryCompiler2.default)(compiler);
  110. var $Try_1_Post = function () {
  111. return $return(failures);
  112. }.$asyncbind(this, $error);var $Try_1_Catch = function ($exception_2) {
  113. throw $exception_2;
  114. }.$asyncbind(this, $Try_1_Finally($error));try {
  115. return new _promise2.default(function (resolve, reject) {
  116. (0, _registerReadyCallback2.default)(compiler, function (err, webpackStats) {
  117. _this2.emit('webpack:ready', err, webpackStats);
  118. if (err || !webpackStats) {
  119. reject();
  120. return;
  121. }
  122. try {
  123. var mocha = _this2.prepareMocha(config, webpackStats);
  124. _this2.emit('mocha:begin');
  125. try {
  126. mocha.run(function (fails) {
  127. _this2.emit('mocha:finished', fails);
  128. resolve(fails);
  129. });
  130. } catch (e) {
  131. _this2.emit('exception', e);
  132. resolve(1);
  133. }
  134. } catch (e) {
  135. reject(e);
  136. }
  137. });
  138. compiler.run(noop);
  139. }).then(function ($await_7) {
  140. failures = $await_7;
  141. return $Try_1_Finally($Try_1_Post)();
  142. }.$asyncbind(this, $Try_1_Catch), $Try_1_Catch);
  143. } catch ($exception_2) {
  144. $Try_1_Catch($exception_2)
  145. }
  146. }.$asyncbind(this, $error), $error);
  147. }.$asyncbind(this));
  148. }
  149. }, {
  150. key: 'watch',
  151. value: function watch() {
  152. return new Promise(function ($return, $error) {
  153. var _this3, _ref2, config, entryConfig, mochaRunner, stats, compilationScheduler, uncaughtExceptionListener, runMocha, compiler, watchCompiler, watchOptions, pollingInterval, watcher, restartWebpackBuild, fileDeletedOrAdded;
  154. _this3 = this;
  155. return this.createWebpackConfig().then(function ($await_8) {
  156. _ref2 = $await_8, config = _ref2.webpackConfig, entryConfig = _ref2.entryConfig;
  157. mochaRunner = null;
  158. stats = null;
  159. compilationScheduler = null;
  160. uncaughtExceptionListener = function uncaughtExceptionListener(err) {
  161. // mocha catches uncaughtException only while tests are running,
  162. // that's why we register a custom error handler to keep this process alive
  163. _this3.emit('uncaughtException', err);
  164. };
  165. runMocha = function runMocha() {
  166. try {
  167. // $FlowFixMe
  168. var mocha = _this3.prepareMocha(config, stats);
  169. // unregister our custom exception handler (see declaration)
  170. process.removeListener('uncaughtException', uncaughtExceptionListener);
  171. // run tests
  172. _this3.emit('mocha:begin');
  173. mochaRunner = mocha.run((0, _once3.default)(function (failures) {
  174. // register custom exception handler to catch all errors that may happen after mocha think tests are done
  175. process.on('uncaughtException', uncaughtExceptionListener);
  176. // need to wait until next tick, otherwise mochaRunner = null doesn't work..
  177. process.nextTick(function () {
  178. mochaRunner = null;
  179. if (compilationScheduler != null) {
  180. _this3.emit('mocha:aborted');
  181. compilationScheduler();
  182. compilationScheduler = null;
  183. } else {
  184. _this3.emit('mocha:finished', failures);
  185. }
  186. });
  187. }));
  188. } catch (err) {
  189. _this3.emit('exception', err);
  190. }
  191. };
  192. compiler = (0, _createCompiler2.default)(config);
  193. (0, _registerInMemoryCompiler2.default)(compiler);
  194. // register webpack start callback
  195. compiler.hooks.watchRun.tapAsync('mocha-webpack', function (c, cb) {
  196. // check if mocha tests are still running, abort them and start compiling
  197. if (mochaRunner) {
  198. compilationScheduler = function compilationScheduler() {
  199. _this3.emit('webpack:start');
  200. // $FlowFixMe
  201. cb();
  202. };
  203. mochaRunner.abort();
  204. // make sure that the current running test will be aborted when timeouts are disabled for async tests
  205. if (mochaRunner.currentRunnable) {
  206. var runnable = mochaRunner.currentRunnable;
  207. runnable.retries(0);
  208. runnable.enableTimeouts(true);
  209. runnable.timeout(1);
  210. runnable.resetTimeout(1);
  211. }
  212. } else {
  213. _this3.emit('webpack:start');
  214. // $FlowFixMe
  215. cb();
  216. }
  217. });
  218. // register webpack ready callback
  219. (0, _registerReadyCallback2.default)(compiler, function (err, webpackStats) {
  220. _this3.emit('webpack:ready', err, webpackStats);
  221. if (err) {
  222. // wait for fixed tests
  223. return;
  224. }
  225. stats = webpackStats;
  226. runMocha();
  227. });
  228. watchCompiler = (0, _createWatchCompiler2.default)(compiler, config.watchOptions);
  229. // start webpack build immediately
  230. watchCompiler.watch();
  231. // webpack enhances watch options, that's why we use them instead
  232. watchOptions = watchCompiler.getWatchOptions();
  233. pollingInterval = typeof watchOptions.poll === 'number' ? watchOptions.poll : undefined;
  234. watcher = _chokidar2.default.watch(this.entries, {
  235. cwd: this.options.cwd,
  236. // see https://github.com/webpack/watchpack/blob/e5305b53ac3cf2a70d49a772912b115fa77665c2/lib/DirectoryWatcher.js
  237. ignoreInitial: true,
  238. persistent: true,
  239. followSymlinks: false,
  240. ignorePermissionErrors: true,
  241. ignored: watchOptions.ignored,
  242. usePolling: watchOptions.poll ? true : undefined,
  243. interval: pollingInterval,
  244. binaryInterval: pollingInterval
  245. });
  246. restartWebpackBuild = (0, _debounce3.default)(function () {
  247. return watchCompiler.watch();
  248. }, watchOptions.aggregateTimeout);
  249. fileDeletedOrAdded = function fileDeletedOrAdded(file, deleted) {
  250. var filePath = _path2.default.join(_this3.options.cwd, file);
  251. if (deleted) {
  252. _this3.emit('entry:removed', file);
  253. entryConfig.removeFile(filePath);
  254. } else {
  255. _this3.emit('entry:added', file);
  256. entryConfig.addFile(filePath);
  257. }
  258. // pause webpack watch immediately before webpack will be notified
  259. watchCompiler.pause();
  260. // call debounced webpack runner to rebuild files
  261. restartWebpackBuild();
  262. };
  263. // add listener for entry creation & deletion events
  264. watcher.on('add', function (file) {
  265. return fileDeletedOrAdded(file, false);
  266. });
  267. watcher.on('unlink', function (file) {
  268. return fileDeletedOrAdded(file, true);
  269. });
  270. return $return(new _promise2.default(function () {
  271. return undefined;
  272. })); // never ending story
  273. }.$asyncbind(this, $error), $error);
  274. }.$asyncbind(this));
  275. }
  276. }, {
  277. key: 'createWebpackConfig',
  278. value: function createWebpackConfig() {
  279. return new Promise(function ($return, $error) {
  280. var _this4, webpackConfig, files, entryConfig, tmpPath, withCustomPath, outputPath, publicPath, plugins, userLoaders, config;
  281. _this4 = this;
  282. webpackConfig = this.options.webpackConfig;
  283. return (0, _glob.glob)(this.entries, {
  284. cwd: this.options.cwd,
  285. absolute: false // this option isn't covered by the version range in 'globby' for 'glob' (default value is false)
  286. }).then(function ($await_9) {
  287. files = $await_9;
  288. entryConfig = new _entryLoader.EntryConfig();
  289. files.map(function (f) {
  290. return (0, _paths.ensureAbsolutePath)(f, _this4.options.cwd);
  291. }).forEach(function (f) {
  292. return entryConfig.addFile(f);
  293. });
  294. tmpPath = _path2.default.join(this.options.cwd, '.tmp', 'mocha-webpack', Date.now().toString());
  295. withCustomPath = (0, _has3.default)(webpackConfig, 'output.path');
  296. outputPath = _path2.default.normalize((0, _get3.default)(webpackConfig, 'output.path', tmpPath));
  297. publicPath = withCustomPath ? (0, _get3.default)(webpackConfig, 'output.publicPath', undefined) : outputPath + _path2.default.sep;
  298. plugins = [];
  299. if (this.options.interactive) {
  300. plugins.push((0, _buildProgressPlugin2.default)());
  301. }
  302. userLoaders = (0, _get3.default)(webpackConfig, 'module.rules', []);
  303. userLoaders.unshift({
  304. test: entryPath,
  305. use: [{
  306. loader: includeLoaderPath,
  307. options: {
  308. include: this.includes
  309. }
  310. }, {
  311. loader: entryLoaderPath,
  312. options: {
  313. entryConfig: entryConfig
  314. }
  315. }]
  316. });
  317. config = (0, _extends3.default)({}, webpackConfig, {
  318. entry: entryPath,
  319. module: (0, _extends3.default)({}, webpackConfig.module, {
  320. rules: userLoaders
  321. }),
  322. output: (0, _extends3.default)({}, webpackConfig.output, {
  323. path: outputPath,
  324. publicPath: publicPath
  325. }),
  326. plugins: [].concat((0, _toConsumableArray3.default)(webpackConfig.plugins || []), plugins)
  327. });
  328. return $return({
  329. webpackConfig: config,
  330. entryConfig: entryConfig
  331. });
  332. }.$asyncbind(this, $error), $error);
  333. }.$asyncbind(this));
  334. }
  335. }]);
  336. return TestRunner;
  337. }(_events2.default);
  338. exports.default = TestRunner;