diff options
| author | cainus <[email protected]> | 2013-03-27 23:48:04 -0700 |
|---|---|---|
| committer | cainus <[email protected]> | 2013-03-27 23:48:04 -0700 |
| commit | 112119e43cb048cfa0dbd98d6e03833b8ca4b619 (patch) | |
| tree | 1bdb90068b87229dfa04a7a217e907d856ed0981 | |
| parent | 86f733351d4e920a44e9682f105034628f6b0b4d (diff) | |
| download | node-coveralls-112119e43cb048cfa0dbd98d6e03833b8ca4b619.tar.xz node-coveralls-112119e43cb048cfa0dbd98d6e03833b8ca4b619.zip | |
changed to use lcov input format only.
| -rw-r--r-- | .travis.yml | 3 | ||||
| -rw-r--r-- | bin/coveralls.js | 141 | ||||
| -rw-r--r-- | fixtures/lib/index.js | 224 | ||||
| -rw-r--r-- | fixtures/onefile.lcov | 116 | ||||
| -rw-r--r-- | lib/convertLcovToCoveralls.js | 90 | ||||
| -rw-r--r-- | lib/parser.js | 131 | ||||
| -rw-r--r-- | lib/sendToCoveralls.js | 11 | ||||
| -rw-r--r-- | package.json | 6 | ||||
| -rw-r--r-- | test/convertLcovToCoveralls.js | 27 |
9 files changed, 616 insertions, 133 deletions
diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..debfa19 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: node_js +node_js: + - 0.8 diff --git a/bin/coveralls.js b/bin/coveralls.js index 37ea880..7769a0f 100644 --- a/bin/coveralls.js +++ b/bin/coveralls.js @@ -1,149 +1,32 @@ #!/usr/bin/env node - -var http = require('http'); -var request = require('request'); -var FormData = require('form-data'); -var TRAVIS_JOB_ID = process.env.TRAVIS_JOB_ID || 'unknown'; +var sendToCoveralls = require('../lib/sendToCoveralls'); +var convertLcovToCoveralls = require('../lib/convertLcovToCoveralls'); process.stdin.resume(); process.stdin.setEncoding('utf8'); -var inJson = ''; +var input = ''; process.stdin.on('data', function(chunk) { - inJson += chunk; + input += chunk; }); process.stdin.on('end', function() { - reportToCoveralls(inJson); + inputToCoveralls(input); }); -// cleans off any leading / trailing non-json garbage -var trimToJson = function(inJson){ - inJson = inJson.replace(/^[^\{]*/, ''); - inJson = inJson.replace(/\}[^\}]*$/, '}'); - return inJson; -}; - -var convertCoverageValue = function(val){ - if (val === ""){ - return null; - } - if (val > 0){ - return 1; - } - return 0; -}; - -var convertFileObject = function(file){ - var source = ''; - var coverage = []; - for (var lineNumber in file.source){ - source += file.source[lineNumber].source + "\n"; - coverage.push(convertCoverageValue( - file.source[lineNumber].coverage)); - } - return { name : file.filename, - source : source, - coverage : coverage }; -}; - -var convertJsonCovToCoveralls = function(data){ - var files = data.files; - var postJson = { - service_job_id : TRAVIS_JOB_ID, - service_name : "travis-ci", - source_files : [] - }; - files.forEach(function(file){ - postJson.source_files.push(convertFileObject(file)); - }); - return postJson; -}; - -var sendToCoveralls = function(postJson){ - var str = JSON.stringify(postJson); - var url = 'https://coveralls.io/api/v1/jobs'; - request({url : url, method : 'POST', form : { json : str}}, function(err, response, body){ +var inputToCoveralls = function(input){ + console.log(input); + var postData = convertLcovToCoveralls(input); + sendToCoveralls(postData, function(err, response, body){ if (err){ throw err; } + if (response.statusCode >= 400){ + throw "Bad response: " + response.statusCode + " " + body; + } console.log(response.statusCode); console.log(body); }); -}; - -var reportToCoveralls = function(inJson){ - inJson = trimToJson(inJson); - var data = JSON.parse(inJson); - console.log("successfully read json from json-cov."); - postJson = convertJsonCovToCoveralls(data); - console.log(JSON.stringify(postJson)); - console.log("successfully converted input json to coveralls format."); - sendToCoveralls(postJson); }; - - -/* example coveralls json file - - -{ - "service_job_id": "1234567890", - "service_name": "travis-ci", - "source_files": [ - { - "name": "example.rb", - "source": "def four\n 4\nend", - "coverage": [null, 1, null] - }, - { - "name": "two.rb", - "source": "def seven\n eight\n nine\nend", - "coverage": [null, 1, 0, null] - } - ] -} - - -*/ - -/* example json-cov file - -{ - "filename": "CRUDCollection.js", - "coverage": 94.20289855072464, - "hits": 65, - "misses": 4, - "sloc": 69, - "source": { - "1": { - "source": "var JSV = require('JSV').JSV;", - "coverage": 1 - }, - "2": { - "source": "var _ = require('underscore');", - "coverage": 1 - }, - "3": { - "source": "", - "coverage": "" - }, - "4": { - "source": "var CRUDCollection = function(options){", - "coverage": 1 - }, - "5": { - "source": "", - "coverage": "" - }, - "6": { - "source": " if (!options || (!options.list && !options.collectionGET)){", - "coverage": 24 - }, - - - - - -*/ diff --git a/fixtures/lib/index.js b/fixtures/lib/index.js new file mode 100644 index 0000000..4a22c7a --- /dev/null +++ b/fixtures/lib/index.js @@ -0,0 +1,224 @@ +var nodeUrl = require('url'); +var querystring = require('querystring'); +var _ = require('underscore'); + +var UrlGrey = function(url){ + this.url = url; + this._parsed = null; +}; + +UrlGrey.prototype.parsed = function(){ + if (!this._parsed){ + this._parsed = nodeUrl.parse(this.url); + var p = this._parsed; + if (p.protocol){ + p.protocol = p.protocol.slice(0,-1); + } else { + p.protocol = 'http'; + } + if (p.hash){ + p.hash = p.hash.substring(1); + } + p.username = ''; + p.password = ''; + if (!p.hostname){ + p.hostname = 'localhost'; + } + if (!p.port){ + p.port = 80; + } else { + p.port = parseInt(p.port, 10); + } + if (p.auth){ + var auth = p.auth.split(':'); + p.username = auth[0]; + p.password = auth[1]; + } + } + return this._parsed; +}; + +UrlGrey.prototype.query = function(mergeObject){ + var path; + if (mergeObject === false){ + // clear the query entirely if the input === false + return this.queryString(''); + } + + var url = this.url; + if (!mergeObject){ + var parsed = nodeUrl.parse(url); + if (!!parsed.search){ + var qstr = parsed.search.substring(1); + return querystring.parse(qstr); + } + return {}; + } else { + // read the object out + var oldQuery = querystring.parse(this.queryString()); + _.each(mergeObject, function(v, k){ + if (v === null){ + delete oldQuery[k]; + } else { + oldQuery[k] = v; + } + }); + var newString = querystring.stringify(oldQuery, '&', '='); + return this.queryString(newString); + } +}; + + +addPropertyGetterSetter('protocol'); +addPropertyGetterSetter('port'); +addPropertyGetterSetter('username'); +addPropertyGetterSetter('password'); +addPropertyGetterSetter('hostname'); +addPropertyGetterSetter('hash'); +// add a method called queryString that manipulates 'query' +addPropertyGetterSetter('query', 'queryString'); +addPropertyGetterSetter('pathname', 'path'); + +UrlGrey.prototype.path = function(){ + var args = _.toArray(arguments); + if (args.length !== 0){ + var obj = new UrlGrey(this.toString()); + var str = _.flatten(args).join('/'); + str = str.replace(/\/+/g, '/'); // remove double slashes + str = str.replace(/\/$/, ''); // remove all trailing slashes + if (str[0] !== '/'){ str = '/' + str; } + obj.parsed().pathname = str; + return obj; + } + return this.parsed().pathname; +}; + + +UrlGrey.prototype.encode = function(str){ + return querystring.escape(str); +}; + +UrlGrey.prototype.decode = function(str){ + return querystring.unescape(str); +}; + +UrlGrey.prototype.parent = function(){ + // read-only. (can't SET parent) + var pieces = this.path().split("/"); + var popped = pieces.pop(); + if (popped === ''){ // ignore trailing slash + pieces.pop(); + } + return this.path(pieces.join("/")); +}; + +UrlGrey.prototype.child = function(suffix){ + if (suffix){ + suffix = encodeURIComponent(suffix); + return this.path(this.path(), suffix); + } else { + // if no suffix, return the child + var pieces = this.path().split("/"); + var last = _.last(pieces); + if ((pieces.length > 1) && (last === '')){ + // ignore trailing slashes + pieces.pop(); + last = _.last(pieces); + } + return last; + } +}; + +UrlGrey.prototype.toJSON = function(){ + return this.toString(); +}; + +UrlGrey.prototype.toString = function(){ + var p = this.parsed(); + var userinfo = p.username + ':' + p.password; + var retval = this.protocol() + '://'; + if (userinfo != ':'){ + retval += userinfo + '@'; + } + retval += p.hostname; + if (this.port() !== 80){ + retval += ':' + this.port(); + } + retval += this.path() === '/' ? '' : this.path(); + var qs = this.queryString(); + if (qs){ + retval += '?' + qs; + } + if (p.hash){ + retval += '#' + p.hash; + } + return retval; +}; + +/* +UrlGrey.prototype.absolute = function(path){ + if (path[0] == '/'){ + path = path.substring(1); + } + var parsed = nodeUrl.parse(path); + if (!!parsed.protocol){ // if it's already absolute, just return it + return path; + } + return this._protocol + "://" + this._host + '/' + path; +}; + +// TODO make this interpolate vars into the url. both sinatra style and url-tempates +// TODO name this: +UrlGrey.prototype.get = function(nameOrPath, varDict){ + if (!!nameOrPath){ + if (!!varDict){ + return this.absolute(this._router.getUrl(nameOrPath, varDict)); + } + return this.absolute(this._router.getUrl(nameOrPath)); + } + return this.url; +};*/ + +/* +// TODO needs to take a template as an input +UrlGrey.prototype.param = function(key, defaultValue){ + var value = this.params()[key]; + if (!!value) { + return value; + } + return defaultValue; +}; + +// TODO extract params, given a template? +// TODO needs to take a template as an input +UrlGrey.prototype.params = function(inUrl){ + if (!!inUrl){ + return this._router.pathVariables(inUrl); + } + if (!!this._params){ + return this._params; + } + return this._router.pathVariables(this.url); +}; +*/ + +// TODO relative() // takes an absolutepath and returns a relative one +// TODO absolute() // takes a relative path and returns an absolute one. + + + +module.exports = function(url){ return new UrlGrey(url); }; + +function addPropertyGetterSetter(propertyName, methodName){ + if (!methodName){ + methodName = propertyName; + } + UrlGrey.prototype[methodName] = function(str){ + if (!!str || str === ''){ + var obj = new UrlGrey(this.toString()); + obj.parsed()[propertyName] = str; + return obj; + } + return this.parsed()[propertyName]; + }; +} diff --git a/fixtures/onefile.lcov b/fixtures/onefile.lcov new file mode 100644 index 0000000..bc24682 --- /dev/null +++ b/fixtures/onefile.lcov @@ -0,0 +1,116 @@ +make[1]: Entering directory `/home/cainus/urlgrey' +SF:index.js +DA:1,1 +DA:2,1 +DA:3,1 +DA:5,1 +DA:6,66 +DA:7,66 +DA:10,1 +DA:11,323 +DA:12,63 +DA:13,63 +DA:14,63 +DA:15,60 +DA:17,3 +DA:19,63 +DA:20,32 +DA:22,63 +DA:23,63 +DA:24,63 +DA:25,3 +DA:27,63 +DA:28,60 +DA:30,3 +DA:32,63 +DA:33,27 +DA:34,27 +DA:35,27 +DA:38,323 +DA:41,1 +DA:42,5 +DA:43,5 +DA:45,2 +DA:48,3 +DA:49,3 +DA:50,1 +DA:51,1 +DA:52,1 +DA:53,1 +DA:55,0 +DA:58,2 +DA:59,2 +DA:60,2 +DA:61,0 +DA:63,2 +DA:66,2 +DA:67,2 +DA:72,1 +DA:73,1 +DA:74,1 +DA:75,1 +DA:76,1 +DA:77,1 +DA:79,1 +DA:80,1 +DA:82,1 +DA:83,87 +DA:84,87 +DA:85,6 +DA:86,6 +DA:87,6 +DA:88,6 +DA:89,9 +DA:90,6 +DA:91,6 +DA:93,81 +DA:97,1 +DA:98,1 +DA:101,1 +DA:102,1 +DA:105,1 +DA:107,2 +DA:108,2 +DA:109,2 +DA:110,1 +DA:112,2 +DA:115,1 +DA:116,3 +DA:117,1 +DA:118,1 +DA:121,2 +DA:122,2 +DA:123,2 +DA:125,1 +DA:126,1 +DA:128,2 +DA:132,1 +DA:133,1 +DA:136,1 +DA:137,50 +DA:138,50 +DA:139,50 +DA:140,50 +DA:141,20 +DA:143,50 +DA:144,50 +DA:145,2 +DA:147,50 +DA:148,50 +DA:149,50 +DA:150,31 +DA:152,50 +DA:153,24 +DA:155,50 +DA:210,40 +DA:212,1 +DA:213,8 +DA:214,6 +DA:216,8 +DA:217,186 +DA:218,21 +DA:219,21 +DA:220,21 +DA:222,165 +end_of_record +make[1]: Leaving directory `/home/cainus/urlgrey' diff --git a/lib/convertLcovToCoveralls.js b/lib/convertLcovToCoveralls.js new file mode 100644 index 0000000..cd3ee6d --- /dev/null +++ b/lib/convertLcovToCoveralls.js @@ -0,0 +1,90 @@ +var TRAVIS_JOB_ID = process.env.TRAVIS_JOB_ID || 'unknown'; +var fs = require('fs'); +var lcovParse = require('./parser'); + +var detailsToCoverage = function(length, details){ + var coverage = new Array(length); + details.forEach(function(obj){ + coverage[obj.line] = obj.hit; + }); + return coverage; +}; + +var convertLcovFileObject = function(file, filepath){ + var path = filepath + "/" + file.file; + var source = fs.readFileSync(path, 'utf8'); + var lines = source.split("\n"); + var coverage = detailsToCoverage(lines.length, file.lines.details); + return { name : file.file, + source : source, + coverage : coverage }; +}; + +var convertLcovToCoveralls = function(input, filepath){ + filepath = filepath || 'lib'; + if (filepath[0] !== '/'){ + filepath = process.cwd() + '/' + filepath; + } + var parsed = lcovParse(input); + var postJson = { + service_job_id : TRAVIS_JOB_ID, + service_name : "travis-ci", + source_files : [] + }; + parsed.forEach(function(file){ + postJson.source_files.push(convertLcovFileObject(file, filepath)); + }); + return postJson; +}; + +module.exports = convertLcovToCoveralls; + +/* example coveralls json file + + +{ + "service_job_id": "1234567890", + "service_name": "travis-ci", + "source_files": [ + { + "name": "example.rb", + "source": "def four\n 4\nend", + "coverage": [null, 1, null] + }, + { + "name": "two.rb", + "source": "def seven\n eight\n nine\nend", + "coverage": [null, 1, 0, null] + } + ] +} + + +example output from lcov parser: + + [ + { + "file": "index.js", + "lines": { + "found": 0, + "hit": 0, + "details": [ + { + "line": 1, + "hit": 1 + }, + { + "line": 2, + "hit": 1 + }, + { + "line": 3, + "hit": 1 + }, + { + "line": 5, + "hit": 1 + }, + +*/ + diff --git a/lib/parser.js b/lib/parser.js new file mode 100644 index 0000000..83b2c3d --- /dev/null +++ b/lib/parser.js @@ -0,0 +1,131 @@ +/* +Software License Agreement (BSD License) + +Copyright (c) 2012, Dav Glass <[email protected]>. +All rights reserved. + +Redistribution and use of this software in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* The name of Dav Glass may not be used to endorse or promote products + derived from this software without specific prior + written permission of Dav Glass. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +var lcovParse = function(str) { + var data = [], item = {}; + + str = str.split('\n'); + str.forEach(function(line) { + line = line.trim(); + + var parts = line.split(':'), lines, fn; + + switch (parts[0].toUpperCase()) { + case 'TN': + item.title = parts[1].trim(); + break; + case 'SF': + item.file = parts[1].trim(); + break; + case 'FNF': + item.functions.found = Number(parts[1].trim()); + break; + case 'FNH': + item.functions.hit = Number(parts[1].trim()); + break; + case 'LF': + item.lines.found = Number(parts[1].trim()); + break; + case 'LH': + item.lines.hit = Number(parts[1].trim()); + break; + case 'DA': + if (!item.lines) { + item.lines = { + found: 0, + hit: 0, + details: [] + }; + } + lines = parts[1].split(','); + item.lines.details.push({ + line: Number(lines[0]), + hit: Number(lines[1]) + }); + break; + case 'FN': + if (!item.functions) { + item.functions = { + hit: 0, + found: 0, + details: [] + }; + } + fn = parts[1].split(','); + item.functions.details.push({ + name: fn[1], + line: Number(fn[0]) + }); + break; + case 'FNDA': + fn = parts[1].split(','); + item.functions.details.some(function(i, k) { + if (i.name === fn[1] && i.hit === undefined) { + item.functions.details[k].hit = Number(fn[0]); + return true; + } + }); + break; + case 'BRDA': + if (!item.branches) { + item.branches = { + hit: 0, + found: 0, + details: [] + }; + } + fn = parts[1].split(','); + item.branches.details.push({ + line: Number(fn[0]), + block: Number(fn[1]), + branch: Number(fn[2]), + taken: ((fn[3] === '-') ? 0 : Number(fn[3])) + }); + break; + case 'BRF': + item.branches.found = Number(parts[1]); + break; + case 'BRH': + item.branches.hit = Number(parts[1]); + break; + } + + if (line.indexOf('end_of_record') > -1) { + data.push(item); + item = {}; + } + }); + return data; +}; + +module.exports = lcovParse; diff --git a/lib/sendToCoveralls.js b/lib/sendToCoveralls.js new file mode 100644 index 0000000..b3514c6 --- /dev/null +++ b/lib/sendToCoveralls.js @@ -0,0 +1,11 @@ +var request = require('request'); + +var sendToCoveralls = function(obj, cb){ + var str = JSON.stringify(obj); + var url = 'https://coveralls.io/api/v1/jobs'; + request({url : url, method : 'POST', form : { json : str}}, function(err, response, body){ + cb(err, response, body); + }); +}; + +module.exports = sendToCoveralls; diff --git a/package.json b/package.json index 0fc55d9..cb37c26 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "coveralls", "description" : "takes json-cov output into stdin and POSTs to coveralls.io", "keywords" : ["coverage", "coveralls"], - "version": "1.1.3", + "version": "2.0.0", "bugs": { "url": "https://github.com/cainus/node-coveralls/issues" }, @@ -20,9 +20,7 @@ } ], "dependencies": { - "underscore" : "1.3.3", - "request" : "2.16.2", - "form-data" : "0.0.7" + "request" : "2.16.2" }, "devDependencies" : { "mocha" : "1.8.1", diff --git a/test/convertLcovToCoveralls.js b/test/convertLcovToCoveralls.js new file mode 100644 index 0000000..df8c5cf --- /dev/null +++ b/test/convertLcovToCoveralls.js @@ -0,0 +1,27 @@ +var convertLcovToCoveralls = require('../lib/convertLcovToCoveralls'); +var should = require('should'); +var fs = require('fs'); + +describe("convertLcovToCoveralls", function(){ + it ("should convert a simple lcov file", function(){ + process.env.TRAVIS_JOB_ID = -1; + var path = __dirname + "/../fixtures/onefile.lcov"; + var input = fs.readFileSync(path, "utf8"); + var libpath = __dirname + "/../fixtures/lib"; + var output = convertLcovToCoveralls(input, libpath); + output.source_files[0].name.should.equal("index.js"); + output.source_files[0].source.split("\n").length.should.equal(225); + output.source_files[0].coverage[55].should.equal(0); + output.source_files[0].coverage[61].should.equal(0); + }); + + it ("should work with a relative path as well", function(){ + process.env.TRAVIS_JOB_ID = -1; + var path = __dirname + "/../fixtures/onefile.lcov"; + var input = fs.readFileSync(path, "utf8"); + var libpath = "fixtures/lib"; + var output = convertLcovToCoveralls(input, libpath); + output.source_files[0].name.should.equal("index.js"); + output.source_files[0].source.split("\n").length.should.equal(225); + }); +}); |
