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
const {execFile} = require('child_process')
const path = require('path')
const {promisify} = require('util')
exports.execFilePromise = promisify(execFile)
function* reorderings(fixed, reorderable) {
if (!reorderable.length) {
yield fixed
return
}
const [first, ...rest] = reorderable
for (let i = 0; i <= fixed.length; i++) {
yield* reorderings(fixed.slice(0, i).concat([first], fixed.slice(i)), rest)
}
}
exports.reorderings = reorderings
const updatePath = filename => path.join(...filename.split('/'))
exports.updatePath = updatePath
/** Updates the path separators in an expected output to match the OS's separator */
exports.updateExpectedPaths = expected =>
expected.map(lineGroup => lineGroup.map(line => {
const pathEnd = line.indexOf(':')
if (pathEnd < 0) throw new Error('Line does not have a path')
return updatePath(line.slice(0, pathEnd)) + line.slice(pathEnd)
}))
function toLines(text) {
const lines = text.split('\n')
if (lines.length && !lines[lines.length - 1]) lines.pop()
return lines
}
/**
* Checks that grep.js's output matches an array of expected outputs for each file.
* Each file's lines must appear in order,
* but lines from different files can be intermingled.
* The expected lines must be unique.
*/
exports.matchOutput = (t, actual, expected) => {
// Has an entry for each file with remaining lines.
// The first remaining line in the file is mapped to the set of subsequent lines.
const nextLines = new Map()
function addNextLine(lines) {
if (lines.size) {
const [firstLine] = lines
lines.delete(firstLine)
nextLines.set(firstLine, lines)
}
}
for (const expectedLines of expected) addNextLine(new Set(expectedLines))
const expectedOutput = []
for (const line of toLines(actual)) {
const expectedRest = nextLines.get(line)
// If this was not the next line in any file, skip it
if (!expectedRest) continue
// If this was the next line in some file, record that it was matched
// and remove it from the remaining lines in that file
expectedOutput.push(line)
nextLines.delete(line)
addNextLine(expectedRest)
}
// Add all unmatched lines to the expected output
for (const [firstLine, remainingLines] of nextLines) {
expectedOutput.push(firstLine, ...remainingLines)
}
t.deepEqual(
actual,
expectedOutput.map(line => `${line}\n`).join(''),
'Unexpected grep.js output'
)
}