Fix dependabot issues
This commit is contained in:
parent
c89d9bd8b0
commit
531c6ba7c8
705 changed files with 53406 additions and 20466 deletions
29
node_modules/ava/lib/api.js
generated
vendored
29
node_modules/ava/lib/api.js
generated
vendored
|
|
@ -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
108
node_modules/ava/lib/assert.js
generated
vendored
|
|
@ -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
102
node_modules/ava/lib/cli.js
generated
vendored
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
2
node_modules/ava/lib/code-excerpt.js
generated
vendored
2
node_modules/ava/lib/code-excerpt.js
generated
vendored
|
|
@ -19,7 +19,7 @@ module.exports = (source, options = {}) => {
|
|||
let contents;
|
||||
try {
|
||||
contents = fs.readFileSync(file, 'utf8');
|
||||
} catch (_) {
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
3
node_modules/ava/lib/concordance-options.js
generated
vendored
3
node_modules/ava/lib/concordance-options.js
generated
vendored
|
|
@ -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
5
node_modules/ava/lib/extensions.js
generated
vendored
|
|
@ -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
102
node_modules/ava/lib/fork.js
generated
vendored
|
|
@ -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
6
node_modules/ava/lib/globs.js
generated
vendored
|
|
@ -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
39
node_modules/ava/lib/ipc-flow-control.js
generated
vendored
Normal 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
37
node_modules/ava/lib/like-selector.js
generated
vendored
Normal 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;
|
||||
4
node_modules/ava/lib/line-numbers.js
generated
vendored
4
node_modules/ava/lib/line-numbers.js
generated
vendored
|
|
@ -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
143
node_modules/ava/lib/load-config.js
generated
vendored
|
|
@ -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
75
node_modules/ava/lib/module-types.js
generated
vendored
Normal 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);
|
||||
};
|
||||
252
node_modules/ava/lib/plugin-support/shared-worker-loader.js
generated
vendored
Normal file
252
node_modules/ava/lib/plugin-support/shared-worker-loader.js
generated
vendored
Normal 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
140
node_modules/ava/lib/plugin-support/shared-workers.js
generated
vendored
Normal 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;
|
||||
2
node_modules/ava/lib/provider-manager.js
generated
vendored
2
node_modules/ava/lib/provider-manager.js
generated
vendored
|
|
@ -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
920
node_modules/ava/lib/reporters/default.js
generated
vendored
Normal 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} Couldn’t find any files to test` + firstLinePostfix));
|
||||
this.lineWriter.writeLine();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.matching && this.stats.selectedTests === 0) {
|
||||
this.lineWriter.writeLine(colors.error(`${figures.cross} Couldn’t 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;
|
||||
619
node_modules/ava/lib/reporters/mini.js
generated
vendored
619
node_modules/ava/lib/reporters/mini.js
generated
vendored
|
|
@ -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} Couldn’t find any files to test`));
|
||||
this.lineWriter.writeLine();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.matching && this.stats.selectedTests === 0) {
|
||||
this.lineWriter.writeLine(colors.error(`${figures.cross} Couldn’t 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;
|
||||
19
node_modules/ava/lib/reporters/tap.js
generated
vendored
19
node_modules/ava/lib/reporters/tap.js
generated
vendored
|
|
@ -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);
|
||||
|
|
|
|||
463
node_modules/ava/lib/reporters/verbose.js
generated
vendored
463
node_modules/ava/lib/reporters/verbose.js
generated
vendored
|
|
@ -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} Couldn’t find any files to test`));
|
||||
this.lineWriter.writeLine();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.matching && this.stats.selectedTests === 0) {
|
||||
this.lineWriter.writeLine(colors.error(`${figures.cross} Couldn’t 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;
|
||||
13
node_modules/ava/lib/reporters/while-corked.js
generated
vendored
13
node_modules/ava/lib/reporters/while-corked.js
generated
vendored
|
|
@ -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
5
node_modules/ava/lib/run-status.js
generated
vendored
|
|
@ -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
79
node_modules/ava/lib/runner.js
generated
vendored
|
|
@ -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) {
|
||||
|
|
|
|||
76
node_modules/ava/lib/snapshot-manager.js
generated
vendored
76
node_modules/ava/lib/snapshot-manager.js
generated
vendored
|
|
@ -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
65
node_modules/ava/lib/test.js
generated
vendored
|
|
@ -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
203
node_modules/ava/lib/worker/ipc.js
generated
vendored
|
|
@ -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
121
node_modules/ava/lib/worker/plugin.js
generated
vendored
Normal 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;
|
||||
43
node_modules/ava/lib/worker/subprocess.js
generated
vendored
43
node_modules/ava/lib/worker/subprocess.js
generated
vendored
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue