From acabb67d6f23a4df3ce41777dda3bd9a26d66550 Mon Sep 17 00:00:00 2001 From: Lyle Toronto Date: Thu, 25 Jan 2018 10:17:21 -0700 Subject: [PATCH] Add Step reporter Show usage of retryable-fail-event --- lib/reporters/index.js | 1 + lib/reporters/step.js | 206 +++++++++++++++++++++++++++++++++++ test/reporters/step.spec.js | 210 ++++++++++++++++++++++++++++++++++++ 3 files changed, 417 insertions(+) create mode 100644 lib/reporters/step.js create mode 100644 test/reporters/step.spec.js diff --git a/lib/reporters/index.js b/lib/reporters/index.js index d3b5481e41..31356e2a4d 100644 --- a/lib/reporters/index.js +++ b/lib/reporters/index.js @@ -11,6 +11,7 @@ exports.HTML = exports.html = require('./html'); exports.List = exports.list = require('./list'); exports.Min = exports.min = require('./min'); exports.Spec = exports.spec = require('./spec'); +exports.Step = exports.step = require('./step'); exports.Nyan = exports.nyan = require('./nyan'); exports.XUnit = exports.xunit = require('./xunit'); exports.Markdown = exports.markdown = require('./markdown'); diff --git a/lib/reporters/step.js b/lib/reporters/step.js new file mode 100644 index 0000000000..250f9af6e8 --- /dev/null +++ b/lib/reporters/step.js @@ -0,0 +1,206 @@ +'use strict'; + +/** + * Module dependencies. + */ + +var Base = require('./base'); +var inherits = require('../utils').inherits; +var color = Base.color; +var utils = require('../utils'); + +/** + * Expose `Step`. + */ + +exports = module.exports = Step; + +/** + * Initialize a new `Step` test reporter. + * + * @api public + * @param {Runner} runner + */ +function Step (runner) { + Base.call(this, runner); + Base.colors.skipped = 35; + Base.colors.suite = 1; + var indents = 0; + + function indent () { + return new Array(indents).join(' '); + } + + runner.on('start', function () { + // console.log("------------------on start---------------------"); + }); + + runner.on('suite', function (suite) { + suite.startTime = new Date().getTime(); + var fmt = indent() + color('suite', '%s'); + console.log(fmt, suite.title); + indents++; + }); + + runner.on('pending', function (test) { + var fmt = indent() + color('skipped', '* skipped %s'); + console.log(fmt, test.title); + }); + + runner.on('hook', function (test) { + // before: before, before each, after, after each + // console.log("------------------hook---------------------"); + }); + + runner.on('hook end', function (test) { + // after: before, before each, after, after each + // console.log("------------------hook end---------------------"); + }); + + runner.on('test', function (test) { + var fmt; + // log the beginning of each test + if (test.currentRetry() > 0) { + fmt = indent() + Base.color('bright yellow', '- retry' + test._currentRetry + ' %s'); + } else { + fmt = indent() + color('pending', '- start %s'); + } + console.log(fmt, test.title); + }); + + runner.on('retryable fail', function (test, err) { + // log error and warning + var fmt = indent() + color('bright fail', Base.symbols.bang + ' failed %s') + color('fail', ' (%dms)'); + console.log(fmt, test.title, test.duration); + console.log(errMsg(test, err), errStack(err)); + }); + + runner.on('test end', function (test) { + // console.log("------------------test end---------------------"); + }); + + runner.on('pass', function (test) { + var fmt = indent() + color('bright pass', Base.symbols.ok + ' passed %s') + color(test.speed, ' (%dms)'); + console.log(fmt, test.title, test.duration); + }); + + runner.on('fail', function (test, err) { + var fmt = indent() + color('fail', Base.symbols.err + ' failed %s') + color('fail', ' (%dms)'); + console.log(fmt, test.title, test.duration); + console.log(errMsg(test, test.err), errStack(err)); + }); + + runner.on('suite end', function (suite) { + indents--; + suite.endTime = new Date().getTime(); + suite.totalDuration = suite.endTime - suite.startTime; + var fmt; + if (indents > 0) { + fmt = indent() + color('suite', 'end ' + suite.title); + } + if (indents === 0) { + fmt = indent() + color('suite', 'Total Duration (' + suite.totalDuration + 'ms)'); + } + console.log(fmt); + }); + + runner.on('end', function () { + // console.log("--------------end-------------"); + }); + + function errMsg (test, err) { + // msg + var msg; + if (!err) { + err = test.err; + } + // get best message + var message; + if (err.message && typeof err.message.toString === 'function') { + message = err.message + ''; + } else if (typeof err.inspect === 'function') { + message = err.inspect() + ''; + } else { + message = ''; + } + var stack = err.stack || message; + var index = message ? stack.indexOf(message) : -1; + var actual = err.actual; + var expected = err.expected; + + if (index === -1) { + msg = message; + } else { + index += message.length; + msg = stack.slice(0, index); + } + + // uncaught + if (err.uncaught) { + msg = 'Uncaught ' + msg; + } + // explicitly show diff + if (err.showDiff !== false && sameType(actual, expected) && expected !== undefined) { + if (!(utils.isString(actual) && utils.isString(expected))) { + err.actual = utils.stringify(actual); + err.expected = utils.stringify(expected); + } + + var match = message.match(/^([^:]+): expected/); + indents++; + msg = indent() + color('error message', match ? match[1] : msg); + indents++; + // legend + msg += '\n' + indent() + color('diff removed', 'actual') + ' | ' + color('diff added', 'expected'); + // values + msg += '\n' + indent() + color('diff removed', err.actual) + ' | ' + color('diff added', err.expected) + '\n\n'; + indents--; + indents--; + } + return msg; + } + + function errStack (err) { + // Get message and remove it from stack trace + var message; + if (err.message && typeof err.message.toString === 'function') { + message = err.message + ''; + } else if (typeof err.inspect === 'function') { + message = err.inspect() + ''; + } else { + message = ''; + } + var stack = err.stack || message; + var index = message ? stack.indexOf(message) : -1; + + if (index !== -1) { + index += message.length; + // remove msg from stack + stack = stack.slice(index + 1); + } + + return stack; + } +} + +/** + * Object#toString reference. + */ +var objToString = Object.prototype.toString; + +/** + * Check that a / b have the same type. + * + * @api private + * @param {Object} a + * @param {Object} b + * @return {boolean} + */ +function sameType (a, b) { + return objToString.call(a) === objToString.call(b); +} + +/** + * Inherit from `Base.prototype`. + */ +inherits(Step, Base); diff --git a/test/reporters/step.spec.js b/test/reporters/step.spec.js new file mode 100644 index 0000000000..37b7301462 --- /dev/null +++ b/test/reporters/step.spec.js @@ -0,0 +1,210 @@ +'use strict'; + +var reporters = require('../../').reporters; +var Step = reporters.Step; +var Base = reporters.Base; +var Test = require('../../').test; +var Suite = require('../../').suite; + +describe('Step reporter', function () { + var stdout; + var stdoutWrite; + var runner; + + beforeEach(function () { + stdout = []; + runner = {}; + stdoutWrite = process.stdout.write; + process.stdout.write = function (string) { + stdout.push(string); + }; + }); + + describe('on suite', function () { + it('should return title', function () { + var expectedTitle = 'expectedTitle'; + var suite = new Suite(expectedTitle, function () {}); + runner.on = function (event, callback) { + if (event === 'suite') { + callback(suite); + } + }; + Step.call({ + epilogue: function () { + } + }, runner); + process.stdout.write = stdoutWrite; + var expectedArray = [ + expectedTitle + '\n' + ]; + stdout.should.deepEqual(expectedArray); + }); + }); + describe('on pending', function () { + it('should return title', function () { + var expectedTitle = 'expectedTitle'; + var suite = new Suite(expectedTitle, function () {}); + runner.on = function (event, callback) { + if (event === 'pending') { + callback(suite); + } + }; + Step.call({epilogue: function () {}}, runner); + process.stdout.write = stdoutWrite; + var expectedArray = [ + '* skipped ' + expectedTitle + '\n' + ]; + stdout.should.deepEqual(expectedArray); + }); + }); + describe('on test', function () { + describe('first time starting test', function () { + it('should return start title', function () { + var expectedTitle = 'expectedTitle'; + var test = new Test(expectedTitle, function () {}); + test.currentRetry(0); + runner.on = function (event, callback) { + if (event === 'test') { + callback(test); + } + }; + Step.call({ + epilogue: function () { + } + }, runner); + process.stdout.write = stdoutWrite; + var message = '- start ' + expectedTitle + '\n'; + stdout[0].should.deepEqual(message); + }); + }); + describe('retry starting test', function () { + it('should return retry title', function () { + var expectedTitle = 'expectedTitle'; + var test = new Test(expectedTitle, function () { + }); + test.currentRetry(1); + runner.on = function (event, callback) { + if (event === 'test') { + callback(test); + } + }; + Step.call({ + epilogue: function () { + } + }, runner); + process.stdout.write = stdoutWrite; + var message = '- retry1 ' + expectedTitle + '\n'; + stdout[0].should.deepEqual(message); + }); + }); + }); + describe('on pass', function () { + describe('if test speed is slow', function () { + it('should return expected tick, title and duration', function () { + var expectedTitle = 'expectedTitle'; + var expectedDuration = 2; + var test = new Test(expectedTitle); + test.duration = expectedDuration; + test.slow = function () { return 1; }; + + runner.on = function (event, callback) { + if (event === 'pass') { + callback(test); + } + }; + Step.call({epilogue: function () {}}, runner); + process.stdout.write = stdoutWrite; + var expectedString = Base.symbols.ok + ' passed ' + expectedTitle + ' (' + expectedDuration + 'ms)' + '\n'; + stdout[0].should.equal(expectedString); + }); + }); + describe('if test speed is fast', function () { + it('should return expected tick, title and without a duration', function () { + var expectedTitle = 'expectedTitle'; + var expectedDuration = 1; + var test = new Test(expectedTitle); + test.duration = expectedDuration; + test.slow = function () { return 2; }; + + runner.on = function (event, callback) { + if (event === 'pass') { + callback(test); + } + }; + Step.call({epilogue: function () {}}, runner); + process.stdout.write = stdoutWrite; + var expectedString = Base.symbols.ok + ' passed ' + expectedTitle + ' (' + expectedDuration + 'ms)' + '\n'; + stdout[0].should.equal(expectedString); + }); + }); + }); + describe('on fail', function () { + it('should return title and stack trace', function () { + var expectedTitle = 'expectedTitle'; + var expectedDuration = 1; + var err = new Error('test'); + err.actual = 'a\ninline\ndiff\nwith\nmultiple lines'; + err.expected = 'a\ninline\ndiff\nwith\nmultiple lines'; + err.showDiff = false; + var test = new Test(expectedTitle); + test.duration = expectedDuration; + test.retries(1); + runner.on = function (event, callback) { + if (event === 'fail') { + callback(test, err); + } + }; + Step.call({epilogue: function () {}}, runner); + process.stdout.write = stdoutWrite; + var message = Base.symbols.err + ' failed ' + expectedTitle + ' (' + expectedDuration + 'ms)' + '\n'; + var stackTrace = 'Error: test'; + stdout[0].should.deepEqual(message); + expect(stdout[1]).to.contain(stackTrace); + }); + }); + describe('on retryable fail', function () { + it('should log stack trace should return retry title', function () { + var expectedTitle = 'expectedTitle'; + var expectedDuration = 1; + var err = new Error('test'); + err.actual = 'a\ninline\ndiff\nwith\nmultiple lines'; + err.expected = 'a\ninline\ndiff\nwith\nmultiple lines'; + err.showDiff = false; + var test = new Test(expectedTitle); + test.duration = expectedDuration; + runner.on = function (event, callback) { + if (event === 'retryable fail') { + callback(test, err); + } + }; + Step.call({epilogue: function () {}}, runner); + process.stdout.write = stdoutWrite; + var message = '! failed ' + expectedTitle + ' (' + expectedDuration + 'ms)' + '\n'; + var stackTrace = 'Error: test'; + stdout[0].should.deepEqual(message); + expect(stdout[1]).to.contain(stackTrace); + }); + }); + describe('on suite end', function () { + it('should log total duration', function () { + var suiteTitle = 'Suite title'; + var parentSuite = new Suite('', function () {}); + var suite = new Suite(suiteTitle, parentSuite); + runner.on = function (event, callback) { + if (event === 'suite') { + callback(parentSuite); + callback(suite); + } + if (event === 'suite end') { + callback(suite); + callback(parentSuite); + } + }; + Step.call({epilogue: function () {}}, runner); + process.stdout.write = stdoutWrite; + var message = 'Total Duration'; + expect(stdout[2]).to.contain('end ' + suiteTitle); + expect(stdout[3]).to.contain(message); + }); + }); +});