Skip to content

Commit df54324

Browse files
committed
Clean up test-worker and process-adapter
* Clarify responsibilities * Consistently import dependencies * Clarify significance of `exports.avaRequired` * Stop mimicking process with process-adapter. Reference it using `adapter` instead. Use the `process` global where applicable. Masking the process global with a mimicked object is unnecessarily confusing. * Remove superstitious delays in exiting workers The worker now only exits when told by the main process. This means the IPC channel must have drained before the main process can send the instruction. There's no need to wait before sending the message that teardown has completed. The AppVeyor workaround was introduced to solve Node.js 0.10 issues. We're no longer supporting that version. In theory, issues around flushing I/O exist regardless of whether AVA is running in AppVeyor. There is no clear way around this though, so let's assume it's not actually an issue.
1 parent f25935e commit df54324

File tree

4 files changed

+74
-94
lines changed

4 files changed

+74
-94
lines changed

lib/main.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
'use strict';
2-
const process = require('./process-adapter');
2+
const worker = require('./test-worker');
3+
const adapter = require('./process-adapter');
34
const serializeError = require('./serialize-error');
45
const globals = require('./globals');
56
const Runner = require('./runner');
6-
const send = process.send;
77

88
const opts = globals.options;
99
const runner = new Runner({
@@ -13,7 +13,7 @@ const runner = new Runner({
1313
});
1414

1515
// Note that test files have require('ava')
16-
require('./test-worker').avaRequired = true;
16+
worker.avaRequired = true;
1717

1818
// If fail-fast is enabled, use this variable to detect
1919
// that no more tests should be logged
@@ -39,7 +39,7 @@ function test(props) {
3939
props.error = null;
4040
}
4141

42-
send('test', props);
42+
adapter.send('test', props);
4343

4444
if (hasError && opts.failFast) {
4545
isFailed = true;
@@ -50,19 +50,19 @@ function test(props) {
5050
function exit() {
5151
const stats = runner._buildStats();
5252

53-
send('results', {stats});
53+
adapter.send('results', {stats});
5454
}
5555

5656
globals.setImmediate(() => {
5757
const hasExclusive = runner.tests.hasExclusive;
5858
const numberOfTests = runner.tests.testCount;
5959

6060
if (numberOfTests === 0) {
61-
send('no-tests', {avaRequired: true});
61+
adapter.send('no-tests', {avaRequired: true});
6262
return;
6363
}
6464

65-
send('stats', {
65+
adapter.send('stats', {
6666
testCount: numberOfTests,
6767
hasExclusive
6868
});

lib/process-adapter.js

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
11
'use strict';
22
const fs = require('fs');
33
const path = require('path');
4-
const chalk = require('chalk');
4+
const debug = require('debug')('ava');
55
const sourceMapSupport = require('source-map-support');
66
const installPrecompiler = require('require-precompiled');
77

8-
const debug = require('debug')('ava');
9-
10-
// Check if the test is being run without AVA cli
11-
const isForked = typeof process.send === 'function';
12-
13-
if (!isForked) {
14-
const fp = path.relative('.', process.argv[1]);
15-
16-
console.log();
17-
console.error('Test files must be run with the AVA CLI:\n\n ' + chalk.grey.dim('$') + ' ' + chalk.cyan('ava ' + fp) + '\n');
8+
// Parse and re-emit AVA messages
9+
process.on('message', message => {
10+
if (!message.ava) {
11+
return;
12+
}
1813

19-
process.exit(1); // eslint-disable-line unicorn/no-process-exit
20-
}
14+
process.emit(message.name, message.data);
15+
});
2116

2217
exports.send = (name, data) => {
2318
process.send({
@@ -27,11 +22,6 @@ exports.send = (name, data) => {
2722
});
2823
};
2924

30-
exports.on = process.on.bind(process);
31-
exports.emit = process.emit.bind(process);
32-
exports.exit = process.exit.bind(process);
33-
exports.env = process.env;
34-
3525
const opts = JSON.parse(process.argv[2]);
3626
exports.opts = opts;
3727

lib/test-worker.js

Lines changed: 55 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,56 @@
11
'use strict';
2-
/* eslint-disable import/order */
3-
const isObj = require('is-obj');
4-
const process = require('./process-adapter');
52

6-
const opts = process.opts;
7-
const testPath = opts.file;
3+
// Check if the test is being run without AVA cli
4+
{
5+
/* eslint-disable import/order */
6+
const path = require('path');
7+
const chalk = require('chalk');
8+
9+
const isForked = typeof process.send === 'function';
10+
if (!isForked) {
11+
const fp = path.relative('.', process.argv[1]);
12+
13+
console.log();
14+
console.error('Test files must be run with the AVA CLI:\n\n ' + chalk.grey.dim('$') + ' ' + chalk.cyan('ava ' + fp) + '\n');
15+
16+
process.exit(1); // eslint-disable-line unicorn/no-process-exit
17+
}
18+
}
819

9-
// Bind globals first before anything has a chance to interfere
20+
/* eslint-enable import/order */
21+
const Bluebird = require('bluebird');
22+
const currentlyUnhandled = require('currently-unhandled')();
23+
const isObj = require('is-obj');
24+
const adapter = require('./process-adapter');
1025
const globals = require('./globals');
26+
const serializeError = require('./serialize-error');
27+
const throwsHelper = require('./throws-helper');
28+
29+
const opts = adapter.opts;
30+
const testPath = opts.file;
1131
globals.options = opts;
12-
const Promise = require('bluebird');
1332

1433
// Bluebird specific
15-
Promise.longStackTraces();
34+
Bluebird.longStackTraces();
1635

1736
(opts.require || []).forEach(require);
1837

19-
process.installSourceMapSupport();
38+
adapter.installSourceMapSupport();
39+
adapter.installPrecompilerHook();
2040

21-
const currentlyUnhandled = require('currently-unhandled')();
22-
const serializeError = require('./serialize-error');
23-
const send = process.send;
24-
const throwsHelper = require('./throws-helper');
41+
const dependencies = [];
42+
adapter.installDependencyTracking(dependencies, testPath);
2543

2644
// Check if test files required ava and show error, when they didn't
2745
exports.avaRequired = false;
2846

29-
process.installPrecompilerHook();
30-
31-
const dependencies = [];
32-
process.installDependencyTracking(dependencies, testPath);
33-
3447
require(testPath); // eslint-disable-line import/no-dynamic-require
3548

49+
// If AVA was not required, show an error
50+
if (!exports.avaRequired) {
51+
adapter.send('no-tests', {avaRequired: false});
52+
}
53+
3654
process.on('unhandledRejection', throwsHelper);
3755

3856
process.on('uncaughtException', exception => {
@@ -51,30 +69,7 @@ process.on('uncaughtException', exception => {
5169
stack: err.stack
5270
};
5371
}
54-
send('uncaughtException', {exception: serialized});
55-
});
56-
57-
// If AVA was not required, show an error
58-
if (!exports.avaRequired) {
59-
send('no-tests', {avaRequired: false});
60-
}
61-
62-
// Parse and re-emit AVA messages
63-
process.on('message', message => {
64-
if (!message.ava) {
65-
return;
66-
}
67-
68-
process.emit(message.name, message.data);
69-
});
70-
71-
process.on('ava-exit', () => {
72-
// Use a little delay when running on AppVeyor (because it's shit)
73-
const delay = process.env.AVA_APPVEYOR ? 100 : 0;
74-
75-
globals.setTimeout(() => {
76-
process.exit(0); // eslint-disable-line xo/no-process-exit
77-
}, delay);
72+
adapter.send('uncaughtException', {exception: serialized});
7873
});
7974

8075
let tearingDown = false;
@@ -87,28 +82,26 @@ process.on('ava-teardown', () => {
8782

8883
let rejections = currentlyUnhandled();
8984

90-
if (rejections.length === 0) {
91-
exit();
92-
return;
85+
if (rejections.length > 0) {
86+
rejections = rejections.map(rejection => {
87+
let reason = rejection.reason;
88+
if (!isObj(reason) || typeof reason.message !== 'string') {
89+
reason = {
90+
message: String(reason)
91+
};
92+
}
93+
return serializeError(reason);
94+
});
95+
96+
adapter.send('unhandledRejections', {rejections});
9397
}
9498

95-
rejections = rejections.map(rejection => {
96-
let reason = rejection.reason;
97-
if (!isObj(reason) || typeof reason.message !== 'string') {
98-
reason = {
99-
message: String(reason)
100-
};
101-
}
102-
return serializeError(reason);
103-
});
104-
105-
send('unhandledRejections', {rejections});
106-
globals.setTimeout(exit, 100);
107-
});
108-
109-
function exit() {
11099
// Include dependencies in the final teardown message. This ensures the full
111100
// set of dependencies is included no matter how the process exits, unless
112101
// it flat out crashes.
113-
send('teardown', {dependencies});
114-
}
102+
adapter.send('teardown', {dependencies});
103+
});
104+
105+
process.on('ava-exit', () => {
106+
process.exit(0); // eslint-disable-line xo/no-process-exit
107+
});

test/fixture/trigger-worker-exception/hack.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
'use strict';
22

3-
require('../../../lib/serialize-error');
3+
const StackUtils = require('stack-utils');
44

5-
const serializeModule = require.cache[require.resolve('../../../lib/serialize-error')];
6-
7-
const original = serializeModule.exports;
5+
const original = StackUtils.prototype.parseLine;
86
let restored = false;
97
let restoreAfterFirstCall = false;
10-
serializeModule.exports = error => {
8+
StackUtils.prototype.parseLine = function(line) {
119
if (restored) {
12-
return original(error);
10+
return original.call(this, line);
1311
}
1412
if (restoreAfterFirstCall) {
1513
restored = true;
1614
}
17-
1815
throw new Error('Forced error');
1916
};
2017

0 commit comments

Comments
 (0)