Bug 1866777 - Disable test_race_cache_with_network.js on windows opt for frequent...
[gecko.git] / testing / modules / CoverageUtils.sys.mjs
bloba19432305a38198eb1d5b98c4b58baf27fab6e93
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 import { addDebuggerToGlobal } from "resource://gre/modules/jsdebugger.sys.mjs";
7 // eslint-disable-next-line mozilla/reject-globalThis-modification
8 addDebuggerToGlobal(globalThis);
10 /**
11  * Records coverage for each test by way of the js debugger.
12  */
13 export var CoverageCollector = function (prefix) {
14   this._prefix = prefix;
15   this._dbg = new Debugger();
16   this._dbg.collectCoverageInfo = true;
17   this._dbg.addAllGlobalsAsDebuggees();
18   this._scripts = this._dbg.findScripts();
20   this._dbg.onNewScript = script => {
21     this._scripts.push(script);
22   };
24   // Source -> coverage data;
25   this._allCoverage = {};
26   this._encoder = new TextEncoder();
28   this._testIndex = 0;
31 CoverageCollector.prototype._getLinesCovered = function () {
32   let coveredLines = {};
33   let currentCoverage = {};
34   this._scripts.forEach(s => {
35     let scriptName = s.url;
36     let cov = s.getOffsetsCoverage();
37     if (!cov) {
38       return;
39     }
41     cov.forEach(covered => {
42       let { lineNumber, columnNumber, offset, count } = covered;
43       if (!count) {
44         return;
45       }
47       if (!currentCoverage[scriptName]) {
48         currentCoverage[scriptName] = {};
49       }
50       if (!this._allCoverage[scriptName]) {
51         this._allCoverage[scriptName] = {};
52       }
54       // NOTE: columnNumber is 1-origin.
55       let key = [lineNumber, columnNumber - 1, offset].join("#");
56       if (!currentCoverage[scriptName][key]) {
57         currentCoverage[scriptName][key] = count;
58       } else {
59         currentCoverage[scriptName][key] += count;
60       }
61     });
62   });
64   // Covered lines are determined by comparing every offset mentioned as of the
65   // the completion of a test to the last time we measured coverage. If an
66   // offset in a line is novel as of this test, or a count has increased for
67   // any offset on a particular line, that line must have been covered.
68   for (let scriptName in currentCoverage) {
69     for (let key in currentCoverage[scriptName]) {
70       if (
71         !this._allCoverage[scriptName] ||
72         !this._allCoverage[scriptName][key] ||
73         this._allCoverage[scriptName][key] < currentCoverage[scriptName][key]
74       ) {
75         // eslint-disable-next-line no-unused-vars
76         let [lineNumber, colNumber, offset] = key.split("#");
77         if (!coveredLines[scriptName]) {
78           coveredLines[scriptName] = new Set();
79         }
80         coveredLines[scriptName].add(parseInt(lineNumber, 10));
81         this._allCoverage[scriptName][key] = currentCoverage[scriptName][key];
82       }
83     }
84   }
86   return coveredLines;
89 CoverageCollector.prototype._getUncoveredLines = function () {
90   let uncoveredLines = {};
91   this._scripts.forEach(s => {
92     let scriptName = s.url;
93     let scriptOffsets = s.getAllOffsets();
95     if (!uncoveredLines[scriptName]) {
96       uncoveredLines[scriptName] = new Set();
97     }
99     // Get all lines in the script
100     scriptOffsets.forEach(function (element, index) {
101       if (!element) {
102         return;
103       }
104       uncoveredLines[scriptName].add(index);
105     });
106   });
108   // For all covered lines, delete their entry
109   for (let scriptName in this._allCoverage) {
110     for (let key in this._allCoverage[scriptName]) {
111       // eslint-disable-next-line no-unused-vars
112       let [lineNumber, columnNumber, offset] = key.split("#");
113       uncoveredLines[scriptName].delete(parseInt(lineNumber, 10));
114     }
115   }
117   return uncoveredLines;
120 CoverageCollector.prototype._getMethodNames = function () {
121   let methodNames = {};
122   this._scripts.forEach(s => {
123     let method = s.displayName;
124     // If the method name is undefined, we return early
125     if (!method) {
126       return;
127     }
129     let scriptName = s.url;
130     let tempMethodCov = [];
131     let scriptOffsets = s.getAllOffsets();
133     if (!methodNames[scriptName]) {
134       methodNames[scriptName] = {};
135     }
137     /**
138      * Get all lines contained within the method and
139      * push a record of the form:
140      * <method name> : <lines covered>
141      */
142     scriptOffsets.forEach(function (element, index) {
143       if (!element) {
144         return;
145       }
146       tempMethodCov.push(index);
147     });
148     methodNames[scriptName][method] = tempMethodCov;
149   });
151   return methodNames;
155  * Records lines covered since the last time coverage was recorded,
156  * associating them with the given test name. The result is written
157  * to a json file in a specified directory.
158  */
159 CoverageCollector.prototype.recordTestCoverage = function (testName) {
160   dump("Collecting coverage for: " + testName + "\n");
161   let rawLines = this._getLinesCovered(testName);
162   let methods = this._getMethodNames();
163   let uncoveredLines = this._getUncoveredLines();
164   let result = [];
165   let versionControlBlock = { version: 1.0 };
166   result.push(versionControlBlock);
168   for (let scriptName in rawLines) {
169     let rec = {
170       testUrl: testName,
171       sourceFile: scriptName,
172       methods: {},
173       covered: [],
174       uncovered: [],
175     };
177     if (
178       typeof methods[scriptName] != "undefined" &&
179       methods[scriptName] != null
180     ) {
181       for (let [methodName, methodLines] of Object.entries(
182         methods[scriptName]
183       )) {
184         rec.methods[methodName] = methodLines;
185       }
186     }
188     for (let line of rawLines[scriptName]) {
189       rec.covered.push(line);
190     }
192     for (let line of uncoveredLines[scriptName]) {
193       rec.uncovered.push(line);
194     }
196     result.push(rec);
197   }
198   let path = this._prefix + "/jscov_" + Date.now() + ".json";
199   dump("Writing coverage to: " + path + "\n");
200   return IOUtils.writeUTF8(path, JSON.stringify(result, undefined, 2), {
201     tmpPath: `${path}.tmp`,
202   });
206  * Tear down the debugger after all tests are complete.
207  */
208 CoverageCollector.prototype.finalize = function () {
209   this._dbg.removeAllDebuggees();
210   this._dbg.enabled = false;