Fix dependabot issues

This commit is contained in:
Andrew Eisenberg 2021-10-21 15:24:20 -07:00
parent c89d9bd8b0
commit 531c6ba7c8
705 changed files with 53406 additions and 20466 deletions

29
node_modules/ava/lib/api.js generated vendored
View file

@ -17,6 +17,7 @@ const RunStatus = require('./run-status');
const fork = require('./fork');
const serializeError = require('./serialize-error');
const {getApplicableLineNumbers} = require('./line-numbers');
const sharedWorkers = require('./plugin-support/shared-workers');
function resolveModules(modules) {
return arrify(modules).map(name => {
@ -110,21 +111,15 @@ class Api extends Emittery {
}
};
let cacheDir;
let testFiles;
try {
cacheDir = this._createCacheDir();
testFiles = await globs.findTests({cwd: this.options.projectDir, ...apiOptions.globs});
if (selectedFiles.length === 0) {
if (filter.length === 0) {
selectedFiles = testFiles;
} else {
selectedFiles = globs.applyTestFileFilter({
cwd: this.options.projectDir,
filter: filter.map(({pattern}) => pattern),
testFiles
});
}
selectedFiles = filter.length === 0 ? testFiles : globs.applyTestFileFilter({
cwd: this.options.projectDir,
filter: filter.map(({pattern}) => pattern),
testFiles
});
}
} catch (error) {
selectedFiles = [];
@ -147,7 +142,7 @@ class Api extends Emittery {
runStatus = new RunStatus(selectedFiles.length, null);
}
const debugWithoutSpecificFile = Boolean(this.options.debug) && selectedFiles.length !== 1;
const debugWithoutSpecificFile = Boolean(this.options.debug) && !this.options.debug.active && selectedFiles.length !== 1;
await this.emit('run', {
bailWithoutReporting: debugWithoutSpecificFile,
@ -192,7 +187,7 @@ class Api extends Emittery {
const {providers = []} = this.options;
const providerStates = (await Promise.all(providers.map(async ({type, main}) => {
const state = await main.compile({cacheDir, files: testFiles});
const state = await main.compile({cacheDir: this._createCacheDir(), files: testFiles});
return state === null ? null : {type, state};
}))).filter(state => state !== null);
@ -206,6 +201,8 @@ class Api extends Emittery {
concurrency = 1;
}
const deregisteredSharedWorkers = [];
// Try and run each file, limited by `concurrency`.
await pMap(selectedFiles, async file => {
// No new files should be run once a test has timed out or failed,
@ -231,6 +228,7 @@ class Api extends Emittery {
const worker = fork(file, options, apiOptions.nodeArguments);
runStatus.observeWorker(worker, file, {selectingLines: lineNumbers.length > 0});
deregisteredSharedWorkers.push(sharedWorkers.observeWorkerProcess(worker, runStatus));
pendingWorkers.add(worker);
worker.promise.then(() => {
@ -238,8 +236,11 @@ class Api extends Emittery {
});
restartTimer();
return worker.promise;
await worker.promise;
}, {concurrency, stopOnError: false});
// Allow shared workers to clean up before the run ends.
await Promise.all(deregisteredSharedWorkers);
} catch (error) {
if (error && error.name === 'AggregateError') {
for (const err of error) {

108
node_modules/ava/lib/assert.js generated vendored
View file

@ -3,11 +3,11 @@ const concordance = require('concordance');
const isError = require('is-error');
const isPromise = require('is-promise');
const concordanceOptions = require('./concordance-options').default;
const concordanceDiffOptions = require('./concordance-options').diff;
const {CIRCULAR_SELECTOR, isLikeSelector, selectComparable} = require('./like-selector');
const snapshotManager = require('./snapshot-manager');
function formatDescriptorDiff(actualDescriptor, expectedDescriptor, options) {
options = {...options, ...concordanceDiffOptions};
options = {...options, ...concordanceOptions};
return {
label: 'Difference:',
formatted: concordance.diffDescriptors(actualDescriptor, expectedDescriptor, options)
@ -64,6 +64,21 @@ class AssertionError extends Error {
}
exports.AssertionError = AssertionError;
function checkAssertionMessage(assertion, message) {
if (typeof message === 'undefined' || typeof message === 'string') {
return true;
}
return new AssertionError({
assertion,
improperUsage: true,
message: 'The assertion message must be a string',
values: [formatWithLabel('Called with:', message)]
});
}
exports.checkAssertionMessage = checkAssertionMessage;
function getErrorWithLongStackTrace() {
const limitBefore = Error.stackTraceLimit;
Error.stackTraceLimit = Infinity;
@ -72,8 +87,16 @@ function getErrorWithLongStackTrace() {
return err;
}
function validateExpectations(assertion, expectations, numberArgs) { // eslint-disable-line complexity
function validateExpectations(assertion, expectations, numberArgs, experiments) { // eslint-disable-line complexity
if (numberArgs === 1 || expectations === null || expectations === undefined) {
if (experiments.disableNullExpectations && expectations === null) {
throw new AssertionError({
assertion,
message: `The second argument to \`t.${assertion}()\` must be an expectation object or \`undefined\``,
values: [formatWithLabel('Called with:', expectations)]
});
}
expectations = {};
} else if (
typeof expectations === 'function' ||
@ -242,7 +265,9 @@ class Assertions {
fail = notImplemented,
skip = notImplemented,
compareWithSnapshot = notImplemented,
powerAssert
powerAssert,
experiments = {},
disableSnapshots = false
} = {}) {
const withSkip = assertionFn => {
assertionFn.skip = skip;
@ -267,22 +292,16 @@ class Assertions {
});
const checkMessage = (assertion, message, powerAssert = false) => {
if (typeof message === 'undefined' || typeof message === 'string') {
return true;
const result = checkAssertionMessage(assertion, message);
if (result === true) {
return this.true;
}
const error = new AssertionError({
assertion,
improperUsage: true,
message: 'The assertion message must be a string',
values: [formatWithLabel('Called with:', message)]
});
if (powerAssert) {
throw error;
throw result;
}
fail(error);
fail(result);
return false;
};
@ -387,6 +406,52 @@ class Assertions {
}
});
this.like = withSkip((actual, selector, message) => {
if (!checkMessage('like', message)) {
return;
}
if (!isLikeSelector(selector)) {
fail(new AssertionError({
assertion: 'like',
improperUsage: true,
message: '`t.like()` selector must be a non-empty object',
values: [formatWithLabel('Called with:', selector)]
}));
return;
}
let comparable;
try {
comparable = selectComparable(actual, selector);
} catch (error) {
if (error === CIRCULAR_SELECTOR) {
fail(new AssertionError({
assertion: 'like',
improperUsage: true,
message: '`t.like()` selector must not contain circular references',
values: [formatWithLabel('Called with:', selector)]
}));
return;
}
throw error;
}
const result = concordance.compare(comparable, selector, concordanceOptions);
if (result.pass) {
pass();
} else {
const actualDescriptor = result.actual || concordance.describe(comparable, concordanceOptions);
const expectedDescriptor = result.expected || concordance.describe(selector, concordanceOptions);
fail(new AssertionError({
assertion: 'like',
message,
values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)]
}));
}
});
this.throws = withSkip((...args) => {
// Since arrow functions do not support 'arguments', we are using rest
// operator, so we can determine the total number of arguments passed
@ -408,7 +473,7 @@ class Assertions {
}
try {
expectations = validateExpectations('throws', expectations, args.length);
expectations = validateExpectations('throws', expectations, args.length, experiments);
} catch (error) {
fail(error);
return;
@ -474,7 +539,7 @@ class Assertions {
}
try {
expectations = validateExpectations('throwsAsync', expectations, args.length);
expectations = validateExpectations('throwsAsync', expectations, args.length, experiments);
} catch (error) {
fail(error);
return Promise.resolve();
@ -634,6 +699,15 @@ class Assertions {
});
this.snapshot = withSkip((expected, ...rest) => {
if (disableSnapshots && experiments.disableSnapshotsInHooks) {
fail(new AssertionError({
assertion: 'snapshot',
message: '`t.snapshot()` can only be used in tests',
improperUsage: true
}));
return;
}
let message;
let snapshotOptions;
if (rest.length > 1) {

102
node_modules/ava/lib/cli.js generated vendored
View file

@ -7,7 +7,7 @@ const arrify = require('arrify');
const yargs = require('yargs');
const readPkg = require('read-pkg');
const isCi = require('./is-ci');
const loadConfig = require('./load-config');
const {loadConfig} = require('./load-config');
function exit(message) {
console.error(`\n ${require('./chalk').get().red(figures.cross)} ${message}`);
@ -83,12 +83,24 @@ exports.run = async () => { // eslint-disable-line complexity
let confError = null;
try {
const {argv: {config: configFile}} = yargs.help(false);
conf = loadConfig({configFile});
conf = await loadConfig({configFile});
} catch (error) {
confError = error;
}
let debug = null;
// Enter debug mode if the main process is being inspected. This assumes the
// worker processes are automatically inspected, too. It is not necessary to
// run AVA with the debug command, though it's allowed.
const activeInspector = require('inspector').url() !== undefined; // eslint-disable-line node/no-unsupported-features/node-builtins
let debug = activeInspector ?
{
active: true,
break: false,
files: [],
host: undefined,
port: undefined
} : null;
let resetCache = false;
const {argv} = yargs
.parserConfiguration({
@ -122,7 +134,11 @@ exports.run = async () => { // eslint-disable-line complexity
array: true,
describe: 'Glob patterns to select what test files to run. Leave empty if you want AVA to run all test files instead. Add a colon and specify line numbers of specific tests to run',
type: 'string'
}))
}), argv => {
if (activeInspector) {
debug.files = argv.pattern || [];
}
})
.command(
'debug [<pattern>...]',
'Activate Node.js inspector and run a single test file',
@ -148,6 +164,7 @@ exports.run = async () => { // eslint-disable-line complexity
}),
argv => {
debug = {
active: activeInspector,
break: argv.break === true,
files: argv.pattern,
host: argv.host,
@ -182,6 +199,10 @@ exports.run = async () => { // eslint-disable-line complexity
const chalkOptions = {level: combined.color === false ? 0 : require('chalk').level};
const chalk = require('./chalk').set(chalkOptions);
if (combined.updateSnapshots && combined.match) {
exit('Snapshots cannot be updated when matching specific tests.');
}
if (confError) {
if (confError.parent) {
exit(`${confError.message}\n\n${chalk.gray((confError.parent && confError.parent.stack) || confError.parent)}`);
@ -259,11 +280,11 @@ exports.run = async () => { // eslint-disable-line complexity
const ciParallelVars = require('ci-parallel-vars');
const Api = require('./api');
const VerboseReporter = require('./reporters/verbose');
const MiniReporter = require('./reporters/mini');
const DefaultReporter = require('./reporters/default');
const TapReporter = require('./reporters/tap');
const Watcher = require('./watcher');
const normalizeExtensions = require('./extensions');
const normalizeModuleTypes = require('./module-types');
const {normalizeGlobs, normalizePattern} = require('./globs');
const normalizeNodeArguments = require('./node-arguments');
const validateEnvironmentVariables = require('./environment-variables');
@ -281,12 +302,6 @@ exports.run = async () => { // eslint-disable-line complexity
const {type: defaultModuleType = 'commonjs'} = pkg || {};
const moduleTypes = {
cjs: 'commonjs',
mjs: 'module',
js: defaultModuleType
};
const providers = [];
if (Reflect.has(conf, 'babel')) {
try {
@ -328,6 +343,13 @@ exports.run = async () => { // eslint-disable-line complexity
exit(error.message);
}
let moduleTypes;
try {
moduleTypes = normalizeModuleTypes(conf.extensions, defaultModuleType, experiments);
} catch (error) {
exit(error.message);
}
let globs;
try {
globs = normalizeGlobs({files: conf.files, ignoredByWatcher: conf.ignoredByWatcher, extensions, providers});
@ -357,6 +379,9 @@ exports.run = async () => { // eslint-disable-line complexity
pattern: normalizePattern(path.relative(projectDir, path.resolve(process.cwd(), pattern))),
...rest
}));
if (combined.updateSnapshots && filter.some(condition => condition.lineNumbers !== null)) {
exit('Snapshots cannot be updated when selecting specific tests by their line number.');
}
const api = new Api({
cacheEnabled: combined.cache !== false,
@ -384,32 +409,37 @@ exports.run = async () => { // eslint-disable-line complexity
workerArgv: argv['--']
});
let reporter;
if (combined.tap && !combined.watch && debug === null) {
reporter = new TapReporter({
projectDir,
reportStream: process.stdout,
stdStream: process.stderr
});
} else if (debug !== null || combined.verbose || isCi || !process.stdout.isTTY) {
reporter = new VerboseReporter({
projectDir,
reportStream: process.stdout,
stdStream: process.stderr,
watching: combined.watch
});
} else {
reporter = new MiniReporter({
projectDir,
reportStream: process.stdout,
stdStream: process.stderr,
watching: combined.watch
});
}
const reporter = combined.tap && !combined.watch && debug === null ? new TapReporter({
projectDir,
reportStream: process.stdout,
stdStream: process.stderr
}) : new DefaultReporter({
projectDir,
reportStream: process.stdout,
stdStream: process.stderr,
watching: combined.watch,
verbose: debug !== null || combined.verbose || isCi || !process.stdout.isTTY
});
api.on('run', plan => {
reporter.startRun(plan);
if (process.env.AVA_EMIT_RUN_STATUS_OVER_IPC === 'I\'ll find a payphone baby / Take some time to talk to you') {
const {controlFlow} = require('./ipc-flow-control');
const bufferedSend = controlFlow(process);
if (process.versions.node >= '12.16.0') {
plan.status.on('stateChange', evt => {
bufferedSend(evt);
});
} else {
const v8 = require('v8');
plan.status.on('stateChange', evt => {
bufferedSend([...v8.serialize(evt)]);
});
}
}
plan.status.on('stateChange', evt => {
if (evt.type === 'interrupt') {
reporter.endRun();
@ -431,14 +461,14 @@ exports.run = async () => { // eslint-disable-line complexity
} else {
let debugWithoutSpecificFile = false;
api.on('run', plan => {
if (plan.debug && plan.files.length !== 1) {
if (debug !== null && plan.files.length !== 1) {
debugWithoutSpecificFile = true;
}
});
const runStatus = await api.run({filter});
if (debugWithoutSpecificFile) {
if (debugWithoutSpecificFile && !debug.active) {
exit('Provide the path to the test file you wish to debug');
return;
}

View file

@ -19,7 +19,7 @@ module.exports = (source, options = {}) => {
let contents;
try {
contents = fs.readFileSync(file, 'utf8');
} catch (_) {
} catch {
return null;
}

View file

@ -1,5 +1,5 @@
'use strict';
const util = require('util');
const util = require('util'); // eslint-disable-line unicorn/import-style
const ansiStyles = require('ansi-styles');
const stripAnsi = require('strip-ansi');
const cloneDeepWith = require('lodash/cloneDeepWith');
@ -135,5 +135,4 @@ exports.default = {
theme
};
exports.diff = {maxDepth: 1, plugins, theme};
exports.snapshotManager = {plugins, theme: plainTheme};

5
node_modules/ava/lib/extensions.js generated vendored
View file

@ -2,8 +2,11 @@ module.exports = (configuredExtensions, providers = []) => {
// Combine all extensions possible for testing. Remove duplicate extensions.
const duplicates = new Set();
const seen = new Set();
const normalize = extensions => Array.isArray(extensions) ? extensions : Object.keys(extensions);
const combine = extensions => {
for (const ext of extensions) {
for (const ext of normalize(extensions)) {
if (seen.has(ext)) {
duplicates.add(ext);
} else {

102
node_modules/ava/lib/fork.js generated vendored
View file

@ -3,6 +3,7 @@ const childProcess = require('child_process');
const path = require('path');
const fs = require('fs');
const Emittery = require('emittery');
const {controlFlow} = require('./ipc-flow-control');
if (fs.realpathSync(__filename) !== __filename) {
console.warn('WARNING: `npm link ava` and the `--preserve-symlink` flag are incompatible. We have detected that AVA is linked via `npm link`, and that you are using either an early version of Node 6, or the `--preserve-symlink` flag. This breaks AVA. You should upgrade to Node 6.2.0+, avoid the `--preserve-symlink` flag, or avoid using `npm link ava`.');
@ -11,10 +12,57 @@ if (fs.realpathSync(__filename) !== __filename) {
// In case the test file imports a different AVA install,
// the presence of this variable allows it to require this one instead
const AVA_PATH = path.resolve(__dirname, '..');
const WORKER_PATH = require.resolve('./worker/subprocess');
const workerPath = require.resolve('./worker/subprocess');
class SharedWorkerChannel extends Emittery {
constructor({channelId, filename, initialData}, sendToFork) {
super();
this.id = channelId;
this.filename = filename;
this.initialData = initialData;
this.sendToFork = sendToFork;
}
signalReady() {
this.sendToFork({
type: 'shared-worker-ready',
channelId: this.id
});
}
signalError() {
this.sendToFork({
type: 'shared-worker-error',
channelId: this.id
});
}
emitMessage({messageId, replyTo, serializedData}) {
this.emit('message', {
messageId,
replyTo,
serializedData
});
}
forwardMessageToFork({messageId, replyTo, serializedData}) {
this.sendToFork({
type: 'shared-worker-message',
channelId: this.id,
messageId,
replyTo,
serializedData
});
}
}
let forkCounter = 0;
module.exports = (file, options, execArgv = process.execArgv) => {
const forkId = `fork/${++forkCounter}`;
const sharedWorkerChannels = new Map();
let finished = false;
const emitter = new Emittery();
@ -25,12 +73,13 @@ module.exports = (file, options, execArgv = process.execArgv) => {
};
options = {
file,
baseDir: process.cwd(),
file,
forkId,
...options
};
const subprocess = childProcess.fork(workerPath, options.workerArgv, {
const subprocess = childProcess.fork(WORKER_PATH, options.workerArgv, {
cwd: options.projectDir,
silent: true,
env: {NODE_ENV: 'test', ...process.env, ...options.environmentVariables, AVA_PATH},
@ -45,12 +94,12 @@ module.exports = (file, options, execArgv = process.execArgv) => {
emitStateChange({type: 'worker-stderr', chunk});
});
const bufferedSend = controlFlow(subprocess);
let forcedExit = false;
const send = evt => {
if (subprocess.connected && !finished && !forcedExit) {
subprocess.send({ava: evt}, () => {
// Disregard errors.
});
if (!finished && !forcedExit) {
bufferedSend({ava: evt});
}
};
@ -65,15 +114,25 @@ module.exports = (file, options, execArgv = process.execArgv) => {
return;
}
if (message.ava.type === 'ready-for-options') {
send({type: 'options', options});
return;
}
switch (message.ava.type) {
case 'ready-for-options':
send({type: 'options', options});
break;
case 'shared-worker-connect': {
const channel = new SharedWorkerChannel(message.ava, send);
sharedWorkerChannels.set(channel.id, channel);
emitter.emit('connectSharedWorker', channel);
break;
}
if (message.ava.type === 'ping') {
send({type: 'pong'});
} else {
emitStateChange(message.ava);
case 'shared-worker-message':
sharedWorkerChannels.get(message.ava.channelId).emitMessage(message.ava);
break;
case 'ping':
send({type: 'pong'});
break;
default:
emitStateChange(message.ava);
}
});
@ -98,6 +157,10 @@ module.exports = (file, options, execArgv = process.execArgv) => {
});
return {
file,
forkId,
promise,
exit() {
forcedExit = true;
subprocess.kill();
@ -107,11 +170,12 @@ module.exports = (file, options, execArgv = process.execArgv) => {
send({type: 'peer-failed'});
},
onStateChange(listener) {
return emitter.on('stateChange', listener);
onConnectSharedWorker(listener) {
return emitter.on('connectSharedWorker', listener);
},
file,
promise
onStateChange(listener) {
return emitter.on('stateChange', listener);
}
};
};

6
node_modules/ava/lib/globs.js generated vendored
View file

@ -82,11 +82,7 @@ function normalizeGlobs({extensions, files: filePatterns, ignoredByWatcher: igno
filePatterns = defaultTestPatterns;
}
if (ignoredByWatcherPatterns) {
ignoredByWatcherPatterns = [...defaultIgnoredByWatcherPatterns, ...normalizePatterns(ignoredByWatcherPatterns)];
} else {
ignoredByWatcherPatterns = [...defaultIgnoredByWatcherPatterns];
}
ignoredByWatcherPatterns = ignoredByWatcherPatterns ? [...defaultIgnoredByWatcherPatterns, ...normalizePatterns(ignoredByWatcherPatterns)] : [...defaultIgnoredByWatcherPatterns];
for (const {level, main} of providers) {
if (level >= providerManager.levels.pathRewrites) {

39
node_modules/ava/lib/ipc-flow-control.js generated vendored Normal file
View file

@ -0,0 +1,39 @@
function controlFlow(channel) {
let errored = false;
let deliverImmediately = true;
const backlog = [];
const deliverNext = error => {
if (error !== null) {
errored = true;
}
if (errored || !channel.connected) {
backlog.length = 0; // Free memory.
return; // We can't send.
}
let ok = true;
while (ok && backlog.length > 0) { // Stop sending after backpressure.
ok = channel.send(backlog.shift(), deliverNext);
}
// Re-enable immediate delivery if there is no backpressure and the backlog
// has been cleared.
deliverImmediately = ok && backlog.length === 0;
};
return message => {
if (errored || !channel.connected) {
return;
}
if (deliverImmediately) {
deliverImmediately = channel.send(message, deliverNext);
} else {
backlog.push(message);
}
};
}
exports.controlFlow = controlFlow;

37
node_modules/ava/lib/like-selector.js generated vendored Normal file
View file

@ -0,0 +1,37 @@
'use strict';
function isLikeSelector(selector) {
return selector !== null &&
typeof selector === 'object' &&
Reflect.getPrototypeOf(selector) === Object.prototype &&
Reflect.ownKeys(selector).length > 0;
}
exports.isLikeSelector = isLikeSelector;
const CIRCULAR_SELECTOR = new Error('Encountered a circular selector');
exports.CIRCULAR_SELECTOR = CIRCULAR_SELECTOR;
function selectComparable(lhs, selector, circular = new Set()) {
if (circular.has(selector)) {
throw CIRCULAR_SELECTOR;
}
circular.add(selector);
if (lhs === null || typeof lhs !== 'object') {
return lhs;
}
const comparable = {};
for (const [key, rhs] of Object.entries(selector)) {
if (isLikeSelector(rhs)) {
comparable[key] = selectComparable(Reflect.get(lhs, key), rhs, circular);
} else {
comparable[key] = Reflect.get(lhs, key);
}
}
return comparable;
}
exports.selectComparable = selectComparable;

View file

@ -1,6 +1,6 @@
'use strict';
const micromatch = require('micromatch');
const picomatch = require('picomatch');
const flatten = require('lodash/flatten');
const NUMBER_REGEX = /^\d+$/;
@ -56,7 +56,7 @@ exports.splitPatternAndLineNumbers = splitPatternAndLineNumbers;
function getApplicableLineNumbers(normalizedFilePath, filter) {
return sortNumbersAscending(distinctArray(flatten(
filter
.filter(({pattern, lineNumbers}) => lineNumbers && micromatch.isMatch(normalizedFilePath, pattern))
.filter(({pattern, lineNumbers}) => lineNumbers && picomatch.isMatch(normalizedFilePath, pattern))
.map(({lineNumbers}) => lineNumbers)
)));
}

143
node_modules/ava/lib/load-config.js generated vendored
View file

@ -1,27 +1,48 @@
'use strict';
const fs = require('fs');
const path = require('path');
const url = require('url');
const vm = require('vm');
const isPlainObject = require('is-plain-object');
const {isPlainObject} = require('is-plain-object');
const pkgConf = require('pkg-conf');
const NO_SUCH_FILE = Symbol('no ava.config.js file');
const MISSING_DEFAULT_EXPORT = Symbol('missing default export');
const EXPERIMENTS = new Set();
const EXPERIMENTS = new Set([
'configurableModuleFormat',
'disableNullExpectations',
'disableSnapshotsInHooks',
'nextGenConfig',
'reverseTeardowns',
'sharedWorkers'
]);
// *Very* rudimentary support for loading ava.config.js files containing an `export default` statement.
const evaluateJsConfig = configFile => {
const contents = fs.readFileSync(configFile, 'utf8');
const script = new vm.Script(`'use strict';(()=>{let __export__;\n${contents.replace(/export default/g, '__export__ =')};return __export__;})()`, {
const evaluateJsConfig = (contents, configFile) => {
const script = new vm.Script(`'use strict';(()=>{let __export__;\n${contents.toString('utf8').replace(/export default/g, '__export__ =')};return __export__;})()`, {
filename: configFile,
lineOffset: -1
});
return {
default: script.runInThisContext()
};
return script.runInThisContext();
};
const loadJsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.config.js')}) => {
const importConfig = async ({configFile, fileForErrorMessage}) => {
let module;
try {
module = await import(url.pathToFileURL(configFile)); // eslint-disable-line node/no-unsupported-features/es-syntax
} catch (error) {
throw Object.assign(new Error(`Error loading ${fileForErrorMessage}: ${error.message}`), {parent: error});
}
const {default: config = MISSING_DEFAULT_EXPORT} = module;
if (config === MISSING_DEFAULT_EXPORT) {
throw new Error(`${fileForErrorMessage} must have a default export`);
}
return config;
};
const loadJsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.config.js')}, useImport = false) => {
if (!configFile.endsWith('.js')) {
return null;
}
@ -30,7 +51,10 @@ const loadJsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.confi
let config;
try {
({default: config = MISSING_DEFAULT_EXPORT} = evaluateJsConfig(configFile));
const contents = fs.readFileSync(configFile);
config = useImport && contents.includes('nonSemVerExperiments') && contents.includes('nextGenConfig') ?
importConfig({configFile, fileForErrorMessage}) :
evaluateJsConfig(contents, configFile) || MISSING_DEFAULT_EXPORT;
} catch (error) {
if (error.code === 'ENOENT') {
return null;
@ -63,14 +87,17 @@ const loadCjsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.conf
}
};
const loadMjsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.config.mjs')}) => {
const loadMjsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.config.mjs')}, experimentally = false) => {
if (!configFile.endsWith('.mjs')) {
return null;
}
const fileForErrorMessage = path.relative(projectDir, configFile);
try {
fs.readFileSync(configFile);
const contents = fs.readFileSync(configFile);
if (experimentally && contents.includes('nonSemVerExperiments') && contents.includes('nextGenConfig')) {
return {config: importConfig({configFile, fileForErrorMessage}), fileForErrorMessage};
}
} catch (error) {
if (error.code === 'ENOENT') {
return null;
@ -82,11 +109,7 @@ const loadMjsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.conf
throw new Error(`AVA cannot yet load ${fileForErrorMessage} files`);
};
function loadConfig({configFile, resolveFrom = process.cwd(), defaults = {}} = {}) { // eslint-disable-line complexity
let packageConf = pkgConf.sync('ava', {cwd: resolveFrom});
const filepath = pkgConf.filepath(packageConf);
const projectDir = filepath === null ? resolveFrom : path.dirname(filepath);
function resolveConfigFile(projectDir, configFile) {
if (configFile) {
configFile = path.resolve(configFile); // Relative to CWD
if (path.basename(configFile) !== path.relative(projectDir, configFile)) {
@ -98,6 +121,15 @@ function loadConfig({configFile, resolveFrom = process.cwd(), defaults = {}} = {
}
}
return configFile;
}
function loadConfigSync({configFile, resolveFrom = process.cwd(), defaults = {}} = {}) {
let packageConf = pkgConf.sync('ava', {cwd: resolveFrom});
const filepath = pkgConf.filepath(packageConf);
const projectDir = filepath === null ? resolveFrom : path.dirname(filepath);
configFile = resolveConfigFile(projectDir, configFile);
const allowConflictWithPackageJson = Boolean(configFile);
let [{config: fileConf, fileForErrorMessage} = {config: NO_SUCH_FILE, fileForErrorMessage: undefined}, ...conflicting] = [
@ -157,4 +189,79 @@ function loadConfig({configFile, resolveFrom = process.cwd(), defaults = {}} = {
return config;
}
module.exports = loadConfig;
exports.loadConfigSync = loadConfigSync;
async function loadConfig({configFile, resolveFrom = process.cwd(), defaults = {}} = {}) {
let packageConf = await pkgConf('ava', {cwd: resolveFrom});
const filepath = pkgConf.filepath(packageConf);
const projectDir = filepath === null ? resolveFrom : path.dirname(filepath);
configFile = resolveConfigFile(projectDir, configFile);
const allowConflictWithPackageJson = Boolean(configFile);
// TODO: Refactor resolution logic to implement https://github.com/avajs/ava/issues/2285.
let [{config: fileConf, fileForErrorMessage} = {config: NO_SUCH_FILE, fileForErrorMessage: undefined}, ...conflicting] = [
loadJsConfig({projectDir, configFile}, true),
loadCjsConfig({projectDir, configFile}),
loadMjsConfig({projectDir, configFile}, true)
].filter(result => result !== null);
if (conflicting.length > 0) {
throw new Error(`Conflicting configuration in ${fileForErrorMessage} and ${conflicting.map(({fileForErrorMessage}) => fileForErrorMessage).join(' & ')}`);
}
let sawPromise = false;
if (fileConf !== NO_SUCH_FILE) {
if (allowConflictWithPackageJson) {
packageConf = {};
} else if (Object.keys(packageConf).length > 0) {
throw new Error(`Conflicting configuration in ${fileForErrorMessage} and package.json`);
}
if (fileConf && typeof fileConf.then === 'function') { // eslint-disable-line promise/prefer-await-to-then
sawPromise = true;
fileConf = await fileConf;
}
if (!isPlainObject(fileConf) && typeof fileConf !== 'function') {
throw new TypeError(`${fileForErrorMessage} must export a plain object or factory function`);
}
if (typeof fileConf === 'function') {
fileConf = fileConf({projectDir});
if (fileConf && typeof fileConf.then === 'function') { // eslint-disable-line promise/prefer-await-to-then
sawPromise = true;
fileConf = await fileConf;
}
if (!isPlainObject(fileConf)) {
throw new TypeError(`Factory method exported by ${fileForErrorMessage} must return a plain object`);
}
}
if ('ava' in fileConf) {
throw new Error(`Encountered ava property in ${fileForErrorMessage}; avoid wrapping the configuration`);
}
}
const config = {...defaults, nonSemVerExperiments: {}, ...fileConf, ...packageConf, projectDir};
const {nonSemVerExperiments: experiments} = config;
if (!isPlainObject(experiments)) {
throw new Error(`nonSemVerExperiments from ${fileForErrorMessage} must be an object`);
}
for (const key of Object.keys(experiments)) {
if (!EXPERIMENTS.has(key)) {
throw new Error(`nonSemVerExperiments.${key} from ${fileForErrorMessage} is not a supported experiment`);
}
}
if (sawPromise && experiments.nextGenConfig !== true) {
throw new Error(`${fileForErrorMessage} exported a promise or an asynchronous factory function. You must enable the asyncConfigurationLoading experiment for this to work.`);
}
return config;
}
exports.loadConfig = loadConfig;

75
node_modules/ava/lib/module-types.js generated vendored Normal file
View file

@ -0,0 +1,75 @@
const requireTrueValue = value => {
if (value !== true) {
throw new TypeError('When specifying module types, use `true` for cjs, mjs and js extensions');
}
};
const normalize = (extension, type, defaultModuleType) => {
switch (extension) {
case 'cjs':
requireTrueValue(type);
return 'commonjs';
case 'mjs':
requireTrueValue(type);
return 'module';
case 'js':
requireTrueValue(type);
return defaultModuleType;
default:
if (type !== 'commonjs' && type !== 'module') {
throw new TypeError(`Module type for ${extension} must be commonjs or module`);
}
return type;
}
};
const deriveFromObject = (extensionsObject, defaultModuleType) => {
const moduleTypes = {};
for (const [extension, type] of Object.entries(extensionsObject)) {
moduleTypes[extension] = normalize(extension, type, defaultModuleType);
}
return moduleTypes;
};
const deriveFromArray = (extensions, defaultModuleType) => {
const moduleTypes = {};
for (const extension of extensions) {
switch (extension) {
case 'cjs':
moduleTypes.cjs = 'commonjs';
break;
case 'mjs':
moduleTypes.mjs = 'module';
break;
case 'js':
moduleTypes.js = defaultModuleType;
break;
default:
moduleTypes[extension] = 'commonjs';
}
}
return moduleTypes;
};
module.exports = (configuredExtensions, defaultModuleType, experiments) => {
if (configuredExtensions === undefined) {
return {
cjs: 'commonjs',
mjs: 'module',
js: defaultModuleType
};
}
if (Array.isArray(configuredExtensions)) {
return deriveFromArray(configuredExtensions, defaultModuleType);
}
if (!experiments.configurableModuleFormat) {
throw new Error('You must enable the `configurableModuleFormat` experiment in order to specify module types');
}
return deriveFromObject(configuredExtensions, defaultModuleType);
};

View file

@ -0,0 +1,252 @@
const {EventEmitter, on} = require('events');
const v8 = require('v8');
const {workerData, parentPort} = require('worker_threads');
const pkg = require('../../package.json');
// Used to forward messages received over the `parentPort`. Every subscription
// adds a listener, so do not enforce any maximums.
const events = new EventEmitter().setMaxListeners(0);
// Map of active test workers, used in receiveMessages() to get a reference to
// the TestWorker instance, and relevant release functions.
const activeTestWorkers = new Map();
class TestWorker {
constructor(id, file) {
this.id = id;
this.file = file;
}
teardown(fn) {
let done = false;
const teardownFn = async () => {
if (done) {
return;
}
done = true;
if (activeTestWorkers.has(this.id)) {
activeTestWorkers.get(this.id).teardownFns.delete(teardownFn);
}
await fn();
};
activeTestWorkers.get(this.id).teardownFns.add(teardownFn);
return teardownFn;
}
publish(data) {
return publishMessage(this, data);
}
async * subscribe() {
yield * receiveMessages(this);
}
}
class ReceivedMessage {
constructor(testWorker, id, serializedData) {
this.testWorker = testWorker;
this.id = id;
this.data = v8.deserialize(new Uint8Array(serializedData));
}
reply(data) {
return publishMessage(this.testWorker, data, this.id);
}
}
// Ensure that, no matter how often it's received, we have a stable message
// object.
const messageCache = new WeakMap();
async function * receiveMessages(fromTestWorker, replyTo) {
for await (const [message] of on(events, 'message')) {
if (fromTestWorker !== undefined) {
if (message.type === 'deregister-test-worker' && message.id === fromTestWorker.id) {
return;
}
if (message.type === 'message' && message.testWorkerId !== fromTestWorker.id) {
continue;
}
}
if (message.type !== 'message') {
continue;
}
if (replyTo === undefined && message.replyTo !== undefined) {
continue;
}
if (replyTo !== undefined && message.replyTo !== replyTo) {
continue;
}
const active = activeTestWorkers.get(message.testWorkerId);
// It is possible for a message to have been buffering for so long — perhaps
// due to the caller waiting before iterating to the next message — that the
// test worker has been deregistered. Ignore such messages.
//
// (This is really hard to write a test for, however!)
if (active === undefined) {
continue;
}
let received = messageCache.get(message);
if (received === undefined) {
received = new ReceivedMessage(active.instance, message.messageId, message.serializedData);
messageCache.set(message, received);
}
yield received;
}
}
let messageCounter = 0;
const messageIdPrefix = `${workerData.id}/message`;
const nextMessageId = () => `${messageIdPrefix}/${++messageCounter}`;
function publishMessage(testWorker, data, replyTo) {
const id = nextMessageId();
parentPort.postMessage({
type: 'message',
messageId: id,
testWorkerId: testWorker.id,
serializedData: [...v8.serialize(data)],
replyTo
});
return {
id,
async * replies() {
yield * receiveMessages(testWorker, id);
}
};
}
function broadcastMessage(data) {
const id = nextMessageId();
parentPort.postMessage({
type: 'broadcast',
messageId: id,
serializedData: [...v8.serialize(data)]
});
return {
id,
async * replies() {
yield * receiveMessages(undefined, id);
}
};
}
async function loadFactory() {
try {
const mod = require(workerData.filename);
if (typeof mod === 'function') {
return mod;
}
return mod.default;
} catch (error) {
if (error && (error.code === 'ERR_REQUIRE_ESM' || (error.code === 'MODULE_NOT_FOUND' && workerData.filename.startsWith('file://')))) {
const {default: factory} = await import(workerData.filename); // eslint-disable-line node/no-unsupported-features/es-syntax
return factory;
}
throw error;
}
}
let signalAvailable = () => {
parentPort.postMessage({type: 'available'});
signalAvailable = () => {};
};
let fatal;
loadFactory(workerData.filename).then(factory => {
if (typeof factory !== 'function') {
throw new TypeError(`Missing default factory function export for shared worker plugin at ${workerData.filename}`);
}
factory({
negotiateProtocol(supported) {
if (!supported.includes('experimental')) {
fatal = new Error(`This version of AVA (${pkg.version}) is not compatible with shared worker plugin at ${workerData.filename}`);
throw fatal;
}
const produceTestWorker = instance => events.emit('testWorker', instance);
parentPort.on('message', async message => {
if (message.type === 'register-test-worker') {
const {id, file} = message;
const instance = new TestWorker(id, file);
activeTestWorkers.set(id, {instance, teardownFns: new Set()});
produceTestWorker(instance);
}
if (message.type === 'deregister-test-worker') {
const {id} = message;
const {teardownFns} = activeTestWorkers.get(id);
activeTestWorkers.delete(id);
// Run possibly asynchronous release functions serially, in reverse
// order. Any error will crash the worker.
for await (const fn of [...teardownFns].reverse()) {
await fn();
}
parentPort.postMessage({
type: 'deregistered-test-worker',
id
});
}
// Wait for a turn of the event loop, to allow new subscriptions to be
// set up in response to the previous message.
setImmediate(() => events.emit('message', message));
});
return {
initialData: workerData.initialData,
protocol: 'experimental',
ready() {
signalAvailable();
return this;
},
broadcast(data) {
return broadcastMessage(data);
},
async * subscribe() {
yield * receiveMessages();
},
async * testWorkers() {
for await (const [worker] of on(events, 'testWorker')) {
yield worker;
}
}
};
}
});
}).catch(error => {
if (fatal === undefined) {
fatal = error;
}
}).finally(() => {
if (fatal !== undefined) {
process.nextTick(() => {
throw fatal;
});
}
});

140
node_modules/ava/lib/plugin-support/shared-workers.js generated vendored Normal file
View file

@ -0,0 +1,140 @@
const events = require('events');
const serializeError = require('../serialize-error');
let Worker;
try {
({Worker} = require('worker_threads'));
} catch {}
const LOADER = require.resolve('./shared-worker-loader');
let sharedWorkerCounter = 0;
const launchedWorkers = new Map();
const waitForAvailable = async worker => {
for await (const [message] of events.on(worker, 'message')) {
if (message.type === 'available') {
return;
}
}
};
function launchWorker({filename, initialData}) {
if (launchedWorkers.has(filename)) {
return launchedWorkers.get(filename);
}
const id = `shared-worker/${++sharedWorkerCounter}`;
const worker = new Worker(LOADER, {
// Ensure the worker crashes for unhandled rejections, rather than allowing undefined behavior.
execArgv: ['--unhandled-rejections=strict'],
workerData: {
filename,
id,
initialData
}
});
worker.setMaxListeners(0);
const launched = {
statePromises: {
available: waitForAvailable(worker),
error: events.once(worker, 'error').then(([error]) => error) // eslint-disable-line promise/prefer-await-to-then
},
exited: false,
worker
};
launchedWorkers.set(filename, launched);
worker.once('exit', () => {
launched.exited = true;
});
return launched;
}
async function observeWorkerProcess(fork, runStatus) {
let registrationCount = 0;
let signalDeregistered;
const deregistered = new Promise(resolve => {
signalDeregistered = resolve;
});
fork.promise.finally(() => {
if (registrationCount === 0) {
signalDeregistered();
}
});
fork.onConnectSharedWorker(async channel => {
const launched = launchWorker(channel);
const handleChannelMessage = ({messageId, replyTo, serializedData}) => {
launched.worker.postMessage({
type: 'message',
testWorkerId: fork.forkId,
messageId,
replyTo,
serializedData
});
};
const handleWorkerMessage = async message => {
if (message.type === 'broadcast' || (message.type === 'message' && message.testWorkerId === fork.forkId)) {
const {messageId, replyTo, serializedData} = message;
channel.forwardMessageToFork({messageId, replyTo, serializedData});
}
if (message.type === 'deregistered-test-worker' && message.id === fork.forkId) {
launched.worker.off('message', handleWorkerMessage);
registrationCount--;
if (registrationCount === 0) {
signalDeregistered();
}
}
};
launched.statePromises.error.then(error => { // eslint-disable-line promise/prefer-await-to-then
signalDeregistered();
launched.worker.off('message', handleWorkerMessage);
runStatus.emitStateChange({type: 'shared-worker-error', err: serializeError('Shared worker error', true, error)});
channel.signalError();
});
try {
await launched.statePromises.available;
registrationCount++;
launched.worker.postMessage({
type: 'register-test-worker',
id: fork.forkId,
file: fork.file
});
fork.promise.finally(() => {
launched.worker.postMessage({
type: 'deregister-test-worker',
id: fork.forkId
});
channel.off('message', handleChannelMessage);
});
launched.worker.on('message', handleWorkerMessage);
channel.on('message', handleChannelMessage);
channel.signalReady();
} catch {
return;
} finally {
// Attaching listeners has the side-effect of referencing the worker.
// Explicitly unreference it now so it does not prevent the main process
// from exiting.
launched.worker.unref();
}
});
return deregistered;
}
exports.observeWorkerProcess = observeWorkerProcess;

View file

@ -21,7 +21,7 @@ function load(providerModule, projectDir) {
let level;
const provider = makeProvider({
negotiateProtocol(identifiers, {version}) {
const [identifier] = identifiers.filter(identifier => Reflect.has(levelsByProtocol, identifier));
const identifier = identifiers.find(identifier => Reflect.has(levelsByProtocol, identifier));
if (identifier === undefined) {
fatal = new Error(`This version of AVA (${ava.version}) is not compatible with ${providerModule}@${version}`);

920
node_modules/ava/lib/reporters/default.js generated vendored Normal file
View file

@ -0,0 +1,920 @@
'use strict';
const os = require('os');
const path = require('path');
const stream = require('stream');
const cliCursor = require('cli-cursor');
const figures = require('figures');
const indentString = require('indent-string');
const ora = require('ora');
const plur = require('plur');
const prettyMs = require('pretty-ms');
const trimOffNewlines = require('trim-off-newlines');
const chalk = require('../chalk').get();
const codeExcerpt = require('../code-excerpt');
const beautifyStack = require('./beautify-stack');
const colors = require('./colors');
const formatSerializedError = require('./format-serialized-error');
const improperUsageMessages = require('./improper-usage-messages');
const prefixTitle = require('./prefix-title');
const nodeInternals = require('stack-utils').nodeInternals();
class LineWriter extends stream.Writable {
constructor(dest) {
super();
this.dest = dest;
this.columns = dest.columns || 80;
this.lastLineIsEmpty = false;
}
_write(chunk, _, callback) {
this.dest.write(chunk);
callback();
}
writeLine(string) {
if (string) {
this.write(indentString(string, 2) + os.EOL);
this.lastLineIsEmpty = false;
} else {
this.write(os.EOL);
this.lastLineIsEmpty = true;
}
}
ensureEmptyLine() {
if (!this.lastLineIsEmpty) {
this.writeLine();
}
}
}
class LineWriterWithSpinner extends LineWriter {
constructor(dest, spinner) {
super(dest);
this.lastSpinnerText = '';
this.spinner = spinner;
}
_write(chunk, _, callback) {
this.spinner.clear();
this._writeWithSpinner(chunk.toString('utf8'));
callback();
}
_writev(pieces, callback) {
// Discard the current spinner output. Any lines that were meant to be
// preserved should be rewritten.
this.spinner.clear();
const last = pieces.pop();
for (const piece of pieces) {
this.dest.write(piece.chunk);
}
this._writeWithSpinner(last.chunk.toString('utf8'));
callback();
}
_writeWithSpinner(string) {
if (!this.spinner.isSpinning) {
this.dest.write(string);
return;
}
this.lastSpinnerText = string;
// Ignore whitespace at the end of the chunk. We're continiously rewriting
// the last line through the spinner. Also be careful to remove the indent
// as the spinner adds its own.
this.spinner.text = string.trimEnd().slice(2);
this.spinner.render();
}
}
function manageCorking(stream) {
let corked = false;
const cork = () => {
corked = true;
stream.cork();
};
const uncork = () => {
corked = false;
stream.uncork();
};
return {
decorateFlushingWriter(fn) {
return function (...args) {
if (corked) {
stream.uncork();
}
try {
return fn.apply(this, args);
} finally {
if (corked) {
stream.cork();
}
}
};
},
decorateWriter(fn) {
return function (...args) {
cork();
try {
return fn.apply(this, args);
} finally {
uncork();
}
};
}
};
}
class Reporter {
constructor({
verbose,
reportStream,
stdStream,
projectDir,
watching,
spinner,
durationThreshold
}) {
this.verbose = verbose;
this.reportStream = reportStream;
this.stdStream = stdStream;
this.watching = watching;
this.relativeFile = file => path.relative(projectDir, file);
const {decorateWriter, decorateFlushingWriter} = manageCorking(this.reportStream);
this.consumeStateChange = decorateWriter(this.consumeStateChange);
this.endRun = decorateWriter(this.endRun);
if (this.verbose) {
this.durationThreshold = durationThreshold || 100;
this.spinner = null;
this.clearSpinner = () => {};
this.lineWriter = new LineWriter(this.reportStream);
} else {
this.spinner = ora({
isEnabled: true,
color: spinner ? spinner.color : 'gray',
discardStdin: !watching,
hideCursor: false,
spinner: spinner || (process.platform === 'win32' ? 'line' : 'dots'),
stream: reportStream
});
this.clearSpinner = decorateFlushingWriter(this.spinner.clear.bind(this.spinner));
this.lineWriter = new LineWriterWithSpinner(this.reportStream, this.spinner);
}
this.reset();
}
reset() {
if (this.removePreviousListener) {
this.removePreviousListener();
}
this.prefixTitle = (testFile, title) => title;
this.runningTestFiles = new Map();
this.filesWithMissingAvaImports = new Set();
this.filesWithoutDeclaredTests = new Set();
this.filesWithoutMatchedLineNumbers = new Set();
this.failures = [];
this.internalErrors = [];
this.knownFailures = [];
this.lineNumberErrors = [];
this.sharedWorkerErrors = [];
this.uncaughtExceptions = [];
this.unhandledRejections = [];
this.unsavedSnapshots = [];
this.previousFailures = 0;
this.failFastEnabled = false;
this.lastLineIsEmpty = false;
this.matching = false;
this.removePreviousListener = null;
this.stats = null;
}
startRun(plan) {
if (plan.bailWithoutReporting) {
return;
}
this.reset();
this.failFastEnabled = plan.failFastEnabled;
this.matching = plan.matching;
this.previousFailures = plan.previousFailures;
this.emptyParallelRun = plan.status.emptyParallelRun;
if (this.watching || plan.files.length > 1) {
this.prefixTitle = (testFile, title) => prefixTitle(plan.filePathPrefix, testFile, title);
}
this.removePreviousListener = plan.status.on('stateChange', evt => {
this.consumeStateChange(evt);
});
if (this.watching && plan.runVector > 1) {
this.lineWriter.write(chalk.gray.dim('\u2500'.repeat(this.lineWriter.columns)) + os.EOL);
}
if (this.spinner === null) {
this.lineWriter.writeLine();
} else {
cliCursor.hide(this.reportStream);
this.lineWriter.writeLine();
this.spinner.start();
}
}
consumeStateChange(event) { // eslint-disable-line complexity
const fileStats = this.stats && event.testFile ? this.stats.byFile.get(event.testFile) : null;
switch (event.type) { // eslint-disable-line default-case
case 'hook-failed': {
this.failures.push(event);
this.writeTestSummary(event);
break;
}
case 'stats': {
this.stats = event.stats;
break;
}
case 'test-failed': {
this.failures.push(event);
this.writeTestSummary(event);
break;
}
case 'test-passed': {
if (event.knownFailing) {
this.knownFailures.push(event);
}
this.writeTestSummary(event);
break;
}
case 'timeout': {
this.lineWriter.writeLine(colors.error(`\n${figures.cross} Timed out while running tests`));
this.lineWriter.writeLine('');
this.writePendingTests(event);
break;
}
case 'interrupt': {
this.lineWriter.writeLine(colors.error(`\n${figures.cross} Exiting due to SIGINT`));
this.lineWriter.writeLine('');
this.writePendingTests(event);
break;
}
case 'internal-error': {
this.internalErrors.push(event);
if (event.testFile) {
this.write(colors.error(`${figures.cross} Internal error when running ${this.relativeFile(event.testFile)}`));
} else {
this.write(colors.error(`${figures.cross} Internal error`));
}
if (this.verbose) {
this.lineWriter.writeLine(colors.stack(event.err.summary));
this.lineWriter.writeLine(colors.errorStack(event.err.stack));
this.lineWriter.writeLine();
this.lineWriter.writeLine();
}
break;
}
case 'line-number-selection-error': {
this.lineNumberErrors.push(event);
this.write(colors.information(`${figures.warning} Could not parse ${this.relativeFile(event.testFile)} for line number selection`));
break;
}
case 'missing-ava-import': {
this.filesWithMissingAvaImports.add(event.testFile);
this.write(colors.error(`${figures.cross} No tests found in ${this.relativeFile(event.testFile)}, make sure to import "ava" at the top of your test file`));
break;
}
case 'hook-finished': {
if (this.verbose && event.logs.length > 0) {
this.lineWriter.writeLine(` ${this.prefixTitle(event.testFile, event.title)}`);
this.writeLogs(event);
}
break;
}
case 'selected-test': {
if (this.verbose) {
if (event.skip) {
this.lineWriter.writeLine(colors.skip(`- ${this.prefixTitle(event.testFile, event.title)}`));
} else if (event.todo) {
this.lineWriter.writeLine(colors.todo(`- ${this.prefixTitle(event.testFile, event.title)}`));
}
}
break;
}
case 'shared-worker-error': {
this.sharedWorkerErrors.push(event);
if (this.verbose) {
this.lineWriter.ensureEmptyLine();
this.lineWriter.writeLine(colors.error(`${figures.cross} Error in shared worker`));
this.lineWriter.writeLine();
this.writeErr(event);
}
break;
}
case 'snapshot-error':
this.unsavedSnapshots.push(event);
break;
case 'uncaught-exception': {
this.uncaughtExceptions.push(event);
if (this.verbose) {
this.lineWriter.ensureEmptyLine();
this.lineWriter.writeLine(colors.title(`Uncaught exception in ${this.relativeFile(event.testFile)}`));
this.lineWriter.writeLine();
this.writeErr(event);
}
break;
}
case 'unhandled-rejection': {
this.unhandledRejections.push(event);
if (this.verbose) {
this.lineWriter.ensureEmptyLine();
this.lineWriter.writeLine(colors.title(`Unhandled rejection in ${this.relativeFile(event.testFile)}`));
this.lineWriter.writeLine();
this.writeErr(event);
}
break;
}
case 'worker-failed': {
if (fileStats.declaredTests === 0) {
this.filesWithoutDeclaredTests.add(event.testFile);
}
if (this.verbose && !this.filesWithMissingAvaImports.has(event.testFile)) {
if (event.nonZeroExitCode) {
this.lineWriter.writeLine(colors.error(`${figures.cross} ${this.relativeFile(event.testFile)} exited with a non-zero exit code: ${event.nonZeroExitCode}`));
} else {
this.lineWriter.writeLine(colors.error(`${figures.cross} ${this.relativeFile(event.testFile)} exited due to ${event.signal}`));
}
}
break;
}
case 'worker-finished': {
if (!event.forcedExit && !this.filesWithMissingAvaImports.has(event.testFile)) {
if (fileStats.declaredTests === 0) {
this.filesWithoutDeclaredTests.add(event.testFile);
this.write(colors.error(`${figures.cross} No tests found in ${this.relativeFile(event.testFile)}`));
} else if (fileStats.selectingLines && fileStats.selectedTests === 0) {
this.filesWithoutMatchedLineNumbers.add(event.testFile);
this.lineWriter.writeLine(colors.error(`${figures.cross} Line numbers for ${this.relativeFile(event.testFile)} did not match any tests`));
} else if (this.verbose && !this.failFastEnabled && fileStats.remainingTests > 0) {
this.lineWriter.writeLine(colors.error(`${figures.cross} ${fileStats.remainingTests} ${plur('test', fileStats.remainingTests)} remaining in ${this.relativeFile(event.testFile)}`));
}
}
break;
}
case 'worker-stderr': {
// Forcibly clear the spinner, writing the chunk corrupts the TTY.
this.clearSpinner();
this.stdStream.write(event.chunk);
// If the chunk does not end with a linebreak, *forcibly* write one to
// ensure it remains visible in the TTY.
// Tests cannot assume their standard output is not interrupted. Indeed
// we multiplex stdout and stderr into a single stream. However as
// long as stdStream is different from reportStream users can read
// their original output by redirecting the streams.
if (event.chunk[event.chunk.length - 1] !== 0x0A) {
this.reportStream.write(os.EOL);
}
if (this.spinner !== null) {
this.lineWriter.write(this.lineWriter.lastSpinnerText);
}
break;
}
case 'worker-stdout': {
// Forcibly clear the spinner, writing the chunk corrupts the TTY.
this.clearSpinner();
this.stdStream.write(event.chunk);
// If the chunk does not end with a linebreak, *forcibly* write one to
// ensure it remains visible in the TTY.
// Tests cannot assume their standard output is not interrupted. Indeed
// we multiplex stdout and stderr into a single stream. However as
// long as stdStream is different from reportStream users can read
// their original output by redirecting the streams.
if (event.chunk[event.chunk.length - 1] !== 0x0A) {
this.reportStream.write(os.EOL);
}
if (this.spinner !== null) {
this.lineWriter.write(this.lineWriter.lastSpinnerText);
}
}
}
}
writePendingTests(evt) {
for (const [file, testsInFile] of evt.pendingTests) {
if (testsInFile.size === 0) {
continue;
}
this.lineWriter.writeLine(`${testsInFile.size} tests were pending in ${this.relativeFile(file)}\n`);
for (const title of testsInFile) {
this.lineWriter.writeLine(`${figures.circleDotted} ${this.prefixTitle(file, title)}`);
}
this.lineWriter.writeLine('');
}
}
write(string) {
if (this.verbose) {
this.lineWriter.writeLine(string);
} else {
this.writeWithCounts(string);
}
}
writeWithCounts(string) {
if (!this.stats) {
return this.lineWriter.writeLine(string);
}
string = string || '';
if (string !== '') {
string += os.EOL;
}
let firstLinePostfix = this.watching ? ' ' + chalk.gray.dim('[' + new Date().toLocaleTimeString('en-US', {hour12: false}) + ']') : '';
if (this.stats.passedTests > 0) {
string += os.EOL + colors.pass(`${this.stats.passedTests} passed`) + firstLinePostfix;
firstLinePostfix = '';
}
if (this.stats.passedKnownFailingTests > 0) {
string += os.EOL + colors.error(`${this.stats.passedKnownFailingTests} ${plur('known failure', this.stats.passedKnownFailingTests)}`);
}
if (this.stats.failedHooks > 0) {
string += os.EOL + colors.error(`${this.stats.failedHooks} ${plur('hook', this.stats.failedHooks)} failed`) + firstLinePostfix;
firstLinePostfix = '';
}
if (this.stats.failedTests > 0) {
string += os.EOL + colors.error(`${this.stats.failedTests} ${plur('test', this.stats.failedTests)} failed`) + firstLinePostfix;
firstLinePostfix = '';
}
if (this.stats.skippedTests > 0) {
string += os.EOL + colors.skip(`${this.stats.skippedTests} skipped`);
}
if (this.stats.todoTests > 0) {
string += os.EOL + colors.todo(`${this.stats.todoTests} todo`);
}
this.lineWriter.writeLine(string);
}
writeErr(event) {
if (event.err.name === 'TSError' && event.err.object && event.err.object.diagnosticText) {
this.lineWriter.writeLine(colors.errorStack(trimOffNewlines(event.err.object.diagnosticText)));
this.lineWriter.writeLine();
return;
}
if (event.err.source) {
this.lineWriter.writeLine(colors.errorSource(`${this.relativeFile(event.err.source.file)}:${event.err.source.line}`));
const excerpt = codeExcerpt(event.err.source, {maxWidth: this.reportStream.columns - 2});
if (excerpt) {
this.lineWriter.writeLine();
this.lineWriter.writeLine(excerpt);
this.lineWriter.writeLine();
}
}
if (event.err.avaAssertionError) {
const result = formatSerializedError(event.err);
if (result.printMessage) {
this.lineWriter.writeLine(event.err.message);
this.lineWriter.writeLine();
}
if (result.formatted) {
this.lineWriter.writeLine(result.formatted);
this.lineWriter.writeLine();
}
const message = improperUsageMessages.forError(event.err);
if (message) {
this.lineWriter.writeLine(message);
this.lineWriter.writeLine();
}
} else if (event.err.nonErrorObject) {
this.lineWriter.writeLine(trimOffNewlines(event.err.formatted));
this.lineWriter.writeLine();
} else {
this.lineWriter.writeLine(event.err.summary);
this.lineWriter.writeLine();
}
const formatted = this.formatErrorStack(event.err);
if (formatted.length > 0) {
this.lineWriter.writeLine(formatted.join('\n'));
this.lineWriter.writeLine();
}
}
formatErrorStack(error) {
if (!error.stack) {
return [];
}
if (error.shouldBeautifyStack) {
return beautifyStack(error.stack).map(line => {
if (nodeInternals.some(internal => internal.test(line))) {
return colors.errorStackInternal(`${figures.pointerSmall} ${line}`);
}
return colors.errorStack(`${figures.pointerSmall} ${line}`);
});
}
return [error.stack];
}
writeLogs(event, surroundLines) {
if (event.logs && event.logs.length > 0) {
if (surroundLines) {
this.lineWriter.writeLine();
}
for (const log of event.logs) {
const logLines = indentString(colors.log(log), 4);
const logLinesWithLeadingFigure = logLines.replace(/^ {4}/, ` ${colors.information(figures.info)} `);
this.lineWriter.writeLine(logLinesWithLeadingFigure);
}
if (surroundLines) {
this.lineWriter.writeLine();
}
return true;
}
return false;
}
writeTestSummary(event) {
if (event.type === 'hook-failed' || event.type === 'test-failed') {
if (this.verbose) {
this.write(`${colors.error(figures.cross)} ${this.prefixTitle(event.testFile, event.title)} ${colors.error(event.err.message)}`);
} else {
this.write(this.prefixTitle(event.testFile, event.title));
}
} else if (event.knownFailing) {
if (this.verbose) {
this.write(`${colors.error(figures.tick)} ${colors.error(this.prefixTitle(event.testFile, event.title))}`);
} else {
this.write(colors.error(this.prefixTitle(event.testFile, event.title)));
}
} else if (this.verbose) {
const duration = event.duration > this.durationThreshold ? colors.duration(' (' + prettyMs(event.duration) + ')') : '';
this.write(`${colors.pass(figures.tick)} ${this.prefixTitle(event.testFile, event.title)}${duration}`);
} else {
this.write(this.prefixTitle(event.testFile, event.title));
}
if (this.verbose) {
this.writeLogs(event);
}
}
writeFailure(event) {
this.lineWriter.writeLine(colors.title(this.prefixTitle(event.testFile, event.title)));
if (!this.writeLogs(event, true)) {
this.lineWriter.writeLine();
}
this.writeErr(event);
}
endRun() {// eslint-disable-line complexity
let firstLinePostfix = this.watching ? ` ${chalk.gray.dim(`[${new Date().toLocaleTimeString('en-US', {hour12: false})}]`)}` : '';
let wroteSomething = false;
if (!this.verbose) {
this.spinner.stop();
cliCursor.show(this.reportStream);
} else if (this.emptyParallelRun) {
this.lineWriter.writeLine('No files tested in this parallel run');
this.lineWriter.writeLine();
return;
}
if (!this.stats) {
this.lineWriter.writeLine(colors.error(`${figures.cross} Couldnt find any files to test` + firstLinePostfix));
this.lineWriter.writeLine();
return;
}
if (this.matching && this.stats.selectedTests === 0) {
this.lineWriter.writeLine(colors.error(`${figures.cross} Couldnt find any matching tests` + firstLinePostfix));
this.lineWriter.writeLine();
return;
}
if (this.verbose) {
this.lineWriter.writeLine(colors.log(figures.line));
this.lineWriter.writeLine();
} else {
if (this.filesWithMissingAvaImports.size > 0) {
for (const testFile of this.filesWithMissingAvaImports) {
this.lineWriter.writeLine(colors.error(`${figures.cross} No tests found in ${this.relativeFile(testFile)}, make sure to import "ava" at the top of your test file`) + firstLinePostfix);
firstLinePostfix = '';
wroteSomething = true;
}
}
if (this.filesWithoutDeclaredTests.size > 0) {
for (const testFile of this.filesWithoutDeclaredTests) {
if (!this.filesWithMissingAvaImports.has(testFile)) {
this.lineWriter.writeLine(colors.error(`${figures.cross} No tests found in ${this.relativeFile(testFile)}`) + firstLinePostfix);
firstLinePostfix = '';
wroteSomething = true;
}
}
}
if (this.lineNumberErrors.length > 0) {
for (const event of this.lineNumberErrors) {
this.lineWriter.writeLine(colors.information(`${figures.warning} Could not parse ${this.relativeFile(event.testFile)} for line number selection` + firstLinePostfix));
firstLinePostfix = '';
wroteSomething = true;
}
}
if (this.filesWithoutMatchedLineNumbers.size > 0) {
for (const testFile of this.filesWithoutMatchedLineNumbers) {
if (!this.filesWithMissingAvaImports.has(testFile) && !this.filesWithoutDeclaredTests.has(testFile)) {
this.lineWriter.writeLine(colors.error(`${figures.cross} Line numbers for ${this.relativeFile(testFile)} did not match any tests`) + firstLinePostfix);
firstLinePostfix = '';
wroteSomething = true;
}
}
}
if (wroteSomething) {
this.lineWriter.writeLine();
this.lineWriter.writeLine(colors.log(figures.line));
this.lineWriter.writeLine();
wroteSomething = false;
}
}
if (this.failures.length > 0) {
const writeTrailingLines = this.internalErrors.length > 0 || this.sharedWorkerErrors.length > 0 || this.uncaughtExceptions.length > 0 || this.unhandledRejections.length > 0;
const lastFailure = this.failures[this.failures.length - 1];
for (const event of this.failures) {
this.writeFailure(event);
if (event !== lastFailure) {
this.lineWriter.writeLine();
this.lineWriter.writeLine();
} else if (!this.verbose && writeTrailingLines) {
this.lineWriter.writeLine();
this.lineWriter.writeLine();
}
wroteSomething = true;
}
if (this.verbose) {
this.lineWriter.writeLine(colors.log(figures.line));
this.lineWriter.writeLine();
}
}
if (!this.verbose) {
if (this.internalErrors.length > 0) {
const writeTrailingLines = this.sharedWorkerErrors.length > 0 || this.uncaughtExceptions.length > 0 || this.unhandledRejections.length > 0;
const last = this.internalErrors[this.internalErrors.length - 1];
for (const event of this.internalErrors) {
if (event.testFile) {
this.lineWriter.writeLine(colors.error(`${figures.cross} Internal error when running ${this.relativeFile(event.testFile)}`));
} else {
this.lineWriter.writeLine(colors.error(`${figures.cross} Internal error`));
}
this.lineWriter.writeLine(colors.stack(event.err.summary));
this.lineWriter.writeLine(colors.errorStack(event.err.stack));
if (event !== last || writeTrailingLines) {
this.lineWriter.writeLine();
this.lineWriter.writeLine();
this.lineWriter.writeLine();
}
wroteSomething = true;
}
}
if (this.sharedWorkerErrors.length > 0) {
const writeTrailingLines = this.uncaughtExceptions.length > 0 || this.unhandledRejections.length > 0;
const last = this.sharedWorkerErrors[this.sharedWorkerErrors.length - 1];
for (const evt of this.sharedWorkerErrors) {
this.lineWriter.writeLine(colors.error(`${figures.cross} Error in shared worker`));
this.lineWriter.writeLine();
this.writeErr(evt.err);
if (evt !== last || writeTrailingLines) {
this.lineWriter.writeLine();
this.lineWriter.writeLine();
}
wroteSomething = true;
}
}
if (this.uncaughtExceptions.length > 0) {
const writeTrailingLines = this.unhandledRejections.length > 0;
const last = this.uncaughtExceptions[this.uncaughtExceptions.length - 1];
for (const event of this.uncaughtExceptions) {
this.lineWriter.writeLine(colors.title(`Uncaught exception in ${this.relativeFile(event.testFile)}`));
this.lineWriter.writeLine();
this.writeErr(event);
if (event !== last || writeTrailingLines) {
this.lineWriter.writeLine();
this.lineWriter.writeLine();
}
wroteSomething = true;
}
}
if (this.unhandledRejections.length > 0) {
const last = this.unhandledRejections[this.unhandledRejections.length - 1];
for (const event of this.unhandledRejections) {
this.lineWriter.writeLine(colors.title(`Unhandled rejection in ${this.relativeFile(event.testFile)}`));
this.lineWriter.writeLine();
this.writeErr(event);
if (event !== last) {
this.lineWriter.writeLine();
this.lineWriter.writeLine();
}
wroteSomething = true;
}
}
if (wroteSomething) {
this.lineWriter.writeLine(colors.log(figures.line));
this.lineWriter.writeLine();
}
}
if (this.unsavedSnapshots.length > 0) {
this.lineWriter.writeLine(colors.title('Could not update snapshots for the following test files:'));
this.lineWriter.writeLine();
for (const event of this.unsavedSnapshots) {
this.lineWriter.writeLine(`${figures.warning} ${this.relativeFile(event.testFile)}`);
}
this.lineWriter.writeLine();
}
if (this.failFastEnabled && (this.stats.remainingTests > 0 || this.stats.files > this.stats.finishedWorkers)) {
let remaining = '';
if (this.stats.remainingTests > 0) {
remaining += `At least ${this.stats.remainingTests} ${plur('test was', 'tests were', this.stats.remainingTests)} skipped`;
if (this.stats.files > this.stats.finishedWorkers) {
remaining += ', as well as ';
}
}
if (this.stats.files > this.stats.finishedWorkers) {
const skippedFileCount = this.stats.files - this.stats.finishedWorkers;
remaining += `${skippedFileCount} ${plur('test file', 'test files', skippedFileCount)}`;
if (this.stats.remainingTests === 0) {
remaining += ` ${plur('was', 'were', skippedFileCount)} skipped`;
}
}
this.lineWriter.writeLine(colors.information(`\`--fail-fast\` is on. ${remaining}.`));
if (this.verbose) {
this.lineWriter.writeLine();
}
}
if (this.verbose && this.stats.parallelRuns) {
const {
currentFileCount,
currentIndex,
totalRuns
} = this.stats.parallelRuns;
this.lineWriter.writeLine(colors.information(`Ran ${currentFileCount} test ${plur('file', currentFileCount)} out of ${this.stats.files} for job ${currentIndex + 1} of ${totalRuns}`));
this.lineWriter.writeLine();
}
if (this.stats.failedHooks > 0) {
this.lineWriter.writeLine(colors.error(`${this.stats.failedHooks} ${plur('hook', this.stats.failedHooks)} failed`) + firstLinePostfix);
firstLinePostfix = '';
}
if (this.stats.failedTests > 0) {
this.lineWriter.writeLine(colors.error(`${this.stats.failedTests} ${plur('test', this.stats.failedTests)} failed`) + firstLinePostfix);
firstLinePostfix = '';
}
if (
this.stats.failedHooks === 0 &&
this.stats.failedTests === 0 &&
this.stats.passedTests > 0
) {
this.lineWriter.writeLine(colors.pass(`${this.stats.passedTests} ${plur('test', this.stats.passedTests)} passed`) + firstLinePostfix
);
firstLinePostfix = '';
}
if (this.stats.passedKnownFailingTests > 0) {
this.lineWriter.writeLine(colors.error(`${this.stats.passedKnownFailingTests} ${plur('known failure', this.stats.passedKnownFailingTests)}`));
}
if (this.stats.skippedTests > 0) {
this.lineWriter.writeLine(colors.skip(`${this.stats.skippedTests} ${plur('test', this.stats.skippedTests)} skipped`));
}
if (this.stats.todoTests > 0) {
this.lineWriter.writeLine(colors.todo(`${this.stats.todoTests} ${plur('test', this.stats.todoTests)} todo`));
}
if (this.stats.unhandledRejections > 0) {
this.lineWriter.writeLine(colors.error(`${this.stats.unhandledRejections} unhandled ${plur('rejection', this.stats.unhandledRejections)}`));
}
if (this.stats.uncaughtExceptions > 0) {
this.lineWriter.writeLine(colors.error(`${this.stats.uncaughtExceptions} uncaught ${plur('exception', this.stats.uncaughtExceptions)}`));
}
if (this.previousFailures > 0) {
this.lineWriter.writeLine(colors.error(`${this.previousFailures} previous ${plur('failure', this.previousFailures)} in test files that were not rerun`));
}
if (this.watching) {
this.lineWriter.writeLine();
}
}
}
module.exports = Reporter;

View file

@ -1,619 +0,0 @@
'use strict';
const os = require('os');
const path = require('path');
const stream = require('stream');
const cliCursor = require('cli-cursor');
const figures = require('figures');
const indentString = require('indent-string');
const ora = require('ora');
const plur = require('plur');
const trimOffNewlines = require('trim-off-newlines');
const beautifyStack = require('./beautify-stack');
const chalk = require('../chalk').get();
const codeExcerpt = require('../code-excerpt');
const colors = require('./colors');
const formatSerializedError = require('./format-serialized-error');
const improperUsageMessages = require('./improper-usage-messages');
const prefixTitle = require('./prefix-title');
const whileCorked = require('./while-corked');
const nodeInternals = require('stack-utils').nodeInternals();
class LineWriter extends stream.Writable {
constructor(dest, spinner) {
super();
this.dest = dest;
this.columns = dest.columns || 80;
this.spinner = spinner;
this.lastSpinnerText = '';
}
_write(chunk, encoding, callback) {
// Discard the current spinner output. Any lines that were meant to be
// preserved should be rewritten.
this.spinner.clear();
this._writeWithSpinner(chunk.toString('utf8'));
callback();
}
_writev(pieces, callback) {
// Discard the current spinner output. Any lines that were meant to be
// preserved should be rewritten.
this.spinner.clear();
const last = pieces.pop();
for (const piece of pieces) {
this.dest.write(piece.chunk);
}
this._writeWithSpinner(last.chunk.toString('utf8'));
callback();
}
_writeWithSpinner(string) {
if (!this.spinner.id) {
this.dest.write(string);
return;
}
this.lastSpinnerText = string;
// Ignore whitespace at the end of the chunk. We're continiously rewriting
// the last line through the spinner. Also be careful to remove the indent
// as the spinner adds its own.
this.spinner.text = string.trimEnd().slice(2);
this.spinner.render();
}
writeLine(string) {
if (string) {
this.write(indentString(string, 2) + os.EOL);
} else {
this.write(os.EOL);
}
}
}
class MiniReporter {
constructor(options) {
this.reportStream = options.reportStream;
this.stdStream = options.stdStream;
this.watching = options.watching;
this.spinner = ora({
isEnabled: true,
color: options.spinner ? options.spinner.color : 'gray',
discardStdin: !options.watching,
hideCursor: false,
spinner: options.spinner || (process.platform === 'win32' ? 'line' : 'dots'),
stream: options.reportStream
});
this.lineWriter = new LineWriter(this.reportStream, this.spinner);
this.consumeStateChange = whileCorked(this.reportStream, whileCorked(this.lineWriter, this.consumeStateChange));
this.endRun = whileCorked(this.reportStream, whileCorked(this.lineWriter, this.endRun));
this.relativeFile = file => path.relative(options.projectDir, file);
this.reset();
}
reset() {
if (this.removePreviousListener) {
this.removePreviousListener();
}
this.failFastEnabled = false;
this.failures = [];
this.filesWithMissingAvaImports = new Set();
this.filesWithoutDeclaredTests = new Set();
this.filesWithoutMatchedLineNumbers = new Set();
this.internalErrors = [];
this.knownFailures = [];
this.lineNumberErrors = [];
this.matching = false;
this.prefixTitle = (testFile, title) => title;
this.previousFailures = 0;
this.removePreviousListener = null;
this.stats = null;
this.uncaughtExceptions = [];
this.unhandledRejections = [];
}
startRun(plan) {
if (plan.bailWithoutReporting) {
return;
}
this.reset();
this.failFastEnabled = plan.failFastEnabled;
this.matching = plan.matching;
this.previousFailures = plan.previousFailures;
if (this.watching || plan.files.length > 1) {
this.prefixTitle = (testFile, title) => prefixTitle(plan.filePathPrefix, testFile, title);
}
this.removePreviousListener = plan.status.on('stateChange', evt => this.consumeStateChange(evt));
if (this.watching && plan.runVector > 1) {
this.reportStream.write(chalk.gray.dim('\u2500'.repeat(this.lineWriter.columns)) + os.EOL);
}
cliCursor.hide(this.reportStream);
this.lineWriter.writeLine();
this.spinner.start();
}
consumeStateChange(evt) { // eslint-disable-line complexity
const fileStats = this.stats && evt.testFile ? this.stats.byFile.get(evt.testFile) : null;
switch (evt.type) {
case 'declared-test':
// Ignore
break;
case 'hook-failed':
this.failures.push(evt);
this.writeTestSummary(evt);
break;
case 'internal-error':
this.internalErrors.push(evt);
if (evt.testFile) {
this.writeWithCounts(colors.error(`${figures.cross} Internal error when running ${this.relativeFile(evt.testFile)}`));
} else {
this.writeWithCounts(colors.error(`${figures.cross} Internal error`));
}
break;
case 'line-number-selection-error':
this.lineNumberErrors.push(evt);
this.writeWithCounts(colors.information(`${figures.warning} Could not parse ${this.relativeFile(evt.testFile)} for line number selection`));
break;
case 'missing-ava-import':
this.filesWithMissingAvaImports.add(evt.testFile);
this.writeWithCounts(colors.error(`${figures.cross} No tests found in ${this.relativeFile(evt.testFile)}, make sure to import "ava" at the top of your test file`));
break;
case 'selected-test':
// Ignore
break;
case 'stats':
this.stats = evt.stats;
break;
case 'test-failed':
this.failures.push(evt);
this.writeTestSummary(evt);
break;
case 'test-passed':
if (evt.knownFailing) {
this.knownFailures.push(evt);
}
this.writeTestSummary(evt);
break;
case 'timeout':
this.lineWriter.writeLine(colors.error(`\n${figures.cross} Timed out while running tests`));
this.lineWriter.writeLine('');
this.writePendingTests(evt);
break;
case 'interrupt':
this.lineWriter.writeLine(colors.error(`\n${figures.cross} Exiting due to SIGINT`));
this.lineWriter.writeLine('');
this.writePendingTests(evt);
break;
case 'uncaught-exception':
this.uncaughtExceptions.push(evt);
break;
case 'unhandled-rejection':
this.unhandledRejections.push(evt);
break;
case 'worker-failed':
if (fileStats.declaredTests === 0) {
this.filesWithoutDeclaredTests.add(evt.testFile);
}
break;
case 'worker-finished':
if (fileStats.declaredTests === 0) {
this.filesWithoutDeclaredTests.add(evt.testFile);
this.writeWithCounts(colors.error(`${figures.cross} No tests found in ${this.relativeFile(evt.testFile)}`));
} else if (fileStats.selectingLines && fileStats.selectedTests === 0) {
this.filesWithoutMatchedLineNumbers.add(evt.testFile);
this.writeWithCounts(colors.error(`${figures.cross} Line numbers for ${this.relativeFile(evt.testFile)} did not match any tests`));
}
break;
case 'worker-stderr':
case 'worker-stdout':
// Forcibly clear the spinner, writing the chunk corrupts the TTY.
this.spinner.clear();
this.stdStream.write(evt.chunk);
// If the chunk does not end with a linebreak, *forcibly* write one to
// ensure it remains visible in the TTY.
// Tests cannot assume their standard output is not interrupted. Indeed
// we multiplex stdout and stderr into a single stream. However as
// long as stdStream is different from reportStream users can read
// their original output by redirecting the streams.
if (evt.chunk[evt.chunk.length - 1] !== 0x0A) {
// Use write() rather than writeLine() so the (presumably corked)
// line writer will actually write the empty line before re-rendering
// the last spinner text below.
this.lineWriter.write(os.EOL);
}
this.lineWriter.write(this.lineWriter.lastSpinnerText);
break;
default:
break;
}
}
writeWithCounts(string) {
if (!this.stats) {
return this.lineWriter.writeLine(string);
}
string = string || '';
if (string !== '') {
string += os.EOL;
}
let firstLinePostfix = this.watching ?
' ' + chalk.gray.dim('[' + new Date().toLocaleTimeString('en-US', {hour12: false}) + ']') :
'';
if (this.stats.passedTests > 0) {
string += os.EOL + colors.pass(`${this.stats.passedTests} passed`) + firstLinePostfix;
firstLinePostfix = '';
}
if (this.stats.passedKnownFailingTests > 0) {
string += os.EOL + colors.error(`${this.stats.passedKnownFailingTests} ${plur('known failure', this.stats.passedKnownFailingTests)}`);
}
if (this.stats.failedHooks > 0) {
string += os.EOL + colors.error(`${this.stats.failedHooks} ${plur('hook', this.stats.failedHooks)} failed`) + firstLinePostfix;
firstLinePostfix = '';
}
if (this.stats.failedTests > 0) {
string += os.EOL + colors.error(`${this.stats.failedTests} ${plur('test', this.stats.failedTests)} failed`) + firstLinePostfix;
firstLinePostfix = '';
}
if (this.stats.skippedTests > 0) {
string += os.EOL + colors.skip(`${this.stats.skippedTests} skipped`);
}
if (this.stats.todoTests > 0) {
string += os.EOL + colors.todo(`${this.stats.todoTests} todo`);
}
this.lineWriter.writeLine(string);
}
writeErr(evt) {
if (evt.err.name === 'TSError' && evt.err.object && evt.err.object.diagnosticText) {
this.lineWriter.writeLine(colors.errorStack(trimOffNewlines(evt.err.object.diagnosticText)));
return;
}
if (evt.err.source) {
this.lineWriter.writeLine(colors.errorSource(`${this.relativeFile(evt.err.source.file)}:${evt.err.source.line}`));
const excerpt = codeExcerpt(evt.err.source, {maxWidth: this.lineWriter.columns - 2});
if (excerpt) {
this.lineWriter.writeLine();
this.lineWriter.writeLine(excerpt);
}
}
if (evt.err.avaAssertionError) {
const result = formatSerializedError(evt.err);
if (result.printMessage) {
this.lineWriter.writeLine();
this.lineWriter.writeLine(evt.err.message);
}
if (result.formatted) {
this.lineWriter.writeLine();
this.lineWriter.writeLine(result.formatted);
}
const message = improperUsageMessages.forError(evt.err);
if (message) {
this.lineWriter.writeLine();
this.lineWriter.writeLine(message);
}
} else if (evt.err.nonErrorObject) {
this.lineWriter.writeLine(trimOffNewlines(evt.err.formatted));
} else {
this.lineWriter.writeLine();
this.lineWriter.writeLine(evt.err.summary);
}
const formatted = this.formatErrorStack(evt.err);
if (formatted.length > 0) {
this.lineWriter.writeLine();
this.lineWriter.writeLine(formatted.join('\n'));
}
}
formatErrorStack(error) {
if (!error.stack) {
return [];
}
if (error.shouldBeautifyStack) {
return beautifyStack(error.stack).map(line => {
if (nodeInternals.some(internal => internal.test(line))) {
return colors.errorStackInternal(`${figures.pointerSmall} ${line}`);
}
return colors.errorStack(`${figures.pointerSmall} ${line}`);
});
}
return [error.stack];
}
writeLogs(evt) {
if (evt.logs) {
for (const log of evt.logs) {
const logLines = indentString(colors.log(log), 4);
const logLinesWithLeadingFigure = logLines.replace(
/^ {4}/,
` ${colors.information(figures.info)} `
);
this.lineWriter.writeLine(logLinesWithLeadingFigure);
}
}
}
writeTestSummary(evt) {
if (evt.type === 'hook-failed' || evt.type === 'test-failed') {
this.writeWithCounts(`${this.prefixTitle(evt.testFile, evt.title)}`);
} else if (evt.knownFailing) {
this.writeWithCounts(`${colors.error(this.prefixTitle(evt.testFile, evt.title))}`);
} else {
this.writeWithCounts(`${this.prefixTitle(evt.testFile, evt.title)}`);
}
}
writeFailure(evt) {
this.lineWriter.writeLine(`${colors.title(this.prefixTitle(evt.testFile, evt.title))}`);
this.writeLogs(evt);
this.lineWriter.writeLine();
this.writeErr(evt);
}
writePendingTests(evt) {
for (const [file, testsInFile] of evt.pendingTests) {
if (testsInFile.size === 0) {
continue;
}
this.lineWriter.writeLine(`${testsInFile.size} tests were pending in ${this.relativeFile(file)}\n`);
for (const title of testsInFile) {
this.lineWriter.writeLine(`${figures.circleDotted} ${this.prefixTitle(file, title)}`);
}
this.lineWriter.writeLine('');
}
}
endRun() { // eslint-disable-line complexity
this.spinner.stop();
cliCursor.show(this.reportStream);
if (!this.stats) {
this.lineWriter.writeLine(colors.error(`${figures.cross} Couldnt find any files to test`));
this.lineWriter.writeLine();
return;
}
if (this.matching && this.stats.selectedTests === 0) {
this.lineWriter.writeLine(colors.error(`${figures.cross} Couldnt find any matching tests`));
this.lineWriter.writeLine();
return;
}
this.lineWriter.writeLine();
let firstLinePostfix = this.watching ?
' ' + chalk.gray.dim('[' + new Date().toLocaleTimeString('en-US', {hour12: false}) + ']') :
'';
if (this.filesWithMissingAvaImports.size > 0) {
for (const testFile of this.filesWithMissingAvaImports) {
this.lineWriter.writeLine(colors.error(`${figures.cross} No tests found in ${this.relativeFile(testFile)}, make sure to import "ava" at the top of your test file`) + firstLinePostfix);
firstLinePostfix = '';
}
}
if (this.filesWithoutDeclaredTests.size > 0) {
for (const testFile of this.filesWithoutDeclaredTests) {
if (!this.filesWithMissingAvaImports.has(testFile)) {
this.lineWriter.writeLine(colors.error(`${figures.cross} No tests found in ${this.relativeFile(testFile)}`) + firstLinePostfix);
firstLinePostfix = '';
}
}
}
if (this.lineNumberErrors.length > 0) {
for (const evt of this.lineNumberErrors) {
this.lineWriter.writeLine(colors.information(`${figures.warning} Could not parse ${this.relativeFile(evt.testFile)} for line number selection`));
}
}
if (this.filesWithoutMatchedLineNumbers.size > 0) {
for (const testFile of this.filesWithoutMatchedLineNumbers) {
if (!this.filesWithMissingAvaImports.has(testFile) && !this.filesWithoutDeclaredTests.has(testFile)) {
this.lineWriter.writeLine(colors.error(`${figures.cross} Line numbers for ${this.relativeFile(testFile)} did not match any tests`) + firstLinePostfix);
firstLinePostfix = '';
}
}
}
if (this.filesWithMissingAvaImports.size > 0 || this.filesWithoutDeclaredTests.size > 0 || this.filesWithoutMatchedLineNumbers.size > 0) {
this.lineWriter.writeLine();
}
if (this.stats.failedHooks > 0) {
this.lineWriter.writeLine(colors.error(`${this.stats.failedHooks} ${plur('hook', this.stats.failedHooks)} failed`) + firstLinePostfix);
firstLinePostfix = '';
}
if (this.stats.failedTests > 0) {
this.lineWriter.writeLine(colors.error(`${this.stats.failedTests} ${plur('test', this.stats.failedTests)} failed`) + firstLinePostfix);
firstLinePostfix = '';
}
if (this.stats.failedHooks === 0 && this.stats.failedTests === 0 && this.stats.passedTests > 0) {
this.lineWriter.writeLine(colors.pass(`${this.stats.passedTests} ${plur('test', this.stats.passedTests)} passed`) + firstLinePostfix);
firstLinePostfix = '';
}
if (this.stats.passedKnownFailingTests > 0) {
this.lineWriter.writeLine(colors.error(`${this.stats.passedKnownFailingTests} ${plur('known failure', this.stats.passedKnownFailingTests)}`));
}
if (this.stats.skippedTests > 0) {
this.lineWriter.writeLine(colors.skip(`${this.stats.skippedTests} ${plur('test', this.stats.skippedTests)} skipped`));
}
if (this.stats.todoTests > 0) {
this.lineWriter.writeLine(colors.todo(`${this.stats.todoTests} ${plur('test', this.stats.todoTests)} todo`));
}
if (this.stats.unhandledRejections > 0) {
this.lineWriter.writeLine(colors.error(`${this.stats.unhandledRejections} unhandled ${plur('rejection', this.stats.unhandledRejections)}`));
}
if (this.stats.uncaughtExceptions > 0) {
this.lineWriter.writeLine(colors.error(`${this.stats.uncaughtExceptions} uncaught ${plur('exception', this.stats.uncaughtExceptions)}`));
}
if (this.previousFailures > 0) {
this.lineWriter.writeLine(colors.error(`${this.previousFailures} previous ${plur('failure', this.previousFailures)} in test files that were not rerun`));
}
if (this.stats.passedKnownFailingTests > 0) {
this.lineWriter.writeLine();
for (const evt of this.knownFailures) {
this.lineWriter.writeLine(colors.error(this.prefixTitle(evt.testFile, evt.title)));
}
}
const shouldWriteFailFastDisclaimer = this.failFastEnabled && (this.stats.remainingTests > 0 || this.stats.files > this.stats.finishedWorkers);
if (this.failures.length > 0) {
const writeTrailingLines = shouldWriteFailFastDisclaimer || this.internalErrors.length > 0 || this.uncaughtExceptions.length > 0 || this.unhandledRejections.length > 0;
this.lineWriter.writeLine();
const last = this.failures[this.failures.length - 1];
for (const evt of this.failures) {
this.writeFailure(evt);
if (evt !== last || writeTrailingLines) {
this.lineWriter.writeLine();
this.lineWriter.writeLine();
this.lineWriter.writeLine();
}
}
}
if (this.internalErrors.length > 0) {
const writeLeadingLine = this.failures.length === 0;
const writeTrailingLines = shouldWriteFailFastDisclaimer || this.uncaughtExceptions.length > 0 || this.unhandledRejections.length > 0;
if (writeLeadingLine) {
this.lineWriter.writeLine();
}
const last = this.internalErrors[this.internalErrors.length - 1];
for (const evt of this.internalErrors) {
if (evt.testFile) {
this.lineWriter.writeLine(colors.error(`${figures.cross} Internal error when running ${this.relativeFile(evt.testFile)}`));
} else {
this.lineWriter.writeLine(colors.error(`${figures.cross} Internal error`));
}
this.lineWriter.writeLine(colors.stack(evt.err.summary));
this.lineWriter.writeLine(colors.errorStack(evt.err.stack));
if (evt !== last || writeTrailingLines) {
this.lineWriter.writeLine();
this.lineWriter.writeLine();
this.lineWriter.writeLine();
}
}
}
if (this.uncaughtExceptions.length > 0) {
const writeLeadingLine = this.failures.length === 0 && this.internalErrors.length === 0;
const writeTrailingLines = shouldWriteFailFastDisclaimer || this.unhandledRejections.length > 0;
if (writeLeadingLine) {
this.lineWriter.writeLine();
}
const last = this.uncaughtExceptions[this.uncaughtExceptions.length - 1];
for (const evt of this.uncaughtExceptions) {
this.lineWriter.writeLine(colors.title(`Uncaught exception in ${this.relativeFile(evt.testFile)}`));
this.lineWriter.writeLine();
this.writeErr(evt);
if (evt !== last || writeTrailingLines) {
this.lineWriter.writeLine();
this.lineWriter.writeLine();
this.lineWriter.writeLine();
}
}
}
if (this.unhandledRejections.length > 0) {
const writeLeadingLine = this.failures.length === 0 && this.internalErrors.length === 0 && this.uncaughtExceptions.length === 0;
const writeTrailingLines = shouldWriteFailFastDisclaimer;
if (writeLeadingLine) {
this.lineWriter.writeLine();
}
const last = this.unhandledRejections[this.unhandledRejections.length - 1];
for (const evt of this.unhandledRejections) {
this.lineWriter.writeLine(colors.title(`Unhandled rejection in ${this.relativeFile(evt.testFile)}`));
this.lineWriter.writeLine();
this.writeErr(evt);
if (evt !== last || writeTrailingLines) {
this.lineWriter.writeLine();
this.lineWriter.writeLine();
this.lineWriter.writeLine();
}
}
}
if (shouldWriteFailFastDisclaimer) {
let remaining = '';
if (this.stats.remainingTests > 0) {
remaining += `At least ${this.stats.remainingTests} ${plur('test was', 'tests were', this.stats.remainingTests)} skipped`;
if (this.stats.files > this.stats.finishedWorkers) {
remaining += ', as well as ';
}
}
if (this.stats.files > this.stats.finishedWorkers) {
const skippedFileCount = this.stats.files - this.stats.finishedWorkers;
remaining += `${skippedFileCount} ${plur('test file', 'test files', skippedFileCount)}`;
if (this.stats.remainingTests === 0) {
remaining += ` ${plur('was', 'were', skippedFileCount)} skipped`;
}
}
this.lineWriter.writeLine(colors.information(`\`--fail-fast\` is on. ${remaining}.`));
}
this.lineWriter.writeLine();
}
}
module.exports = MiniReporter;

View file

@ -30,7 +30,7 @@ function dumpError(error) {
}
if (error.values.length > 0) {
object.values = error.values.reduce((acc, value) => {
object.values = error.values.reduce((acc, value) => { // eslint-disable-line unicorn/no-reduce
acc[value.label] = stripAnsi(value.formatted);
return acc;
}, {});
@ -125,12 +125,22 @@ class TapReporter {
this.reportStream.write(`# ${stripAnsi(title)}${os.EOL}`);
if (evt.logs) {
for (const log of evt.logs) {
const logLines = indentString(log, 4).replace(/^ {4}/, ' # ');
const logLines = indentString(log, 4).replace(/^ {4}/gm, '# ');
this.reportStream.write(`${logLines}${os.EOL}`);
}
}
}
writeTimeout(evt) {
const err = new Error(`Exited because no new tests completed within the last ${evt.period}ms of inactivity`);
for (const [testFile, tests] of evt.pendingTests) {
for (const title of tests) {
this.writeTest({testFile, title, err}, {passed: false, todo: false, skip: false});
}
}
}
consumeStateChange(evt) { // eslint-disable-line complexity
const fileStats = this.stats && evt.testFile ? this.stats.byFile.get(evt.testFile) : null;
@ -158,6 +168,9 @@ class TapReporter {
this.writeTest(evt, {passed: false, todo: true, skip: false});
}
break;
case 'snapshot-error':
this.writeComment(evt, {title: 'Could not update snapshots'});
break;
case 'stats':
this.stats = evt.stats;
@ -169,7 +182,7 @@ class TapReporter {
this.writeTest(evt, {passed: true, todo: false, skip: false});
break;
case 'timeout':
this.writeCrash(evt, `Exited because no new tests completed within the last ${evt.period}ms of inactivity`);
this.writeTimeout(evt);
break;
case 'uncaught-exception':
this.writeCrash(evt);

View file

@ -1,463 +0,0 @@
'use strict';
const os = require('os');
const path = require('path');
const stream = require('stream');
const figures = require('figures');
const indentString = require('indent-string');
const plur = require('plur');
const prettyMs = require('pretty-ms');
const trimOffNewlines = require('trim-off-newlines');
const beautifyStack = require('./beautify-stack');
const chalk = require('../chalk').get();
const codeExcerpt = require('../code-excerpt');
const colors = require('./colors');
const formatSerializedError = require('./format-serialized-error');
const improperUsageMessages = require('./improper-usage-messages');
const prefixTitle = require('./prefix-title');
const whileCorked = require('./while-corked');
const nodeInternals = require('stack-utils').nodeInternals();
class LineWriter extends stream.Writable {
constructor(dest) {
super();
this.dest = dest;
this.columns = dest.columns || 80;
this.lastLineIsEmpty = false;
}
_write(chunk, encoding, callback) {
this.dest.write(chunk);
callback();
}
writeLine(string) {
if (string) {
this.write(indentString(string, 2) + os.EOL);
this.lastLineIsEmpty = false;
} else {
this.write(os.EOL);
this.lastLineIsEmpty = true;
}
}
ensureEmptyLine() {
if (!this.lastLineIsEmpty) {
this.writeLine();
}
}
}
class VerboseReporter {
constructor(options) {
this.durationThreshold = options.durationThreshold || 100;
this.reportStream = options.reportStream;
this.stdStream = options.stdStream;
this.watching = options.watching;
this.lineWriter = new LineWriter(this.reportStream);
this.consumeStateChange = whileCorked(this.reportStream, this.consumeStateChange);
this.endRun = whileCorked(this.reportStream, this.endRun);
this.relativeFile = file => path.relative(options.projectDir, file);
this.reset();
}
reset() {
if (this.removePreviousListener) {
this.removePreviousListener();
}
this.failFastEnabled = false;
this.failures = [];
this.filesWithMissingAvaImports = new Set();
this.knownFailures = [];
this.runningTestFiles = new Map();
this.lastLineIsEmpty = false;
this.matching = false;
this.prefixTitle = (testFile, title) => title;
this.previousFailures = 0;
this.removePreviousListener = null;
this.stats = null;
}
startRun(plan) {
if (plan.bailWithoutReporting) {
return;
}
this.reset();
this.failFastEnabled = plan.failFastEnabled;
this.matching = plan.matching;
this.previousFailures = plan.previousFailures;
this.emptyParallelRun = plan.status.emptyParallelRun;
if (this.watching || plan.files.length > 1) {
this.prefixTitle = (testFile, title) => prefixTitle(plan.filePathPrefix, testFile, title);
}
this.removePreviousListener = plan.status.on('stateChange', evt => this.consumeStateChange(evt));
if (this.watching && plan.runVector > 1) {
this.lineWriter.write(chalk.gray.dim('\u2500'.repeat(this.reportStream.columns || 80)) + os.EOL);
}
this.lineWriter.writeLine();
}
consumeStateChange(evt) { // eslint-disable-line complexity
const fileStats = this.stats && evt.testFile ? this.stats.byFile.get(evt.testFile) : null;
switch (evt.type) {
case 'hook-failed':
this.failures.push(evt);
this.writeTestSummary(evt);
break;
case 'internal-error':
if (evt.testFile) {
this.lineWriter.writeLine(colors.error(`${figures.cross} Internal error when running ${this.relativeFile(evt.testFile)}`));
} else {
this.lineWriter.writeLine(colors.error(`${figures.cross} Internal error`));
}
this.lineWriter.writeLine(colors.stack(evt.err.summary));
this.lineWriter.writeLine(colors.errorStack(evt.err.stack));
this.lineWriter.writeLine();
this.lineWriter.writeLine();
break;
case 'line-number-selection-error':
this.lineWriter.writeLine(colors.information(`${figures.warning} Could not parse ${this.relativeFile(evt.testFile)} for line number selection`));
break;
case 'missing-ava-import':
this.filesWithMissingAvaImports.add(evt.testFile);
this.lineWriter.writeLine(colors.error(`${figures.cross} No tests found in ${this.relativeFile(evt.testFile)}, make sure to import "ava" at the top of your test file`));
break;
case 'hook-finished':
if (evt.logs.length > 0) {
this.lineWriter.writeLine(` ${this.prefixTitle(evt.testFile, evt.title)}`);
this.writeLogs(evt);
}
break;
case 'selected-test':
if (evt.skip) {
this.lineWriter.writeLine(colors.skip(`- ${this.prefixTitle(evt.testFile, evt.title)}`));
} else if (evt.todo) {
this.lineWriter.writeLine(colors.todo(`- ${this.prefixTitle(evt.testFile, evt.title)}`));
}
break;
case 'stats':
this.stats = evt.stats;
break;
case 'test-failed':
this.failures.push(evt);
this.writeTestSummary(evt);
break;
case 'test-passed':
if (evt.knownFailing) {
this.knownFailures.push(evt);
}
this.writeTestSummary(evt);
break;
case 'timeout':
this.lineWriter.writeLine(colors.error(`\n${figures.cross} Timed out while running tests`));
this.lineWriter.writeLine('');
this.writePendingTests(evt);
break;
case 'interrupt':
this.lineWriter.writeLine(colors.error(`\n${figures.cross} Exiting due to SIGINT`));
this.lineWriter.writeLine('');
this.writePendingTests(evt);
break;
case 'uncaught-exception':
this.lineWriter.ensureEmptyLine();
this.lineWriter.writeLine(colors.title(`Uncaught exception in ${this.relativeFile(evt.testFile)}`));
this.lineWriter.writeLine();
this.writeErr(evt);
this.lineWriter.writeLine();
break;
case 'unhandled-rejection':
this.lineWriter.ensureEmptyLine();
this.lineWriter.writeLine(colors.title(`Unhandled rejection in ${this.relativeFile(evt.testFile)}`));
this.lineWriter.writeLine();
this.writeErr(evt);
this.lineWriter.writeLine();
break;
case 'worker-failed':
if (!this.filesWithMissingAvaImports.has(evt.testFile)) {
if (evt.nonZeroExitCode) {
this.lineWriter.writeLine(colors.error(`${figures.cross} ${this.relativeFile(evt.testFile)} exited with a non-zero exit code: ${evt.nonZeroExitCode}`));
} else {
this.lineWriter.writeLine(colors.error(`${figures.cross} ${this.relativeFile(evt.testFile)} exited due to ${evt.signal}`));
}
}
break;
case 'worker-finished':
if (!evt.forcedExit && !this.filesWithMissingAvaImports.has(evt.testFile)) {
if (fileStats.declaredTests === 0) {
this.lineWriter.writeLine(colors.error(`${figures.cross} No tests found in ${this.relativeFile(evt.testFile)}`));
} else if (fileStats.selectingLines && fileStats.selectedTests === 0) {
this.lineWriter.writeLine(colors.error(`${figures.cross} Line numbers for ${this.relativeFile(evt.testFile)} did not match any tests`));
} else if (!this.failFastEnabled && fileStats.remainingTests > 0) {
this.lineWriter.writeLine(colors.error(`${figures.cross} ${fileStats.remainingTests} ${plur('test', fileStats.remainingTests)} remaining in ${this.relativeFile(evt.testFile)}`));
}
}
break;
case 'worker-stderr':
case 'worker-stdout':
this.stdStream.write(evt.chunk);
// If the chunk does not end with a linebreak, *forcibly* write one to
// ensure it remains visible in the TTY.
// Tests cannot assume their standard output is not interrupted. Indeed
// we multiplex stdout and stderr into a single stream. However as
// long as stdStream is different from reportStream users can read
// their original output by redirecting the streams.
if (evt.chunk[evt.chunk.length - 1] !== 0x0A) {
this.reportStream.write(os.EOL);
}
break;
default:
break;
}
}
writeErr(evt) {
if (evt.err.name === 'TSError' && evt.err.object && evt.err.object.diagnosticText) {
this.lineWriter.writeLine(colors.errorStack(trimOffNewlines(evt.err.object.diagnosticText)));
return;
}
if (evt.err.source) {
this.lineWriter.writeLine(colors.errorSource(`${this.relativeFile(evt.err.source.file)}:${evt.err.source.line}`));
const excerpt = codeExcerpt(evt.err.source, {maxWidth: this.reportStream.columns - 2});
if (excerpt) {
this.lineWriter.writeLine();
this.lineWriter.writeLine(excerpt);
}
}
if (evt.err.avaAssertionError) {
const result = formatSerializedError(evt.err);
if (result.printMessage) {
this.lineWriter.writeLine();
this.lineWriter.writeLine(evt.err.message);
}
if (result.formatted) {
this.lineWriter.writeLine();
this.lineWriter.writeLine(result.formatted);
}
const message = improperUsageMessages.forError(evt.err);
if (message) {
this.lineWriter.writeLine();
this.lineWriter.writeLine(message);
}
} else if (evt.err.nonErrorObject) {
this.lineWriter.writeLine(trimOffNewlines(evt.err.formatted));
} else {
this.lineWriter.writeLine();
this.lineWriter.writeLine(evt.err.summary);
}
const formatted = this.formatErrorStack(evt.err);
if (formatted.length > 0) {
this.lineWriter.writeLine();
this.lineWriter.writeLine(formatted.join('\n'));
}
}
formatErrorStack(error) {
if (!error.stack) {
return [];
}
if (error.shouldBeautifyStack) {
return beautifyStack(error.stack).map(line => {
if (nodeInternals.some(internal => internal.test(line))) {
return colors.errorStackInternal(`${figures.pointerSmall} ${line}`);
}
return colors.errorStack(`${figures.pointerSmall} ${line}`);
});
}
return [error.stack];
}
writePendingTests(evt) {
for (const [file, testsInFile] of evt.pendingTests) {
if (testsInFile.size === 0) {
continue;
}
this.lineWriter.writeLine(`${testsInFile.size} tests were pending in ${this.relativeFile(file)}\n`);
for (const title of testsInFile) {
this.lineWriter.writeLine(`${figures.circleDotted} ${this.prefixTitle(file, title)}`);
}
this.lineWriter.writeLine('');
}
}
writeLogs(evt) {
if (evt.logs) {
for (const log of evt.logs) {
const logLines = indentString(colors.log(log), 4);
const logLinesWithLeadingFigure = logLines.replace(
/^ {4}/,
` ${colors.information(figures.info)} `
);
this.lineWriter.writeLine(logLinesWithLeadingFigure);
}
}
}
writeTestSummary(evt) {
if (evt.type === 'hook-failed' || evt.type === 'test-failed') {
this.lineWriter.writeLine(`${colors.error(figures.cross)} ${this.prefixTitle(evt.testFile, evt.title)} ${colors.error(evt.err.message)}`);
} else if (evt.knownFailing) {
this.lineWriter.writeLine(`${colors.error(figures.tick)} ${colors.error(this.prefixTitle(evt.testFile, evt.title))}`);
} else {
const duration = evt.duration > this.durationThreshold ? colors.duration(' (' + prettyMs(evt.duration) + ')') : '';
this.lineWriter.writeLine(`${colors.pass(figures.tick)} ${this.prefixTitle(evt.testFile, evt.title)}${duration}`);
}
this.writeLogs(evt);
}
writeFailure(evt) {
this.lineWriter.writeLine(`${colors.title(this.prefixTitle(evt.testFile, evt.title))}`);
this.writeLogs(evt);
this.lineWriter.writeLine();
this.writeErr(evt);
}
endRun() { // eslint-disable-line complexity
if (this.emptyParallelRun) {
this.lineWriter.writeLine('No files tested in this parallel run');
this.lineWriter.writeLine();
return;
}
if (!this.stats) {
this.lineWriter.writeLine(colors.error(`${figures.cross} Couldnt find any files to test`));
this.lineWriter.writeLine();
return;
}
if (this.matching && this.stats.selectedTests === 0) {
this.lineWriter.writeLine(colors.error(`${figures.cross} Couldnt find any matching tests`));
this.lineWriter.writeLine();
return;
}
this.lineWriter.writeLine();
if (this.stats.parallelRuns) {
const {currentFileCount, currentIndex, totalRuns} = this.stats.parallelRuns;
this.lineWriter.writeLine(colors.information(`Ran ${currentFileCount} test ${plur('file', currentFileCount)} out of ${this.stats.files} for job ${currentIndex + 1} of ${totalRuns}`));
this.lineWriter.writeLine();
}
let firstLinePostfix = this.watching ?
' ' + chalk.gray.dim('[' + new Date().toLocaleTimeString('en-US', {hour12: false}) + ']') :
'';
if (this.stats.failedHooks > 0) {
this.lineWriter.writeLine(colors.error(`${this.stats.failedHooks} ${plur('hook', this.stats.failedHooks)} failed`) + firstLinePostfix);
firstLinePostfix = '';
}
if (this.stats.failedTests > 0) {
this.lineWriter.writeLine(colors.error(`${this.stats.failedTests} ${plur('test', this.stats.failedTests)} failed`) + firstLinePostfix);
firstLinePostfix = '';
}
if (this.stats.failedHooks === 0 && this.stats.failedTests === 0 && this.stats.passedTests > 0) {
this.lineWriter.writeLine(colors.pass(`${this.stats.passedTests} ${plur('test', this.stats.passedTests)} passed`) + firstLinePostfix);
firstLinePostfix = '';
}
if (this.stats.passedKnownFailingTests > 0) {
this.lineWriter.writeLine(colors.error(`${this.stats.passedKnownFailingTests} ${plur('known failure', this.stats.passedKnownFailingTests)}`));
}
if (this.stats.skippedTests > 0) {
this.lineWriter.writeLine(colors.skip(`${this.stats.skippedTests} ${plur('test', this.stats.skippedTests)} skipped`));
}
if (this.stats.todoTests > 0) {
this.lineWriter.writeLine(colors.todo(`${this.stats.todoTests} ${plur('test', this.stats.todoTests)} todo`));
}
if (this.stats.unhandledRejections > 0) {
this.lineWriter.writeLine(colors.error(`${this.stats.unhandledRejections} unhandled ${plur('rejection', this.stats.unhandledRejections)}`));
}
if (this.stats.uncaughtExceptions > 0) {
this.lineWriter.writeLine(colors.error(`${this.stats.uncaughtExceptions} uncaught ${plur('exception', this.stats.uncaughtExceptions)}`));
}
if (this.previousFailures > 0) {
this.lineWriter.writeLine(colors.error(`${this.previousFailures} previous ${plur('failure', this.previousFailures)} in test files that were not rerun`));
}
if (this.stats.passedKnownFailingTests > 0) {
this.lineWriter.writeLine();
for (const evt of this.knownFailures) {
this.lineWriter.writeLine(colors.error(this.prefixTitle(evt.testFile, evt.title)));
}
}
const shouldWriteFailFastDisclaimer = this.failFastEnabled && (this.stats.remainingTests > 0 || this.stats.files > this.stats.finishedWorkers);
if (this.failures.length > 0) {
this.lineWriter.writeLine();
const lastFailure = this.failures[this.failures.length - 1];
for (const evt of this.failures) {
this.writeFailure(evt);
if (evt !== lastFailure || shouldWriteFailFastDisclaimer) {
this.lineWriter.writeLine();
this.lineWriter.writeLine();
this.lineWriter.writeLine();
}
}
}
if (shouldWriteFailFastDisclaimer) {
let remaining = '';
if (this.stats.remainingTests > 0) {
remaining += `At least ${this.stats.remainingTests} ${plur('test was', 'tests were', this.stats.remainingTests)} skipped`;
if (this.stats.files > this.stats.finishedWorkers) {
remaining += ', as well as ';
}
}
if (this.stats.files > this.stats.finishedWorkers) {
const skippedFileCount = this.stats.files - this.stats.finishedWorkers;
remaining += `${skippedFileCount} ${plur('test file', 'test files', skippedFileCount)}`;
if (this.stats.remainingTests === 0) {
remaining += ` ${plur('was', 'were', skippedFileCount)} skipped`;
}
}
this.lineWriter.writeLine(colors.information(`\`--fail-fast\` is on. ${remaining}.`));
}
this.lineWriter.writeLine();
}
}
module.exports = VerboseReporter;

View file

@ -1,13 +0,0 @@
'use strict';
function whileCorked(stream, fn) {
return function (...args) {
stream.cork();
try {
fn.apply(this, args);
} finally {
stream.uncork();
}
};
}
module.exports = whileCorked;

5
node_modules/ava/lib/run-status.js generated vendored
View file

@ -27,6 +27,7 @@ class RunStatus extends Emittery {
passedKnownFailingTests: 0,
passedTests: 0,
selectedTests: 0,
sharedWorkerErrors: 0,
skippedTests: 0,
timeouts: 0,
todoTests: 0,
@ -93,6 +94,9 @@ class RunStatus extends Emittery {
this.addPendingTest(event);
}
break;
case 'shared-worker-error':
stats.sharedWorkerErrors++;
break;
case 'test-failed':
stats.failedTests++;
@ -164,6 +168,7 @@ class RunStatus extends Emittery {
this.stats.failedHooks > 0 ||
this.stats.failedTests > 0 ||
this.stats.failedWorkers > 0 ||
this.stats.sharedWorkerErrors > 0 ||
this.stats.timeouts > 0 ||
this.stats.uncaughtExceptions > 0 ||
this.stats.unhandledRejections > 0

79
node_modules/ava/lib/runner.js generated vendored
View file

@ -23,13 +23,17 @@ class Runner extends Emittery {
this.recordNewSnapshots = options.recordNewSnapshots === true;
this.runOnlyExclusive = options.runOnlyExclusive === true;
this.serial = options.serial === true;
this.skippingTests = false;
this.snapshotDir = options.snapshotDir;
this.updateSnapshots = options.updateSnapshots;
this.activeRunnables = new Set();
this.boundCompareTestSnapshot = this.compareTestSnapshot.bind(this);
this.skippedSnapshots = false;
this.boundSkipSnapshot = this.skipSnapshot.bind(this);
this.interrupted = false;
this.snapshots = null;
this.nextTaskIndex = 0;
this.tasks = {
after: [],
afterAlways: [],
@ -41,6 +45,7 @@ class Runner extends Emittery {
serial: [],
todo: []
};
this.waitForReady = [];
const uniqueTestTitles = new Set();
this.registerUniqueTitle = title => {
@ -74,6 +79,8 @@ class Runner extends Emittery {
});
}
metadata.taskIndex = this.nextTaskIndex++;
const {args, buildTitle, implementations, rawTitle} = parseTestArgs(testArgs);
if (this.checkSelectedByLineNumbers) {
@ -147,6 +154,10 @@ class Runner extends Emittery {
task.metadata.exclusive = matcher([title], this.match).length === 1;
}
if (task.metadata.skipped) {
this.skippingTests = true;
}
if (task.metadata.exclusive) {
this.runOnlyExclusive = true;
}
@ -182,7 +193,7 @@ class Runner extends Emittery {
fixedLocation: this.snapshotDir,
projectDir: this.projectDir,
recordNewSnapshots: this.recordNewSnapshots,
updating: this.updateSnapshots
updating: this.updateSnapshots && !this.runOnlyExclusive && !this.skippingTests
});
this.emit('dependency', this.snapshots.snapPath);
}
@ -190,18 +201,35 @@ class Runner extends Emittery {
return this.snapshots.compare(options);
}
skipSnapshot() {
this.skippedSnapshots = true;
}
saveSnapshotState() {
if (
this.updateSnapshots &&
(
this.runOnlyExclusive ||
this.skippingTests ||
this.skippedSnapshots
)
) {
return {cannotSave: true};
}
if (this.snapshots) {
return this.snapshots.save();
return {touchedFiles: this.snapshots.save()};
}
if (this.updateSnapshots) {
// TODO: There may be unused snapshot files if no test caused the
// snapshots to be loaded. Prune them. But not if tests (including hooks!)
// were skipped. Perhaps emit a warning if this occurs?
return {touchedFiles: snapshotManager.cleanSnapshots({
file: this.file,
fixedLocation: this.snapshotDir,
projectDir: this.projectDir
})};
}
return null;
return {};
}
onRun(runnable) {
@ -241,7 +269,7 @@ class Runner extends Emittery {
};
let waitForSerial = Promise.resolve();
await runnables.reduce((previous, runnable) => {
await runnables.reduce((previous, runnable) => { // eslint-disable-line unicorn/no-reduce
if (runnable.metadata.serial || this.serial) {
waitForSerial = previous.then(() => {
// Serial runnables run as long as there was no previous failure, unless
@ -275,7 +303,7 @@ class Runner extends Emittery {
return result;
}
async runHooks(tasks, contextRef, titleSuffix, testPassed) {
async runHooks(tasks, contextRef, {titleSuffix, testPassed, associatedTaskIndex} = {}) {
const hooks = tasks.map(task => new Runnable({
contextRef,
experiments: this.experiments,
@ -284,8 +312,9 @@ class Runner extends Emittery {
task.implementation :
t => task.implementation.apply(null, [t].concat(task.args)),
compareTestSnapshot: this.boundCompareTestSnapshot,
skipSnapshot: this.boundSkipSnapshot,
updateSnapshots: this.updateSnapshots,
metadata: task.metadata,
metadata: {...task.metadata, associatedTaskIndex},
powerAssert: this.powerAssert,
title: `${task.title}${titleSuffix || ''}`,
isHook: true,
@ -316,7 +345,14 @@ class Runner extends Emittery {
async runTest(task, contextRef) {
const hookSuffix = ` for ${task.title}`;
let hooksOk = await this.runHooks(this.tasks.beforeEach, contextRef, hookSuffix);
let hooksOk = await this.runHooks(
this.tasks.beforeEach,
contextRef,
{
titleSuffix: hookSuffix,
associatedTaskIndex: task.metadata.taskIndex
}
);
let testOk = false;
if (hooksOk) {
@ -329,6 +365,7 @@ class Runner extends Emittery {
task.implementation :
t => task.implementation.apply(null, [t].concat(task.args)),
compareTestSnapshot: this.boundCompareTestSnapshot,
skipSnapshot: this.boundSkipSnapshot,
updateSnapshots: this.updateSnapshots,
metadata: task.metadata,
powerAssert: this.powerAssert,
@ -348,7 +385,14 @@ class Runner extends Emittery {
logs: result.logs
});
hooksOk = await this.runHooks(this.tasks.afterEach, contextRef, hookSuffix, testOk);
hooksOk = await this.runHooks(
this.tasks.afterEach,
contextRef,
{
titleSuffix: hookSuffix,
testPassed: testOk,
associatedTaskIndex: task.metadata.taskIndex
});
} else {
this.emit('stateChange', {
type: 'test-failed',
@ -362,7 +406,14 @@ class Runner extends Emittery {
}
}
const alwaysOk = await this.runHooks(this.tasks.afterEachAlways, contextRef, hookSuffix, testOk);
const alwaysOk = await this.runHooks(
this.tasks.afterEachAlways,
contextRef,
{
titleSuffix: hookSuffix,
testPassed: testOk,
associatedTaskIndex: task.metadata.taskIndex
});
return alwaysOk && hooksOk && testOk;
}
@ -435,6 +486,8 @@ class Runner extends Emittery {
});
}
await Promise.all(this.waitForReady);
if (concurrentTests.length === 0 && serialTests.length === 0) {
this.emit('finish');
// Don't run any hooks if there are no tests to run.
@ -451,7 +504,7 @@ class Runner extends Emittery {
return false;
}
return serialTests.reduce(async (previous, task) => {
return serialTests.reduce(async (previous, task) => { // eslint-disable-line unicorn/no-reduce
const previousOk = await previous;
// Don't start tests after an interrupt.
if (this.interrupted) {

View file

@ -104,13 +104,32 @@ function combineEntries(entries) {
const buffers = [];
let byteLength = 0;
const sortedKeys = [...entries.keys()].sort();
const sortedKeys = [...entries.keys()].sort((keyA, keyB) => {
const [a, b] = [entries.get(keyA), entries.get(keyB)];
const taskDifference = a.taskIndex - b.taskIndex;
if (taskDifference !== 0) {
return taskDifference;
}
const [assocA, assocB] = [a.associatedTaskIndex, b.associatedTaskIndex];
if (assocA !== undefined && assocB !== undefined) {
const assocDifference = assocA - assocB;
if (assocDifference !== 0) {
return assocDifference;
}
}
return a.snapIndex - b.snapIndex;
});
for (const key of sortedKeys) {
const keyBuffer = Buffer.from(`\n\n## ${key}\n\n`, 'utf8');
buffers.push(keyBuffer);
byteLength += keyBuffer.byteLength;
const formattedEntries = entries.get(key);
const formattedEntries = entries.get(key).buffers;
const last = formattedEntries[formattedEntries.length - 1];
for (const entry of formattedEntries) {
buffers.push(entry);
@ -176,10 +195,11 @@ function encodeSnapshots(buffersByHash) {
byteOffset += 2;
const entries = [];
for (const pair of buffersByHash) {
const hash = pair[0];
const snapshotBuffers = pair[1];
// Maps can't have duplicate keys, so all items in [...buffersByHash.keys()]
// are unique, so sortedHashes should be deterministic.
const sortedHashes = [...buffersByHash.keys()].sort();
const sortedBuffersByHash = [...sortedHashes.map(hash => [hash, buffersByHash.get(hash)])];
for (const [hash, snapshotBuffers] of sortedBuffersByHash) {
buffers.push(Buffer.from(hash, 'hex'));
byteOffset += MD5_HASH_LENGTH;
@ -332,6 +352,7 @@ class Manager {
const descriptor = concordance.describe(options.expected, concordanceOptions);
const snapshot = concordance.serialize(descriptor);
const entry = formatEntry(options.label, descriptor);
const {taskIndex, snapIndex, associatedTaskIndex} = options;
return () => { // Must be called in order!
this.hasChanges = true;
@ -353,9 +374,9 @@ class Manager {
snapshots.push(snapshot);
if (this.reportEntries.has(options.belongsTo)) {
this.reportEntries.get(options.belongsTo).push(entry);
this.reportEntries.get(options.belongsTo).buffers.push(entry);
} else {
this.reportEntries.set(options.belongsTo, [entry]);
this.reportEntries.set(options.belongsTo, {buffers: [entry], taskIndex, snapIndex, associatedTaskIndex});
}
};
}
@ -428,12 +449,49 @@ const determineSnapshotDir = mem(({file, fixedLocation, projectDir}) => {
exports.determineSnapshotDir = determineSnapshotDir;
function load({file, fixedLocation, projectDir, recordNewSnapshots, updating}) {
function determineSnapshotPaths({file, fixedLocation, projectDir}) {
const dir = determineSnapshotDir({file, fixedLocation, projectDir});
const relFile = path.relative(projectDir, resolveSourceFile(file));
const name = path.basename(relFile);
const reportFile = `${name}.md`;
const snapFile = `${name}.snap`;
return {
dir,
relFile,
snapFile,
reportFile
};
}
function cleanFile(file) {
try {
fs.unlinkSync(file);
return [file];
} catch (error) {
if (error.code === 'ENOENT') {
return [];
}
throw error;
}
}
// Remove snapshot and report if they exist. Returns an array containing the
// paths of the touched files.
function cleanSnapshots({file, fixedLocation, projectDir}) {
const {dir, snapFile, reportFile} = determineSnapshotPaths({file, fixedLocation, projectDir});
return [
...cleanFile(path.join(dir, snapFile)),
...cleanFile(path.join(dir, reportFile))
];
}
exports.cleanSnapshots = cleanSnapshots;
function load({file, fixedLocation, projectDir, recordNewSnapshots, updating}) {
const {dir, relFile, snapFile, reportFile} = determineSnapshotPaths({file, fixedLocation, projectDir});
const snapPath = path.join(dir, snapFile);
let appendOnly = !updating;

65
node_modules/ava/lib/test.js generated vendored
View file

@ -39,7 +39,9 @@ class ExecutionContext extends assert.Assertions {
compareWithSnapshot: options => {
return test.compareWithSnapshot(options);
},
powerAssert: test.powerAssert
powerAssert: test.powerAssert,
experiments: test.experiments,
disableSnapshots: test.isHook === true
});
testMap.set(this, test);
@ -64,8 +66,8 @@ class ExecutionContext extends assert.Assertions {
this.plan.skip = () => {};
this.timeout = ms => {
test.timeout(ms);
this.timeout = (ms, message) => {
test.timeout(ms, message);
};
this.teardown = callback => {
@ -73,6 +75,12 @@ class ExecutionContext extends assert.Assertions {
};
this.try = async (...attemptArgs) => {
if (test.isHook) {
const error = new Error('`t.try()` can only be used in tests');
test.saveFirstError(error);
throw error;
}
const {args, buildTitle, implementations, receivedImplementationArray} = parseTestArgs(attemptArgs);
if (implementations.length === 0) {
@ -179,7 +187,8 @@ class ExecutionContext extends assert.Assertions {
}
get passed() {
return testMap.get(this).testPassed;
const test = testMap.get(this);
return test.isHook ? test.testPassed : !test.assertError;
}
_throwsArgStart(assertion, file, line) {
@ -221,7 +230,17 @@ class Test {
const index = id ? 0 : this.nextSnapshotIndex++;
const label = id ? '' : message || `Snapshot ${index + 1}`; // Human-readable labels start counting at 1.
const {record, ...result} = options.compareTestSnapshot({belongsTo, deferRecording, expected, index, label});
const {taskIndex, associatedTaskIndex} = this.metadata;
const {record, ...result} = options.compareTestSnapshot({
belongsTo,
deferRecording,
expected,
index,
label,
taskIndex,
snapIndex: this.snapshotCount,
associatedTaskIndex
});
if (record) {
this.deferredSnapshotRecordings.push(record);
}
@ -230,6 +249,10 @@ class Test {
};
this.skipSnapshot = () => {
if (typeof options.skipSnapshot === 'function') {
options.skipSnapshot();
}
if (options.updateSnapshots) {
this.addFailedAssertion(new Error('Snapshot assertions cannot be skipped when updating snapshots'));
} else {
@ -289,11 +312,8 @@ class Test {
};
}
if (this.metadata.inline) {
throw new Error('`t.end()` is not supported inside `t.try()`');
} else {
throw new Error('`t.end()` is not supported in this context. To use `t.end()` as a callback, you must use "callback mode" via `test.cb(testName, fn)`');
}
const error_ = this.metadata.inline ? new Error('`t.end()` is not supported inside `t.try()`') : new Error('`t.end()` is not supported in this context. To use `t.end()` as a callback, you must use "callback mode" via `test.cb(testName, fn)`');
throw error_;
}
endCallback(error, savedError) {
@ -430,7 +450,14 @@ class Test {
this.planError = planError;
}
timeout(ms) {
timeout(ms, message) {
const result = assert.checkAssertionMessage('timeout', message);
if (result !== true) {
this.saveFirstError(result);
// Allow the timeout to be set even when the message is invalid.
message = '';
}
if (this.finishing) {
return;
}
@ -438,7 +465,7 @@ class Test {
this.clearTimeout();
this.timeoutMs = ms;
this.timeoutTimer = nowAndTimers.setTimeout(() => {
this.saveFirstError(new Error('Test timeout exceeded'));
this.saveFirstError(new Error(message || 'Test timeout exceeded'));
if (this.finishDueToTimeout) {
this.finishDueToTimeout();
@ -482,7 +509,13 @@ class Test {
}
async runTeardowns() {
for (const teardown of this.teardowns) {
const teardowns = [...this.teardowns];
if (this.experiments.reverseTeardowns) {
teardowns.reverse();
}
for (const teardown of teardowns) {
try {
await teardown(); // eslint-disable-line no-await-in-loop
} catch (error) {
@ -714,11 +747,7 @@ class Test {
if (this.metadata.failing) {
passed = !passed;
if (passed) {
error = null;
} else {
error = new Error('Test was expected to fail, but succeeded, you should stop marking the test as failing');
}
error = passed ? null : new Error('Test was expected to fail, but succeeded, you should stop marking the test as failing');
}
return {

203
node_modules/ava/lib/worker/ipc.js generated vendored
View file

@ -1,50 +1,42 @@
'use strict';
const Emittery = require('emittery');
const events = require('events');
const pEvent = require('p-event');
const {controlFlow} = require('../ipc-flow-control');
const {get: getOptions} = require('./options');
const emitter = new Emittery();
process.on('message', message => {
if (!message.ava) {
return;
}
const selectAvaMessage = type => message => message.ava && message.ava.type === type;
switch (message.ava.type) {
case 'options':
emitter.emit('options', message.ava.options);
break;
case 'peer-failed':
emitter.emit('peerFailed');
break;
case 'pong':
emitter.emit('pong');
break;
default:
break;
}
});
exports.options = emitter.once('options');
exports.peerFailed = emitter.once('peerFailed');
exports.options = pEvent(process, 'message', selectAvaMessage('options')).then(message => message.ava.options);
exports.peerFailed = pEvent(process, 'message', selectAvaMessage('peer-failed'));
const bufferedSend = controlFlow(process);
function send(evt) {
if (process.connected) {
process.send({ava: evt});
}
bufferedSend({ava: evt});
}
exports.send = send;
let refs = 1;
function ref() {
if (++refs === 1) {
process.channel.ref();
}
}
function unref() {
process.channel.unref();
if (refs > 0 && --refs === 0) {
process.channel.unref();
}
}
exports.unref = unref;
let pendingPings = Promise.resolve();
async function flush() {
process.channel.ref();
ref();
const promise = pendingPings.then(async () => { // eslint-disable-line promise/prefer-await-to-then
send({type: 'ping'});
await emitter.once('pong');
await pEvent(process, 'message', selectAvaMessage('pong'));
if (promise === pendingPings) {
unref();
}
@ -54,3 +46,156 @@ async function flush() {
}
exports.flush = flush;
let channelCounter = 0;
let messageCounter = 0;
const channelEmitters = new Map();
function createChannelEmitter(channelId) {
if (channelEmitters.size === 0) {
process.on('message', message => {
if (!message.ava) {
return;
}
const {channelId, type, ...payload} = message.ava;
if (
type === 'shared-worker-error' ||
type === 'shared-worker-message' ||
type === 'shared-worker-ready'
) {
const emitter = channelEmitters.get(channelId);
if (emitter !== undefined) {
emitter.emit(type, payload);
}
}
});
}
const emitter = new events.EventEmitter();
channelEmitters.set(channelId, emitter);
return [emitter, () => channelEmitters.delete(channelId)];
}
function registerSharedWorker(filename, initialData) {
const channelId = `${getOptions().forkId}/channel/${++channelCounter}`;
const [channelEmitter, unsubscribe] = createChannelEmitter(channelId);
let forcedUnref = false;
let refs = 0;
const forceUnref = () => {
if (forcedUnref) {
return;
}
forcedUnref = true;
if (refs > 0) {
unref();
}
};
const refChannel = () => {
if (!forcedUnref && ++refs === 1) {
ref();
}
};
const unrefChannel = () => {
if (!forcedUnref && refs > 0 && --refs === 0) {
unref();
}
};
send({
type: 'shared-worker-connect',
channelId,
filename,
initialData
});
let currentlyAvailable = false;
let error = null;
refChannel();
const ready = pEvent(channelEmitter, 'shared-worker-ready').then(() => { // eslint-disable-line promise/prefer-await-to-then
currentlyAvailable = error === null;
}).finally(unrefChannel);
const messageEmitters = new Set();
const handleMessage = message => {
// Wait for a turn of the event loop, to allow new subscriptions to be set
// up in response to the previous message.
setImmediate(() => {
for (const emitter of messageEmitters) {
emitter.emit('message', message);
}
});
};
channelEmitter.on('shared-worker-message', handleMessage);
pEvent(channelEmitter, 'shared-worker-error').then(() => { // eslint-disable-line promise/prefer-await-to-then
unsubscribe();
forceUnref();
error = new Error('The shared worker is no longer available');
currentlyAvailable = false;
for (const emitter of messageEmitters) {
emitter.emit('error', error);
}
});
return {
forceUnref,
ready,
channel: {
available: ready,
get currentlyAvailable() {
return currentlyAvailable;
},
async * receive() {
if (error !== null) {
throw error;
}
const emitter = new events.EventEmitter();
messageEmitters.add(emitter);
try {
refChannel();
for await (const [message] of events.on(emitter, 'message')) {
yield message;
}
} finally {
unrefChannel();
messageEmitters.delete(emitter);
}
},
post(serializedData, replyTo) {
if (error !== null) {
throw error;
}
if (!currentlyAvailable) {
throw new Error('Shared worker is not yet available');
}
const messageId = `${channelId}/message/${++messageCounter}`;
send({
type: 'shared-worker-message',
channelId,
messageId,
replyTo,
serializedData
});
return messageId;
}
}
};
}
exports.registerSharedWorker = registerSharedWorker;

121
node_modules/ava/lib/worker/plugin.js generated vendored Normal file
View file

@ -0,0 +1,121 @@
const v8 = require('v8');
const pkg = require('../../package.json');
const subprocess = require('./subprocess');
const options = require('./options');
const workers = new Map();
const workerTeardownFns = new WeakMap();
function createSharedWorker(filename, initialData, teardown) {
const channel = subprocess.registerSharedWorker(filename, initialData, teardown);
class ReceivedMessage {
constructor(id, serializedData) {
this.id = id;
this.data = v8.deserialize(new Uint8Array(serializedData));
}
reply(data) {
return publishMessage(data, this.id);
}
}
// Ensure that, no matter how often it's received, we have a stable message
// object.
const messageCache = new WeakMap();
async function * receiveMessages(replyTo) {
for await (const evt of channel.receive()) {
if (replyTo === undefined && evt.replyTo !== undefined) {
continue;
}
if (replyTo !== undefined && evt.replyTo !== replyTo) {
continue;
}
let message = messageCache.get(evt);
if (message === undefined) {
message = new ReceivedMessage(evt.messageId, evt.serializedData);
messageCache.set(evt, message);
}
yield message;
}
}
function publishMessage(data, replyTo) {
const id = channel.post([...v8.serialize(data)], replyTo);
return {
id,
async * replies() {
yield * receiveMessages(id);
}
};
}
return {
available: channel.available,
protocol: 'experimental',
get currentlyAvailable() {
return channel.currentlyAvailable;
},
publish(data) {
return publishMessage(data);
},
async * subscribe() {
yield * receiveMessages();
}
};
}
const supportsSharedWorkers = process.versions.node >= '12.17.0';
function registerSharedWorker({
filename,
initialData,
supportedProtocols,
teardown
}) {
if (!options.get().experiments.sharedWorkers) {
throw new Error('Shared workers are experimental. Opt in to them in your AVA configuration');
}
if (!supportsSharedWorkers) {
throw new Error('Shared workers require Node.js 12.17 or newer');
}
if (!supportedProtocols.includes('experimental')) {
throw new Error(`This version of AVA (${pkg.version}) does not support any of the desired shared worker protocols: ${supportedProtocols.join()}`);
}
let worker = workers.get(filename);
if (worker === undefined) {
worker = createSharedWorker(filename, initialData, async () => {
// Run possibly asynchronous teardown functions serially, in reverse
// order. Any error will crash the worker.
const teardownFns = workerTeardownFns.get(worker);
if (teardownFns !== undefined) {
for await (const fn of [...teardownFns].reverse()) {
await fn();
}
}
});
workers.set(filename, worker);
}
if (teardown !== undefined) {
if (workerTeardownFns.has(worker)) {
workerTeardownFns.get(worker).push(teardown);
} else {
workerTeardownFns.set(worker, [teardown]);
}
}
return worker;
}
exports.registerSharedWorker = registerSharedWorker;

View file

@ -32,6 +32,8 @@ ipc.options.then(async options => {
const dependencyTracking = require('./dependency-tracker');
const lineNumberSelection = require('./line-numbers');
const sharedWorkerTeardowns = [];
async function exit(code) {
if (!process.exitCode) {
process.exitCode = code;
@ -89,10 +91,12 @@ ipc.options.then(async options => {
exit(1);
});
runner.on('finish', () => {
runner.on('finish', async () => {
try {
const touchedFiles = runner.saveSnapshotState();
if (touchedFiles) {
const {cannotSave, touchedFiles} = runner.saveSnapshotState();
if (cannotSave) {
ipc.send({type: 'snapshot-error'});
} else if (touchedFiles) {
ipc.send({type: 'touched-files', files: touchedFiles});
}
} catch (error) {
@ -101,6 +105,14 @@ ipc.options.then(async options => {
return;
}
try {
await Promise.all(sharedWorkerTeardowns.map(fn => fn()));
} catch (error) {
ipc.send({type: 'uncaught-exception', err: serializeError('Shared worker teardown error', false, error, runner.file)});
exit(1);
return;
}
nowAndTimers.setImmediate(() => {
currentlyUnhandled()
.filter(rejection => !attributedRejections.has(rejection.promise))
@ -127,6 +139,19 @@ ipc.options.then(async options => {
return runner;
};
exports.registerSharedWorker = (filename, initialData, teardown) => {
const {channel, forceUnref, ready} = ipc.registerSharedWorker(filename, initialData);
runner.waitForReady.push(ready);
sharedWorkerTeardowns.push(async () => {
try {
await teardown();
} finally {
forceUnref();
}
});
return channel;
};
// Store value to prevent required modules from modifying it.
const testPath = options.file;
@ -196,15 +221,21 @@ ipc.options.then(async options => {
if (Reflect.has(mod, Symbol.for('esm:package'))) {
requireFn = mod(module);
}
} catch (_) {}
} catch {}
}
// Install dependency tracker after the require configuration has been evaluated
// to make sure we also track dependencies with custom require hooks
dependencyTracking.install(testPath);
if (options.debug) {
require('inspector').open(options.debug.port, options.debug.host, true); // eslint-disable-line node/no-unsupported-features/node-builtins
if (options.debug && options.debug.port !== undefined && options.debug.host !== undefined) {
// If an inspector was active when the main process started, and is
// already active for the worker process, do not open a new one.
const inspector = require('inspector'); // eslint-disable-line node/no-unsupported-features/node-builtins
if (!options.debug.active || inspector.url() === undefined) {
inspector.open(options.debug.port, options.debug.host, true);
}
if (options.debug.break) {
debugger; // eslint-disable-line no-debugger
}