runner.js 3.2 KB

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