diff --git a/.travis.yml b/.travis.yml index f3e544fc..7d1bec72 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,4 @@ node_js: before_install: npm install -g grunt-cli -script: grunt travis +script: grunt travis && ./scripts/post-coverage.sh diff --git a/Gruntfile.js b/Gruntfile.js index ecf0d6bb..2daf0608 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -33,8 +33,8 @@ module.exports = function(grunt) { }, test: { files: { - 'build/tests.js': ['test/**/*-test.js'] - }, + 'build/tests.js': ['src/**/*.js', 'test/**/*-test.js', '!src/main.js'] + } }, options: { transform: [ @@ -51,8 +51,34 @@ module.exports = function(grunt) { } } }, + jscoverage: { + src: { + expand: true, + cwd: 'build/', + src: ['tests.js'], + dest: 'build/cov/', + ext: '.js' + } + }, + exorcise: { + bundle: { + options: {}, + files: { + 'build/tests.map': ['build/tests.js'], // externalize source map + } + } + }, mocha_phantomjs: { - all: ['test/**/*.html'] + run: { + src: ['test/runner.html'] + }, + cov: { + src: ['test/coverage.html'], + options: { + reporter: 'test/lcov-reporter.js', + output: 'build/bundled.lcov' + } + } } }); @@ -60,11 +86,15 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-flow-type-check'); grunt.loadNpmTasks('grunt-mocha-phantomjs'); + grunt.loadNpmTasks("grunt-jscoverage"); + grunt.loadNpmTasks("grunt-exorcise"); grunt.registerTask('watchFlow', ['flow:app:start', 'watch:flow']); grunt.registerTask('watchFlowProd', ['flow:app:start', 'watch:flowProd']); grunt.registerTask('prod', ['browserify:dist']); grunt.registerTask('browsertests', ['browserify:test']); - grunt.registerTask('test', ['browsertests', 'mocha_phantomjs']); + grunt.registerTask('test', ['browsertests', 'mocha_phantomjs:run']); grunt.registerTask('travis', ['flow', 'test']); + grunt.registerTask('coverage', + ['browsertests', 'exorcise', 'jscoverage', 'mocha_phantomjs:cov']); }; diff --git a/package.json b/package.json index 9ff1b834..eb7a2400 100644 --- a/package.json +++ b/package.json @@ -20,19 +20,26 @@ }, "devDependencies": { "chai": "^2.0.0", + "coveralls": "^2.11.2", "es5-shim": "^4.1.0", "flow-bin": "^0.4.0", "grunt": "^0.4.5", "grunt-browserify": "^3.3.0", "grunt-contrib-watch": "^0.6.1", "grunt-env": "^0.4.2", + "grunt-exorcise": "^1.0.0", "grunt-flow-type-check": "^0.4.2", + "grunt-jscoverage": "^0.1.3", "grunt-mocha-phantomjs": "^0.6.0", "grunt-mocha-test": "^0.12.7", + "lcov-parse": "0.0.9", "mocha": "^2.1.0", + "mocha-lcov-reporter": "0.0.1", + "parse-data-uri": "^0.2.0", "react-tools": "^0.12.2", "reactify": "^1.0.0", "sinon": "^1.12.2", + "source-map": "^0.3.0", "text-encoding": "^0.5.2" } } diff --git a/scripts/post-coverage.sh b/scripts/post-coverage.sh new file mode 100755 index 00000000..2c304f6c --- /dev/null +++ b/scripts/post-coverage.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# Generate code coverage and post it to Coveralls. +set -o errexit +set -x + +# Generate LCOV data for the bundled tests +grunt coverage + +# Convert code coverage data on the bundled test file back to the originals. +./scripts/transform-coverage.js \ + build/tests.map \ + build/bundled.lcov \ + | ./node_modules/.bin/coveralls diff --git a/scripts/transform-coverage.js b/scripts/transform-coverage.js new file mode 100755 index 00000000..e33fcead --- /dev/null +++ b/scripts/transform-coverage.js @@ -0,0 +1,66 @@ +#!/usr/bin/env node +/** + * This script applies a source map to LCOV data. If you have coverage data for + * a concatenated file, plus a source map, this will output LCOV data for your + * original source files. + * + * Usage: + * + * transform-coverage.js path/to/soure.map path/to/coverage.lcov > out.lcov + */ + +var sourcemap = require('source-map'); +var fs = require('fs'); +var parseDataUri = require('parse-data-uri') +var lcovParse = require('lcov-parse'); +var assert = require('assert'); + +var sourcemapfile = process.argv[2]; +var lcovfile = process.argv[3]; + +var sourcemap_data = fs.readFileSync(sourcemapfile).toString(); +var sourcemap_consumer = new sourcemap.SourceMapConsumer(sourcemap_data); + +var SOURCE = 'src/'; + +lcovParse(lcovfile, function(err, data) { + assert(!err); + // TODO: 0 --> the correct file + var lines = data[0].lines.details; + + var fileToCov = {}; // filename -> { line num -> hits } + + lines.forEach(function(line) { + var num = line.line, hits = line.hit; + var original_position = sourcemap_consumer.originalPositionFor({ line: num, column: 0 }); + if (original_position == null) { + return; + } + + original_filename = original_position.source; + original_num = original_position.line; + + if (!original_filename || original_filename.indexOf('node_modules') >= 0) { + return; + } + + var base = original_filename.indexOf(SOURCE); + if (base == -1) return; + original_filename = original_filename.slice(base); + + if (!fileToCov[original_filename]) fileToCov[original_filename] = []; + fileToCov[original_filename][original_num] = hits; + }); + + // Convert to LCOV format + for (var filename in fileToCov) { + var cov = fileToCov[filename] + console.log('SF:' + filename); + for (var i = 0; i < cov.length; i++) { + if (cov[i] != null) { + console.log('DA:' + i + ',' + cov[i]); + } + } + console.log('end_of_record'); + } +}); diff --git a/test/RemoteFile-test.js b/test/RemoteFile-test.js index fe55f2a2..f3b78c0d 100644 --- a/test/RemoteFile-test.js +++ b/test/RemoteFile-test.js @@ -13,12 +13,12 @@ describe('RemoteFile', () => { FakeXHR.restore(); }); - it('should fetch a subset of a file', () => { + it('should fetch a subset of a file', (done) => { FakeXHR.addResponse('http://example.com/file.txt', new TextEncoder('utf-8').encode('01234567890').buffer); var f = new RemoteFile('http://example.com/file.txt'); - var promisedData = f.getBytes(10, 20); + var promisedData = f.getBytes(10, 11); expect(FakeXHR.numRequests).to.equal(1); // expect(req.requestHeaders.Range).to.equal('bytes=10-29'); diff --git a/test/coverage.html b/test/coverage.html new file mode 100644 index 00000000..74b456af --- /dev/null +++ b/test/coverage.html @@ -0,0 +1,32 @@ + + + + pileup.js Tests + + + +
+ + + + + + + + + + + + + + + + + diff --git a/test/lcov-reporter.js b/test/lcov-reporter.js new file mode 100644 index 00000000..fb4c16dc --- /dev/null +++ b/test/lcov-reporter.js @@ -0,0 +1,44 @@ +// See https://github.com/StevenLooman/mocha-lcov-reporter/pull/7 + +/** + * Expose `LCov`. + */ + +exports = module.exports = LCov; + +/** + * Initialize a new LCOV reporter. + * File format of LCOV can be found here: http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php + * The reporter is built after this parser: https://raw.github.com/SonarCommunity/sonar-javascript/master/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/coverage/LCOVParser.java + * + * @param {Runner} runner + * @api public + */ + +function LCov(runner) { + runner.on('end', function(){ + // In a browser context, coverage will be in window.$jscoverage. + var g = typeof(global) != 'undefined' ? global : window; + var cov = g._$jscoverage || {}; + + for (var filename in cov) { + var data = cov[filename]; + reportFile(filename, data); + } + }); +} + +function reportFile(filename, data) { + process.stdout.write('SF:' + filename + '\n'); + + data.source.forEach(function(line, num) { + // increase the line number, as JS arrays are zero-based + num++; + + if (data[num] !== undefined) { + process.stdout.write('DA:' + num + ',' + data[num] + '\n'); + } + }); + + process.stdout.write('end_of_record\n'); +}