Skip to content

Refactor how test files are told to run their tests, and more #1693

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Feb 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
395 changes: 188 additions & 207 deletions api.js

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ export interface SerialInterface<Context = {}> {
(title: string, macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;
(macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

after: AfterInterface<Context>;
afterEach: AfterInterface<Context>;
before: BeforeInterface<Context>;
beforeEach: BeforeInterface<Context>;
cb: CbInterface<Context>;
failing: FailingInterface<Context>;
only: OnlyInterface<Context>;
Expand Down
4 changes: 4 additions & 0 deletions index.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ export interface SerialInterface<Context = {}> {
(title: string, macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;
(macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

after: AfterInterface<Context>;
afterEach: AfterInterface<Context>;
before: BeforeInterface<Context>;
beforeEach: BeforeInterface<Context>;
cb: CbInterface<Context>;
failing: FailingInterface<Context>;
only: OnlyInterface<Context>;
Expand Down
2 changes: 1 addition & 1 deletion lib/concordance-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const chalk = require('chalk');
const stripAnsi = require('strip-ansi');
const cloneDeepWith = require('lodash.clonedeepwith');
const reactPlugin = require('@concordance/react');
const options = require('./globals').options;
const options = require('./worker-options').get();

// Wrap Concordance's React plugin. Change the name to avoid collisions if in
// the future users can register plugins themselves.
Expand Down
64 changes: 0 additions & 64 deletions lib/concurrent.js

This file was deleted.

41 changes: 41 additions & 0 deletions lib/context-ref.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use strict';
const clone = require('lodash.clone');

class ContextRef {
constructor() {
this.value = {};
}

get() {
return this.value;
}

set(newValue) {
this.value = newValue;
}

copy() {
return new LateBinding(this); // eslint-disable-line no-use-before-define
}
}
module.exports = ContextRef;

class LateBinding extends ContextRef {
constructor(ref) {
super();
this.ref = ref;
this.bound = false;
}

get() {
if (!this.bound) {
this.set(clone(this.ref.get()));
}
return super.get();
}

set(newValue) {
this.bound = true;
super.set(newValue);
}
}
108 changes: 108 additions & 0 deletions lib/create-chain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
'use strict';
const chainRegistry = new WeakMap();

function startChain(name, call, defaults) {
const fn = function () {
call(Object.assign({}, defaults), Array.from(arguments));
};
Object.defineProperty(fn, 'name', {value: name});
chainRegistry.set(fn, {call, defaults, fullName: name});
return fn;
}

function extendChain(prev, name, flag) {
if (!flag) {
flag = name;
}

const fn = function () {
callWithFlag(prev, flag, Array.from(arguments));
};
const fullName = `${chainRegistry.get(prev).fullName}.${name}`;
Object.defineProperty(fn, 'name', {value: fullName});
prev[name] = fn;

chainRegistry.set(fn, {flag, fullName, prev});
return fn;
}

function callWithFlag(prev, flag, args) {
const combinedFlags = {[flag]: true};
do {
const step = chainRegistry.get(prev);
if (step.call) {
step.call(Object.assign({}, step.defaults, combinedFlags), args);
prev = null;
} else {
combinedFlags[step.flag] = true;
prev = step.prev;
}
} while (prev);
}

function createHookChain(hook, isAfterHook) {
// Hook chaining rules:
// * `always` comes immediately after "after hooks"
// * `skip` must come at the end
// * no `only`
// * no repeating
extendChain(hook, 'cb', 'callback');
extendChain(hook, 'skip', 'skipped');
extendChain(hook.cb, 'skip', 'skipped');
if (isAfterHook) {
extendChain(hook, 'always');
extendChain(hook.always, 'cb', 'callback');
extendChain(hook.always, 'skip', 'skipped');
extendChain(hook.always.cb, 'skip', 'skipped');
}
return hook;
}

function createChain(fn, defaults) {
// Test chaining rules:
// * `serial` must come at the start
// * `only` and `skip` must come at the end
// * `failing` must come at the end, but can be followed by `only` and `skip`
// * `only` and `skip` cannot be chained together
// * no repeating
const root = startChain('test', fn, Object.assign({}, defaults, {type: 'test'}));
extendChain(root, 'cb', 'callback');
extendChain(root, 'failing');
extendChain(root, 'only', 'exclusive');
extendChain(root, 'serial');
extendChain(root, 'skip', 'skipped');
extendChain(root.cb, 'failing');
extendChain(root.cb, 'only', 'exclusive');
extendChain(root.cb, 'skip', 'skipped');
extendChain(root.cb.failing, 'only', 'exclusive');
extendChain(root.cb.failing, 'skip', 'skipped');
extendChain(root.failing, 'only', 'exclusive');
extendChain(root.failing, 'skip', 'skipped');
extendChain(root.serial, 'cb', 'callback');
extendChain(root.serial, 'failing');
extendChain(root.serial, 'only', 'exclusive');
extendChain(root.serial, 'skip', 'skipped');
extendChain(root.serial.cb, 'failing');
extendChain(root.serial.cb, 'only', 'exclusive');
extendChain(root.serial.cb, 'skip', 'skipped');
extendChain(root.serial.cb.failing, 'only', 'exclusive');
extendChain(root.serial.cb.failing, 'skip', 'skipped');

root.after = createHookChain(startChain('test.after', fn, Object.assign({}, defaults, {type: 'after'})), true);
root.afterEach = createHookChain(startChain('test.afterEach', fn, Object.assign({}, defaults, {type: 'afterEach'})), true);
root.before = createHookChain(startChain('test.before', fn, Object.assign({}, defaults, {type: 'before'})), false);
root.beforeEach = createHookChain(startChain('test.beforeEach', fn, Object.assign({}, defaults, {type: 'beforeEach'})), false);

root.serial.after = createHookChain(startChain('test.after', fn, Object.assign({}, defaults, {serial: true, type: 'after'})), true);
root.serial.afterEach = createHookChain(startChain('test.afterEach', fn, Object.assign({}, defaults, {serial: true, type: 'afterEach'})), true);
root.serial.before = createHookChain(startChain('test.before', fn, Object.assign({}, defaults, {serial: true, type: 'before'})), false);
root.serial.beforeEach = createHookChain(startChain('test.beforeEach', fn, Object.assign({}, defaults, {serial: true, type: 'beforeEach'})), false);

// "todo" tests cannot be chained. Allow todo tests to be flagged as needing
// to be serial.
root.todo = startChain('test.todo', fn, Object.assign({}, defaults, {type: 'test', todo: true}));
root.serial.todo = startChain('test.serial.todo', fn, Object.assign({}, defaults, {serial: true, type: 'test', todo: true}));

return root;
}
module.exports = createChain;
39 changes: 9 additions & 30 deletions lib/fork.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ module.exports = (file, opts, execArgv) => {
}
};

let loadedFile = false;
const testResults = [];
let results;

Expand Down Expand Up @@ -100,21 +101,20 @@ module.exports = (file, opts, execArgv) => {

if (results) {
resolve(results);
} else if (loadedFile) {
reject(new AvaError(`No tests found in ${relFile}`));
} else {
reject(new AvaError(`Test results were not received from ${relFile}`));
}
});

ps.on('no-tests', data => {
send('teardown');

let message = `No tests found in ${relFile}`;
ps.on('loaded-file', data => {
loadedFile = true;

if (!data.avaRequired) {
message += ', make sure to import "ava" at the top of your test file';
send('teardown');
reject(new AvaError(`No tests found in ${relFile}, make sure to import "ava" at the top of your test file`));
}

reject(new AvaError(message));
});
});

Expand Down Expand Up @@ -142,34 +142,13 @@ module.exports = (file, opts, execArgv) => {
return promise;
};

promise.send = (name, data) => {
send(name, data);
return promise;
};

promise.exit = () => {
send('init-exit');
return promise;
};

// Send 'run' event only when fork is listening for it
let isReady = false;

ps.on('stats', () => {
isReady = true;
});

promise.run = options => {
if (isReady) {
send('run', options);
return promise;
}

ps.on('stats', () => {
send('run', options);
});

return promise;
promise.notifyOfPeerFailure = () => {
send('peer-failed');
};

return promise;
Expand Down
10 changes: 0 additions & 10 deletions lib/globals.js

This file was deleted.

2 changes: 1 addition & 1 deletion lib/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Logger {
}

test(test, runStatus) {
this.write(this.reporter.test(test, runStatus), runStatus);
this.write(this.reporter.test(test), runStatus);
}

unhandledError(err, runStatus) {
Expand Down
Loading