/* eslint-env mocha */ 'use strict'; var Reporter = require('../index'); var mochaVersion = process.env.MOCHA_VERSION || ''; var Mocha = require('mocha' + mochaVersion); var Runner = Mocha.Runner; var Suite = Mocha.Suite; var Test = Mocha.Test; var fs = require('fs'); var path = require('path'); var rimraf = require('rimraf'); var chai = require('chai'); var expect = chai.expect; var FakeTimer = require('@sinonjs/fake-timers'); var xmllint = require('xmllint'); var chaiXML = require('chai-xml'); var mockXml = require('./mock-results'); var mockJunitSuites = require('./mock-junit-suites'); var testConsole = require('test-console'); var debug = require('debug')('mocha-junit-reporter:tests'); chai.use(chaiXML); describe('mocha-junit-reporter', function() { var filePath; var MOCHA_FILE; var stdout; function mockStdout () { stdout = testConsole.stdout.inspect(); return stdout; } function createTest(name, options, fn) { if (typeof options === 'function') { fn = options; options = null; } options = options || {}; // null fn means no callback which mocha treats as pending test. // undefined fn means caller wants a default fn. if (fn === undefined) { fn = function () {}; } var test = new Test(name, fn); var duration = options.duration; if (duration != null) { // mock duration so we have consistent output Object.defineProperty(test, 'duration', { set: function() { // do nothing }, get: function() { return duration; } }); } return test; } function runTests(reporter, options, callback) { if (!callback) { callback = options; options = null; } options = options || {}; options.invalidChar = options.invalidChar || ''; options.title = options.title || 'Foo Bar'; var runner = reporter.runner; var rootSuite = runner.suite; var suite1 = Suite.create(rootSuite, options.title); suite1.addTest(createTest('can weez the juice', { duration: 101 })); suite1.addTest(createTest('can narfle the garthog', {duration: 2002}, function(done) { var err = new Error(options.invalidChar + 'expected garthog to be dead' + options.invalidChar); err.stack = 'this is where the stack would be'; done(err); })); suite1.addTest(createTest('can behave like a flandip', {duration: 30003}, function(done) { var err = new Error('expected baz to be masher, a hustler, an uninvited grasper of cone'); err.name = 'BazError'; err.stack = 'stack'; done(err); })); var suite2 = Suite.create(rootSuite, 'Another suite!'); suite2.addTest(createTest('works', {duration: 400004})); if (options.includePending) { var pendingSuite = Suite.create(rootSuite, 'Pending suite!'); pendingSuite.addTest(createTest('pending', null, null)); } var _onSuiteEnd = reporter._onSuiteEnd.bind(reporter); reporter._onSuiteEnd = function(suite) { if (suite === rootSuite) { // root suite took no time to execute reporter._Date.clock.tick(0); } else if (suite === suite1) { // suite1 took an arbitrary amount of time that includes time to run each test + setup and teardown reporter._Date.clock.tick(100001); } else if (suite === suite2) { reporter._Date.clock.tick(400005); } return _onSuiteEnd(suite); }; runRunner(runner, callback); } function assertXmlEquals(actual, expected) { expect(actual).xml.to.be.valid(); expect(actual).xml.to.equal(expected); } function verifyMochaFile(runner, path, options) { var now = (new Date()).toISOString(); debug('verify', now); var output = fs.readFileSync(path, 'utf-8'); assertXmlEquals(output, mockXml(runner.stats, options)); debug('done', now); } function removeTestPath(callback) { rimraf(__dirname + '/output', function(err) { if (err) { return callback(err); } // tests that exercise defaults will write to $CWD/test-results.xml rimraf(__dirname + '/../test-results.xml', callback); }); } function createRunner() { // mocha always has a root suite var rootSuite = new Suite('', 'root', true); // We don't want Mocha to emit timeout errors. // If we want to simulate errors, we'll emit them ourselves. rootSuite.timeout(0); return new Runner(rootSuite); } function createReporter(options) { options = options || {}; filePath = path.join(path.dirname(__dirname), options.mochaFile || ''); var mocha = new Mocha({ reporter: Reporter, allowUncaught: true }); return new mocha._reporter(createRunner(), { reporterOptions: options, Date: FakeTimer.createClock(0).Date }); } function runRunner(runner, callback) { runner.run(function(failureCount) { if (runner.dispose) { // Ensure uncaught exception handlers are cleared before we execute test assertions. // Otherwise, this runner would intercept uncaught exceptions that were already handled by the mocha instance // running our tests. runner.dispose(); } callback(failureCount); }); } function getFileNameWithHash(path) { var filenames = fs.readdirSync(path); var expected = /(^results\.)([a-f0-9]{32})(\.xml)$/i; for (var i = 0; i < filenames.length; i++) { if (expected.test(filenames[i])) { return filenames[i]; } } } before(function(done) { // cache this MOCHA_FILE = process.env.MOCHA_FILE; removeTestPath(done); }); after(function() { // reset this process.env.MOCHA_FILE = MOCHA_FILE; }); beforeEach(function() { filePath = undefined; delete process.env.MOCHA_FILE; delete process.env.PROPERTIES; }); afterEach(function(done) { debug('after'); if (stdout) { stdout.restore(); } removeTestPath(done); }); it('can produce a JUnit XML report', function(done) { var reporter = createReporter({mochaFile: 'test/output/mocha.xml'}); runTests(reporter, function() { verifyMochaFile(reporter.runner, filePath); done(); }); }); it('can handle getXml being called twice', function() { var reporter = createReporter({mochaFile: 'test/output/mocha.xml'}); var testsuites = mockJunitSuites.withStringTimes(); reporter.getXml(testsuites); }); it('respects `process.env.MOCHA_FILE`', function(done) { process.env.MOCHA_FILE = 'test/output/results.xml'; var reporter = createReporter(); runTests(reporter, function() { verifyMochaFile(reporter.runner, process.env.MOCHA_FILE); done(); }); }); it('respects `process.env.PROPERTIES`', function(done) { process.env.PROPERTIES = 'CUSTOM_PROPERTY:ABC~123'; var reporter = createReporter({mochaFile: 'test/output/properties.xml'}); runTests(reporter, function() { verifyMochaFile(reporter.runner, filePath, { properties: [ { name: 'CUSTOM_PROPERTY', value: 'ABC~123' } ] }); done(); }); }); it('respects `--reporter-options mochaFile=`', function(done) { var reporter = createReporter({mochaFile: 'test/output/results.xml'}); runTests(reporter, function() { verifyMochaFile(reporter.runner, filePath); done(); }); }); it('respects `[hash]` pattern in test results report filename', function(done) { var dir = 'test/output/'; var path = dir + 'results.[hash].xml'; var reporter = createReporter({mochaFile: path}); runTests(reporter, function() { verifyMochaFile(reporter.runner, dir + getFileNameWithHash(dir)); done(); }); }); it("respects `[testsuitesTitle]` pattern in test results report filename", function (done) { var dir = "test/output/"; var path = dir + "results.[testsuitesTitle].xml"; var reporter = createReporter({ mochaFile: path }); runTests(reporter, function () { verifyMochaFile( reporter.runner, dir + "results." + reporter._options.testsuitesTitle + ".xml" ); done(); }); }); it("respects `[rootSuiteTitle]` pattern in test results report filename", function (done) { var dir = "test/output/"; var path = dir + "results.[rootSuiteTitle].xml"; var reporter = createReporter({ mochaFile: path }); runTests(reporter, function () { verifyMochaFile( reporter.runner, dir + "results." + reporter._testsuites[0].testsuite[0]._attr.name + ".xml" ); done(); }); }); it("respects `[suiteFilename]` pattern in test results report filename", function (done) { var dir = "test/output/"; var path = dir + "results.[suiteFilename].xml"; var reporter = createReporter({ mochaFile: path }); runTests(reporter, function () { verifyMochaFile( reporter.runner, dir + "results." + reporter._testsuites[0].testsuite[0]._attr.file + ".xml" ); done(); }); }); it("respects `[suiteName]` pattern in test results report filename", function (done) { var dir = "test/output/"; var path = dir + "results.[suiteName].xml"; var reporter = createReporter({ mochaFile: path }); runTests(reporter, function () { verifyMochaFile( reporter.runner, dir + "results." + reporter._testsuites[1].testsuite[0]._attr.name + ".xml" ); done(); }); }); it('will create intermediate directories', function(done) { var reporter = createReporter({mochaFile: 'test/output/foo/mocha.xml'}); runTests(reporter, function() { verifyMochaFile(reporter.runner, filePath); done(); }); }); it('creates valid XML report for invalid message', function(done) { var reporter = createReporter({mochaFile: 'test/output/mocha.xml'}); runTests(reporter, {invalidChar: '\u001b'}, function() { assertXmlEquals(reporter._xml, mockXml(reporter.runner.stats)); done(); }); }); it('creates valid XML report even if title contains ANSI character sequences', function(done) { var reporter = createReporter({mochaFile: 'test/output/mocha.xml'}); runTests(reporter, {title: '