util.js 2.4 KB
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'
  )
}