UNPKG

4.36 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 twoSeconds = 2000
17
18const waitOnTimeout = process.env.WAIT_ON_TIMEOUT
19 ? Number(process.env.WAIT_ON_TIMEOUT)
20 : fiveMinutes
21
22const waitOnInterval = process.env.WAIT_ON_INTERVAL
23 ? Number(process.env.WAIT_ON_INTERVAL)
24 : twoSeconds
25
26const isDebug = () =>
27 process.env.DEBUG && process.env.DEBUG.indexOf('start-server-and-test') !== -1
28
29const isInsecure = () => process.env.START_SERVER_AND_TEST_INSECURE
30
31function 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
122const runTheTests = testCommand => () => {
123 debug('running test script command: %s', testCommand)
124 return execa(testCommand, { shell: true, stdio: 'inherit' })
125}
126
127/**
128 * Starts a single service and runs tests or recursively
129 * runs a service, then goes to the next list, until it reaches 1 service and runs test.
130 */
131function 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
164module.exports = {
165 startAndTest
166}