Skip to content

Commit

Permalink
refactor: split reporter output from Dredd's internal logging
Browse files Browse the repository at this point in the history
A part of the effort to separate application logging from the reporters output.

Enables #1089 and subsequently #765, supersedes #1099

BREAKING CHANGE: The --level option is no longer able to affect reporter output, it only affects application logging output.
  • Loading branch information
honzajavorek committed Jan 17, 2019
1 parent 1629924 commit 3597a58
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 89 deletions.
3 changes: 2 additions & 1 deletion lib/addHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const proxyquire = require('proxyquire').noCallThru();
const Hooks = require('./Hooks');
const HooksWorkerClient = require('./HooksWorkerClient');
const logger = require('./logger');
const reporterOutputLogger = require('./reporters/reporterOutputLogger');
const resolveHookfiles = require('./resolveHookfiles');

// Note: runner.configuration.options must be defined
Expand All @@ -24,7 +25,7 @@ Stack: ${error.stack}
}

if (!runner.logs) { runner.logs = []; }
runner.hooks = new Hooks({ logs: runner.logs, logger });
runner.hooks = new Hooks({ logs: runner.logs, logger: reporterOutputLogger });

if (!runner.hooks.transactions) { runner.hooks.transactions = {}; }

Expand Down
14 changes: 13 additions & 1 deletion lib/configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ const clone = require('clone');
const { EventEmitter } = require('events');

const logger = require('./logger');
const reporterOutputLogger = require('./reporters/reporterOutputLogger');


