2015-12-24 01:04:49 +03:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
this.EXPORTED_SYMBOLS = [
|
|
|
|
"CoverageCollector",
|
|
|
|
]
|
|
|
|
|
|
|
|
const Cc = Components.classes;
|
|
|
|
const Ci = Components.interfaces;
|
|
|
|
const Cu = Components.utils;
|
|
|
|
|
|
|
|
const {addDebuggerToGlobal} = Cu.import("resource://gre/modules/jsdebugger.jsm",
|
|
|
|
{});
|
|
|
|
addDebuggerToGlobal(this);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Records coverage for each test by way of the js debugger.
|
|
|
|
*/
|
|
|
|
this.CoverageCollector = function (prefix) {
|
|
|
|
this._prefix = prefix;
|
|
|
|
this._dbg = new Debugger();
|
|
|
|
this._dbg.collectCoverageInfo = true;
|
|
|
|
this._dbg.addAllGlobalsAsDebuggees();
|
|
|
|
this._scripts = this._dbg.findScripts();
|
|
|
|
|
|
|
|
this._dbg.onNewScript = (script) => {
|
|
|
|
this._scripts.push(script);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Source -> coverage data;
|
|
|
|
this._allCoverage = {};
|
2017-04-29 17:47:24 +03:00
|
|
|
this._encoder = null;
|
2015-12-24 01:04:49 +03:00
|
|
|
this._testIndex = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
CoverageCollector.prototype._getLinesCovered = function () {
|
|
|
|
let coveredLines = {};
|
|
|
|
let currentCoverage = {};
|
|
|
|
this._scripts.forEach(s => {
|
|
|
|
let scriptName = s.url;
|
|
|
|
let cov = s.getOffsetsCoverage();
|
|
|
|
if (!cov) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
cov.forEach(covered => {
|
|
|
|
let {lineNumber, columnNumber, offset, count} = covered;
|
|
|
|
if (!count) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!currentCoverage[scriptName]) {
|
|
|
|
currentCoverage[scriptName] = {};
|
|
|
|
}
|
|
|
|
if (!this._allCoverage[scriptName]) {
|
|
|
|
this._allCoverage[scriptName] = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
let key = [lineNumber, columnNumber, offset].join('#');
|
|
|
|
if (!currentCoverage[scriptName][key]) {
|
|
|
|
currentCoverage[scriptName][key] = count;
|
|
|
|
} else {
|
|
|
|
currentCoverage[scriptName][key] += count;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
// Covered lines are determined by comparing every offset mentioned as of the
|
|
|
|
// the completion of a test to the last time we measured coverage. If an
|
|
|
|
// offset in a line is novel as of this test, or a count has increased for
|
|
|
|
// any offset on a particular line, that line must have been covered.
|
|
|
|
for (let scriptName in currentCoverage) {
|
|
|
|
for (let key in currentCoverage[scriptName]) {
|
|
|
|
if (!this._allCoverage[scriptName] ||
|
|
|
|
!this._allCoverage[scriptName][key] ||
|
|
|
|
(this._allCoverage[scriptName][key] <
|
|
|
|
currentCoverage[scriptName][key])) {
|
|
|
|
let [lineNumber, colNumber, offset] = key.split('#');
|
|
|
|
if (!coveredLines[scriptName]) {
|
|
|
|
coveredLines[scriptName] = new Set();
|
|
|
|
}
|
|
|
|
coveredLines[scriptName].add(parseInt(lineNumber, 10));
|
|
|
|
this._allCoverage[scriptName][key] = currentCoverage[scriptName][key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return coveredLines;
|
|
|
|
}
|
|
|
|
|
2016-03-12 03:13:33 +03:00
|
|
|
CoverageCollector.prototype._getUncoveredLines = function() {
|
|
|
|
let uncoveredLines = {};
|
|
|
|
this._scripts.forEach(s => {
|
|
|
|
let scriptName = s.url;
|
|
|
|
let scriptOffsets = s.getAllOffsets();
|
|
|
|
|
2017-01-22 02:04:28 +03:00
|
|
|
if (!uncoveredLines[scriptName]) {
|
2016-03-12 03:13:33 +03:00
|
|
|
uncoveredLines[scriptName] = new Set();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get all lines in the script
|
|
|
|
scriptOffsets.forEach( function(element, index) {
|
2017-01-22 02:04:28 +03:00
|
|
|
if (!element) {
|
2016-03-12 03:13:33 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
uncoveredLines[scriptName].add(index);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// For all covered lines, delete their entry
|
2017-01-22 02:04:28 +03:00
|
|
|
for (let scriptName in this._allCoverage) {
|
|
|
|
for (let key in this._allCoverage[scriptName]) {
|
2016-03-12 03:13:33 +03:00
|
|
|
let [lineNumber, columnNumber, offset] = key.split('#');
|
|
|
|
uncoveredLines[scriptName].delete(parseInt(lineNumber, 10));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return uncoveredLines;
|
|
|
|
}
|
2015-12-24 01:04:49 +03:00
|
|
|
|
2016-03-12 03:18:19 +03:00
|
|
|
CoverageCollector.prototype._getMethodNames = function() {
|
|
|
|
let methodNames = {};
|
|
|
|
this._scripts.forEach(s => {
|
|
|
|
let method = s.displayName;
|
|
|
|
// If the method name is undefined, we return early
|
2017-01-22 02:04:28 +03:00
|
|
|
if (!method) {
|
2016-03-12 03:18:19 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let scriptName = s.url;
|
|
|
|
let tempMethodCov = [];
|
|
|
|
let scriptOffsets = s.getAllOffsets();
|
|
|
|
|
2017-01-22 02:04:28 +03:00
|
|
|
if (!methodNames[scriptName]) {
|
2016-03-12 03:18:19 +03:00
|
|
|
methodNames[scriptName] = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get all lines contained within the method and
|
|
|
|
* push a record of the form:
|
|
|
|
* <method name> : <lines covered>
|
|
|
|
*/
|
2017-01-22 02:04:28 +03:00
|
|
|
scriptOffsets.forEach( function (element, index) {
|
|
|
|
if (!element) {
|
2016-03-12 03:18:19 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
tempMethodCov.push(index);
|
|
|
|
});
|
|
|
|
methodNames[scriptName][method] = tempMethodCov;
|
|
|
|
});
|
|
|
|
|
|
|
|
return methodNames;
|
|
|
|
}
|
|
|
|
|
2017-01-22 02:04:28 +03:00
|
|
|
/**
|
|
|
|
* Implements an iterator for objects. It is
|
|
|
|
* used to iterate over the elements of the object obtained
|
|
|
|
* from the function _getMethodNames.
|
|
|
|
*/
|
|
|
|
Object.prototype[Symbol.iterator] = function * () {
|
|
|
|
for (var [key, value] of Object.entries(this)) {
|
|
|
|
yield [key, value];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2015-12-24 01:04:49 +03:00
|
|
|
/**
|
|
|
|
* Records lines covered since the last time coverage was recorded,
|
|
|
|
* associating them with the given test name. The result is written
|
|
|
|
* to a json file in a specified directory.
|
|
|
|
*/
|
|
|
|
CoverageCollector.prototype.recordTestCoverage = function (testName) {
|
2017-04-29 17:47:24 +03:00
|
|
|
let ccov_scope = {};
|
|
|
|
const {TextEncoder, OS} = Cu.import("resource://gre/modules/osfile.jsm", ccov_scope);
|
|
|
|
this._encoder = new TextEncoder();
|
|
|
|
|
2015-12-24 01:04:49 +03:00
|
|
|
dump("Collecting coverage for: " + testName + "\n");
|
|
|
|
let rawLines = this._getLinesCovered(testName);
|
2016-03-12 03:21:36 +03:00
|
|
|
let methods = this._getMethodNames();
|
|
|
|
let uncoveredLines = this._getUncoveredLines();
|
2015-12-24 01:04:49 +03:00
|
|
|
let result = [];
|
2016-03-12 03:21:36 +03:00
|
|
|
let versionControlBlock = {version: 1.0};
|
|
|
|
result.push(versionControlBlock);
|
|
|
|
|
2015-12-24 01:04:49 +03:00
|
|
|
for (let scriptName in rawLines) {
|
|
|
|
let rec = {
|
|
|
|
testUrl: testName,
|
|
|
|
sourceFile: scriptName,
|
2016-03-12 03:21:36 +03:00
|
|
|
methods: {},
|
|
|
|
covered: [],
|
|
|
|
uncovered: []
|
2015-12-24 01:04:49 +03:00
|
|
|
};
|
2016-03-12 03:21:36 +03:00
|
|
|
|
2017-01-22 02:04:28 +03:00
|
|
|
if (typeof(methods[scriptName]) != 'undefined' && methods[scriptName] != null) {
|
|
|
|
for (let [methodName, methodLines] of methods[scriptName]) {
|
|
|
|
rec.methods[methodName] = methodLines;
|
|
|
|
}
|
2016-03-12 03:21:36 +03:00
|
|
|
}
|
|
|
|
|
2015-12-24 01:04:49 +03:00
|
|
|
for (let line of rawLines[scriptName]) {
|
|
|
|
rec.covered.push(line);
|
|
|
|
}
|
2016-03-12 03:21:36 +03:00
|
|
|
|
2017-01-22 02:04:28 +03:00
|
|
|
for (let line of uncoveredLines[scriptName]) {
|
2016-03-12 03:21:36 +03:00
|
|
|
rec.uncovered.push(line);
|
|
|
|
}
|
|
|
|
|
2015-12-24 01:04:49 +03:00
|
|
|
result.push(rec);
|
|
|
|
}
|
|
|
|
let arr = this._encoder.encode(JSON.stringify(result, null, 2));
|
|
|
|
let path = this._prefix + '/' + 'jscov_' + Date.now() + '.json';
|
|
|
|
dump("Writing coverage to: " + path + "\n");
|
|
|
|
return OS.File.writeAtomic(path, arr, {tmpPath: path + '.tmp'});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tear down the debugger after all tests are complete.
|
|
|
|
*/
|
|
|
|
CoverageCollector.prototype.finalize = function () {
|
|
|
|
this._dbg.removeAllDebuggees();
|
|
|
|
this._dbg.enabled = false;
|
|
|
|
}
|