diff --git a/src/TaskRunner.js b/src/TaskRunner.js index 31500a5b..8f24332a 100644 --- a/src/TaskRunner.js +++ b/src/TaskRunner.js @@ -41,7 +41,14 @@ export default class TaskRunner { } : { maxConcurrentWorkers: this.maxConcurrentWorkers }; this.workers = workerFarm(workerOptions, workerFile); - this.boundWorkers = (options, cb) => this.workers(serialize(options), cb); + this.boundWorkers = (options, cb) => { + try { + this.workers(serialize(options), cb); + } catch (error) { + // worker-farm can fail with ENOMEM or something else + cb(error); + } + }; } else { this.boundWorkers = (options, cb) => { try { diff --git a/test/__snapshots__/parallel-failure.test.js.snap b/test/__snapshots__/parallel-failure.test.js.snap new file mode 100644 index 00000000..13615713 --- /dev/null +++ b/test/__snapshots__/parallel-failure.test.js.snap @@ -0,0 +1,231 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`when applied with \`parallel\` option matches snapshot for errors into \`worker-farm\` and \`cache\` is \`true\`: errors 1`] = ` +Array [ + "Error: one.9cf5e356924aeff1105d.js from UglifyJs +Error: worker-farm failed", +] +`; + +exports[`when applied with \`parallel\` option matches snapshot for errors into \`worker-farm\` and \`cache\` is \`true\`: one.9cf5e356924aeff1105d.js 1`] = ` +"/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = \\"\\"; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports) { + +// foo +/* @preserve*/ +// bar +var a = 2 + 2; + +module.exports = function Foo() { + var b = 2 + 2; + console.log(b + 1 + 2); +}; + + +/***/ }) +/******/ ]);" +`; + +exports[`when applied with \`parallel\` option matches snapshot for errors into \`worker-farm\` and \`cache\` is \`true\`: warnings 1`] = `Array []`; + +exports[`when applied with \`parallel\` option matches snapshot for errors into \`worker-farm\`: errors 1`] = ` +Array [ + "Error: one.9cf5e356924aeff1105d.js from UglifyJs +Error: worker-farm failed", +] +`; + +exports[`when applied with \`parallel\` option matches snapshot for errors into \`worker-farm\`: one.9cf5e356924aeff1105d.js 1`] = ` +"/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = \\"\\"; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports) { + +// foo +/* @preserve*/ +// bar +var a = 2 + 2; + +module.exports = function Foo() { + var b = 2 + 2; + console.log(b + 1 + 2); +}; + + +/***/ }) +/******/ ]);" +`; + +exports[`when applied with \`parallel\` option matches snapshot for errors into \`worker-farm\`: warnings 1`] = `Array []`; diff --git a/test/parallel-failure.test.js b/test/parallel-failure.test.js new file mode 100644 index 00000000..7582b37d --- /dev/null +++ b/test/parallel-failure.test.js @@ -0,0 +1,86 @@ +import workerFarm from 'worker-farm'; + +import UglifyJsPlugin from '../src/index'; + +import { createCompiler, compile, cleanErrorStack } from './helpers'; + +// Based on https://github.com/facebook/jest/blob/edde20f75665c2b1e3c8937f758902b5cf28a7b4/packages/jest-runner/src/__tests__/test_runner.test.js +let workerFarmMock; + +jest.mock('worker-farm', () => { + const mock = jest.fn( + () => + (workerFarmMock = jest.fn(() => { + throw new Error('worker-farm failed'); + })) + ); + mock.end = jest.fn(); + return mock; +}); + +describe('when applied with `parallel` option', () => { + let compiler; + + beforeEach(() => { + workerFarm.mockClear(); + workerFarm.end.mockClear(); + + compiler = createCompiler({ + entry: { + one: `${__dirname}/fixtures/entry.js`, + }, + }); + }); + + it('matches snapshot for errors into `worker-farm`', () => { + new UglifyJsPlugin({ parallel: true, cache: false }).apply(compiler); + + return compile(compiler).then((stats) => { + const errors = stats.compilation.errors.map(cleanErrorStack); + const warnings = stats.compilation.warnings.map(cleanErrorStack); + + expect(workerFarm.mock.calls.length).toBe(1); + expect(workerFarmMock.mock.calls.length).toBe( + Object.keys(stats.compilation.assets).length + ); + expect(workerFarm.end.mock.calls.length).toBe(1); + + expect(errors).toMatchSnapshot('errors'); + expect(warnings).toMatchSnapshot('warnings'); + + for (const file in stats.compilation.assets) { + if ( + Object.prototype.hasOwnProperty.call(stats.compilation.assets, file) + ) { + expect(stats.compilation.assets[file].source()).toMatchSnapshot(file); + } + } + }); + }); + + it('matches snapshot for errors into `worker-farm` and `cache` is `true`', () => { + new UglifyJsPlugin({ parallel: true, cache: true }).apply(compiler); + + return compile(compiler).then((stats) => { + const errors = stats.compilation.errors.map(cleanErrorStack); + const warnings = stats.compilation.warnings.map(cleanErrorStack); + + expect(workerFarm.mock.calls.length).toBe(1); + expect(workerFarmMock.mock.calls.length).toBe( + Object.keys(stats.compilation.assets).length + ); + expect(workerFarm.end.mock.calls.length).toBe(1); + + expect(errors).toMatchSnapshot('errors'); + expect(warnings).toMatchSnapshot('warnings'); + + for (const file in stats.compilation.assets) { + if ( + Object.prototype.hasOwnProperty.call(stats.compilation.assets, file) + ) { + expect(stats.compilation.assets[file].source()).toMatchSnapshot(file); + } + } + }); + }); +});