diff --git a/lib/server.js b/lib/server.js index c9d52ddf2..dad58884d 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,62 +1,52 @@ -var SocketIO = require('socket.io') -var di = require('di') -var util = require('util') -var Promise = require('bluebird') -var spawn = require('child_process').spawn -var tmp = require('tmp') -var fs = require('fs') -var path = require('path') -var root = global || window || this - -var cfg = require('./config') -var logger = require('./logger') -var constant = require('./constants') -var watcher = require('./watcher') -var plugin = require('./plugin') - -var ws = require('./web-server') -var preprocessor = require('./preprocessor') -var Launcher = require('./launcher').Launcher -var FileList = require('./file-list') -var reporter = require('./reporter') -var helper = require('./helper') -var events = require('./events') -var KarmaEventEmitter = events.EventEmitter -var EventEmitter = require('events').EventEmitter -var Executor = require('./executor') -var Browser = require('./browser') -var BrowserCollection = require('./browser_collection') -var EmitterWrapper = require('./emitter_wrapper') -var processWrapper = new EmitterWrapper(process) +'use strict' + +const SocketIO = require('socket.io') +const di = require('di') +const util = require('util') +const Promise = require('bluebird') +const spawn = require('child_process').spawn +const tmp = require('tmp') +const fs = require('fs') +const path = require('path') +const root = global || window || this + +const cfg = require('./config') +const logger = require('./logger') +const constant = require('./constants') +const watcher = require('./watcher') +const plugin = require('./plugin') + +const ws = require('./web-server') +const preprocessor = require('./preprocessor') +const Launcher = require('./launcher').Launcher +const FileList = require('./file-list') +const reporter = require('./reporter') +const helper = require('./helper') +const events = require('./events') +const KarmaEventEmitter = events.EventEmitter +const EventEmitter = require('events').EventEmitter +const Executor = require('./executor') +const Browser = require('./browser') +const BrowserCollection = require('./browser_collection') +const EmitterWrapper = require('./emitter_wrapper') +const processWrapper = new EmitterWrapper(process) const karmaJsPath = path.join(__dirname, '/../static/karma.js') const contextJsPath = path.join(__dirname, '/../static/context.js') -// Dynamic dependence on brwoserify -var browserify = null - -/** - * Bundles a static resource using Browserify. - * @param {string} inPath the path to the file to browserify - * @param {string} outPath the path to output the bundle to - * @returns {Promise} - */ function bundleResource (inPath, outPath) { + const browserify = require('browserify') return new Promise((resolve, reject) => { - browserify = browserify || require('browserify') - var bundler = browserify(inPath) - bundler.bundle().pipe(fs.createWriteStream(outPath)) - .once('finish', () => { - resolve() - }) - .once('error', (e) => { - reject(e) - }) + browserify(inPath) + .bundle() + .pipe(fs.createWriteStream(outPath)) + .once('finish', () => resolve()) + .once('error', (e) => reject(e)) }) } function createSocketIoServer (webServer, executor, config) { - var server = new SocketIO(webServer, { + const server = new SocketIO(webServer, { // avoid destroying http upgrades from socket.io to get proxied websockets working destroyUpgrade: false, path: config.urlRoot + 'socket.io/', @@ -70,426 +60,337 @@ function createSocketIoServer (webServer, executor, config) { return server } -// Constructor -var Server = function (cliOptions, done) { - Object.keys(EventEmitter.prototype).forEach(function (method) { - this[method] = EventEmitter.prototype[method] - }, this) - - this.bind = KarmaEventEmitter.prototype.bind.bind(this) - this.emitAsync = KarmaEventEmitter.prototype.emitAsync.bind(this) - - logger.setupFromConfig(cliOptions) - - this.log = logger.create() - - this.loadErrors = [] - - var config = cfg.parseConfig(cliOptions.configFile, cliOptions) - - var modules = [{ - helper: ['value', helper], - logger: ['value', logger], - done: ['value', done || process.exit], - emitter: ['value', this], - server: ['value', this], - launcher: ['type', Launcher], - config: ['value', config], - preprocess: ['factory', preprocessor.createPreprocessor], - fileList: ['factory', FileList.factory], - webServer: ['factory', ws.create], - socketServer: ['factory', createSocketIoServer], - executor: ['factory', Executor.factory], - // TODO(vojta): remove - customFileHandlers: ['value', []], - // TODO(vojta): remove, once karma-dart does not rely on it - customScriptTypes: ['value', []], - reporter: ['factory', reporter.createReporters], - capturedBrowsers: ['factory', BrowserCollection.factory], - args: ['value', {}], - timer: ['value', { - setTimeout: function () { - return setTimeout.apply(root, arguments) - }, - clearTimeout: function (timeoutId) { - clearTimeout(timeoutId) - } +class Server extends KarmaEventEmitter { + constructor (cliOptions, done) { + super() + logger.setupFromConfig(cliOptions) + + this.log = logger.create() + + this.loadErrors = [] + + const config = cfg.parseConfig(cliOptions.configFile, cliOptions) + + let modules = [{ + helper: ['value', helper], + logger: ['value', logger], + done: ['value', done || process.exit], + emitter: ['value', this], + server: ['value', this], + launcher: ['type', Launcher], + config: ['value', config], + preprocess: ['factory', preprocessor.createPreprocessor], + fileList: ['factory', FileList.factory], + webServer: ['factory', ws.create], + socketServer: ['factory', createSocketIoServer], + executor: ['factory', Executor.factory], + // TODO(vojta): remove + customFileHandlers: ['value', []], + // TODO(vojta): remove, once karma-dart does not rely on it + customScriptTypes: ['value', []], + reporter: ['factory', reporter.createReporters], + capturedBrowsers: ['factory', BrowserCollection.factory], + args: ['value', {}], + timer: ['value', { + setTimeout () { + return setTimeout.apply(root, arguments) + }, + clearTimeout + }] }] - }] - this._setUpLoadErrorListener() - // Load the plugins - modules = modules.concat(plugin.resolve(config.plugins, this)) - - this._injector = new di.Injector(modules) -} + this.on('load_error', (type, name) => { + this.log.debug(`Registered a load error of type ${type} with name ${name}`) + this.loadErrors.push([type, name]) + }) -// Inherit from events.EventEmitter -util.inherits(Server, EventEmitter) + modules = modules.concat(plugin.resolve(config.plugins, this)) + this._injector = new di.Injector(modules) + } -// Public Methods -// -------------- + start () { + this._injector.invoke(this._start, this) + } -// Start the server -Server.prototype.start = function () { - this._injector.invoke(this._start, this) -} + get (token) { + return this._injector.get(token) + } -/** - * Backward-compatibility with karma-intellij bundled with WebStorm. - * Deprecated since version 0.13, to be removed in 0.14 - */ -Server.start = function (cliOptions, done) { - var server = new Server(cliOptions, done) - server.start() -} + refreshFiles () { + return this._fileList ? this._fileList.refresh() : Promise.resolve() + } -// Get properties from the injector -// -// token - String -Server.prototype.get = function (token) { - return this._injector.get(token) -} + _start (config, launcher, preprocess, fileList, capturedBrowsers, executor, done) { + if (config.detached) { + this._detach(config, done) + return + } -// Force a refresh of the file list -Server.prototype.refreshFiles = function () { - if (!this._fileList) return Promise.resolve() + this._fileList = fileList - return this._fileList.refresh() -} + config.frameworks.forEach((framework) => this._injector.get('framework:' + framework)) -// Private Methods -// --------------- + const webServer = this._injector.get('webServer') + const socketServer = this._injector.get('socketServer') -Server.prototype._start = function (config, launcher, preprocess, fileList, - capturedBrowsers, executor, done) { - var self = this - if (config.detached) { - this._detach(config, done) - return - } + const singleRunDoneBrowsers = Object.create(null) + const singleRunBrowsers = new BrowserCollection(new EventEmitter()) + let singleRunBrowserNotCaptured = false - self._fileList = fileList + webServer.on('error', (e) => { + if (e.code === 'EADDRINUSE') { + this.log.warn('Port %d in use', config.port) + config.port++ + webServer.listen(config.port, config.listenAddress) + } else { + throw e + } + }) - config.frameworks.forEach(function (framework) { - self._injector.get('framework:' + framework) - }) + const afterPreprocess = () => { + if (config.autoWatch) { + this._injector.invoke(watcher.watch) + } - var webServer = self._injector.get('webServer') - var socketServer = self._injector.get('socketServer') + const startWebServer = () => { + webServer.listen(config.port, config.listenAddress, () => { + this.log.info(`Karma v${constant.VERSION} server started at ${config.protocol}//${config.listenAddress}:${config.port}${config.urlRoot}`) + + this.emit('listening', config.port) + if (config.browsers && config.browsers.length) { + this._injector.invoke(launcher.launch, launcher).forEach((browserLauncher) => { + singleRunDoneBrowsers[browserLauncher.id] = false + }) + } + if (this.loadErrors.length > 0) { + this.log.error('Found %d load error%s', this.loadErrors.length, this.loadErrors.length === 1 ? '' : 's') + process.exitCode = 1 + process.kill(process.pid, 'SIGINT') + } + }) + } - // A map of launched browsers. - var singleRunDoneBrowsers = Object.create(null) + if (fs.existsSync(karmaJsPath) && fs.existsSync(contextJsPath)) { + startWebServer() + } else { + this.log.info('Front-end scripts not present. Compiling...') + Promise.all([ + bundleResource(path.join(__dirname, '/../client/main.js'), karmaJsPath), + bundleResource(path.join(__dirname, '/../context/main.js'), contextJsPath) + ]) + .then(startWebServer) + .catch((error) => { + this.log.error('Front-end script compile failed with error: ' + error) + process.exitCode = 1 + process.kill(process.pid, 'SIGINT') + }) + } + } - // Passing fake event emitter, so that it does not emit on the global, - // we don't care about these changes. - var singleRunBrowsers = new BrowserCollection(new EventEmitter()) + fileList.refresh().then(afterPreprocess, afterPreprocess) - // Some browsers did not get captured. - var singleRunBrowserNotCaptured = false + this.on('browsers_change', () => socketServer.sockets.emit('info', capturedBrowsers.serialize())) - webServer.on('error', function (e) { - if (e.code === 'EADDRINUSE') { - self.log.warn('Port %d in use', config.port) - config.port++ - webServer.listen(config.port, config.listenAddress) - } else { - throw e - } - }) + this.on('browser_register', (browser) => { + launcher.markCaptured(browser.id) - var afterPreprocess = function () { - if (config.autoWatch) { - self._injector.invoke(watcher.watch) - } + if (launcher.areAllCaptured()) { + this.emit('browsers_ready') - var startWebServer = function () { - webServer.listen(config.port, config.listenAddress, function () { - self.log.info('Karma v%s server started at %s//%s:%s%s', constant.VERSION, - config.protocol, config.listenAddress, config.port, config.urlRoot) + if (config.autoWatch) { + executor.schedule() + } + } + }) - self.emit('listening', config.port) - if (config.browsers && config.browsers.length) { - self._injector.invoke(launcher.launch, launcher).forEach(function (browserLauncher) { - singleRunDoneBrowsers[browserLauncher.id] = false - }) + if (config.browserConsoleLogOptions && config.browserConsoleLogOptions.path) { + const configLevel = config.browserConsoleLogOptions.level || 'debug' + const configFormat = config.browserConsoleLogOptions.format || '%b %T: %m' + const configPath = config.browserConsoleLogOptions.path + this.log.info(`Writing browser console to file: ${configPath}`) + const browserLogFile = fs.openSync(configPath, 'w+') + const levels = ['log', 'error', 'warn', 'info', 'debug'] + this.on('browser_log', function (browser, message, level) { + if (levels.indexOf(level.toLowerCase()) > levels.indexOf(configLevel)) { + return } - var noLoadErrors = self.loadErrors.length - if (noLoadErrors > 0) { - self.log.error('Found %d load error%s', noLoadErrors, noLoadErrors === 1 ? '' : 's') - process.exitCode = 1 - process.kill(process.pid, 'SIGINT') + if (!helper.isString(message)) { + message = util.inspect(message, { showHidden: false, colors: false }) } + const logMap = {'%m': message, '%t': level.toLowerCase(), '%T': level.toUpperCase(), '%b': browser} + const logString = configFormat.replace(/%[mtTb]/g, (m) => logMap[m]) + this.log.debug(`Writing browser console line: ${logString}`) + fs.writeSync(browserLogFile, logString + '\n') }) } - // Check if the static files haven't been compiled - if (!(fs.existsSync(karmaJsPath) && fs.existsSync(contextJsPath))) { - self.log.info('Front-end scripts not present. Compiling...') - var mainPromise = bundleResource(path.join(__dirname, '/../client/main.js'), karmaJsPath) - var contextPromise = bundleResource(path.join(__dirname, '/../context/main.js'), contextJsPath) - Promise.all([mainPromise, contextPromise]).then(() => { - startWebServer() - }).catch((error) => { - self.log.error('Front-end script compile failed with error: ' + error) - process.exitCode = 1 - process.kill(process.pid, 'SIGINT') - }) - } else { - startWebServer() - } - } - - fileList.refresh().then(afterPreprocess, afterPreprocess) + socketServer.sockets.on('connection', (socket) => { + this.log.debug('A browser has connected on socket ' + socket.id) - self.on('browsers_change', function () { - // TODO(vojta): send only to interested browsers - socketServer.sockets.emit('info', capturedBrowsers.serialize()) - }) + const replySocketEvents = events.bufferEvents(socket, ['start', 'info', 'karma_error', 'result', 'complete']) - self.on('browser_register', function (browser) { - launcher.markCaptured(browser.id) + socket.on('complete', (data, ack) => ack()) - // TODO(vojta): This is lame, browser can get captured and then - // crash (before other browsers get captured). - if (launcher.areAllCaptured()) { - self.emit('browsers_ready') + socket.on('register', (info) => { + let newBrowser = info.id ? (capturedBrowsers.getById(info.id) || singleRunBrowsers.getById(info.id)) : null - if (config.autoWatch) { - executor.schedule() - } - } - }) + if (newBrowser) { + newBrowser.reconnect(socket) - if (config.browserConsoleLogOptions && config.browserConsoleLogOptions.path) { - var configLevel = config.browserConsoleLogOptions.level || 'debug' - var configFormat = config.browserConsoleLogOptions.format || '%b %T: %m' - var configPath = config.browserConsoleLogOptions.path - self.log.info('Writing browser console to file: %s', configPath) - var browserLogFile = fs.openSync(configPath, 'w+') - var levels = ['log', 'error', 'warn', 'info', 'debug'] - self.on('browser_log', function (browser, message, level) { - if (levels.indexOf(level.toLowerCase()) > levels.indexOf(configLevel)) return - if (!helper.isString(message)) { - message = util.inspect(message, {showHidden: false, colors: false}) - } - var logMap = {'%m': message, '%t': level.toLowerCase(), '%T': level.toUpperCase(), '%b': browser} - var logString = configFormat.replace(/%[mtTb]/g, function (m) { - return logMap[m] - }) - self.log.debug('Writing browser console line: %s', logString) - fs.writeSync(browserLogFile, logString + '\n') - }) - } + // We are restarting a previously disconnected browser. + if (newBrowser.state === Browser.STATE_DISCONNECTED && config.singleRun) { + newBrowser.execute(config.client) + } + } else { + newBrowser = this._injector.createChild([{ + id: ['value', info.id || null], + fullName: ['value', (helper.isDefined(info.displayName) ? info.displayName : info.name)], + socket: ['value', socket] + }]).invoke(Browser.factory) - var EVENTS_TO_REPLY = ['start', 'info', 'karma_error', 'result', 'complete'] - socketServer.sockets.on('connection', function (socket) { - self.log.debug('A browser has connected on socket ' + socket.id) + newBrowser.init() - var replySocketEvents = events.bufferEvents(socket, EVENTS_TO_REPLY) + if (config.singleRun) { + newBrowser.execute(config.client) + singleRunBrowsers.add(newBrowser) + } + } - socket.on('complete', function (data, ack) { - ack() + replySocketEvents() + }) }) - socket.on('register', function (info) { - var newBrowser - var isRestart - - if (info.id) { - newBrowser = capturedBrowsers.getById(info.id) || singleRunBrowsers.getById(info.id) + const emitRunCompleteIfAllBrowsersDone = () => { + if (Object.keys(singleRunDoneBrowsers).every((key) => singleRunDoneBrowsers[key])) { + const results = singleRunBrowsers.getResults() + if (singleRunBrowserNotCaptured) { + results.exitCode = 1 + } else if (results.success + results.failed === 0 && !config.failOnEmptyTestSuite) { + results.exitCode = 0 + this.log.warn('Test suite was empty.') + } + this.emit('run_complete', singleRunBrowsers, results) } + } - if (newBrowser) { - isRestart = newBrowser.state === Browser.STATE_DISCONNECTED - newBrowser.reconnect(socket) + this.on('browser_complete', (completedBrowser) => { + if (completedBrowser.lastResult.disconnected && completedBrowser.disconnectsCount <= config.browserDisconnectTolerance) { + this.log.info(`Restarting ${completedBrowser.name} (${completedBrowser.disconnectsCount} of ${config.browserDisconnectTolerance} attempts)`) - // We are restarting a previously disconnected browser. - if (isRestart && config.singleRun) { - newBrowser.execute(config.client) + if (!launcher.restart(completedBrowser.id)) { + this.emit('browser_restart_failure', completedBrowser) } } else { - newBrowser = self._injector.createChild([{ - id: ['value', info.id || null], - fullName: ['value', (helper.isDefined(info.displayName) ? info.displayName : info.name)], - socket: ['value', socket] - }]).invoke(Browser.factory) - - newBrowser.init() - - // execute in this browser immediately - if (config.singleRun) { - newBrowser.execute(config.client) - singleRunBrowsers.add(newBrowser) - } + this.emit('browser_complete_with_no_more_retries', completedBrowser) } - - replySocketEvents() }) - }) - var emitRunCompleteIfAllBrowsersDone = function () { - // all browsers done - var isDone = Object.keys(singleRunDoneBrowsers).reduce(function (isDone, id) { - return isDone && singleRunDoneBrowsers[id] - }, true) - - if (isDone) { - var results = singleRunBrowsers.getResults() - if (singleRunBrowserNotCaptured) { - results.exitCode = 1 - } else if (results.success + results.failed === 0 && !config.failOnEmptyTestSuite) { - results.exitCode = 0 - self.log.warn('Test suite was empty.') - } - self.emit('run_complete', singleRunBrowsers, results) - } - } + if (config.singleRun) { + this.on('browser_restart_failure', (completedBrowser) => { + singleRunDoneBrowsers[completedBrowser.id] = true + emitRunCompleteIfAllBrowsersDone() + }) + this.on('browser_complete_with_no_more_retries', function (completedBrowser) { + singleRunDoneBrowsers[completedBrowser.id] = true - self.on('browser_complete', function (completedBrowser) { - if (completedBrowser.lastResult.disconnected && - completedBrowser.disconnectsCount <= config.browserDisconnectTolerance) { - self.log.info('Restarting %s (%d of %d attempts)', completedBrowser.name, - completedBrowser.disconnectsCount, config.browserDisconnectTolerance) + if (launcher.kill(completedBrowser.id)) { + // workaround to supress "disconnect" warning + completedBrowser.state = Browser.STATE_DISCONNECTED + } - if (!launcher.restart(completedBrowser.id)) { - self.emit('browser_restart_failure', completedBrowser) - } - } else { - self.emit('browser_complete_with_no_more_retries', completedBrowser) - } - }) + emitRunCompleteIfAllBrowsersDone() + }) - if (config.singleRun) { - self.on('browser_restart_failure', function (completedBrowser) { - singleRunDoneBrowsers[completedBrowser.id] = true - emitRunCompleteIfAllBrowsersDone() - }) - self.on('browser_complete_with_no_more_retries', function (completedBrowser) { - singleRunDoneBrowsers[completedBrowser.id] = true + this.on('browser_process_failure', (browserLauncher) => { + singleRunDoneBrowsers[browserLauncher.id] = true + singleRunBrowserNotCaptured = true - if (launcher.kill(completedBrowser.id)) { - // workaround to supress "disconnect" warning - completedBrowser.state = Browser.STATE_DISCONNECTED - } + emitRunCompleteIfAllBrowsersDone() + }) - emitRunCompleteIfAllBrowsersDone() - }) + this.on('run_complete', function (browsers, results) { + this.log.debug('Run complete, exiting.') + disconnectBrowsers(results.exitCode) + }) - self.on('browser_process_failure', function (browserLauncher) { - singleRunDoneBrowsers[browserLauncher.id] = true - singleRunBrowserNotCaptured = true + this.emit('run_start', singleRunBrowsers) + } - emitRunCompleteIfAllBrowsersDone() - }) + if (config.autoWatch) { + this.on('file_list_modified', () => { + this.log.debug('List of files has changed, trying to execute') + if (config.restartOnFileChange) { + socketServer.sockets.emit('stop') + } + executor.schedule() + }) + } - self.on('run_complete', function (browsers, results) { - self.log.debug('Run complete, exiting.') - disconnectBrowsers(results.exitCode) - }) + const webServerCloseTimeout = 3000 + const disconnectBrowsers = (code) => { + const sockets = socketServer.sockets.sockets - self.emit('run_start', singleRunBrowsers) - } + Object.keys(sockets).forEach((id) => { + const socket = sockets[id] + socket.removeAllListeners('disconnect') + if (!socket.disconnected) { + process.nextTick(socket.disconnect.bind(socket)) + } + }) - if (config.autoWatch) { - self.on('file_list_modified', function () { - self.log.debug('List of files has changed, trying to execute') - if (config.restartOnFileChange) { - socketServer.sockets.emit('stop') + let removeAllListenersDone = false + const removeAllListeners = () => { + if (removeAllListenersDone) { + return + } + removeAllListenersDone = true + webServer.removeAllListeners() + processWrapper.removeAllListeners() + done(code || 0) } - executor.schedule() - }) - } - var webServerCloseTimeout = 3000 - var disconnectBrowsers = function (code) { - // Slightly hacky way of removing disconnect listeners - // to suppress "browser disconnect" warnings - // TODO(vojta): change the client to not send the event (if disconnected by purpose) - var sockets = socketServer.sockets.sockets - - Object.keys(sockets).forEach(function (id) { - var socket = sockets[id] - socket.removeAllListeners('disconnect') - if (!socket.disconnected) { - // Disconnect asynchronously. Socket.io mutates the `sockets.sockets` array - // underneath us so this would skip every other browser/socket. - process.nextTick(socket.disconnect.bind(socket)) - } - }) + this.emitAsync('exit').then(() => { + socketServer.sockets.removeAllListeners() + socketServer.close() + const closeTimeout = setTimeout(removeAllListeners, webServerCloseTimeout) - var removeAllListenersDone = false - var removeAllListeners = function () { - // make sure we don't execute cleanup twice - if (removeAllListenersDone) { - return - } - removeAllListenersDone = true - webServer.removeAllListeners() - processWrapper.removeAllListeners() - done(code || 0) + webServer.close(() => { + clearTimeout(closeTimeout) + removeAllListeners() + }) + }) } - self.emitAsync('exit').then(function () { - // Remove Socket.IO listeners. `connection` callback closes over `Server` - // instance so it leaks Plugin state e.g. Webpack compilations. - socketServer.sockets.removeAllListeners() - socketServer.close() - // don't wait forever on webServer.close() because - // pending client connections prevent it from closing. - var closeTimeout = setTimeout(removeAllListeners, webServerCloseTimeout) - - // shutdown the server... - webServer.close(function () { - clearTimeout(closeTimeout) - removeAllListeners() - }) + processWrapper.on('SIGINT', () => disconnectBrowsers(process.exitCode)) + processWrapper.on('SIGTERM', disconnectBrowsers) + + processWrapper.on('uncaughtException', (error) => { + this.log.error(error) + disconnectBrowsers(1) }) } - processWrapper.on('SIGINT', function () { - disconnectBrowsers(process.exitCode) - }) - processWrapper.on('SIGTERM', disconnectBrowsers) - - // Handle all unhandled exceptions, so we don't just exit but - // disconnect the browsers before exiting. - processWrapper.on('uncaughtException', function (error) { - self.log.error(error) - disconnectBrowsers(1) - }) -} - -Server.prototype._setUpLoadErrorListener = function () { - var self = this - self.on('load_error', function (type, name) { - self.log.debug('Registered a load error of type %s with name %s', type, name) - self.loadErrors.push([type, name]) - }) -} - -Server.prototype._detach = function (config, done) { - var log = this.log - var tmpFile = tmp.fileSync({keep: true}) - log.info('Starting karma detached') - log.info('Run "karma stop" to stop the server.') - log.debug('Writing config to tmp-file %s', tmpFile.name) - config.detached = false - try { - fs.writeFileSync(tmpFile.name, JSON.stringify(config), 'utf8') - } catch (e) { - log.error("Couldn't write temporary configuration file") - done(1) - return + _detach (config, done) { + const tmpFile = tmp.fileSync({ keep: true }) + this.log.info('Starting karma detached') + this.log.info('Run "karma stop" to stop the server.') + this.log.debug(`Writing config to tmp-file ${tmpFile.name}`) + config.detached = false + try { + fs.writeFileSync(tmpFile.name, JSON.stringify(config), 'utf8') + } catch (e) { + this.log.error("Couldn't write temporary configuration file") + done(1) + return + } + const child = spawn(process.argv[0], [path.resolve(__dirname, '../lib/detached.js'), tmpFile.name], { + detached: true, + stdio: 'ignore' + }) + child.unref() } - var child = spawn(process.argv[0], [path.resolve(__dirname, '../lib/detached.js'), tmpFile.name], { - detached: true, - stdio: 'ignore' - }) - child.unref() } -// Export -// ------ +Server.prototype._start.$inject = ['config', 'launcher', 'preprocess', 'fileList', 'capturedBrowsers', 'executor', 'done'] module.exports = Server