Skip to content

Commit b0aef86

Browse files
committed
Refactor so that make() and fork() can be implemented
1 parent e5e736d commit b0aef86

File tree

3 files changed

+223
-98
lines changed

3 files changed

+223
-98
lines changed

Diff for: lib/create-chain.js

+23-17
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
'use strict';
22
const chainRegistry = new WeakMap();
33

4-
function startChain(name, declare, annotations) {
4+
function startChain(name, {annotations, declare, type}) {
55
const fn = (...args) => {
6-
declare(annotations, args);
6+
declare(type, annotations, args);
77
};
88

99
Object.defineProperty(fn, 'name', {value: name});
10-
chainRegistry.set(fn, {annotations, declare, fullName: name});
10+
chainRegistry.set(fn, {
11+
declare(flags, args) {
12+
declare(type, {...annotations, ...flags}, args);
13+
},
14+
fullName: name
15+
});
1116
return fn;
1217
}
1318

@@ -32,7 +37,7 @@ function declareWithFlag(previous, flag, args) {
3237
combinedFlags[step.flag] = true;
3338
previous = step.previous;
3439
} else {
35-
step.declare({...step.annotations, ...combinedFlags}, args);
40+
step.declare(combinedFlags, args);
3641
break;
3742
}
3843
} while (previous);
@@ -78,11 +83,12 @@ function createChain({
7883
allowMultipleImplementations
7984
};
8085

81-
const declare = (declaredAnnotations, args) => {
86+
const declare = (type, declaredAnnotations, args) => {
8287
declareWithOptions({
8388
annotations: {...annotations, ...declaredAnnotations},
8489
args,
85-
options
90+
options,
91+
type
8692
});
8793
};
8894

@@ -111,7 +117,7 @@ function createChain({
111117
// * `failing` must come at the end, but can be followed by `only` and `skip`
112118
// * `only` and `skip` cannot be chained together
113119
// * no repeating
114-
const root = startChain('test', declare, {type: 'test'});
120+
const root = startChain('test', {declare, type: 'test'});
115121
extendChain(root, 'failing');
116122
extendChain(root, 'only', 'exclusive');
117123
extendChain(root, 'serial');
@@ -139,21 +145,21 @@ function createChain({
139145
extendChain(root.serial.cb.failing, 'skip', 'skipped');
140146
}
141147

142-
root.after = createHookChain({allowCallbacks, isAfterHook: true}, startChain('test.after', declare, {type: 'after'}));
143-
root.afterEach = createHookChain({allowCallbacks, isAfterHook: true}, startChain('test.afterEach', declare, {type: 'afterEach'}));
144-
root.before = createHookChain({allowCallbacks}, startChain('test.before', declare, {type: 'before'}));
145-
root.beforeEach = createHookChain({allowCallbacks}, startChain('test.beforeEach', declare, {type: 'beforeEach'}));
148+
root.after = createHookChain({allowCallbacks, isAfterHook: true}, startChain('test.after', {declare, type: 'after'}));
149+
root.afterEach = createHookChain({allowCallbacks, isAfterHook: true}, startChain('test.afterEach', {declare, type: 'afterEach'}));
150+
root.before = createHookChain({allowCallbacks}, startChain('test.before', {declare, type: 'before'}));
151+
root.beforeEach = createHookChain({allowCallbacks}, startChain('test.beforeEach', {declare, type: 'beforeEach'}));
146152

147-
root.serial.after = createHookChain({allowCallbacks, isAfterHook: true}, startChain('test.after', declare, {serial: true, type: 'after'}));
148-
root.serial.afterEach = createHookChain({allowCallbacks, isAfterHook: true}, startChain('test.afterEach', declare, {serial: true, type: 'afterEach'}));
149-
root.serial.before = createHookChain({allowCallbacks}, startChain('test.before', declare, {serial: true, type: 'before'}));
150-
root.serial.beforeEach = createHookChain({allowCallbacks}, startChain('test.beforeEach', declare, {serial: true, type: 'beforeEach'}));
153+
root.serial.after = createHookChain({allowCallbacks, isAfterHook: true}, startChain('test.after', {annotations: {serial: true}, declare, type: 'after'}));
154+
root.serial.afterEach = createHookChain({allowCallbacks, isAfterHook: true}, startChain('test.afterEach', {annotations: {serial: true}, declare, type: 'afterEach'}));
155+
root.serial.before = createHookChain({allowCallbacks}, startChain('test.before', {annotations: {serial: true}, declare, type: 'before'}));
156+
root.serial.beforeEach = createHookChain({allowCallbacks}, startChain('test.beforeEach', {annotations: {serial: true}, declare, type: 'beforeEach'}));
151157
root.serial.macro = macro;
152158

153159
// "todo" tests cannot be chained. Allow todo tests to be flagged as needing
154160
// to be serial.
155-
root.todo = startChain('test.todo', declare, {type: 'test', todo: true});
156-
root.serial.todo = startChain('test.serial.todo', declare, {serial: true, type: 'test', todo: true});
161+
root.todo = startChain('test.todo', {declare, type: 'todo'});
162+
root.serial.todo = startChain('test.serial.todo', {annotations: {serial: true}, declare, type: 'todo'});
157163

158164
root.macro = macro;
159165
root.meta = meta;

Diff for: lib/runner.js

+57-81
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const parseTestArgs = require('./parse-test-args');
77
const snapshotManager = require('./snapshot-manager');
88
const serializeError = require('./serialize-error');
99
const Runnable = require('./test');
10+
const {Task, TaskList} = require('./task-list');
1011

1112
class Runner extends Emittery {
1213
constructor(options = {}) {
@@ -29,17 +30,7 @@ class Runner extends Emittery {
2930
this.boundCompareTestSnapshot = this.compareTestSnapshot.bind(this);
3031
this.interrupted = false;
3132
this.snapshots = null;
32-
this.tasks = {
33-
after: [],
34-
afterAlways: [],
35-
afterEach: [],
36-
afterEachAlways: [],
37-
before: [],
38-
beforeEach: [],
39-
concurrent: [],
40-
serial: [],
41-
todo: []
42-
};
33+
this.tasks = new TaskList();
4334

4435
const uniqueTestTitles = new Set();
4536
this.registerUniqueTitle = title => {
@@ -61,8 +52,7 @@ class Runner extends Emittery {
6152
failing: false,
6253
inline: false, // Default value; only attempts created by `t.try()` have this annotation set to `true`.
6354
serial: false,
64-
skipped: false,
65-
todo: false
55+
skipped: false
6656
},
6757
meta: Object.freeze({
6858
file: options.file,
@@ -74,11 +64,9 @@ class Runner extends Emittery {
7464
declare: ({ // eslint-disable-line complexity
7565
annotations,
7666
args: declarationArguments,
77-
options: {
78-
allowExperimentalMacros,
79-
allowImplementationTitleFns,
80-
allowMultipleImplementations
81-
}}) => {
67+
options,
68+
type
69+
}) => {
8270
if (hasStarted) {
8371
throw new Error('All tests and hooks must be declared synchronously in your test file, and cannot be nested within other tests or hooks.');
8472
}
@@ -91,13 +79,9 @@ class Runner extends Emittery {
9179
});
9280
}
9381

94-
const {args, buildTitle, implementations, rawTitle} = parseTestArgs(declarationArguments, {
95-
allowExperimentalMacros,
96-
allowImplementationTitleFns,
97-
allowMultipleImplementations
98-
});
82+
const {args, buildTitle, implementations, rawTitle} = parseTestArgs(declarationArguments, options);
9983

100-
if (annotations.todo) {
84+
if (type === 'todo') {
10185
if (implementations.length > 0) {
10286
throw new TypeError('`todo` tests are not allowed to have an implementation. Use `test.skip()` for tests with an implementation.');
10387
}
@@ -118,7 +102,7 @@ class Runner extends Emittery {
118102
}
119103
}
120104

121-
this.tasks.todo.push({title: rawTitle, annotations});
105+
this.tasks.add(Task.todo({annotations, title: rawTitle}));
122106
this.emit('stateChange', {
123107
type: 'declared-test',
124108
title: rawTitle,
@@ -138,48 +122,51 @@ class Runner extends Emittery {
138122
}
139123

140124
if (isEmpty) {
141-
if (annotations.type === 'test') {
125+
if (type === 'test') {
142126
throw new TypeError('Tests must have a title');
143127
} else if (annotations.always) {
144-
title = `${annotations.type}.always hook`;
128+
title = `${type}.always hook`;
145129
} else {
146-
title = `${annotations.type} hook`;
130+
title = `${type} hook`;
147131
}
148132
}
149133

150-
if (annotations.type === 'test' && !this.registerUniqueTitle(title)) {
134+
if (type === 'test' && !this.registerUniqueTitle(title)) {
151135
throw new Error(`Duplicate test title: ${title}`);
152136
}
153137

154-
const task = {
155-
allowExperimentalMacros,
156-
allowImplementationTitleFns,
157-
allowMultipleImplementations,
158-
annotations: {...annotations},
159-
args,
160-
implementation,
161-
title
162-
};
163-
164-
if (annotations.type === 'test') {
138+
if (type === 'test') {
139+
let {exclusive} = annotations;
165140
if (this.match.length > 0) {
166141
// --match overrides .only()
167-
task.annotations.exclusive = matcher([title], this.match).length === 1;
142+
exclusive = matcher([title], this.match).length === 1;
168143
}
169144

170-
if (task.annotations.exclusive) {
145+
if (exclusive) {
171146
this.runOnlyExclusive = true;
172147
}
173148

174-
this.tasks[annotations.serial ? 'serial' : 'concurrent'].push(task);
149+
this.tasks.add(Task.test({
150+
annotations: {...annotations, exclusive},
151+
args,
152+
implementation,
153+
options,
154+
title
155+
}));
175156
this.emit('stateChange', {
176157
type: 'declared-test',
177158
title,
178159
knownFailing: annotations.failing,
179160
todo: false
180161
});
181-
} else if (!annotations.skipped) {
182-
this.tasks[annotations.type + (annotations.always ? 'Always' : '')].push(task);
162+
} else {
163+
this.tasks.add(Task[type]({
164+
annotations,
165+
args,
166+
implementation,
167+
options,
168+
title
169+
}));
183170
}
184171
}
185172
}
@@ -305,11 +292,9 @@ class Runner extends Emittery {
305292
return result;
306293
}
307294

308-
async runHooks(tasks, contextRef, titleSuffix, testPassed) {
309-
const hooks = tasks.map(task => new Runnable({
310-
allowExperimentalMacros: task.allowExperimentalMacros,
311-
allowImplementationTitleFns: task.allowImplementationTitleFns,
312-
allowMultipleImplementations: task.allowMultipleImplementations,
295+
async runHooks(type, contextRef, titleSuffix, testPassed) {
296+
const hooks = [...this.tasks.select(type)].map(task => new Runnable({
297+
...task.options,
313298
annotations: task.annotations,
314299
contextRef,
315300
experiments: this.experiments,
@@ -348,7 +333,7 @@ class Runner extends Emittery {
348333

349334
async runTest(task, contextRef) {
350335
const hookSuffix = ` for ${task.title}`;
351-
let hooksOk = await this.runHooks(this.tasks.beforeEach, contextRef, hookSuffix);
336+
let hooksOk = await this.runHooks('beforeEach', contextRef, hookSuffix);
352337

353338
let testOk = false;
354339
if (hooksOk) {
@@ -383,7 +368,7 @@ class Runner extends Emittery {
383368
logs: result.logs
384369
});
385370

386-
hooksOk = await this.runHooks(this.tasks.afterEach, contextRef, hookSuffix, testOk);
371+
hooksOk = await this.runHooks('afterEach', contextRef, hookSuffix, testOk);
387372
} else {
388373
this.emit('stateChange', {
389374
type: 'test-failed',
@@ -397,14 +382,14 @@ class Runner extends Emittery {
397382
}
398383
}
399384

400-
const alwaysOk = await this.runHooks(this.tasks.afterEachAlways, contextRef, hookSuffix, testOk);
385+
const alwaysOk = await this.runHooks('afterEachAlways', contextRef, hookSuffix, testOk);
401386
return alwaysOk && hooksOk && testOk;
402387
}
403388

404389
async start() {
405-
const concurrentTests = [];
406-
const serialTests = [];
407-
for (const task of this.tasks.serial) {
390+
let concurrentTests = [];
391+
let serialTests = [];
392+
for (const task of this.tasks.select('test')) {
408393
if (this.runOnlyExclusive && !task.annotations.exclusive) {
409394
continue;
410395
}
@@ -417,34 +402,25 @@ class Runner extends Emittery {
417402
todo: false
418403
});
419404

420-
if (!task.annotations.skipped) {
421-
serialTests.push(task);
422-
}
423-
}
424-
425-
for (const task of this.tasks.concurrent) {
426-
if (this.runOnlyExclusive && !task.annotations.exclusive) {
405+
if (task.annotations.skipped) {
427406
continue;
428407
}
429408

430-
this.emit('stateChange', {
431-
type: 'selected-test',
432-
title: task.title,
433-
knownFailing: task.annotations.failing,
434-
skip: task.annotations.skipped,
435-
todo: false
436-
});
437-
438-
if (!task.annotations.skipped) {
439-
if (this.serial) {
440-
serialTests.push(task);
441-
} else {
442-
concurrentTests.push(task);
443-
}
409+
if (task.annotations.serial) {
410+
serialTests.push(task);
411+
} else {
412+
concurrentTests.push(task);
444413
}
445414
}
446415

447-
for (const task of this.tasks.todo) {
416+
// Reassign the concurrent tasks, but always run them after the explicitly
417+
// serial ones.
418+
if (this.serial) {
419+
serialTests = [...serialTests, ...concurrentTests];
420+
concurrentTests = [];
421+
}
422+
423+
for (const task of this.tasks.select('todo')) {
448424
if (this.runOnlyExclusive && !task.annotations.exclusive) {
449425
continue;
450426
}
@@ -467,7 +443,7 @@ class Runner extends Emittery {
467443
const contextRef = new ContextRef();
468444

469445
// Note that the hooks and tests always begin running asynchronously.
470-
const beforePromise = this.runHooks(this.tasks.before, contextRef);
446+
const beforePromise = this.runHooks('before', contextRef);
471447
const serialPromise = beforePromise.then(beforeHooksOk => { // eslint-disable-line promise/prefer-await-to-then
472448
// Don't run tests if a `before` hook failed.
473449
if (!beforeHooksOk) {
@@ -517,11 +493,11 @@ class Runner extends Emittery {
517493
const ok = await concurrentPromise;
518494
// Only run `after` hooks if all hooks and tests passed.
519495
if (ok) {
520-
await this.runHooks(this.tasks.after, contextRef);
496+
await this.runHooks('after', contextRef);
521497
}
522498

523499
// Always run `after.always` hooks.
524-
await this.runHooks(this.tasks.afterAlways, contextRef);
500+
await this.runHooks('afterAlways', contextRef);
525501
process.removeListener('beforeExit', beforeExitHandler);
526502
await this.emit('finish');
527503
} catch (error) {

0 commit comments

Comments
 (0)