diff --git a/.gitignore b/.gitignore index 3fa830af3..d0c0ee017 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,6 @@ tunnel node_modules .idea .coverage_data -cover_html \ No newline at end of file +cover_html +test-results.xml +reports diff --git a/lib/ci/index.js b/lib/ci/index.js index 0181ce5ed..3accb0896 100644 --- a/lib/ci/index.js +++ b/lib/ci/index.js @@ -10,6 +10,7 @@ var HookRunner = require('../hook_runner') var log = require('npmlog') var cleanExit = require('../clean_exit') var isa = require('../isa') +var ReportFile = require('./report_file') function App(config, finalizer){ this.exited = false @@ -19,7 +20,9 @@ function App(config, finalizer){ this.Process = Process this.hookRunners = {} this.results = [] - this.reporter = this.initReporter(this.config.get('reporter')) + this.reportFileName = this.config.get('report_file') + this.reportFile = this.initReportFileStream(this.reportFileName) + this.reporter = this.initReporter(this.config.get('reporter'), this.reportFile) if (!this.reporter){ console.error('Test reporter `' + reporter + '` not found.') this.cleanExit(1) @@ -28,13 +31,22 @@ function App(config, finalizer){ App.prototype = { __proto__: EventEmitter.prototype, - initReporter: function(reporter){ + initReportFileStream: function(path) { + if(path) { + var reportFile = new ReportFile(path, process.stdout) + return reportFile.stream + } else { + return process.stdout + } + + }, + initReporter: function(reporter, stream){ if (isa(reporter, String)){ var TestReporter = test_reporters[reporter] if (!TestReporter){ return null } - return new TestReporter + return new TestReporter(false, stream) } else { return reporter } diff --git a/lib/ci/report_file.js b/lib/ci/report_file.js new file mode 100644 index 000000000..f8059be35 --- /dev/null +++ b/lib/ci/report_file.js @@ -0,0 +1,28 @@ +var fs = require('fs') +var path = require('path') +var mkdirp = require('mkdirp') +var PassThrough = require('stream').PassThrough + +function ReportFile(reportFile, out) { + this.file = reportFile + + this.outputStream = new PassThrough() + + mkdirp.sync(path.dirname(path.resolve(reportFile))) + + var fileStream = fs.createWriteStream(reportFile, 'w+') + + this.outputStream.on('data', function(data) { + out.write(data) + fileStream.write(data) + }) + + this.outputStream.on('end', function(data) { + out.end(data) + fileStream.end(data) + }) + + this.stream = this.outputStream +} + +module.exports = ReportFile diff --git a/package.json b/package.json index 0fb25dcde..b0f26d067 100644 --- a/package.json +++ b/package.json @@ -62,9 +62,11 @@ "cheerio": "^0.18.0", "concat-stream": "^1.4.7", "ispy": "^0.1.2", + "mkdirp": "^0.5.0", "mocha": "^2.1.0", "request": "^2.51.0", - "sinon": "^1.12.2" + "sinon": "^1.12.2", + "tmp": "0.0.25" }, "bin": { "testem": "./testem.js" diff --git a/tests/ci/report_file_tests.js b/tests/ci/report_file_tests.js new file mode 100644 index 000000000..6ea70dc9a --- /dev/null +++ b/tests/ci/report_file_tests.js @@ -0,0 +1,103 @@ +var fs = require('fs') +var App = require('../../lib/ci') +var TestReporter = require('../../lib/ci/test_reporters/tap_reporter') +var Config = require('../../lib/config') +var bd = require('bodydouble') +var mock = bd.mock +var stub = bd.stub +var assert = require('chai').assert +var expect = require('chai').expect +var Process = require('did_it_work') +var exec = require('child_process').exec +var rimraf = require('rimraf') +var path = require('path') +var PassThrough = require('stream').PassThrough +var ReportFile = require('../../lib/ci/report_file') +var XUnitReporter = require('../../lib/ci/test_reporters/xunit_reporter') +var tmp = require('tmp') + +describe('report file output', function() { + var mainReportDir, reportDir, filename; + before(function(done) { + tmp.dir(function(err, path) { + mainReportDir = path + done() + }) + }) + after(function(done) { + rimraf(mainReportDir, function() { + done() + }) + }) + + beforeEach(function(done) { + tmp.dir({template: path.join(mainReportDir, '/reports-XXXXXX')}, function(err, dirPath){ + if(err) throw err + reportDir = dirPath + + tmp.file({dir: dirPath, name: 'test-reports.xml'}, function(err, filePath) { + filename = filePath + done() + }) + }) + }) + + it('allows passing in report_file from config', function(){ + var fakeReporter = {} + var config = new Config('ci', { + reporter: fakeReporter, + report_file: filename + }) + var app = new App(config) + assert.strictEqual(app.reportFileName, filename) + }) + + it("doesn't create a file if the report_file parameter is not passed in", function(done){ + var filename = tmp.tmpNameSync() + var fakeReporter = {} + var config = new Config('ci', { + reporter: fakeReporter, + }) + var app = new App(config) + fs.readFile(filename, function(error, data) { + expect(error).not.to.be.null + done() + }) + }) + + it('writes out results to the normal output stream', function(){ + var fakeStdout = new PassThrough() + var reportFile = new ReportFile(filename, fakeStdout) + reportFile.stream.write('some test results') + var output = fakeStdout.read().toString() + assert.match(output, /some test results/) + }) + + it('writes out results to the file', function(done){ + var stream = new PassThrough() + var reportFile = new ReportFile(filename, stream) + var reportStream = reportFile.stream + + reportStream.on('finish', function() { + fs.readFile(filename, function(error, data) { + assert.match(data, /test data/) + done() + }) + }) + reportStream.write('test data') + reportStream.end() + }) + + it("creates folders in the path if they don't exist", function(done) { + var nestedFilename = tmp.tmpNameSync({dir: reportDir, name: 'nested/test/folders/test-reports.xml'}) + + var fakeStdout = new PassThrough() + var reportFile = new ReportFile(nestedFilename, fakeStdout) + + fs.open(nestedFilename, 'r', function(error, fd) { + expect(error).to.be.null + done() + }) + }) +}) +