-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathpcc-js-coverage.js
More file actions
263 lines (240 loc) · 8.5 KB
/
pcc-js-coverage.js
File metadata and controls
263 lines (240 loc) · 8.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
/* 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/. */
////////////////////////////////////////////////////////////////////////////////// List of things that want/need to be done:
// 1. Handle preprocessed-scripts
// 2. Handle non-flat-format builds
// 3. Support embedded scripts
// 4. Make this faster if possible
// 5. Branch coverage data
// 6. Fix function hit counts.
////////////////////////////////////////////////////////////////////////////////
let Cc = Components.classes, Ci = Components.interfaces;
Components.utils.import("resource:///modules/FileUtils.jsm");
Components.utils.import("resource:///modules/NetUtil.jsm");
Components.utils.import("resource:///modules/ctypes.jsm");
if (arguments.length < 2) {
throw ("Need two arguments: <output json data> <output lcov> " +
"[<directories to scan for more scripts>]\n");
}
// Snarf a file to a string
function loadFileToString(file) {
let frawin = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
frawin.init(file, -1, 0, 0);
let fin = Cc["@mozilla.org/scriptableinputstream;1"]
.createInstance(Ci.nsIScriptableInputStream);
fin.init(frawin);
let data = "";
let str = fin.read(4096);
while (str.length > 0) {
data += str;
str = fin.read(4096);
}
fin.close();
return data;
}
// A generator that returns the lines of the file. Useful for not blowing up
// memory usage.
function yieldFileLines(file) {
let frawin = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
frawin.init(file, -1, 0, 0);
frawin.QueryInterface(Ci.nsILineInputStream);
let line = {};
while (frawin.readLine(line)) {
yield line.value;
}
yield line.value;
}
// Map a script "filename" to a real one
function find_file(file, base) {
// I don't know why this comes up, but I sometimes see things like
// resource:///foo -> resource:///bar (the correct file is the latter)
let parts = file.split(" -> ");
file = parts[parts.length - 1];
if (file.indexOf(":") >= 0) {
// resource URIs make channels whose specs are the file URIs. This is a
// shortcut to get the filename [leaks the channel, though]
try {
var channel = NetUtil.newChannel(file);
file = channel.URI.spec;
} catch (e) {
print("Unable to view URL " + file);
return file;
}
}
// Delete the beginning of file URIs
if (file.substring(0, "file://".length) == "file://")
file = file.substring("file://".length);
else if (file.indexOf(":") >= 0) {
// Any other URI is impossible to handle
print("Unknown URL: " + file);
return file;
}
// We want to follow symlinks (hope people do flat chrome files!)
let fd = FileUtils.File(file[0] == "/" ? file : base + "/" + file);
if (fd.exists()) {
fd.normalize();
return fd.isSymlink() ? fd.target : fd.path;
}
return '';
}
// Handle a result line for a single execution.
function process_test(test, totals) {
// We want to get all the counts, and we don't care about how it gets executed
// so sum it all up
function exec_count(data) {
let sum = 0;
for (let type in data)
sum += data[type];
return sum;
}
// Helper for default stuff
function get_default(obj, prop, def) {
return prop in obj ? obj[prop] : def;
}
// Each line is [base, summary, contents]
let base_file = test.shift();
for (let line of test) {
// Ignore files that don't map to source code (e.g., eval inner scripts).
let file = find_file(line[0].file, base_file);
if (file == "") continue;
if (!(file in totals))
totals[file] = {funcs: {}, lines: {}};
// If the contents isn't null
// XXX? Still needed?
if (line[1] !== null) {
// pcccount data looks like:
// {text: ''; opcodes: []}
// Each opcode looks like:
// {line: 123; counts: {typea: 13, typeb:12}, textOffset: 534, text: '',
// name: ''} [textOffset/text isn't always present]
let line_data = {};
for (let opcodeData of line[1].opcodes) {
//if (!('textOffset' in opcodeData) && !('text' in opcodeData)) {
// continue;
//}
let counts = exec_count(opcodeData.counts);
// There can be multiple opcodes per line. Instead of adding everything
// up, just take the maximum of any operand in the line. Then add all
// the data in one go to the output array
line_data[opcodeData.line] = Math.max(counts,
get_default(line_data, opcodeData.line, 0));
}
let lines = totals[file].lines;
for (let line in line_data) {
lines[line] = get_default(lines, line, 0) + line_data[line];
}
}
// Process function coverage using the summary. This produces insane counts,
// since it uses the sum of all codes, but it's simplest for now.
let funcname = line[0].name;
if (funcname) {
let data = get_default(totals[file].funcs, funcname,
{line: line[0].line, count: 0});
data.count += exec_count(line[0].totals);
totals[file].funcs[funcname] = data;
}
}
}
// Process the input to produce the coverage data
let totals = {};
for (let line of yieldFileLines(FileUtils.File(arguments[0]))) {
if (line.length == 0)
continue;
let test = JSON.parse(line);
process_test(test, totals);
}
function insert_not_exec(file, file_data) {
// So, the JS shell only outputs this to stdout. We use a system+ctypes thunk
// to shove this where it needs to go.
let outFile = FileUtils.File("/tmp/disrendezvous");
outFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 6 * 8 * 8);
let libc = ctypes.open("libc.so.6");
let system = libc.declare("system", ctypes.default_abi, ctypes.int, ctypes.char.ptr);
system("/src/build/trunk/mail/mozilla/dist/bin/js -e 'disfile(" +
'"-l", "-r", "' + file + '")\' > ' + outFile.path);
let dbg = Components.utils.getJSTestingFunctions();
let lines = loadFileToString(outFile).split('\n');
outFile.remove(false);
libc.close();
// Process the data. We're mostly interested in the line numbers of each
// opcode
let ignoreData = true;
for (let line of lines) {
if (ignoreData) {
// Opcode 0
if (line.substring(0,6) == '00000:')
ignoreData = false;
else
continue;
}
// Ignore source notes
if (line.substring(0, 6) == "Source") {
ignoreData = true;
continue;
}
let data = /^0*([0-9]+): *([0-9]+)/.exec(line);
// No line data
if (data === null)
continue;
// line number is column 2
var lno = parseInt(data[2]);
if (!(lno in file_data.lines))
file_data.lines[lno] = 0;
// Look for an explicit mention of a function
let match = /function ([a-zA-Z_$0-9]+)\(/.exec(line);
if (match !== null) {
let fname = match[1];
if (!(fname in file_data.funcs)) {
file_data.funcs[fname] = {line: lno, count: 0};
}
}
}
}
// pccounts necessarily don't show counts for scripts that aren't run. Load in
// all the files and find functions that weren't run.
for (let file in totals) {
insert_not_exec(file, totals[file]);
}
// Walk the directory tree to find other files not even loaded (!)
function process_directory(dir, totals) {
let files = dir.directoryEntries;
while (files.hasMoreElements()) {
let file = files.getNext().QueryInterface(Ci.nsIFile);
if (file.isDirectory())
process_directory(file, totals);
else {
let path = file.isSymlink() ? file.target : file.path;
// Don't process any file we've already talked
if (path in totals)
continue;
let ext = path.split('.');
ext = ext[ext.length - 1];
if (ext != 'js' && ext != 'jsm')
continue;
totals[path] = {funcs: {}, lines: {}};
insert_not_exec(path, totals[path]);
}
}
}
for (let i = 2; i < arguments.length; i++) {
process_directory(FileUtils.File(arguments[i]), totals);
}
// Output in lcov's format
let fout = FileUtils.openFileOutputStream(FileUtils.File(arguments[1]));
function write(str) { fout.write(str, str.length); }
write("TN:\n");
for (let file in totals) {
write("SF:" + file + "\n");
for (let func in totals[file].funcs) {
let data = totals[file].funcs[func];
write("FN:" + data.line + "," + func + "\n");
write("FNDA:" + data.count + "," + func + "\n");
}
for (let line in totals[file].lines) {
write("DA:" + line + "," + totals[file].lines[line] + "\n");
}
write("end_of_record\n");
}