UNPKG

3.73 kBJavaScriptView Raw
1// @ts-check
2'use strict'
3
4const la = require('lazy-ass')
5const is = require('check-more-types')
6const execa = require('execa')
7const waitOn = require('wait-on')
8const Promise = require('bluebird')
9const psTree = require('ps-tree')
10const debug = require('debug')('start-server-and-test')
11
12/**
13 * Used for timeout (ms)
14 */
15const fiveMinutes = 5 * 60 * 1000
16const waitOnTimeout = process.env.WAIT_ON_TIMEOUT
17 ? Number(process.env.WAIT_ON_TIMEOUT)
18 : fiveMinutes
19
20const isDebug = () =>
21 process.env.DEBUG && process.env.DEBUG.indexOf('start-server-and-test') !== -1
22
23const isInsecure = () => process.env.START_SERVER_AND_TEST_INSECURE
24
25function waitAndRun ({ start, url, runFn }) {
26 la(is.unemptyString(start), 'missing start script name', start)
27 la(is.fn(runFn), 'missing test script name', runFn)
28 la(
29 is.unemptyString(url) || is.unemptyArray(url),
30 'missing url to wait on',
31 url
32 )
33
34 debug('starting server with command "%s", verbose mode?', start, isDebug())
35
36 const server = execa(start, { shell: true, stdio: 'inherit' })
37 let serverStopped
38
39 function stopServer () {
40 debug('getting child processes')
41 if (!serverStopped) {
42 serverStopped = true
43 return Promise.fromNode(cb => psTree(server.pid, cb))
44 .then(children => {
45 debug('stopping child processes')
46 children.forEach(child => {
47 try {
48 process.kill(child.PID, 'SIGINT')
49 } catch (e) {
50 if (e.code === 'ESRCH') {
51 console.log(
52 `Child process ${child.PID} exited before trying to stop it`
53 )
54 } else {
55 throw e
56 }
57 }
58 })
59 })
60 .then(() => {
61 debug('stopping server')
62 server.kill()
63 })
64 }
65 }
66
67 const waited = new Promise((resolve, reject) => {
68 const onClose = () => {
69 reject(new Error('server closed unexpectedly'))
70 }
71
72 server.on('close', onClose)
73
74 debug('starting waitOn %s', url)
75 const options = {
76 resources: Array.isArray(url) ? url : [url],
77 interval: 2000,
78 window: 1000,
79 timeout: waitOnTimeout,
80 verbose: isDebug(),
81 strictSSL: !isInsecure(),
82 log: isDebug(),
83 headers: {
84 Accept: 'text/html, application/json, text/plain, */*'
85 }
86 }
87 debug('wait-on options %o', options)
88
89 waitOn(options, err => {
90 if (err) {
91 debug('error waiting for url', url)
92 debug(err.message)
93 return reject(err)
94 }
95 debug('waitOn finished successfully')
96 server.removeListener('close', onClose)
97 resolve()
98 })
99 })
100
101 return waited
102 .tapCatch(stopServer)
103 .then(runFn)
104 .finally(stopServer)
105}
106
107const runTheTests = testCommand => () => {
108 debug('running test script command: %s', testCommand)
109 return execa(testCommand, { shell: true, stdio: 'inherit' })
110}
111
112/**
113 * Starts a single service and runs tests or recursively
114 * runs a service, then goes to the next list, until it reaches 1 service and runs test.
115 */
116function startAndTest ({ services, test }) {
117 if (services.length === 0) {
118 throw new Error('Got zero services to start ...')
119 }
120
121 if (services.length === 1) {
122 const runTests = runTheTests(test)
123 debug('single service "%s" to run and test', services[0].start)
124 return waitAndRun({
125 start: services[0].start,
126 url: services[0].url,
127 runFn: runTests
128 })
129 }
130
131 return waitAndRun({
132 start: services[0].start,
133 url: services[0].url,
134 runFn: () => {
135 debug('previous service started, now going to the next one')
136 return startAndTest({ services: services.slice(1), test })
137 }
138 })
139}
140
141module.exports = {
142 startAndTest
143}