Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Reporter Output via Mixins #3874

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/reporters/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ function Base(runner, options) {
this.options = options || {};
this.runner = runner;
this.stats = runner.stats; // assigned so Reporters keep a closer reference
this.preferConsole = !!(this.options.reporterOptions || {}).preferConsole;

runner.on(EVENT_TEST_PASS, function(test) {
if (test.duration > test.slow()) {
Expand Down
73 changes: 73 additions & 0 deletions lib/reporters/writers/console.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
'use strict';

/**
* Save timer references (mochajs/mocha#237).
*/
var setTimeout = global.setTimeout;

/**
* Provides methods used for output by console-oriented reporters.
*
* @description
* Not meant to be used directly.
*
* @package
* @mixin ConsoleWriter
*/
var ConsoleWriter = function() {
var buffer = '';

/**
* Writes string to reporter output.
*
* @package
* @param {string} str - String to write to the reporter output.
* @returns {this}
* @chainable
*/
this.write = function(str) {
if (str) {
buffer += str; // Store until `console.log` invoked
}
return this;
};

/**
* Writes newline-terminated string to reporter output.
*
* @package
* @param {string} str - String to write to the reporter output.
* @returns {this}
* @chainable
*/
this.writeln = function(str) {
this.write(str);
console.log(buffer);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth saving a reference like the timer? (let users stub)

Copy link
Contributor Author

@plroebuck plroebuck Apr 20, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't bothered integrating with your PR at this point, but it would be done in this one file for any reporter implementing ReportWriter. It's quite honestly trivial to implement for a reporter; it's writing the unit tests that takes all the time...

buffer = ''; // Reset
return this;
};

/**
* Invokes provided completion function.
*
* @description
* Writes any buffered output.
*
* @package
* @param {Function} cb - Callback invoked when cleanup done
*/
this._done = function(cb) {
if (buffer) {
var delay = 1000; // One second

this.writeln();
setTimeout(cb, delay);
Copy link
Contributor

@craigtaub craigtaub Apr 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does console writer need a timeout? Why 1s?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this method is invoked immediately prior to Mocha.run finishing up, the delay is there to (hopefully) allow completion of flushing any buffered (i.e., unwritten) output to the screen. The '1s' time is arbirtrary for the moment; it's rather long for CPU-related tasks, but as this is IO-related I'm uncertain just what a good value would be.

Be happy to change the comment to "arbitrary", as I usually would have...

} else {
cb();
}
};

return this;
};

module.exports = ConsoleWriter;
97 changes: 97 additions & 0 deletions lib/reporters/writers/file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
'use strict';

/**
* Module dependencies.
*/
var fs = require('fs');
var path = require('path');
var mkdirp = require('mkdirp');
var errors = require('../../errors');

var createUnsupportedError = errors.createUnsupportedError;

/**
* Provides methods used for output by file-oriented reporters.
*
* @description
* Not meant to be used directly.
*
* @package
* @mixin FileWriter
*/
var FileWriter = function() {
/**
* Writes string to reporter output.
*
* @package
* @param {string} str - String to write to the reporter output.
* @returns {this}
* @chainable
*/
this.write = function(str) {
this._fileStream.write(str);
return this;
};

/**
* Writes newline-terminated string to reporter output.
*
* @package
* @param {string} str - String to write to the reporter output.
* @returns {this}
* @chainable
*/
this.writeln = function(str) {
return this.write(str + '\n');
};

/**
* Invokes provided completion function.
*
* @description
* Closes the filesystem output stream.
*
* @package
* @param {Function} cb - Callback invoked when cleanup done
*/
this._done = function(cb) {
var self = this;
this._fileStream.end(function() {
self._fileStream = null;
cb();
});
};

return this;
};

/**
* Creates filesystem output stream, if possible.
*
* @static
* @package
* @param {Object} reporter - Instance of reporter
* @param {string} pathname - Pathname of desired output file
* @throws Various errors are possible
*/
FileWriter.createWriteStream = function(reporter, pathname) {
if (!fs.createWriteStream) {
throw createUnsupportedError('file output not supported in browser');
}

mkdirp.sync(path.dirname(pathname));
reporter._fileStream = fs.createWriteStream(pathname);
};

/**
* Determines if writer should write output to filesystem.
*
* @public
* @static
* @returns {boolean} whether writer should use `FileWriter` methods
*/
FileWriter.isFileWriter = function(reporter) {
return reporter.hasOwnProperty('_fileStream') && !!reporter._fileStream;
};

module.exports = FileWriter;
3 changes: 3 additions & 0 deletions lib/reporters/writers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict';

exports.ReportWriter = require('./report');
181 changes: 181 additions & 0 deletions lib/reporters/writers/report.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
'use strict';

/**
* Module dependencies.
*/
var sprintf = require('util').format;
var ConsoleWriter = require('./console');
var FileWriter = require('./file');
var StreamWriter = require('./stream');

/**
* Provides methods used for output by reporters.
*
* @description
* Not meant to be used directly. Runtime-adaptive.
* Once _any_ functional mixin method is iinvoked, specializations will occur
* which may augment/replace existing methods/properties.
*
* @public
* @mixin ReportWriter
* @example
* var ReportWriter = require('./writers/report');
* var MyReporter = function(runner, options) {
* runner.on(EVENT_TEST_PASS, function(test) {
* this.println(test);
* });
* };
* ReportWriter.call(MyReporter.prototype);
*/
var ReportWriter = function() {
/**
* Adapts prototype methods to specific output destination.
*
* @param {Object} reporter - Reporter instance
* @returns {reporter}
* @chainable
*/
function adapt(reporter) {
var preferConsole = !!reporter.preferConsole;

// :DEBUG: console.log('reporter:', reporter);
if (ReportWriter.isFileWriter(reporter)) {
// :DEBUG: console.log('### specialized as FileWriter');
FileWriter.call(reporter);
} else if (!preferConsole && ReportWriter.stdoutExists()) {
// :DEBUG: console.log('### specialized as StreamWriter');
StreamWriter.call(reporter);
} else {
// :DEBUG: console.log('### specialized as ConsoleWriter');
ConsoleWriter.call(reporter);
}

return reporter;
}

/**
* Writes string to reporter output.
*
* @description
* Overrides this class method by creating same-named instance method.
*
* @package
* @param {string} str - String to write to the reporter output.
* @returns {this}
* @chainable
*/
this.write = function(str) {
return adapt(this).write(str);
};

/**
* Writes newline-terminated string to reporter output.
*
* @description
* Overrides this class method by creating same-named instance method.
*
* @package
* @param {string} str - String to write to the reporter output.
* @returns {this}
* @chainable
*/
this.writeln = function(str) {
return adapt(this).writeln(str);
};

/**
* Writes formatted string to reporter output.
* @see {@link https://nodejs.org/api/util.html#util_util_format_format_args}
*
* @package
* @param {string} format - `printf`-like format string
* @param {...*} [varArgs] - Format string arguments
* @returns {this}
* @chainable
*/
this.print = function(format, varArgs) {
var vargs = Array.prototype.slice.call(arguments);
var str = sprintf.apply(null, vargs);
return this.write(str);
};

/**
* Writes newline-terminated formatted string to reporter output.
*
* @package
* @param {string} format - `printf`-like format string
* @param {...*} [varArgs] - Format string arguments
* @returns {this}
* @chainable
*/
this.println = function(format, varArgs) {
var vargs = Array.prototype.slice.call(arguments);
vargs[0] += '\n';
return this.print(this, vargs);
};

/**
* Invokes provided completion function.
*
* @package
* @param {number} nfailures - Count of failed tests (integer)
* @param {DoneCB} fn - Callback invoked when test execution completes
*/
this.done = function(nfailures, fn) {
if (this._done) {
this._done(function() {
fn(nfailures);
});
} else {
fn(nfailures);
}
};

return this;
};

/**
* Attempts creation of filesystem output stream, if
* <code>reporter.options.output</code> given.
*
* @public
* @static
* @param {Object} reporter - Instance of reporter
* @throws Various errors are possible if file creation attempted
*/
ReportWriter.maybeCreateFileOutput = function(reporter) {
var reporterOptions = (reporter.options || {}).reporterOptions || {};
var pathname = reporterOptions.output;

if (pathname) {
FileWriter.createWriteStream(reporter, pathname);
}
};

/**
* Determines if writer can write output to filesystem.
*
* @private
* @static
* @param {Object} reporter - Instance of reporter
* @returns {boolean} whether writer should use `FileWriter` methods
*/
ReportWriter.isFileWriter = function(reporter) {
return FileWriter.isFileWriter(reporter);
};

/**
* Determines if `process.stdout` exists in current execution environment.
*
* @description
* On PhantomJS, `process.stdout` doesn't exist...
*
* @public
* @static
* @returns {boolean} whether `process.stdout` can be used
*/
ReportWriter.stdoutExists = function() {
return typeof process === 'object' && !!process.stdout;
};

module.exports = ReportWriter;
Loading