Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collect test files later #3953

Merged
merged 1 commit into from
Jun 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions lib/cli/collect-files.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
'use strict';

const path = require('path');
const ansi = require('ansi-colors');
const debug = require('debug')('mocha:cli:run:helpers');
const minimatch = require('minimatch');
const utils = require('../utils');

juergba marked this conversation as resolved.
Show resolved Hide resolved
/**
* Exports a function that collects test files from CLI parameters.
* @see module:lib/cli/run-helpers
* @see module:lib/cli/watch-run
* @module
* @private
*/

/**
* Smash together an array of test files in the correct order
* @param {Object} opts - Options
* @param {string[]} opts.extension - File extensions to use
* @param {string[]} opts.spec - Files, dirs, globs to run
* @param {string[]} opts.ignore - Files, dirs, globs to ignore
* @param {string[]} opts.file - List of additional files to include
* @param {boolean} opts.recursive - Find files recursively
* @param {boolean} opts.sort - Sort test files
* @returns {string[]} List of files to test
* @private
*/
module.exports = ({ignore, extension, file, recursive, sort, spec} = {}) => {
let files = [];
const unmatched = [];
spec.forEach(arg => {
let newFiles;
try {
newFiles = utils.lookupFiles(arg, extension, recursive);
} catch (err) {
if (err.code === 'ERR_MOCHA_NO_FILES_MATCH_PATTERN') {
unmatched.push({message: err.message, pattern: err.pattern});
return;
}

throw err;
}

if (typeof newFiles !== 'undefined') {
if (typeof newFiles === 'string') {
newFiles = [newFiles];
}
newFiles = newFiles.filter(fileName =>
ignore.every(pattern => !minimatch(fileName, pattern))
);
}

files = files.concat(newFiles);
});

if (!files.length) {
// give full message details when only 1 file is missing
const noneFoundMsg =
unmatched.length === 1
? `Error: No test files found: ${JSON.stringify(unmatched[0].pattern)}` // stringify to print escaped characters raw
: 'Error: No test files found';
console.error(ansi.red(noneFoundMsg));
process.exit(1);
} else {
// print messages as an warning
unmatched.forEach(warning => {
console.warn(ansi.yellow(`Warning: ${warning.message}`));
});
}

const fileArgs = file.map(filepath => path.resolve(filepath));
files = files.map(filepath => path.resolve(filepath));

// ensure we don't sort the stuff from fileArgs; order is important!
if (sort) {
files.sort();
}

// add files given through --file to be ran first
files = fileArgs.concat(files);
debug('files (in order): ', files);
return files;
};
130 changes: 34 additions & 96 deletions lib/cli/run-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@

const fs = require('fs');
const path = require('path');
const ansi = require('ansi-colors');
const debug = require('debug')('mocha:cli:run:helpers');
const minimatch = require('minimatch');
const utils = require('../utils');
const watchRun = require('./watch-run');
const collectFiles = require('./collect-files');

const cwd = (exports.cwd = process.cwd());

Expand Down Expand Up @@ -94,115 +92,55 @@ exports.handleRequires = (requires = []) => {
};

/**
* Smash together an array of test files in the correct order
* @param {Object} [opts] - Options
* @param {string[]} [opts.extension] - File extensions to use
* @param {string[]} [opts.spec] - Files, dirs, globs to run
* @param {string[]} [opts.ignore] - Files, dirs, globs to ignore
* @param {boolean} [opts.recursive=false] - Find files recursively
* @param {boolean} [opts.sort=false] - Sort test files
* @returns {string[]} List of files to test
* @private
*/
exports.handleFiles = ({
ignore = [],
extension = [],
file = [],
recursive = false,
sort = false,
spec = []
} = {}) => {
let files = [];
const unmatched = [];
spec.forEach(arg => {
let newFiles;
try {
newFiles = utils.lookupFiles(arg, extension, recursive);
} catch (err) {
if (err.code === 'ERR_MOCHA_NO_FILES_MATCH_PATTERN') {
unmatched.push({message: err.message, pattern: err.pattern});
return;
}

throw err;
}

if (typeof newFiles !== 'undefined') {
if (typeof newFiles === 'string') {
newFiles = [newFiles];
}
newFiles = newFiles.filter(fileName =>
ignore.every(pattern => !minimatch(fileName, pattern))
);
}

files = files.concat(newFiles);
});

if (!files.length) {
// give full message details when only 1 file is missing
const noneFoundMsg =
unmatched.length === 1
? `Error: No test files found: ${JSON.stringify(unmatched[0].pattern)}` // stringify to print escaped characters raw
: 'Error: No test files found';
console.error(ansi.red(noneFoundMsg));
process.exit(1);
} else {
// print messages as an warning
unmatched.forEach(warning => {
console.warn(ansi.yellow(`Warning: ${warning.message}`));
});
}

const fileArgs = file.map(filepath => path.resolve(filepath));
files = files.map(filepath => path.resolve(filepath));

// ensure we don't sort the stuff from fileArgs; order is important!
if (sort) {
files.sort();
}

// add files given through --file to be ran first
files = fileArgs.concat(files);
debug('files (in order): ', files);
return files;
};

