ava 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/fork.js CHANGED
@@ -6,7 +6,6 @@ var objectAssign = require('object-assign');
6
6
  var Promise = require('bluebird');
7
7
  var debug = require('debug')('ava');
8
8
  var AvaError = require('./ava-error');
9
- var doSend = require('./send');
10
9
 
11
10
  if (fs.realpathSync(__filename) !== __filename) {
12
11
  console.warn(
@@ -31,7 +30,11 @@ if (env.NODE_PATH) {
31
30
  .join(path.delimiter);
32
31
  }
33
32
 
34
- module.exports = function (file, opts) {
33
+ // In case the test file imports a different AVA install, the presence of this variable allows it to require this one
34
+ // instead.
35
+ env.AVA_PATH = path.resolve(__dirname, '..');
36
+
37
+ module.exports = function (file, opts, execArgv) {
35
38
  opts = objectAssign({
36
39
  file: file,
37
40
  baseDir: process.cwd(),
@@ -42,20 +45,25 @@ module.exports = function (file, opts) {
42
45
  }, opts);
43
46
 
44
47
  var ps = childProcess.fork(path.join(__dirname, 'test-worker.js'), [JSON.stringify(opts)], {
45
- cwd: path.dirname(file),
48
+ cwd: opts.pkgDir,
46
49
  silent: true,
47
- env: env
50
+ env: env,
51
+ execArgv: execArgv || process.execArgv
48
52
  });
49
53
 
50
54
  var relFile = path.relative('.', file);
51
55
 
52
56
  var exiting = false;
53
- var send = function (ps, name, data) {
57
+ var send = function (name, data) {
54
58
  if (!exiting) {
55
59
  // This seems to trigger a Node bug which kills the AVA master process, at
56
60
  // least while running AVA's tests. See
57
61
  // <https://github.com/novemberborn/_ava-tap-crash> for more details.
58
- doSend(ps, name, data);
62
+ ps.send({
63
+ name: 'ava-' + name,
64
+ data: data,
65
+ ava: true
66
+ });
59
67
  }
60
68
  };
61
69
 
@@ -71,7 +79,7 @@ module.exports = function (file, opts) {
71
79
  return;
72
80
  }
73
81
 
74
- event.name = event.name.replace(/^ava\-/, '');
82
+ event.name = event.name.replace(/^ava-/, '');
75
83
  event.data.file = relFile;
76
84
 
77
85
  debug('ipc %s:\n%o', event.name, event.data);
@@ -86,7 +94,7 @@ module.exports = function (file, opts) {
86
94
  ps.on('results', function (data) {
87
95
  results = data;
88
96
  data.tests = testResults;
89
- send(ps, 'teardown');
97
+ send('teardown');
90
98
  });
91
99
 
92
100
  ps.on('exit', function (code, signal) {
@@ -106,7 +114,7 @@ module.exports = function (file, opts) {
106
114
  });
107
115
 
108
116
  ps.on('no-tests', function (data) {
109
- send(ps, 'teardown');
117
+ send('teardown');
110
118
 
111
119
  var message = 'No tests found in ' + relFile;
112
120
 
@@ -120,13 +128,13 @@ module.exports = function (file, opts) {
120
128
 
121
129
  // teardown finished, now exit
122
130
  ps.on('teardown', function () {
123
- send(ps, 'exit');
131
+ send('exit');
124
132
  exiting = true;
125
133
  });
126
134
 
127
135
  // uncaught exception in fork, need to exit
128
136
  ps.on('uncaughtException', function () {
129
- send(ps, 'teardown');
137
+ send('teardown');
130
138
  });
131
139
 
132
140
  ps.stdout.on('data', function (data) {
@@ -144,13 +152,13 @@ module.exports = function (file, opts) {
144
152
  };
145
153
 
146
154
  promise.send = function (name, data) {
147
- send(ps, name, data);
155
+ send(name, data);
148
156
 
149
157
  return promise;
150
158
  };
151
159
 
152
160
  promise.exit = function () {
153
- send(ps, 'init-exit');
161
+ send('init-exit');
154
162
 
155
163
  return promise;
156
164
  };
@@ -164,12 +172,12 @@ module.exports = function (file, opts) {
164
172
 
165
173
  promise.run = function (options) {
166
174
  if (isReady) {
167
- send(ps, 'run', options);
175
+ send('run', options);
168
176
  return promise;
169
177
  }
170
178
 
171
179
  ps.on('stats', function () {
172
- send(ps, 'run', options);
180
+ send('run', options);
173
181
  });
174
182
 
175
183
  return promise;
package/lib/logger.js CHANGED
@@ -1,15 +1,14 @@
1
1
  'use strict';
2
+ var autoBind = require('auto-bind');
2
3
 
3
4
  function Logger(reporter) {
4
5
  if (!(this instanceof Logger)) {
5
6
  throw new TypeError('Class constructor Logger cannot be invoked without \'new\'');
6
7
  }
7
8
 
8
- Object.keys(Logger.prototype).forEach(function (key) {
9
- this[key] = this[key].bind(this);
10
- }, this);
11
-
12
9
  this.reporter = reporter;
10
+
11
+ autoBind(this);
13
12
  }
14
13
 
15
14
  module.exports = Logger;
@@ -99,6 +98,6 @@ Logger.prototype.exit = function (code) {
99
98
 
100
99
  // timeout required to correctly flush IO on Node.js 0.10 on Windows
101
100
  setTimeout(function () {
102
- process.exit(code); // eslint-disable-line
101
+ process.exit(code); // eslint-disable-line unicorn/no-process-exit
103
102
  }, process.env.AVA_APPVEYOR ? 500 : 0);
104
103
  };
package/lib/main.js ADDED
@@ -0,0 +1,89 @@
1
+ 'use strict';
2
+
3
+ var process = require('./process-adapter');
4
+ var serializeError = require('./serialize-error');
5
+ var globals = require('./globals');
6
+ var Runner = require('./runner');
7
+ var send = process.send;
8
+
9
+ var opts = globals.options;
10
+ var runner = new Runner({
11
+ serial: opts.serial,
12
+ bail: opts.failFast,
13
+ match: opts.match
14
+ });
15
+
16
+ // note that test files have require('ava')
17
+ require('./test-worker').avaRequired = true;
18
+
19
+ // if fail-fast is enabled, use this variable to detect
20
+ // that no more tests should be logged
21
+ var isFailed = false;
22
+
23
+ Error.stackTraceLimit = Infinity;
24
+
25
+ function test(props) {
26
+ if (isFailed) {
27
+ return;
28
+ }
29
+
30
+ var hasError = typeof props.error !== 'undefined';
31
+
32
+ // don't display anything if it's a passed hook
33
+ if (!hasError && props.type !== 'test') {
34
+ return;
35
+ }
36
+
37
+ if (hasError) {
38
+ props.error = serializeError(props.error);
39
+ } else {
40
+ props.error = null;
41
+ }
42
+
43
+ send('test', props);
44
+
45
+ if (hasError && opts.failFast) {
46
+ isFailed = true;
47
+ exit();
48
+ }
49
+ }
50
+
51
+ function exit() {
52
+ var stats = runner._buildStats();
53
+
54
+ send('results', {
55
+ stats: stats
56
+ });
57
+ }
58
+
59
+ globals.setImmediate(function () {
60
+ var hasExclusive = runner.tests.hasExclusive;
61
+ var numberOfTests = runner.tests.tests.concurrent.length + runner.tests.tests.serial.length;
62
+
63
+ if (numberOfTests === 0) {
64
+ send('no-tests', {avaRequired: true});
65
+ return;
66
+ }
67
+
68
+ send('stats', {
69
+ testCount: numberOfTests,
70
+ hasExclusive: hasExclusive
71
+ });
72
+
73
+ runner.on('test', test);
74
+
75
+ process.on('ava-run', function (options) {
76
+ runner.run(options).then(exit);
77
+ });
78
+
79
+ process.on('ava-init-exit', function () {
80
+ exit();
81
+ });
82
+ });
83
+
84
+ module.exports = runner.test;
85
+
86
+ // TypeScript imports the `default` property for
87
+ // an ES2015 default import (`import test from 'ava'`)
88
+ // See: https://github.com/Microsoft/TypeScript/issues/2242#issuecomment-83694181
89
+ module.exports.default = runner.test;
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+ var path = require('path');
3
+
4
+ module.exports = function (file, base, separator) {
5
+ var prefix = file
6
+ .replace(base, function (match, offset) {
7
+ // only replace this.base if it is found at the start of the path
8
+ return offset === 0 ? '' : match;
9
+ })
10
+ .replace(/\.spec/, '')
11
+ .replace(/\.test/, '')
12
+ .replace(/test-/g, '')
13
+ .replace(/\.js$/, '')
14
+ .split(path.sep)
15
+ .filter(function (p) {
16
+ return p !== '__tests__';
17
+ })
18
+ .join(separator);
19
+
20
+ if (prefix.length > 0) {
21
+ prefix += separator;
22
+ }
23
+
24
+ return prefix;
25
+ };
@@ -0,0 +1,107 @@
1
+ 'use strict';
2
+ var fs = require('fs');
3
+ var path = require('path');
4
+ var chalk = require('chalk');
5
+ var sourceMapSupport = require('source-map-support');
6
+ var installPrecompiler = require('require-precompiled');
7
+
8
+ var debug = require('debug')('ava');
9
+
10
+ // check if the test is being run without AVA cli
11
+ var isForked = typeof process.send === 'function';
12
+
13
+ if (!isForked) {
14
+ var 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');
18
+
19
+ process.exit(1); // eslint-disable-line unicorn/no-process-exit
20
+ }
21
+
22
+ exports.send = function (name, data) {
23
+ process.send({
24
+ name: 'ava-' + name,
25
+ data: data,
26
+ ava: true
27
+ });
28
+ };
29
+
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
+
35
+ var opts = exports.opts = JSON.parse(process.argv[2]);
36
+
37
+ // Fake TTY support
38
+ if (opts.tty) {
39
+ process.stdout.isTTY = true;
40
+ process.stdout.columns = opts.tty.columns || 80;
41
+ process.stdout.rows = opts.tty.rows;
42
+
43
+ var tty = require('tty');
44
+ var isatty = tty.isatty;
45
+
46
+ tty.isatty = function (fd) {
47
+ if (fd === 1 || fd === process.stdout) {
48
+ return true;
49
+ }
50
+
51
+ return isatty(fd);
52
+ };
53
+ }
54
+
55
+ if (debug.enabled) {
56
+ // Forward the `time-require` `--sorted` flag.
57
+ // Intended for internal optimization tests only.
58
+ if (opts._sorted) {
59
+ process.argv.push('--sorted');
60
+ }
61
+
62
+ require('time-require'); // eslint-disable-line import/no-unassigned-import
63
+ }
64
+
65
+ var sourceMapCache = Object.create(null);
66
+ var cacheDir = opts.cacheDir;
67
+
68
+ exports.installSourceMapSupport = function () {
69
+ sourceMapSupport.install({
70
+ environment: 'node',
71
+ handleUncaughtExceptions: false,
72
+ retrieveSourceMap: function (source) {
73
+ if (sourceMapCache[source]) {
74
+ return {
75
+ url: source,
76
+ map: fs.readFileSync(sourceMapCache[source], 'utf8')
77
+ };
78
+ }
79
+ }
80
+ });
81
+ };
82
+
83
+ exports.installPrecompilerHook = function () {
84
+ installPrecompiler(function (filename) {
85
+ var precompiled = opts.precompiled[filename];
86
+
87
+ if (precompiled) {
88
+ sourceMapCache[filename] = path.join(cacheDir, precompiled + '.js.map');
89
+ return fs.readFileSync(path.join(cacheDir, precompiled + '.js'), 'utf8');
90
+ }
91
+
92
+ return null;
93
+ });
94
+ };
95
+
96
+ exports.installDependencyTracking = function (dependencies, testPath) {
97
+ Object.keys(require.extensions).forEach(function (ext) {
98
+ var wrappedHandler = require.extensions[ext];
99
+ require.extensions[ext] = function (module, filename) {
100
+ if (filename !== testPath) {
101
+ dependencies.push(filename);
102
+ }
103
+
104
+ wrappedHandler(module, filename);
105
+ };
106
+ });
107
+ };
@@ -123,11 +123,11 @@ MiniReporter.prototype.unhandledError = function (err) {
123
123
 
124
124
  MiniReporter.prototype.reportCounts = function (time) {
125
125
  var lines = [
126
- this.passCount > 0 ? '\n ' + colors.pass(this.passCount, 'passed') : '',
127
- this.knownFailureCount > 0 ? '\n ' + colors.error(this.knownFailureCount, plur('known failure', this.knownFailureCount)) : '',
128
- this.failCount > 0 ? '\n ' + colors.error(this.failCount, 'failed') : '',
129
- this.skipCount > 0 ? '\n ' + colors.skip(this.skipCount, 'skipped') : '',
130
- this.todoCount > 0 ? '\n ' + colors.todo(this.todoCount, 'todo') : ''
126
+ this.passCount > 0 ? '\n ' + colors.pass(this.passCount, 'passed') : '',
127
+ this.knownFailureCount > 0 ? '\n ' + colors.error(this.knownFailureCount, plur('known failure', this.knownFailureCount)) : '',
128
+ this.failCount > 0 ? '\n ' + colors.error(this.failCount, 'failed') : '',
129
+ this.skipCount > 0 ? '\n ' + colors.skip(this.skipCount, 'skipped') : '',
130
+ this.todoCount > 0 ? '\n ' + colors.todo(this.todoCount, 'todo') : ''
131
131
  ].filter(Boolean);
132
132
 
133
133
  if (time && lines.length > 0) {
@@ -148,26 +148,22 @@ MiniReporter.prototype.finish = function (runStatus) {
148
148
  var status = this.reportCounts(time);
149
149
 
150
150
  if (this.rejectionCount > 0) {
151
- status += '\n ' + colors.error(this.rejectionCount, plur('rejection', this.rejectionCount));
151
+ status += '\n ' + colors.error(this.rejectionCount, plur('rejection', this.rejectionCount));
152
152
  }
153
153
 
154
154
  if (this.exceptionCount > 0) {
155
- status += '\n ' + colors.error(this.exceptionCount, plur('exception', this.exceptionCount));
155
+ status += '\n ' + colors.error(this.exceptionCount, plur('exception', this.exceptionCount));
156
156
  }
157
157
 
158
158
  if (runStatus.previousFailCount > 0) {
159
- status += '\n ' + colors.error(runStatus.previousFailCount, 'previous', plur('failure', runStatus.previousFailCount), 'in test files that were not rerun');
159
+ status += '\n ' + colors.error(runStatus.previousFailCount, 'previous', plur('failure', runStatus.previousFailCount), 'in test files that were not rerun');
160
160
  }
161
161
 
162
- var i = 0;
163
-
164
162
  if (this.knownFailureCount > 0) {
165
163
  runStatus.knownFailures.forEach(function (test) {
166
- i++;
167
-
168
164
  var title = test.title;
169
165
 
170
- status += '\n\n\n ' + colors.error(i + '.', title);
166
+ status += '\n\n ' + colors.title(title);
171
167
  // TODO output description with link
172
168
  // status += colors.stack(description);
173
169
  });
@@ -179,19 +175,26 @@ MiniReporter.prototype.finish = function (runStatus) {
179
175
  return;
180
176
  }
181
177
 
182
- i++;
183
-
184
178
  var title = test.error ? test.title : 'Unhandled Error';
185
179
  var description;
180
+ var errorTitle = ' ' + test.error.message + '\n';
181
+ var isPowerAssert = test.error.message.split('\n').length > 1;
186
182
 
187
183
  if (test.error) {
188
- description = ' ' + test.error.message + '\n ' + stripFirstLine(test.error.stack).trimRight();
184
+ description = stripFirstLine(test.error.stack).trimRight();
189
185
  } else {
190
186
  description = JSON.stringify(test);
191
187
  }
192
188
 
193
- status += '\n\n\n ' + colors.error(i + '.', title) + '\n';
194
- status += colors.stack(description);
189
+ if (isPowerAssert) {
190
+ description = stripFirstLine(description).replace(/ {3}/g, ' ');
191
+ } else {
192
+ description.replace(/ {3}/g, ' ');
193
+ }
194
+
195
+ status += '\n\n ' + colors.title(title) + '\n';
196
+ status += colors.stack(errorTitle);
197
+ status += colors.errorStack(description);
195
198
  });
196
199
  }
197
200
 
@@ -201,21 +204,23 @@ MiniReporter.prototype.finish = function (runStatus) {
201
204
  return;
202
205
  }
203
206
 
204
- i++;
205
-
206
207
  if (err.type === 'exception' && err.name === 'AvaError') {
207
- status += '\n\n\n ' + colors.error(cross + ' ' + err.message);
208
+ status += '\n\n ' + colors.error(cross + ' ' + err.message);
208
209
  } else {
209
210
  var title = err.type === 'rejection' ? 'Unhandled Rejection' : 'Uncaught Exception';
210
211
  var description = err.stack ? err.stack.trimRight() : JSON.stringify(err);
212
+ description = description.split('\n');
213
+ var errorTitle = description[0];
214
+ var errorStack = description.slice(1).join('\n');
211
215
 
212
- status += '\n\n\n ' + colors.error(i + '.', title) + '\n';
213
- status += ' ' + colors.stack(description);
216
+ status += '\n\n ' + colors.title(title) + '\n';
217
+ status += ' ' + colors.stack(errorTitle) + '\n';
218
+ status += colors.errorStack(errorStack);
214
219
  }
215
220
  });
216
221
  }
217
222
 
218
- return status + '\n';
223
+ return status + '\n\n';
219
224
  };
220
225
 
221
226
  MiniReporter.prototype.section = function () {
@@ -247,7 +252,7 @@ MiniReporter.prototype._update = function (data) {
247
252
  lastLine = lastLine.substring(lastLine.length - (lastLine.length % columns));
248
253
 
249
254
  // Don't delete the last log line if it's completely empty.
250
- if (lastLine.length) {
255
+ if (lastLine.length > 0) {
251
256
  ct++;
252
257
  }
253
258
 
@@ -257,7 +262,7 @@ MiniReporter.prototype._update = function (data) {
257
262
  // Rewrite the last log line.
258
263
  str += lastLine;
259
264
 
260
- if (str.length) {
265
+ if (str.length > 0) {
261
266
  this.stream.write(str);
262
267
  }
263
268
 
@@ -269,7 +274,7 @@ MiniReporter.prototype._update = function (data) {
269
274
 
270
275
  var currentStatus = this.currentStatus;
271
276
 
272
- if (currentStatus.length) {
277
+ if (currentStatus.length > 0) {
273
278
  lastLine = this.lastLineTracker.lastLine();
274
279
  // We need a newline at the end of the last log line, before the status message.
275
280
  // However, if the last log line is the exact width of the terminal a newline is implied,
package/lib/run-status.js CHANGED
@@ -1,12 +1,12 @@
1
1
  'use strict';
2
2
  var EventEmitter = require('events').EventEmitter;
3
- var path = require('path');
4
3
  var util = require('util');
5
4
  var chalk = require('chalk');
6
5
  var isObj = require('is-obj');
7
6
  var flatten = require('arr-flatten');
8
7
  var figures = require('figures');
9
- var formatter = require('./enhance-assert').formatter();
8
+ var autoBind = require('auto-bind');
9
+ var prefixTitle = require('./prefix-title');
10
10
 
11
11
  function RunStatus(opts) {
12
12
  if (!(this instanceof RunStatus)) {
@@ -34,9 +34,7 @@ function RunStatus(opts) {
34
34
  this.stats = [];
35
35
  this.tests = [];
36
36
 
37
- Object.keys(RunStatus.prototype).forEach(function (key) {
38
- this[key] = this[key].bind(this);
39
- }, this);
37
+ autoBind(this);
40
38
  }
41
39
 
42
40
  util.inherits(RunStatus, EventEmitter);
@@ -108,18 +106,8 @@ RunStatus.prototype.handleTest = function (test) {
108
106
  test.title = this.prefixTitle(test.file) + test.title;
109
107
 
110
108
  if (test.error) {
111
- if (test.error.powerAssertContext) {
112
- var message = formatter(test.error.powerAssertContext);
113
-
114
- if (test.error.originalMessage) {
115
- message = test.error.originalMessage + ' ' + message;
116
- }
117
-
118
- test.error.message = message;
119
- }
120
-
121
109
  if (test.error.name !== 'AssertionError') {
122
- test.error.message = 'failed with "' + test.error.message + '"';
110
+ test.error.message = 'Error: ' + test.error.message;
123
111
  }
124
112
 
125
113
  this.errors.push(test);
@@ -139,26 +127,7 @@ RunStatus.prototype.prefixTitle = function (file) {
139
127
 
140
128
  var separator = ' ' + chalk.gray.dim(figures.pointerSmall) + ' ';
141
129
 
142
- var prefix = path.relative('.', file)
143
- .replace(this.base, function (match, offset) {
144
- // only replace this.base if it is found at the start of the path
145
- return offset === 0 ? '' : match;
146
- })
147
- .replace(/\.spec/, '')
148
- .replace(/\.test/, '')
149
- .replace(/test\-/g, '')
150
- .replace(/\.js$/, '')
151
- .split(path.sep)
152
- .filter(function (p) {
153
- return p !== '__tests__';
154
- })
155
- .join(separator);
156
-
157
- if (prefix.length > 0) {
158
- prefix += separator;
159
- }
160
-
161
- return prefix;
130
+ return prefixTitle(file, this.base, separator);
162
131
  };
163
132
 
164
133
  RunStatus.prototype.handleOutput = function (channel, data) {
package/lib/runner.js CHANGED
@@ -46,6 +46,7 @@ function Runner(options) {
46
46
 
47
47
  this.results = [];
48
48
  this.tests = new TestCollection();
49
+ this.hasStarted = false;
49
50
  this._bail = options.bail;
50
51
  this._serial = options.serial;
51
52
  this._match = options.match || [];
@@ -61,6 +62,11 @@ optionChain(chainableMethods, function (opts, args) {
61
62
  var fn;
62
63
  var macroArgIndex;
63
64
 
65
+ if (this.hasStarted) {
66
+ throw new Error('All tests and hooks must be declared synchronously in your ' +
67
+ 'test file, and cannot be nested within other tests or hooks.');
68
+ }
69
+
64
70
  if (typeof args[0] === 'string') {
65
71
  title = args[0];
66
72
  fn = args[1];
@@ -196,5 +202,9 @@ Runner.prototype.run = function (options) {
196
202
 
197
203
  this.tests.on('test', this._addTestResult);
198
204
 
205
+ this.hasStarted = true;
206
+
199
207
  return Promise.resolve(this.tests.build(this._bail).run()).then(this._buildStats);
200
208
  };
209
+
210
+ Runner._chainableMethods = chainableMethods.chainableMethods;
package/lib/sequence.js CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
  var isPromise = require('is-promise');
3
+ var autoBind = require('auto-bind');
3
4
  var AvaError = require('./ava-error');
4
5
 
5
6
  function noop() {}
@@ -21,10 +22,7 @@ function Sequence(tests, bail) {
21
22
  this.tests = tests;
22
23
  this.bail = bail || false;
23
24
 
24
- // TODO(vdemedes): separate into a utility (it's being used in serveral places)
25
- Object.keys(Sequence.prototype).forEach(function (key) {
26
- this[key] = this[key].bind(this);
27
- }, this);
25
+ autoBind(this);
28
26
  }
29
27
 
30
28
  Sequence.prototype.run = function () {