UNPKG

4.84 kBJavaScriptView Raw
1#!/usr/bin/env node
2'use strict';
3var fs = require('fs');
4var path = require('path');
5var figures = require('figures');
6var flatten = require('arr-flatten');
7var globby = require('globby');
8var meow = require('meow');
9var updateNotifier = require('update-notifier');
10var chalk = require('chalk');
11var Promise = require('bluebird');
12var fork = require('./lib/fork');
13var log = require('./lib/logger');
14
15// Bluebird specific
16Promise.longStackTraces();
17
18var cli = meow({
19 help: [
20 'Usage',
21 ' ava [<file|folder|glob> ...]',
22 '',
23 'Options',
24 ' --init Add AVA to your project',
25 ' --fail-fast Stop after first test failure',
26 ' --serial Run tests serially',
27 '',
28 'Examples',
29 ' ava',
30 ' ava test.js test2.js',
31 ' ava test-*.js',
32 ' ava --init',
33 ' ava --init foo.js',
34 '',
35 'Default patterns when no arguments:',
36 'test.js test-*.js test/*.js'
37 ]
38}, {
39 string: ['_'],
40 boolean: [
41 'fail-fast',
42 'serial'
43 ]
44});
45
46var testCount = 0;
47var fileCount = 0;
48var unhandledRejectionCount = 0;
49var uncaughtExceptionCount = 0;
50var errors = [];
51
52function error(err) {
53 console.error(err.stack);
54 flushIoAndExit(1);
55}
56
57function prefixTitle(file) {
58 var separator = ' ' + chalk.gray.dim(figures.pointerSmall) + ' ';
59
60 var base = path.dirname(cli.input[0]);
61
62 if (base === '.') {
63 base = cli.input[0] || 'test';
64 }
65
66 base += path.sep;
67
68 var prefix = path.relative(process.cwd(), file)
69 .replace(base, '')
70 .replace(/\.spec/, '')
71 .replace(/test\-/g, '')
72 .replace(/\.js$/, '')
73 .split(path.sep)
74 .join(separator);
75
76 if (prefix.length > 0) {
77 prefix += separator;
78 }
79
80 return prefix;
81}
82
83function stats(stats) {
84 testCount += stats.testCount;
85}
86
87function test(data) {
88 var isError = data.error.message;
89
90 if (fileCount > 1) {
91 data.title = prefixTitle(data.file) + data.title;
92 }
93
94 if (isError) {
95 log.error(data.title, chalk.red(data.error.message));
96
97 errors.push(data);
98 } else {
99 // if there's only one file and one anonymous test
100 // don't log it
101 if (fileCount === 1 && testCount === 1 && data.title === '[anonymous]') {
102 return;
103 }
104
105 log.test(data);
106 }
107}
108
109function run(file) {
110 var args = [file];
111
112 if (cli.flags.failFast) {
113 args.push('--fail-fast');
114 }
115
116 if (cli.flags.serial) {
117 args.push('--serial');
118 }
119
120 return fork(args)
121 .on('stats', stats)
122 .on('test', test)
123 .on('unhandledRejections', rejections)
124 .on('uncaughtException', uncaughtException)
125 .on('data', function (data) {
126 process.stdout.write(data);
127 });
128}
129
130function rejections(data) {
131 var unhandled = data.unhandledRejections;
132 log.unhandledRejections(data.file, unhandled);
133 unhandledRejectionCount += unhandled.length;
134}
135
136function uncaughtException(data) {
137 uncaughtExceptionCount++;
138 log.uncaughtException(data.file, data.uncaughtException);
139}
140
141function sum(arr, key) {
142 var result = 0;
143
144 arr.forEach(function (item) {
145 result += item[key];
146 });
147
148 return result;
149}
150
151function exit(results) {
152 // assemble stats from all tests
153 var stats = results.map(function (result) {
154 return result.stats;
155 });
156
157 var tests = results.map(function (result) {
158 return result.tests;
159 });
160
161 var passed = sum(stats, 'passCount');
162 var failed = sum(stats, 'failCount');
163
164 log.write();
165 log.report(passed, failed, unhandledRejectionCount, uncaughtExceptionCount);
166 log.write();
167
168 if (failed > 0) {
169 log.errors(flatten(tests));
170 }
171
172 process.stdout.write('');
173
174 flushIoAndExit(
175 failed > 0 || unhandledRejectionCount > 0 || uncaughtExceptionCount > 0 ? 1 : 0
176 );
177}
178
179function flushIoAndExit(code) {
180 // TODO: figure out why this needs to be here to
181 // correctly flush the output when multiple test files
182 process.stdout.write('');
183 process.stderr.write('');
184
185 // timeout required to correctly flush io on Node 0.10 Windows
186 setTimeout(function () {
187 process.exit(code);
188 }, process.env.AVA_APPVEYOR ? 500 : 0);
189}
190
191function init(files) {
192 log.write();
193
194 return handlePaths(files)
195 .map(function (file) {
196 return path.resolve('.', file);
197 })
198 .then(function (files) {
199 if (files.length === 0) {
200 log.error('Couldn\'t find any files to test\n');
201 process.exit(1);
202 }
203
204 fileCount = files.length;
205
206 var tests = files.map(run);
207
208 return Promise.all(tests);
209 });
210}
211
212function handlePaths(files) {
213 if (files.length === 0) {
214 files = [
215 'test.js',
216 'test-*.js',
217 'test/*.js'
218 ];
219 }
220
221 // convert pinkie-promise to Bluebird promise
222 files = Promise.resolve(globby(files));
223
224 return files
225 .map(function (file) {
226 if (fs.statSync(file).isDirectory()) {
227 return handlePaths([path.join(file, '*.js')]);
228 }
229
230 return file;
231 })
232 .then(flatten)
233 .filter(function (file) {
234 return path.extname(file) === '.js' && path.basename(file)[0] !== '_';
235 });
236}
237
238updateNotifier({pkg: cli.pkg}).notify();
239
240if (cli.flags.init) {
241 require('ava-init')().catch(error);
242} else {
243 init(cli.input).then(exit).catch(error);
244}