/**
* Give Mocha files and tell it to run
* Collect test files and run mocha instance.
* @param {Mocha} mocha - Mocha instance
* @param {Options} [opts] - Options
* @param {string[]} [opts.files] - List of test files
* @param {Options} [opts] - Command line options
* @param {boolean} [opts.exit] - Whether or not to force-exit after tests are complete
* @param {Object} fileCollectParams - Parameters that control test
* file collection. See `lib/cli/collect-files.js`.
* @returns {Runner}
* @private
*/
exports.singleRun = (mocha, {files = [], exit = false} = {}) => {
exports.singleRun = (mocha, {exit}, fileCollectParams) => {
const files = collectFiles(fileCollectParams);
debug('running tests with files', files);
mocha.files = files;
return mocha.run(exit ? exitMocha : exitMochaLater);
};

/**
* Actually run tests
* @param {Mocha} mocha - Mocha instance
* @param {Object} [opts] - Options
* @param {boolean} [opts.watch=false] - Enable watch mode
* @param {string[]} [opts.extension] - List of extensions to watch
* @param {string|RegExp} [opts.grep] - Grep for test titles
* @param {string} [opts.ui=bdd] - User interface
* @param {boolean} [opts.exit=false] - Force-exit Mocha when tests done
* @param {string[]} [files] - Array of test files
* @param {Object} opts - Command line options
* @private
*/
exports.runMocha = (
mocha,
{watch = false, extension = [], grep = '', ui = 'bdd', exit = false} = {},
files = []
) => {
exports.runMocha = (mocha, options) => {
const {
watch = false,
extension = [],
grep = '',
ui = 'bdd',
exit = false,
ignore = [],
file = [],
recursive = false,
sort = false,
spec = []
} = options;

const fileCollectParams = {
ignore,
extension,
file,
recursive,
sort,
spec
};

if (watch) {
watchRun(mocha, {extension, grep, ui, files});
watchRun(mocha, {ui, grep}, fileCollectParams);
} else {
exports.singleRun(mocha, {files, exit});
exports.singleRun(mocha, {exit}, fileCollectParams);
}
};

Expand Down
6 changes: 1 addition & 5 deletions lib/cli/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ const {

const {
list,
handleFiles,
handleRequires,
validatePlugin,
runMocha
Expand Down Expand Up @@ -290,8 +289,5 @@ exports.builder = yargs =>
exports.handler = argv => {
debug('post-yargs config', argv);
const mocha = new Mocha(argv);
const files = handleFiles(argv);

debug('running tests with files', files);
runMocha(mocha, argv, files);
runMocha(mocha, argv);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is existing code, I know. But argv is passed twice to runMocha(), once with mocha instance and a second time as a function parameter. Some options may have been processed meanwhile within mocha.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a suggestion how to improve this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least grep is processed within the Mocha constructor, therefore using the unprocessed grep of argv seems incorrect to me. Why don't we use just mocha.options?

I also wonder why I don't see fgrep anywhere. It is also processed within the Mocha constructor, but not used in runMocha().

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it ok if I address this separately? It seems to be an orthogonal concern to me and there is a chance this changes the behavior, something I’d like to avoid. It also looks like this should be tackled more comprehensively by rethinking whether handler and runMocha should be separate functions.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started to address this in #3960 which should also answer the question about fgrep.

};
11 changes: 7 additions & 4 deletions lib/cli/watch-run.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const utils = require('../utils');
const Context = require('../context');
const Mocha = require('../mocha');
const collectFiles = require('./collect-files');

/**
* Exports the `watchRun` function that runs mocha in "watch" mode.
Expand All @@ -15,14 +16,16 @@ const Mocha = require('../mocha');
* Run Mocha in "watch" mode
* @param {Mocha} mocha - Mocha instance
* @param {Object} opts - Options
* @param {string[]} opts.extension - List of extensions to watch
* @param {string|RegExp} opts.grep - Grep for test titles
* @param {string} opts.ui - User interface
* @param {string[]} opts.files - Array of test files
* @param {Object} fileCollectParams - Parameters that control test
* file collection. See `lib/cli/collect-files.js`.
* @param {string[]} fileCollectParams.extension - List of extensions to watch
* @private
*/
module.exports = (mocha, {extension, grep, ui, files}) => {
module.exports = (mocha, {grep, ui}, fileCollectParams) => {
let runner;
const files = collectFiles(fileCollectParams);

console.log();
hideCursor();
Expand All @@ -34,7 +37,7 @@ module.exports = (mocha, {extension, grep, ui, files}) => {
process.exit(128 + 2);
});

const watchFiles = utils.files(process.cwd(), extension);
const watchFiles = utils.files(process.cwd(), fileCollectParams.extension);
let runAgain = false;

const loadAndRun = () => {
Expand Down