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