lib/report-html.js
/* eslint-disable no-useless-escape */
var path = require('path');
var async = require('async');
var _ = require('lodash');
var through = require('through2');
var vfs = require('vinyl-fs');
var include = require('gulp-include');
var readFs = require('read-vinyl-file-stream');
var eos = require('end-of-stream');
var isStream = require('./is-stream.js');
var jsonReport = require('./report-json.js');
var textReport = require('./report-text.js');
var constants = require('./constants.js');
function renderMustaches(text, data) {
_.forEach(data, function(val, key) {
var regex = new RegExp('\{\{' + key + '\}\}', 'g');
text = text.replace(regex, _.escape(val));
});
return text;
}
function endStream(stream, cb) {
if (isStream.stdio(stream)) {
setImmediate(cb);
return;
}
eos(stream, cb);
stream.end();
}
function serializeReport(data, id) {
var ticks = _.map(data.report, function(metric, name) {
if (name === constants.TEST_FULL && metric.status === constants.STATE_ERR) {
name = constants.TEST_FULL_ERR;
}
return {
// x-axis is seconds from the start of the test run
// It might not be great for all custom metrics to have the
// same x-axis, but I am going with it for now.
x: Number((metric.start / 1000).toFixed(2)),
// y-axis is in milliseconds
y: Number(metric.duration.toFixed(2)),
name: name,
categories: [].concat(data.categories),
id: id
};
});
// stringify and remove the first and last characters (the [] from the array)
return JSON.stringify(ticks).slice(1).slice(0, -1);
}
module.exports = function(opts, done) {
var REPORT_FILE = path.resolve(__dirname, '..', 'views', 'html-report', 'html.html');
var input = opts.input;
var output = opts.output;
var firstWrite = true;
var reportCount = 0;
var onFirstWrite = _.noop;
function onJson(data) {
if (data.type === 'report') {
if (firstWrite) {
onFirstWrite();
output.write('[');
firstWrite = false;
} else {
output.write(',');
}
output.write(serializeReport(data, reportCount));
reportCount += 1;
}
}
async.auto({
build: function(next) {
var buildFile;
vfs.src(REPORT_FILE)
.pipe(include())
.on('error', next)
.pipe(readFs(function onFile() {
// stupid eslint rule not letting me have
// 4 arguments... actually, it a good rule,
// but still
var content = arguments[0];
var file = arguments[1];
var cb = arguments[3];
if (file.path === REPORT_FILE) {
buildFile = content;
}
cb();
}, function flush(stream, cb) {
if (buildFile) {
setImmediate(next, null, buildFile);
} else {
setImmediate(next, new Error('building the view failed'));
}
cb();
}))
.on('data', _.noop);
},
view: ['build', function(results, next) {
var htmlParts = results.build.toString().split('{{data}}');
// This is done to prevent writing to the output
// before we have determined that the input stream
// contains correct data, in order to match all
// of the other reporters.
onFirstWrite = function() {
output.write(htmlParts[0]);
onFirstWrite = _.noop;
};
setImmediate(next, null, htmlParts);
}],
inputs: ['view', function(results, next) {
var linesStream = through();
var jsonStream = through();
input.on('data', function(chunk) {
jsonStream.write(chunk);
linesStream.write(chunk);
});
input.on('end', function() {
jsonStream.end();
linesStream.end();
});
input.on('error', function(err) {
jsonStream.emit('error', err);
linesStream.emit('error', err);
});
setImmediate(next, undefined, {
lineStream: linesStream,
jsonStream: jsonStream
});
}],
readLines: ['inputs', function(results, next) {
jsonReport.readLines(results.inputs.lineStream, onJson, function(err) {
if (err) {
return next(err);
}
output.write(']');
next();
});
}],
readJson: ['view', 'inputs', function(results, next) {
jsonReport(results.inputs.jsonStream, function(err, statsObj) {
if (err) {
return next(err);
}
var text = textReport(statsObj, {
color: false
});
if (results.view[1]) {
results.view[1] = renderMustaches(results.view[1], {
testname: statsObj.info.name || 'Test',
textStats: text.trim(),
metadata: opts.metadata ? opts.metadata.toString().trim() : ''
});
}
next();
});
}],
writeEnd: ['readLines', 'readJson', function(results, next) {
if (!output.write(results.view[1])) {
output.once('drain', function() {
next();
});
} else {
setImmediate(next);
}
}],
endStream: ['writeEnd', function(results, next) {
endStream(output, next);
}]
}, done);
};