Bug 1586807 - Make pseudoclass locking work with Fission. r=pbro
[gecko.git] / testing / modules / CoverageUtils.jsm
blob6826d01a357b78c7744cbbafa18d75e1b48f7d3b
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 var EXPORTED_SYMBOLS = ["CoverageCollector"];
9 /* globals Debugger */
10 const { addDebuggerToGlobal } = ChromeUtils.import(
11   "resource://gre/modules/jsdebugger.jsm"
13 addDebuggerToGlobal(Cu.getGlobalForObject(this));
15 /**
16  * Records coverage for each test by way of the js debugger.
17  */
18 var CoverageCollector = function(prefix) {
19   this._prefix = prefix;
20   this._dbg = new Debugger();
21   this._dbg.collectCoverageInfo = true;
22   this._dbg.addAllGlobalsAsDebuggees();
23   this._scripts = this._dbg.findScripts();
25   this._dbg.onNewScript = script => {
26     this._scripts.push(script);
27   };
29   // Source -> coverage data;
30   this._allCoverage = {};
31   this._encoder = new TextEncoder();
33   this._testIndex = 0;
36 CoverageCollector.prototype._getLinesCovered = function() {
37   let coveredLines = {};
38   let currentCoverage = {};
39   this._scripts.forEach(s => {
40     let scriptName = s.url;
41     let cov = s.getOffsetsCoverage();
42     if (!cov) {
43       return;
44     }
46     cov.forEach(covered => {
47       let { lineNumber, columnNumber, offset, count } = covered;
48       if (!count) {
49         return;
50       }
52       if (!currentCoverage[scriptName]) {
53         currentCoverage[scriptName] = {};
54       }
55       if (!this._allCoverage[scriptName]) {
56         this._allCoverage[scriptName] = {};
57       }
59       let key = [lineNumber, columnNumber, offset].join("#");
60       if (!currentCoverage[scriptName][key]) {
61         currentCoverage[scriptName][key] = count;
62       } else {
63         currentCoverage[scriptName][key] += count;
64       }
65     });
66   });
68   // Covered lines are determined by comparing every offset mentioned as of the
69   // the completion of a test to the last time we measured coverage. If an
70   // offset in a line is novel as of this test, or a count has increased for
71   // any offset on a particular line, that line must have been covered.
72   for (let scriptName in currentCoverage) {
73     for (let key in currentCoverage[scriptName]) {
74       if (
75         !this._allCoverage[scriptName] ||
76         !this._allCoverage[scriptName][key] ||
77         this._allCoverage[scriptName][key] < currentCoverage[scriptName][key]
78       ) {
79         // eslint-disable-next-line no-unused-vars
80         let [lineNumber, colNumber, offset] = key.split("#");
81         if (!coveredLines[scriptName]) {
82           coveredLines[scriptName] = new Set();
83         }
84         coveredLines[scriptName].add(parseInt(lineNumber, 10));
85         this._allCoverage[scriptName][key] = currentCoverage[scriptName][key];
86       }
87     }
88   }
90   return coveredLines;
93 CoverageCollector.prototype._getUncoveredLines = function() {
94   let uncoveredLines = {};
95   this._scripts.forEach(s => {
96     let scriptName = s.url;
97     let scriptOffsets = s.getAllOffsets();
99     if (!uncoveredLines[scriptName]) {
100       uncoveredLines[scriptName] = new Set();
101     }
103     // Get all lines in the script
104     scriptOffsets.forEach(function(element, index) {
105       if (!element) {
106         return;
107       }
108       uncoveredLines[scriptName].add(index);
109     });
110   });
112   // For all covered lines, delete their entry
113   for (let scriptName in this._allCoverage) {
114     for (let key in this._allCoverage[scriptName]) {
115       // eslint-disable-next-line no-unused-vars
116       let [lineNumber, columnNumber, offset] = key.split("#");
117       uncoveredLines[scriptName].delete(parseInt(lineNumber, 10));
118     }
119   }
121   return uncoveredLines;
124 CoverageCollector.prototype._getMethodNames = function() {
125   let methodNames = {};
126   this._scripts.forEach(s => {
127     let method = s.displayName;
128     // If the method name is undefined, we return early
129     if (!method) {
130       return;
131     }
133     let scriptName = s.url;
134     let tempMethodCov = [];
135     let scriptOffsets = s.getAllOffsets();
137     if (!methodNames[scriptName]) {
138       methodNames[scriptName] = {};
139     }
141     /**
142      * Get all lines contained within the method and
143      * push a record of the form:
144      * <method name> : <lines covered>
145      */
146     scriptOffsets.forEach(function(element, index) {
147       if (!element) {
148         return;
149       }
150       tempMethodCov.push(index);
151     });
152     methodNames[scriptName][method] = tempMethodCov;
153   });
155   return methodNames;
159  * Records lines covered since the last time coverage was recorded,
160  * associating them with the given test name. The result is written
161  * to a json file in a specified directory.
162  */
163 CoverageCollector.prototype.recordTestCoverage = function(testName) {
164   let ccov_scope = {};
165   const { OS } = ChromeUtils.import(
166     "resource://gre/modules/osfile.jsm",
167     ccov_scope
168   );
170   dump("Collecting coverage for: " + testName + "\n");
171   let rawLines = this._getLinesCovered(testName);
172   let methods = this._getMethodNames();
173   let uncoveredLines = this._getUncoveredLines();
174   let result = [];
175   let versionControlBlock = { version: 1.0 };
176   result.push(versionControlBlock);
178   for (let scriptName in rawLines) {
179     let rec = {
180       testUrl: testName,
181       sourceFile: scriptName,
182       methods: {},
183       covered: [],
184       uncovered: [],
185     };
187     if (
188       typeof methods[scriptName] != "undefined" &&
189       methods[scriptName] != null
190     ) {
191       for (let [methodName, methodLines] of Object.entries(
192         methods[scriptName]
193       )) {
194         rec.methods[methodName] = methodLines;
195       }
196     }
198     for (let line of rawLines[scriptName]) {
199       rec.covered.push(line);
200     }
202     for (let line of uncoveredLines[scriptName]) {
203       rec.uncovered.push(line);
204     }
206     result.push(rec);
207   }
208   let arr = this._encoder.encode(JSON.stringify(result, null, 2));
209   let path = this._prefix + "/jscov_" + Date.now() + ".json";
210   dump("Writing coverage to: " + path + "\n");
211   return OS.File.writeAtomic(path, arr, { tmpPath: path + ".tmp" });
215  * Tear down the debugger after all tests are complete.
216  */
217 CoverageCollector.prototype.finalize = function() {
218   this._dbg.removeAllDebuggees();
219   this._dbg.enabled = false;