function coerceToArray(value) {
if (Array.isArray(value)) {
Expand All @@ -14,6 +16,7 @@ function coerceToArray(value) {
return value;
}


function applyLoggingOptions(options) {
// Color can be either specified as "stringified bool" or bool (nothing else
// is expected valid value). Here we're coercing the value to boolean.
Expand All @@ -24,13 +27,21 @@ function applyLoggingOptions(options) {
}

logger.transports.console.colorize = options.color;
logger.transports.console.silent = options.silent;
reporterOutputLogger.transports.console.colorize = options.color;

logger.transports.console.timestamp = options.timestamp;
reporterOutputLogger.transports.console.timestamp = options.timestamp;

logger.transports.console.silent = options.silent;
reporterOutputLogger.transports.console.silent = options.silent;

logger.transports.console.level = options.level;
reporterOutputLogger.transports.console.level = 'test';

return options;
}


function applyConfiguration(config) {
const configuration = {
server: null,
Expand Down Expand Up @@ -117,6 +128,7 @@ function applyConfiguration(config) {
return configuration;
}


module.exports = {
applyConfiguration,
applyLoggingOptions,
Expand Down
26 changes: 4 additions & 22 deletions lib/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,10 @@ module.exports = new (winston.Logger)({
new (winston.transports.Console)({ colorize: true }),
],
levels: {
silly: 14,
debug: 13,
verbose: 12,
info: 11,
test: 10,
pass: 9,
fail: 8,
complete: 7,
actual: 6,
expected: 5,
hook: 4,
request: 3,
skip: 2,
silly: 5,
debug: 4,
verbose: 3,
info: 2,
warn: 1,
error: 0,
},
Expand All @@ -26,15 +17,6 @@ module.exports = new (winston.Logger)({
debug: 'cyan',
verbose: 'magenta',
info: 'blue',
test: 'yellow',
pass: 'green',
fail: 'red',
complete: 'green',
actual: 'red',
expected: 'red',
hook: 'green',
request: 'green',
skip: 'yellow',
warn: 'yellow',
error: 'red',
},
Expand Down
3 changes: 2 additions & 1 deletion lib/reporters/ApiaryReporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const os = require('os');
const request = require('request');

const logger = require('../logger');
const reporterOutputLogger = require('./reporterOutputLogger');
const packageData = require('../../package.json');


Expand Down Expand Up @@ -186,7 +187,7 @@ ApiaryReporter.prototype.configureEmitter = function configureEmitter(emitter) {
this._performRequestAsync(path, 'PATCH', data, (error) => {
if (error) { return callback(error); }
const reportUrl = this.reportUrl || `https://app.apiary.io/${this.configuration.apiSuite}/tests/run/${this.remoteId}`;
logger.complete(`See results in Apiary at: ${reportUrl}`);
reporterOutputLogger.complete(`See results in Apiary at: ${reportUrl}`);
callback();
});
});
Expand Down
80 changes: 40 additions & 40 deletions lib/reporters/CLIReporter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
const logger = require('../logger');
const reporterOutputLogger = require('./reporterOutputLogger');
const prettifyResponse = require('../prettifyResponse');


const CONNECTION_ERRORS = [
'ECONNRESET',
'ENOTFOUND',
'ESOCKETTIMEDOUT',
'ETIMEDOUT',
'ECONNREFUSED',
'EHOSTUNREACH',
'EPIPE',
];


function CLIReporter(emitter, stats, tests, inlineErrors, details) {
this.type = 'cli';
this.stats = stats;
Expand All @@ -22,76 +35,63 @@ CLIReporter.prototype.configureEmitter = function configureEmitter(emitter) {

emitter.on('end', (callback) => {
if (!this.inlineErrors) {
if (this.errors.length !== 0) { logger.info('Displaying failed tests...'); }
for (const test of this.errors) {
logger.fail(`${test.title} duration: ${test.duration}ms`);
logger.fail(test.message);
if (test.request) { logger.request(`\n${prettifyResponse(test.request)}\n`); }
if (test.expected) { logger.expected(`\n${prettifyResponse(test.expected)}\n`); }
if (test.actual) { logger.actual(`\n${prettifyResponse(test.actual)}\n\n`); }
if (this.errors.length) {
logger.info('Displaying failed tests...');
}
this.errors.forEach((test) => {
reporterOutputLogger.fail(`${test.title} duration: ${test.duration}ms`);
reporterOutputLogger.fail(test.message);
if (test.request) reporterOutputLogger.request(`\n${prettifyResponse(test.request)}\n`);
if (test.expected) reporterOutputLogger.expected(`\n${prettifyResponse(test.expected)}\n`);
if (test.actual) reporterOutputLogger.actual(`\n${prettifyResponse(test.actual)}\n\n`);
});
}

if (this.stats.tests > 0) {
logger.complete(`${this.stats.passes} passing, `
reporterOutputLogger.complete(`${this.stats.passes} passing, `
+ `${this.stats.failures} failing, `
+ `${this.stats.errors} errors, `
+ `${this.stats.skipped} skipped, `
+ `${this.stats.tests} total`);
}

logger.complete(`Tests took ${this.stats.duration}ms`);
reporterOutputLogger.complete(`Tests took ${this.stats.duration}ms`);
callback();
});

emitter.on('test pass', (test) => {
logger.pass(`${test.title} duration: ${test.duration}ms`);
reporterOutputLogger.pass(`${test.title} duration: ${test.duration}ms`);
if (this.details) {
logger.request(`\n${prettifyResponse(test.request)}\n`);
logger.expected(`\n${prettifyResponse(test.expected)}\n`);
logger.actual(`\n${prettifyResponse(test.actual)}\n\n`);
reporterOutputLogger.request(`\n${prettifyResponse(test.request)}\n`);
reporterOutputLogger.expected(`\n${prettifyResponse(test.expected)}\n`);
reporterOutputLogger.actual(`\n${prettifyResponse(test.actual)}\n\n`);
}
});

emitter.on('test skip', test => logger.skip(test.title));
emitter.on('test skip', test => reporterOutputLogger.skip(test.title));

emitter.on('test fail', (test) => {
logger.fail(`${test.title} duration: ${test.duration}ms`);
reporterOutputLogger.fail(`${test.title} duration: ${test.duration}ms`);
if (this.inlineErrors) {
logger.fail(test.message);
if (test.request) { logger.request(`\n${prettifyResponse(test.request)}\n`); }
if (test.expected) { logger.expected(`\n${prettifyResponse(test.expected)}\n`); }
if (test.actual) { logger.actual(`\n${prettifyResponse(test.actual)}\n\n`); }
reporterOutputLogger.fail(test.message);
if (test.request) { reporterOutputLogger.request(`\n${prettifyResponse(test.request)}\n`); }
if (test.expected) { reporterOutputLogger.expected(`\n${prettifyResponse(test.expected)}\n`); }
if (test.actual) { reporterOutputLogger.actual(`\n${prettifyResponse(test.actual)}\n\n`); }
} else {
this.errors.push(test);
}
});

emitter.on('test error', (error, test) => {
const connectionErrors = [
'ECONNRESET',
'ENOTFOUND',
'ESOCKETTIMEDOUT',
'ETIMEDOUT',
'ECONNREFUSED',
'EHOSTUNREACH',
'EPIPE',
];

if (connectionErrors.indexOf(error.code) > -1) {
if (CONNECTION_ERRORS.includes(error.code)) {
test.message = 'Error connecting to server under test!';
reporterOutputLogger.error(test.message);
} else {
reporterOutputLogger.error(error.stack);
}

if (!this.inlineErrors) {
this.errors.push(test);
}

logger.error(`${test.title} duration: ${test.duration}ms`);

if (connectionErrors.indexOf(error.code) > -1) {
return logger.error(test.message);
}
logger.error(error.stack);
reporterOutputLogger.error(`${test.title} duration: ${test.duration}ms`);
if (!this.inlineErrors) { this.errors.push(test); }
});
};

Expand Down
15 changes: 8 additions & 7 deletions lib/reporters/DotReporter.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const logger = require('../logger');
const reporterOutputLogger = require('./reporterOutputLogger');
const prettifyResponse = require('../prettifyResponse');

function DotReporter(emitter, stats, tests) {
Expand All @@ -24,20 +25,20 @@ DotReporter.prototype.configureEmitter = function configureEmitter(emitter) {
this.write('\n');
logger.info('Displaying failed tests...');
for (const test of this.errors) {
logger.fail(`${test.title} duration: ${test.duration}ms`);
logger.fail(test.message);
logger.request(`\n${prettifyResponse(test.request)}\n`);
logger.expected(`\n${prettifyResponse(test.expected)}\n`);
logger.actual(`\n${prettifyResponse(test.actual)}\n\n`);
reporterOutputLogger.fail(`${test.title} duration: ${test.duration}ms`);
reporterOutputLogger.fail(test.message);
reporterOutputLogger.request(`\n${prettifyResponse(test.request)}\n`);
reporterOutputLogger.expected(`\n${prettifyResponse(test.expected)}\n`);
reporterOutputLogger.actual(`\n${prettifyResponse(test.actual)}\n\n`);
}
}
this.write('\n');

logger.complete(`\
reporterOutputLogger.complete(`\
${this.stats.passes} passing, ${this.stats.failures} failing, \
${this.stats.errors} errors, ${this.stats.skipped} skipped\
`);
logger.complete(`Tests took ${this.stats.duration}ms`);
reporterOutputLogger.complete(`Tests took ${this.stats.duration}ms`);

callback();
}
Expand Down
7 changes: 4 additions & 3 deletions lib/reporters/HTMLReporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const md = require('markdown-it')();
const pathmodule = require('path');

const logger = require('../logger');
const reporterOutputLogger = require('./reporterOutputLogger');
const prettifyResponse = require('../prettifyResponse');

function HTMLReporter(emitter, stats, tests, path, details) {
Expand All @@ -29,7 +30,7 @@ function HTMLReporter(emitter, stats, tests, path, details) {
HTMLReporter.prototype.sanitizedPath = function sanitizedPath(path = './report.html') {
const filePath = pathmodule.resolve(untildify(path));
if (fs.existsSync(filePath)) {
logger.info(`File exists at ${filePath}, will be overwritten...`);
logger.warn(`File exists at ${filePath}, will be overwritten...`);
}
return filePath;
};
Expand All @@ -48,12 +49,12 @@ HTMLReporter.prototype.configureEmitter = function configureEmitter(emitter) {
makeDir(pathmodule.dirname(this.path))
.then(() => {
fs.writeFile(this.path, html, (error) => {
if (error) { logger.error(error); }
if (error) { reporterOutputLogger.error(error); }
callback();
});
})
.catch((err) => {
logger.error(err);
reporterOutputLogger.error(err);
callback();
});
});
Expand Down
7 changes: 4 additions & 3 deletions lib/reporters/MarkdownReporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const makeDir = require('make-dir');
const pathmodule = require('path');

const logger = require('../logger');
const reporterOutputLogger = require('./reporterOutputLogger');
const prettifyResponse = require('../prettifyResponse');

function MarkdownReporter(emitter, stats, tests, path, details) {
Expand All @@ -28,7 +29,7 @@ function MarkdownReporter(emitter, stats, tests, path, details) {
MarkdownReporter.prototype.sanitizedPath = function sanitizedPath(path = './report.md') {
const filePath = pathmodule.resolve(untildify(path));
if (fs.existsSync(filePath)) {
logger.info(`File exists at ${filePath}, will be overwritten...`);
logger.warn(`File exists at ${filePath}, will be overwritten...`);
}
return filePath;
};
Expand All @@ -46,12 +47,12 @@ MarkdownReporter.prototype.configureEmitter = function configureEmitter(emitter)
makeDir(pathmodule.dirname(this.path))
.then(() => {
fs.writeFile(this.path, this.buf, (error) => {
if (error) { logger.error(error); }
if (error) { reporterOutputLogger.error(error); }
callback();
});
})
.catch((err) => {
logger.error(err);
reporterOutputLogger.error(err);
callback();
});
});
Expand Down
15 changes: 8 additions & 7 deletions lib/reporters/NyanReporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const tty = require('tty');

const logger = require('../logger');
const prettifyResponse = require('../prettifyResponse');
const reporterOutputLogger = require('./reporterOutputLogger');

function NyanCatReporter(emitter, stats, tests) {
let windowWidth;
Expand Down Expand Up @@ -56,16 +57,16 @@ NyanCatReporter.prototype.configureEmitter = function configureEmitter(emitter)
this.write('\n');
logger.info('Displaying failed tests...');
for (const test of this.errors) {
logger.fail(`${test.title} duration: ${test.duration}ms`);
logger.fail(test.message);
logger.request(`\n${prettifyResponse(test.request)}\n`);
logger.expected(`\n${prettifyResponse(test.expected)}\n`);
logger.actual(`\n${prettifyResponse(test.actual)}\n\n`);
reporterOutputLogger.fail(`${test.title} duration: ${test.duration}ms`);
reporterOutputLogger.fail(test.message);
reporterOutputLogger.request(`\n${prettifyResponse(test.request)}\n`);
reporterOutputLogger.expected(`\n${prettifyResponse(test.expected)}\n`);
reporterOutputLogger.actual(`\n${prettifyResponse(test.actual)}\n\n`);
}
}

logger.complete(`${this.stats.passes} passing, ${this.stats.failures} failing, ${this.stats.errors} errors, ${this.stats.skipped} skipped`);
logger.complete(`Tests took ${this.stats.duration}ms`);
reporterOutputLogger.complete(`${this.stats.passes} passing, ${this.stats.failures} failing, ${this.stats.errors} errors, ${this.stats.skipped} skipped`);
reporterOutputLogger.complete(`Tests took ${this.stats.duration}ms`);
callback();
});

Expand Down
Loading

0 comments on commit 3597a58

Please sign in to comment.