runner.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. /**
  2. * Runner middleware is responsible for communication with `karma run`.
  3. *
  4. * It basically triggers a test run and streams stdout back.
  5. */
  6. const _ = require('lodash')
  7. const path = require('path')
  8. const helper = require('../helper')
  9. const log = require('../logger').create()
  10. const constant = require('../constants')
  11. const json = require('body-parser').json()
  12. // TODO(vojta): disable when single-run mode
  13. function createRunnerMiddleware (emitter, fileList, capturedBrowsers, reporter, executor,
  14. /* config.protocol */ protocol, /* config.hostname */ hostname, /* config.port */
  15. port, /* config.urlRoot */ urlRoot, config) {
  16. helper.saveOriginalArgs(config)
  17. return function (request, response, next) {
  18. if (request.url !== '/__run__' && request.url !== urlRoot + 'run') {
  19. return next()
  20. }
  21. log.debug('Execution (fired by runner)')
  22. response.writeHead(200)
  23. if (!capturedBrowsers.length) {
  24. const url = `${protocol}//${hostname}:${port}${urlRoot}`
  25. return response.end(`No captured browser, open ${url}\n`)
  26. }
  27. json(request, response, function () {
  28. if (!capturedBrowsers.areAllReady([])) {
  29. response.write('Waiting for previous execution...\n')
  30. }
  31. const data = request.body
  32. updateClientArgs(data)
  33. handleRun(data)
  34. refreshFileList(data).then(() => {
  35. executor.schedule()
  36. }).catch((error) => {
  37. const errorMessage = `Error during refresh file list. ${error.stack || error}`
  38. executor.scheduleError(errorMessage)
  39. })
  40. })
  41. function updateClientArgs (data) {
  42. helper.restoreOriginalArgs(config)
  43. if (_.isEmpty(data.args)) {
  44. log.debug('Ignoring empty client.args from run command')
  45. } else if ((_.isArray(data.args) && _.isArray(config.client.args)) ||
  46. (_.isPlainObject(data.args) && _.isPlainObject(config.client.args))) {
  47. log.debug('Merging client.args with ', data.args)
  48. config.client.args = _.merge(config.client.args, data.args)
  49. } else {
  50. log.warn('Replacing client.args with ', data.args, ' as their types do not match.')
  51. config.client.args = data.args
  52. }
  53. }
  54. async function refreshFileList (data) {
  55. let fullRefresh = true
  56. if (helper.isArray(data.changedFiles)) {
  57. await Promise.all(data.changedFiles.map(async function (filepath) {
  58. await fileList.changeFile(path.resolve(config.basePath, filepath))
  59. fullRefresh = false
  60. }))
  61. }
  62. if (helper.isArray(data.addedFiles)) {
  63. await Promise.all(data.addedFiles.map(async function (filepath) {
  64. await fileList.addFile(path.resolve(config.basePath, filepath))
  65. fullRefresh = false
  66. }))
  67. }
  68. if (helper.isArray(data.removedFiles)) {
  69. await Promise.all(data.removedFiles.map(async function (filepath) {
  70. await fileList.removeFile(path.resolve(config.basePath, filepath))
  71. fullRefresh = false
  72. }))
  73. }
  74. if (fullRefresh && data.refresh !== false) {
  75. log.debug('Refreshing all the files / patterns')
  76. await fileList.refresh()
  77. }
  78. }
  79. function handleRun (data) {
  80. emitter.once('run_start', function () {
  81. const responseWrite = response.write.bind(response)
  82. responseWrite.colors = data.colors
  83. reporter.addAdapter(responseWrite)
  84. // clean up, close runner response
  85. emitter.once('run_complete', function (_browsers, results) {
  86. reporter.removeAdapter(responseWrite)
  87. const emptyTestSuite = (results.failed + results.success) === 0 ? 0 : 1
  88. response.end(constant.EXIT_CODE + emptyTestSuite + results.exitCode)
  89. })
  90. })
  91. }
  92. }
  93. }
  94. createRunnerMiddleware.$inject = ['emitter', 'fileList', 'capturedBrowsers', 'reporter', 'executor',
  95. 'config.protocol', 'config.hostname', 'config.port', 'config.urlRoot', 'config']
  96. // PUBLIC API
  97. exports.create = createRunnerMiddleware