$
This commit is contained in:
380
node_modules/mocha/lib/cli/watch-run.js
generated
vendored
Normal file
380
node_modules/mocha/lib/cli/watch-run.js
generated
vendored
Normal file
@@ -0,0 +1,380 @@
|
||||
'use strict';
|
||||
|
||||
const logSymbols = require('log-symbols');
|
||||
const debug = require('debug')('mocha:cli:watch');
|
||||
const path = require('path');
|
||||
const chokidar = require('chokidar');
|
||||
const Context = require('../context');
|
||||
const collectFiles = require('./collect-files');
|
||||
|
||||
/**
|
||||
* Exports the `watchRun` function that runs mocha in "watch" mode.
|
||||
* @see module:lib/cli/run-helpers
|
||||
* @module
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Run Mocha in parallel "watch" mode
|
||||
* @param {Mocha} mocha - Mocha instance
|
||||
* @param {Object} opts - Options
|
||||
* @param {string[]} [opts.watchFiles] - List of paths and patterns to
|
||||
* watch. If not provided all files with an extension included in
|
||||
* `fileCollectionParams.extension` are watched. See first argument of
|
||||
* `chokidar.watch`.
|
||||
* @param {string[]} opts.watchIgnore - List of paths and patterns to
|
||||
* exclude from watching. See `ignored` option of `chokidar`.
|
||||
* @param {FileCollectionOptions} fileCollectParams - Parameters that control test
|
||||
* @private
|
||||
*/
|
||||
exports.watchParallelRun = (
|
||||
mocha,
|
||||
{watchFiles, watchIgnore},
|
||||
fileCollectParams
|
||||
) => {
|
||||
debug('creating parallel watcher');
|
||||
|
||||
return createWatcher(mocha, {
|
||||
watchFiles,
|
||||
watchIgnore,
|
||||
beforeRun({mocha}) {
|
||||
// I don't know why we're cloning the root suite.
|
||||
const rootSuite = mocha.suite.clone();
|
||||
|
||||
// ensure we aren't leaking event listeners
|
||||
mocha.dispose();
|
||||
|
||||
// this `require` is needed because the require cache has been cleared. the dynamic
|
||||
// exports set via the below call to `mocha.ui()` won't work properly if a
|
||||
// test depends on this module (see `required-tokens.spec.js`).
|
||||
const Mocha = require('../mocha');
|
||||
|
||||
// ... and now that we've gotten a new module, we need to use it again due
|
||||
// to `mocha.ui()` call
|
||||
const newMocha = new Mocha(mocha.options);
|
||||
// don't know why this is needed
|
||||
newMocha.suite = rootSuite;
|
||||
// nor this
|
||||
newMocha.suite.ctx = new Context();
|
||||
|
||||
// reset the list of files
|
||||
newMocha.files = collectFiles(fileCollectParams);
|
||||
|
||||
// because we've swapped out the root suite (see the `run` inner function
|
||||
// in `createRerunner`), we need to call `mocha.ui()` again to set up the context/globals.
|
||||
newMocha.ui(newMocha.options.ui);
|
||||
|
||||
// we need to call `newMocha.rootHooks` to set up rootHooks for the new
|
||||
// suite
|
||||
newMocha.rootHooks(newMocha.options.rootHooks);
|
||||
|
||||
// in parallel mode, the main Mocha process doesn't actually load the
|
||||
// files. this flag prevents `mocha.run()` from autoloading.
|
||||
newMocha.lazyLoadFiles(true);
|
||||
return newMocha;
|
||||
},
|
||||
fileCollectParams
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Run Mocha in "watch" mode
|
||||
* @param {Mocha} mocha - Mocha instance
|
||||
* @param {Object} opts - Options
|
||||
* @param {string[]} [opts.watchFiles] - List of paths and patterns to
|
||||
* watch. If not provided all files with an extension included in
|
||||
* `fileCollectionParams.extension` are watched. See first argument of
|
||||
* `chokidar.watch`.
|
||||
* @param {string[]} opts.watchIgnore - List of paths and patterns to
|
||||
* exclude from watching. See `ignored` option of `chokidar`.
|
||||
* @param {FileCollectionOptions} fileCollectParams - Parameters that control test
|
||||
* file collection. See `lib/cli/collect-files.js`.
|
||||
* @private
|
||||
*/
|
||||
exports.watchRun = (mocha, {watchFiles, watchIgnore}, fileCollectParams) => {
|
||||
debug('creating serial watcher');
|
||||
|
||||
return createWatcher(mocha, {
|
||||
watchFiles,
|
||||
watchIgnore,
|
||||
beforeRun({mocha}) {
|
||||
mocha.unloadFiles();
|
||||
|
||||
// I don't know why we're cloning the root suite.
|
||||
const rootSuite = mocha.suite.clone();
|
||||
|
||||
// ensure we aren't leaking event listeners
|
||||
mocha.dispose();
|
||||
|
||||
// this `require` is needed because the require cache has been cleared. the dynamic
|
||||
// exports set via the below call to `mocha.ui()` won't work properly if a
|
||||
// test depends on this module (see `required-tokens.spec.js`).
|
||||
const Mocha = require('../mocha');
|
||||
|
||||
// ... and now that we've gotten a new module, we need to use it again due
|
||||
// to `mocha.ui()` call
|
||||
const newMocha = new Mocha(mocha.options);
|
||||
// don't know why this is needed
|
||||
newMocha.suite = rootSuite;
|
||||
// nor this
|
||||
newMocha.suite.ctx = new Context();
|
||||
|
||||
// reset the list of files
|
||||
newMocha.files = collectFiles(fileCollectParams);
|
||||
|
||||
// because we've swapped out the root suite (see the `run` inner function
|
||||
// in `createRerunner`), we need to call `mocha.ui()` again to set up the context/globals.
|
||||
newMocha.ui(newMocha.options.ui);
|
||||
|
||||
// we need to call `newMocha.rootHooks` to set up rootHooks for the new
|
||||
// suite
|
||||
newMocha.rootHooks(newMocha.options.rootHooks);
|
||||
|
||||
return newMocha;
|
||||
},
|
||||
fileCollectParams
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Bootstraps a chokidar watcher. Handles keyboard input & signals
|
||||
* @param {Mocha} mocha - Mocha instance
|
||||
* @param {Object} opts
|
||||
* @param {BeforeWatchRun} [opts.beforeRun] - Function to call before
|
||||
* `mocha.run()`
|
||||
* @param {string[]} [opts.watchFiles] - List of paths and patterns to watch. If
|
||||
* not provided all files with an extension included in
|
||||
* `fileCollectionParams.extension` are watched. See first argument of
|
||||
* `chokidar.watch`.
|
||||
* @param {string[]} [opts.watchIgnore] - List of paths and patterns to exclude
|
||||
* from watching. See `ignored` option of `chokidar`.
|
||||
* @param {FileCollectionOptions} opts.fileCollectParams - List of extensions to watch if `opts.watchFiles` is not given.
|
||||
* @returns {FSWatcher}
|
||||
* @ignore
|
||||
* @private
|
||||
*/
|
||||
const createWatcher = (
|
||||
mocha,
|
||||
{watchFiles, watchIgnore, beforeRun, fileCollectParams}
|
||||
) => {
|
||||
if (!watchFiles) {
|
||||
watchFiles = fileCollectParams.extension.map(ext => `**/*.${ext}`);
|
||||
}
|
||||
|
||||
debug('ignoring files matching: %s', watchIgnore);
|
||||
let globalFixtureContext;
|
||||
|
||||
// we handle global fixtures manually
|
||||
mocha.enableGlobalSetup(false).enableGlobalTeardown(false);
|
||||
|
||||
const watcher = chokidar.watch(watchFiles, {
|
||||
ignored: watchIgnore,
|
||||
ignoreInitial: true
|
||||
});
|
||||
|
||||
const rerunner = createRerunner(mocha, watcher, {
|
||||
beforeRun
|
||||
});
|
||||
|
||||
watcher.on('ready', async () => {
|
||||
if (!globalFixtureContext) {
|
||||
debug('triggering global setup');
|
||||
globalFixtureContext = await mocha.runGlobalSetup();
|
||||
}
|
||||
rerunner.run();
|
||||
});
|
||||
|
||||
watcher.on('all', () => {
|
||||
rerunner.scheduleRun();
|
||||
});
|
||||
|
||||
hideCursor();
|
||||
process.on('exit', () => {
|
||||
showCursor();
|
||||
});
|
||||
|
||||
// this is for testing.
|
||||
// win32 cannot gracefully shutdown via a signal from a parent
|
||||
// process; a `SIGINT` from a parent will cause the process
|
||||
// to immediately exit. during normal course of operation, a user
|
||||
// will type Ctrl-C and the listener will be invoked, but this
|
||||
// is not possible in automated testing.
|
||||
// there may be another way to solve this, but it too will be a hack.
|
||||
// for our watch tests on win32 we must _fork_ mocha with an IPC channel
|
||||
if (process.connected) {
|
||||
process.on('message', msg => {
|
||||
if (msg === 'SIGINT') {
|
||||
process.emit('SIGINT');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let exiting = false;
|
||||
process.on('SIGINT', async () => {
|
||||
showCursor();
|
||||
console.error(`${logSymbols.warning} [mocha] cleaning up, please wait...`);
|
||||
if (!exiting) {
|
||||
exiting = true;
|
||||
if (mocha.hasGlobalTeardownFixtures()) {
|
||||
debug('running global teardown');
|
||||
try {
|
||||
await mocha.runGlobalTeardown(globalFixtureContext);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
process.exit(130);
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard shortcut for restarting when "rs\n" is typed (ala Nodemon)
|
||||
process.stdin.resume();
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('data', data => {
|
||||
const str = data
|
||||
.toString()
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
if (str === 'rs') rerunner.scheduleRun();
|
||||
});
|
||||
|
||||
return watcher;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an object that allows you to rerun tests on the mocha instance.
|
||||
*
|
||||
* @param {Mocha} mocha - Mocha instance
|
||||
* @param {FSWatcher} watcher - chokidar `FSWatcher` instance
|
||||
* @param {Object} [opts] - Options!
|
||||
* @param {BeforeWatchRun} [opts.beforeRun] - Function to call before `mocha.run()`
|
||||
* @returns {Rerunner}
|
||||
* @ignore
|
||||
* @private
|
||||
*/
|
||||
const createRerunner = (mocha, watcher, {beforeRun} = {}) => {
|
||||
// Set to a `Runner` when mocha is running. Set to `null` when mocha is not
|
||||
// running.
|
||||
let runner = null;
|
||||
|
||||
// true if a file has changed during a test run
|
||||
let rerunScheduled = false;
|
||||
|
||||
const run = () => {
|
||||
try {
|
||||
mocha = beforeRun ? beforeRun({mocha, watcher}) || mocha : mocha;
|
||||
runner = mocha.run(() => {
|
||||
debug('finished watch run');
|
||||
runner = null;
|
||||
blastCache(watcher);
|
||||
if (rerunScheduled) {
|
||||
rerun();
|
||||
} else {
|
||||
console.error(`${logSymbols.info} [mocha] waiting for changes...`);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e.stack);
|
||||
}
|
||||
};
|
||||
|
||||
const scheduleRun = () => {
|
||||
if (rerunScheduled) {
|
||||
return;
|
||||
}
|
||||
|
||||
rerunScheduled = true;
|
||||
if (runner) {
|
||||
runner.abort();
|
||||
} else {
|
||||
rerun();
|
||||
}
|
||||
};
|
||||
|
||||
const rerun = () => {
|
||||
rerunScheduled = false;
|
||||
eraseLine();
|
||||
run();
|
||||
};
|
||||
|
||||
return {
|
||||
scheduleRun,
|
||||
run
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the list of absolute paths watched by a chokidar watcher.
|
||||
*
|
||||
* @param watcher - Instance of a chokidar watcher
|
||||
* @return {string[]} - List of absolute paths
|
||||
* @ignore
|
||||
* @private
|
||||
*/
|
||||
const getWatchedFiles = watcher => {
|
||||
const watchedDirs = watcher.getWatched();
|
||||
return Object.keys(watchedDirs).reduce(
|
||||
(acc, dir) => [
|
||||
...acc,
|
||||
...watchedDirs[dir].map(file => path.join(dir, file))
|
||||
],
|
||||
[]
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide the cursor.
|
||||
* @ignore
|
||||
* @private
|
||||
*/
|
||||
const hideCursor = () => {
|
||||
process.stdout.write('\u001b[?25l');
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the cursor.
|
||||
* @ignore
|
||||
* @private
|
||||
*/
|
||||
const showCursor = () => {
|
||||
process.stdout.write('\u001b[?25h');
|
||||
};
|
||||
|
||||
/**
|
||||
* Erases the line on stdout
|
||||
* @private
|
||||
*/
|
||||
const eraseLine = () => {
|
||||
process.stdout.write('\u001b[2K');
|
||||
};
|
||||
|
||||
/**
|
||||
* Blast all of the watched files out of `require.cache`
|
||||
* @param {FSWatcher} watcher - chokidar FSWatcher
|
||||
* @ignore
|
||||
* @private
|
||||
*/
|
||||
const blastCache = watcher => {
|
||||
const files = getWatchedFiles(watcher);
|
||||
files.forEach(file => {
|
||||
delete require.cache[file];
|
||||
});
|
||||
debug('deleted %d file(s) from the require cache', files.length);
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback to be run before `mocha.run()` is called.
|
||||
* Optionally, it can return a new `Mocha` instance.
|
||||
* @callback BeforeWatchRun
|
||||
* @private
|
||||
* @param {{mocha: Mocha, watcher: FSWatcher}} options
|
||||
* @returns {Mocha}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Object containing run control methods
|
||||
* @typedef {Object} Rerunner
|
||||
* @private
|
||||
* @property {Function} run - Calls `mocha.run()`
|
||||
* @property {Function} scheduleRun - Schedules another call to `run`
|
||||
*/
|
Reference in New Issue
Block a user