runner.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. 'use strict'
  2. const http = require('http')
  3. const constant = require('./constants')
  4. const EventEmitter = require('events').EventEmitter
  5. const helper = require('./helper')
  6. const cfg = require('./config')
  7. const logger = require('./logger')
  8. const log = logger.create('runner')
  9. function parseExitCode (buffer, defaultExitCode, failOnEmptyTestSuite) {
  10. const tailPos = buffer.length - Buffer.byteLength(constant.EXIT_CODE) - 2
  11. if (tailPos < 0) {
  12. return { exitCode: defaultExitCode, buffer }
  13. }
  14. const tail = buffer.slice(tailPos)
  15. const tailStr = tail.toString()
  16. if (tailStr.slice(0, -2) === constant.EXIT_CODE) {
  17. const emptyInt = parseInt(tailStr.slice(-2, -1), 10)
  18. let exitCode = parseInt(tailStr.slice(-1), 10)
  19. if (failOnEmptyTestSuite === false && emptyInt === 0) {
  20. log.warn('Test suite was empty.')
  21. exitCode = 0
  22. }
  23. return { exitCode, buffer: buffer.slice(0, tailPos) }
  24. }
  25. return { exitCode: defaultExitCode, buffer }
  26. }
  27. // TODO(vojta): read config file (port, host, urlRoot)
  28. function run (cliOptionsOrConfig, done) {
  29. cliOptionsOrConfig = cliOptionsOrConfig || {}
  30. done = helper.isFunction(done) ? done : process.exit
  31. let config
  32. if (cliOptionsOrConfig instanceof cfg.Config) {
  33. config = cliOptionsOrConfig
  34. } else {
  35. logger.setupFromConfig({
  36. colors: cliOptionsOrConfig.colors,
  37. logLevel: cliOptionsOrConfig.logLevel
  38. })
  39. const deprecatedCliOptionsMessage =
  40. 'Passing raw CLI options to `runner(config, done)` is deprecated. Use ' +
  41. '`parseConfig(configFilePath, cliOptions, {promiseConfig: true, throwErrors: true})` ' +
  42. 'to prepare a processed `Config` instance and pass that as the ' +
  43. '`config` argument instead.'
  44. log.warn(deprecatedCliOptionsMessage)
  45. try {
  46. config = cfg.parseConfig(
  47. cliOptionsOrConfig.configFile,
  48. cliOptionsOrConfig,
  49. {
  50. promiseConfig: false,
  51. throwErrors: true
  52. }
  53. )
  54. } catch (parseConfigError) {
  55. // TODO: change how `done` falls back to exit in next major version
  56. // SEE: https://github.com/karma-runner/karma/pull/3635#discussion_r565399378
  57. done(1)
  58. }
  59. }
  60. let exitCode = 1
  61. const emitter = new EventEmitter()
  62. const options = {
  63. hostname: config.hostname,
  64. path: config.urlRoot + 'run',
  65. port: config.port,
  66. method: 'POST',
  67. headers: {
  68. 'Content-Type': 'application/json'
  69. }
  70. }
  71. const request = http.request(options, function (response) {
  72. response.on('data', function (buffer) {
  73. const parsedResult = parseExitCode(buffer, exitCode, config.failOnEmptyTestSuite)
  74. exitCode = parsedResult.exitCode
  75. emitter.emit('progress', parsedResult.buffer)
  76. })
  77. response.on('end', () => done(exitCode))
  78. })
  79. request.on('error', function (e) {
  80. if (e.code === 'ECONNREFUSED') {
  81. log.error('There is no server listening on port %d', options.port)
  82. done(1, e.code)
  83. } else {
  84. throw e
  85. }
  86. })
  87. request.end(JSON.stringify({
  88. args: config.clientArgs,
  89. removedFiles: config.removedFiles,
  90. changedFiles: config.changedFiles,
  91. addedFiles: config.addedFiles,
  92. refresh: config.refresh,
  93. colors: config.colors
  94. }))
  95. return emitter
  96. }
  97. exports.run = run