-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathcmd.js
executable file
·223 lines (168 loc) · 5.16 KB
/
cmd.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
#!/usr/bin/env node
const path = require('path')
const { command, flag, rest } = require('paparam')
const Globbie = require('globbie')
const { spawn } = require('child_process')
const TracingPromise = require('./lib/tracing-promise')
const args = process.argv.slice(2).concat((process.env.BRITTLE || '').split(/\s|,/g).map(s => s.trim()).filter(s => s))
const cmd = command('brittle',
flag('--solo, -s', 'Engage solo mode'),
flag('--bail, -b', 'Bail out on first assert failure'),
flag('--coverage, -cov, -c', 'Turn on coverage'),
flag('--cov-dir <dir>', 'Configure coverage output directory (default: ./coverage)'),
flag('--trace', 'Trace all active promises and print them if the test fails'),
flag('--timeout, -t <timeout>', 'Set the test timeout in milliseconds (default: 30000)'),
flag('--runner, -r <runner>', 'Generates an out file that contains all target tests'),
flag('--mine, -m <miners>', 'Keep running the tests in <miners> processes until they fail.'),
rest('<files>')
).parse(args)
if (!cmd) process.exit(0)
const argv = cmd.flags
const files = []
for (const g of cmd.rest) {
const glob = new Globbie(g, { sync: true })
const matches = glob.match()
if (matches.length === 0) {
if (g[0] === '-') continue
console.error(`Error: no files found when resolving ${g}`)
process.exit(1)
}
files.push(...matches)
}
if (files.length === 0) {
console.error('Error: No test files were specified')
process.exit(1)
}
const { solo, bail, timeout, cov, mine, trace } = argv
process.title = 'brittle'
if (trace && !mine) {
TracingPromise.enable()
process.on('exit', function (code) {
if (!code) return
console.error()
console.error('Printing tracing info since the tests failed:')
console.error()
TracingPromise.print()
})
}
if (argv.runner) {
const fs = require('fs')
if (argv.runner === true) {
console.error('--runner must be a path to the generated test runner')
process.exit(2)
}
const out = path.resolve(argv.runner)
const dir = path.dirname(out)
let s = ''
s += 'runTests()\n\nasync function runTests () {\n const test = (await import(\'brittle\')).default\n\n'
if (bail || solo || timeout) {
s += ' test.configure({ bail: ' + !!bail + ', solo: ' + !!solo + ', timeout: ' + timeout + ' })\n'
}
s += ' test.pause()\n\n'
for (const f of files) {
const t = path.resolve(f)
if (t === out) continue
let r = path.relative(dir, t)
if (r[0] !== '.') r = '.' + path.sep + r
s += ' await import(\'' + r + '\')\n'
}
s = s.trimRight()
s += '\n\n test.resume()\n}\n'
s = '// This runner is auto-generated by Brittle\n\n' + s
try {
fs.mkdirSync(dir)
} catch {}
fs.writeFileSync(out, s)
process.exit(0)
}
if (cov && process.env.BRITTLE_COVERAGE !== 'false') require('bare-cov')({ dir: argv['cov-dir'] })
if (mine) startMining().catch()
else start().catch(onerror)
function onerror (err) {
console.error(err.stack)
process.exit(1)
}
async function start () {
const brittle = require('./')
if (bail || solo || timeout) {
brittle.configure({ bail, solo, timeout: timeout ? Number(timeout) : undefined })
}
brittle.pause()
for (const f of files) {
await import('file://' + path.resolve(f))
}
brittle.resume()
}
async function startMining () {
const args = [__filename]
.concat(solo ? ['--solo'] : [])
.concat(bail ? ['--bail'] : [])
.concat(trace ? ['--trace'] : [])
.concat(timeout ? ['--timeout', timeout + ''] : [])
.concat(files)
const running = new Set()
const max = Number(argv.mine) || 1
let runs = 0
let bailed = false
let newline = false
const interval = setInterval(function () {
console.log('Still mining... Total runs: ' + runs)
newline = true
}, 1000)
bump()
process.once('SIGINT', bail)
process.once('SIGTERM', bail)
function bail () {
bailed = true
clearInterval(interval)
for (const r of running) r.kill()
}
async function bump () {
if (running.size >= max || bailed) return
const r = run()
running.add(r)
const { exitCode, output } = await r.promise
running.delete(r)
runs++
if (bailed) return
if (!exitCode) {
bump()
bump()
return
}
bailed = true
clearInterval(interval)
if (newline) console.log()
console.log('Runner failed with exit code ' + exitCode + '!')
console.log('Shutting down the rest and printing output...')
for (const r of running) {
r.kill()
await r.promise
}
console.log('Done! The tests took ' + runs + ' runs to fail.')
console.log()
for (const { stdout, data } of output) {
if (stdout) process.stdout.write(data)
else process.stderr.write(data)
}
process.exit(exitCode)
}
function run () {
const p = spawn(process.execPath, args)
const output = []
p.stdout.on('data', (data) => output.push({ stdout: true, data }))
p.stderr.on('data', (data) => output.push({ stdout: false, data }))
const promise = new Promise((resolve) => {
p.on('close', (exitCode) => {
resolve({
exitCode,
output
})
})
})
return {
promise,
kill: () => p.kill()
}
}
}