async function runMochaWatchAsync(args, opts, change) {
  if (typeof opts === 'string') {
    opts = {cwd: opts};
  }
  opts = {
    sleepMs: 2000,
    stdio: ['pipe', 'pipe', 'inherit'],
    ...opts,
    fork: process.platform === 'win32'
  };
  const [mochaProcess, resultPromise] = invokeMochaAsync(
    [...args, '--watch'],
    opts
  );
  await sleep(opts.sleepMs);
  await change(mochaProcess);
  await sleep(opts.sleepMs);

  if (
    !(mochaProcess.connected
      ? mochaProcess.send('SIGINT')
      : mochaProcess.kill('SIGINT'))
  ) {
    throw new Error('failed to send signal to subprocess');
  }

  const res = await resultPromise;

  // we kill the process with `SIGINT`, so it will always appear as "failed" to our
  // custom assertions (a non-zero exit code 130). just change it to 0.
  res.code = 0;
  return res;
}
Runs the mocha executable in watch mode calls `change` and returns the
raw result.

The function starts mocha with the given arguments and `--watch` and
waits until the first test run has completed. Then it calls `change`
and waits until the second test run has been completed. Mocha is
killed and the result is returned.

On Windows, this will call `child_process.fork()` instead of `spawn()`.

**Exit code will always be 0**
@param {string[]} args - Array of argument strings
@param {object|string} opts - If a `string`, then `cwd`, otherwise options for `child_process`
@param {Function} change - A potentially `Promise`-returning callback to execute which will change a watched file
@returns {Promise<RawResult>}