From 17ea9ecef50368c1569eef3eec1c7c863a8b4750 Mon Sep 17 00:00:00 2001 From: Marc Jansen Date: Thu, 2 Apr 2015 09:16:43 +0200 Subject: [PATCH] Run the istanbul code-coverage via node script. --- .gitignore | 1 - .travis.yml | 4 +- package.json | 9 ++- tasks/test-coverage.js | 176 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 185 insertions(+), 5 deletions(-) create mode 100644 tasks/test-coverage.js diff --git a/.gitignore b/.gitignore index aaa9428346..08d024f5e6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,4 @@ /examples/example-list.xml /node_modules/ /dist/ -/src_instrumented/ /coverage/ diff --git a/.travis.yml b/.travis.yml index 7c7c730b4c..b5af216a42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,4 +7,6 @@ before_script: script: "./build.py ci" -after_success: "npm run test-coverage" +after_success: + - "npm run test-coverage" + - "cat coverage/lcov.info | node ./node_modules/coveralls/bin/coveralls.js" diff --git a/package.json b/package.json index 951d9aa377..961eba0072 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "postinstall": "closure-util update", "start": "node tasks/serve.js", "test": "node tasks/test.js", - "test-coverage": "istanbul instrument src -o src_instrumented && mv src src_old && mv src_instrumented src && istanbul cover npm test istanbul && mv src src_instrumented && mv src_old src && istanbul report --root coverage lcov" + "test-coverage": "node tasks/test-coverage.js" }, "main": "dist/ol.js", "repository": { @@ -28,18 +28,21 @@ "async": "0.9.0", "closure-util": "1.3.0", "fs-extra": "0.12.0", + "glob": "5.0.3", "graceful-fs": "3.0.2", "htmlparser2": "3.7.3", "jsdoc": "3.3.0-alpha9", "nomnom": "1.8.0", "rbush": "1.3.5", "temp": "0.8.1", - "walk": "2.3.4" + "walk": "2.3.4", + "wrench": "1.5.8" }, "devDependencies": { "clean-css": "2.2.16", + "coveralls": "2.11.2", "expect.js": "0.3.1", - "istanbul": "^0.3.13", + "istanbul": "0.3.13", "jquery": "2.1.1", "jshint": "2.5.6", "mocha": "1.21.5", diff --git a/tasks/test-coverage.js b/tasks/test-coverage.js new file mode 100644 index 0000000000..d1b2007478 --- /dev/null +++ b/tasks/test-coverage.js @@ -0,0 +1,176 @@ +/** + * This tasks instruments our source code with istanbul, runs the test suite + * on the instrumented source and collects the coverage data. It the creates + * test coverage reports. + * + * TODO this can be improved in style. We should possibly rewrite it and use + * async.waterfall. + */ + +var fs = require('fs'); +var istanbul = require('istanbul'); +var wrench = require('wrench'); +var path = require('path'); +var glob = require('glob'); + +var runTestsuite = require('./test'); + +// setup some pathes +var dir = path.join(__dirname, '../src'); +var backupDir = path.join(__dirname, '../src-backup'); +var instrumentedDir = path.join(__dirname, '../src-instrumented'); +var coverageDir = path.join(__dirname, '../coverage'); + +// The main players in the coverage generation via istanbul +var instrumenter = new istanbul.Instrumenter(); +var reporter = new istanbul.Reporter(false, coverageDir); +var collector = new istanbul.Collector(); + +// General options used for the resource shuffling / directory copying +var copyOpts = { + // Whether to overwrite existing directory or not + forceDelete: true, + // Whether to copy hidden Unix files or not (preceding .) + excludeHiddenUnix: false, + // If we're overwriting something and the file already exists, keep the + // existing + preserveFiles: false, + // Preserve the mtime and atime when copying files + preserveTimestamps: true, + // Whether to follow symlinks or not when copying files + inflateSymlinks: false +}; + +/** + * A small utility method printing out log messages. + */ +var log = function(msg){ + process.stdout.write(msg + '\n'); +}; + + +/** + * A utility method to recursively delete a non-empty folder. + * + * See http://www.geedew.com/remove-a-directory-that-is-not-empty-in-nodejs/ + * adjusted to use path.join + */ +var deleteFolderRecursive = function(p) { + if( fs.existsSync(p) ) { + fs.readdirSync(p).forEach(function(file,index){ + var curPath = path.join(p, file); + if(fs.lstatSync(curPath).isDirectory()) { // recurse + deleteFolderRecursive(curPath); + } else { // delete file + fs.unlinkSync(curPath); + } + }); + fs.rmdirSync(p); + } +}; + +/** + * Creates folders for backup and instrumentation and copies the contents of the + * current src folder into them. + */ +var setupBackupAndInstrumentationDir = function(){ + if (!fs.existsSync(backupDir)) { + log('• create directory for backup of src: ' + backupDir); + fs.mkdirSync(backupDir); + } + + if (!fs.existsSync(instrumentedDir)) { + log('• create directory for instrumented src: ' + instrumentedDir); + fs.mkdirSync(instrumentedDir); + } + + log('• copy src files to backup folder'); + wrench.copyDirSyncRecursive(dir, backupDir, copyOpts); + log('• copy src files to instrumentation folder'); + wrench.copyDirSyncRecursive(dir, instrumentedDir, copyOpts); +}; + +/** + * Reverts the changes done in setupBackupAndInstrumentationDir, copies the + * backup over the src directory and removes the instrumentation and backup + * directory. + */ +var revertBackupAndInstrumentationDir = function(){ + log('• copy original src back to src folder'); + wrench.copyDirSyncRecursive(backupDir, dir, copyOpts); + log('• delete backup directory'); + deleteFolderRecursive(backupDir); + log('• delete instrumentation directory'); + deleteFolderRecursive(instrumentedDir); +}; + + +/** + * Callback for when runTestsuite() has finished. + */ +var collectAndWriteCoverageData = function(code) { + log('• collect data from coverage.json'); + + var coverageFile = path.join(__dirname,'../coverage/coverage.json'); + var coverageJson = JSON.parse(fs.readFileSync(coverageFile, 'utf8')); + collector.add(coverageJson); + + reporter.addAll(['lcovonly','html']); + + revertBackupAndInstrumentationDir(); + + log('• write report from collected data'); + reporter.write(collector, true, function () { + process.exit(0); + }); +}; + +/** + * Will instrument all JavaScript files that are passed as second parameter. + * This is the callback to the glob call. + */ +var foundAllJavaScriptSourceFiles = function(err, files) { + if (err) { + process.stderr.write(err.message + '\n'); + process.exit(1); + } + log('• instrumenting every src file'); + var cnt = 0; + files.forEach(function(file) { + cnt++; + var content = fs.readFileSync(file, 'utf-8'); + var outfile = file.replace(/\/src\//, '/src-instrumented/'); + var instrumented = instrumenter.instrumentSync(content, file); + fs.writeFileSync(outfile, instrumented); + if (cnt % 10 === 0) { + log(' • instrumented ' + cnt + ' files'); + } + }); + log(' • done. ' + cnt + ' files instrumented'); + log('• copy instrumented src back to src folder'); + + wrench.copyDirSyncRecursive(instrumentedDir, dir, copyOpts); + + log('• run test suite on instrumented code'); + runTestsuite(true, collectAndWriteCoverageData); +}; + +/** + * Our main method, first it sets up certain directory, and then it starts the + * coverage process by gathering all JavaScript files and then instrumenting + * them. + */ +var main = function(){ + setupBackupAndInstrumentationDir(); + glob(dir + '/**/*.js', {}, foundAllJavaScriptSourceFiles); +}; + + + +if (require.main === module) { + main(); +} + +module.exports = main; + +