aboutsummaryrefslogtreecommitdiff
path: root/node_modules/ava/lib/test-worker.js
blob: 0061775f0ecdfcd5f5fa3941171aeee2ec961e2f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
'use strict';

// Check if the test is being run without AVA cli
{
	/* eslint-disable import/order */
	const path = require('path');
	const chalk = require('chalk');

	const isForked = typeof process.send === 'function';
	if (!isForked) {
		const fp = path.relative('.', process.argv[1]);

		console.log();
		console.error('Test files must be run with the AVA CLI:\n\n    ' + chalk.grey.dim('$') + ' ' + chalk.cyan('ava ' + fp) + '\n');

		process.exit(1); // eslint-disable-line unicorn/no-process-exit
	}
}

const adapter = require('./process-adapter');
const globals = require('./globals');

const opts = adapter.opts;
globals.options = opts;

/* eslint-enable import/order */
const Bluebird = require('bluebird');
const currentlyUnhandled = require('currently-unhandled')();
const isObj = require('is-obj');
const serializeError = require('./serialize-error');

// Bluebird specific
Bluebird.longStackTraces();

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

adapter.installSourceMapSupport();
adapter.installPrecompilerHook();

const testPath = opts.file;

const dependencies = new Set();
adapter.installDependencyTracking(dependencies, testPath);

const touchedFiles = new Set();

// Set when main.js is required (since test files should have `require('ava')`).
let runner = null;
exports.setRunner = newRunner => {
	runner = newRunner;
	runner.on('dependency', file => {
		dependencies.add(file);
	});
	runner.on('touched', files => {
		for (const file of files) {
			touchedFiles.add(file);
		}
	});
};

require(testPath);

// If AVA was not required, show an error
if (!runner) {
	adapter.send('no-tests', {avaRequired: false});
}

function attributeLeakedError(err) {
	if (!runner) {
		return false;
	}

	return runner.attributeLeakedError(err);
}

const attributedRejections = new Set();
process.on('unhandledRejection', (reason, promise) => {
	if (attributeLeakedError(reason)) {
		attributedRejections.add(promise);
	}
});

process.on('uncaughtException', exception => {
	if (attributeLeakedError(exception)) {
		return;
	}

	let serialized;
	try {
		serialized = serializeError(exception);
	} catch (ignore) { // eslint-disable-line unicorn/catch-error-name
		// Avoid using serializeError
		const err = new Error('Failed to serialize uncaught exception');
		serialized = {
			avaAssertionError: false,
			name: err.name,
			message: err.message,
			stack: err.stack
		};
	}

	// Ensure the IPC channel is refereced. The uncaught exception will kick off
	// the teardown sequence, for which the messages must be received.
	adapter.ipcChannel.ref();

	adapter.send('uncaughtException', {exception: serialized});
});

let tearingDown = false;
process.on('ava-teardown', () => {
	// AVA-teardown can be sent more than once
	if (tearingDown) {
		return;
	}
	tearingDown = true;

	let rejections = currentlyUnhandled()
		.filter(rejection => !attributedRejections.has(rejection.promise));

	if (rejections.length > 0) {
		rejections = rejections.map(rejection => {
			let reason = rejection.reason;
			if (!isObj(reason) || typeof reason.message !== 'string') {
				reason = {
					message: String(reason)
				};
			}
			return serializeError(reason);
		});

		adapter.send('unhandledRejections', {rejections});
	}

	// Include dependencies in the final teardown message. This ensures the full
	// set of dependencies is included no matter how the process exits, unless
	// it flat out crashes. Also include any files that AVA touched during the
	// test run. This allows the watcher to ignore modifications to those files.
	adapter.send('teardown', {
		dependencies: Array.from(dependencies),
		touchedFiles: Array.from(touchedFiles)
	});
});

process.on('ava-exit', () => {
	process.exit(0); // eslint-disable-line xo/no-process-exit
});