UNPKG

5.49 kBJavaScriptView Raw
1var fs = require('fs');
2var util = require('util');
3var debug = require('debug')('mocha:multi');
4
5// Let mocha decide about tty early
6require('mocha/lib/reporters/base');
7
8module.exports = MochaMulti
9
10// Make sure we don't lose these!
11var stdout = process.stdout;
12var stderr = process.stderr;
13
14function MochaMulti(runner, options) {
15 var setup;
16 var reporters = (options && options.reporterOptions);
17 if (reporters && Object.keys(reporters).length > 0) {
18 debug("options %j", options);
19 setup = [];
20 Object.keys(reporters).forEach(function(reporter) {
21 debug("adding reporter %j %j", reporter, reporters[reporter]);
22 setup.push([ reporter, reporters[reporter].stdout, reporters[reporter].options ]);
23 });
24 } else {
25 setup = parseSetup();
26 }
27 debug("setup %j", setup);
28 var streams = initReportersAndStreams(runner, setup);
29 // Remove nulls
30 streams = streams.filter(identity);
31
32 //we actually need to wait streams only if they are present
33 if(streams.length > 0) {
34 awaitStreamsOnExit(streams);
35 }
36}
37
38var msgs = {
39 no_definitions: "reporter definitions should be set in \
40the `multi` shell variable\n\
41eg. `multi='dot=- xunit=file.xml' mocha`",
42 invalid_definition: "'%s' is an invalid definition\n\
43expected <reporter>=<destination>",
44 invalid_reporter: "Unable to find '%s' reporter"
45}
46function bombOut(id) {
47 var args = Array.prototype.slice.call(arguments, 0);
48 args[0] = 'ERROR: ' + msgs[id];
49 stderr.write(util.format.apply(util, args) + "\n");
50 process.exit(1);
51}
52
53function parseSetup() {
54 var reporterDefinition = process.env.multi || '';
55 var reporterDefs = reporterDefinition.trim().split(/\s/).filter(identity);
56 if (!reporterDefs.length) {
57 bombOut('no_definitions');
58 }
59 debug("Got reporter defs: %j", reporterDefs);
60 return reporterDefs.map(parseReporter);
61}
62
63function parseReporter(definition) {
64 var pair = definition.split('=');
65 if (pair.length != 2) {
66 bombOut('invalid_definition', definition);
67 }
68 return pair;
69}
70
71function initReportersAndStreams(runner, setup) {
72 return setup.map(function(definition) {
73 var reporter=definition[0], outstream=definition[1], options=definition[2]
74
75 debug("Initialising reporter '%s' to '%s' with options %j", reporter, outstream, options);
76
77 var stream = resolveStream(outstream);
78 var shim = createRunnerShim(runner, stream);
79
80 debug("Shimming runner into reporter '%s' %j", reporter, options);
81
82 withReplacedStdout(stream, function() {
83 var Reporter = resolveReporter(reporter);
84 return new Reporter(shim, options || {});
85 })
86
87 return stream;
88 })
89}
90
91function awaitStreamsOnExit(streams) {
92 var exit = process.exit;
93 var num = streams.length;
94 process.exit = function(code) {
95 var quit = exit.bind(process, code);
96 streams.forEach(function(stream) {
97 stream.end(function() {
98 num--;
99 onClose();
100 });
101 });
102 function onClose() {
103 if(num === 0) {
104 quit();
105 }
106 }
107 };
108}
109
110function resolveReporter(name) {
111 // Cribbed from Mocha.prototype.reporter()
112 var reporter;
113 reporter = safeRequire('mocha/lib/reporters/' + name);
114 if (!reporter) {
115 reporter = safeRequire(name);
116 }
117 if (!reporter) {
118 bombOut('invalid_reporter', name);
119 }
120 debug("Resolved reporter '%s' into '%s'", name, util.inspect(reporter));
121 return reporter;
122}
123
124function safeRequire(module) {
125 try {
126 return require(module);
127 } catch (err) {
128 if (!/Cannot find/.exec(err.message)) {
129 throw err;
130 }
131 return null;
132 }
133}
134
135function resolveStream(destination) {
136 if (destination == '-') {
137 debug("Resolved stream '-' into stdout and stderr");
138 return null;
139 }
140 debug("Resolved stream '%s' into writeable file stream", destination);
141 // Ensure we can write here
142 fs.writeFileSync(destination, '');
143 return fs.createWriteStream(destination);
144}
145
146function createRunnerShim(runner, stream) {
147 var shim = new (require('events').EventEmitter)();
148
149 addDelegate('grepTotal');
150 addDelegate('suite');
151 addDelegate('total');
152
153 function addDelegate(prop) {
154 shim.__defineGetter__(prop, function() {
155 var property = runner[prop];
156 if (typeof property === 'function') {
157 property = property.bind(runner);
158 }
159 return property;
160 });
161 }
162
163 var delegatedEvents = {};
164
165 shim.on('newListener', function(event) {
166
167 if (event in delegatedEvents) return;
168
169 delegatedEvents[event] = true;
170 debug("Shim: Delegating '%s'", event);
171
172 runner.on(event, function() {
173 var eventArgs = Array.prototype.slice.call(arguments, 0);
174 eventArgs.unshift(event);
175
176 withReplacedStdout(stream, function() {
177 shim.emit.apply(shim, eventArgs)
178 })
179
180 })
181
182 })
183
184 return shim;
185}
186
187function withReplacedStdout(stream, func) {
188 if (!stream) {
189 return func();
190 }
191
192 // The hackiest of hacks
193 debug('Replacing stdout');
194
195 var stdoutGetter = Object.getOwnPropertyDescriptor(process, 'stdout').get;
196 var stderrGetter = Object.getOwnPropertyDescriptor(process, 'stderr').get;
197
198 console._stdout = stream;
199 console._stderr = stream;
200 process.__defineGetter__('stdout', function() { return stream });
201 process.__defineGetter__('stderr', function() { return stream });
202
203 try {
204 func();
205 } finally {
206 console._stdout = stdout;
207 console._stderr = stderr;
208 process.__defineGetter__('stdout', stdoutGetter);
209 process.__defineGetter__('stderr', stderrGetter);
210 debug('stdout restored');
211 }
212}
213
214function identity(x) {
215 return x;
216}