Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(430)

Side by Side Diff: tools/full-coverage.dart

Issue 24654003: Improve code coverage generation. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « runtime/vm/object.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 import "dart:async";
6 import "dart:convert";
7 import "dart:io";
8 import "dart:isolate";
9 import "dart:mirrors";
10
11 import "package:args/args.dart";
12 import "package:path/path.dart";
13
14 /// [Environment] stores gathered arguments information.
15 class Environment {
16 String sdkRoot;
17 String pkgRoot;
18 var input;
19 var output;
20 int workers;
21 bool prettyPrint;
22 bool lcov;
23 bool expectMarkers;
24 bool verbose;
25 }
26
27 /// [Resolver] resolves imports with respect to a given environment.
28 class Resolver {
29 const DART_PREFIX = "dart:";
30 const PACKAGE_PREFIX = "package:";
31 const FILE_PREFIX = "file://";
32 const HTTP_PREFIX = "http://";
33
34 Map _env;
35 List failed = [];
36
37 Resolver(this._env);
38
39 /// Returns the absolute path wrt. to the given environment or null, if the
40 /// import could not be resolved.
41 resolve(String import) {
42 if (import.startsWith(DART_PREFIX)) {
43 var slashPos = import.indexOf("/");
44 var filePath;
45 if (slashPos != -1) {
46 var path = import.substring(DART_PREFIX.length, slashPos);
47 // Drop patch files, since we don't have their source in the compiled
48 // SDK.
49 if (path.endsWith("-patch")) {
50 failed.add(import);
51 return null;
52 }
53 // Canonicalize path. For instance: _collection-dev => _collection_dev.
54 path = path.replaceAll("-", "_");
55 filePath = "${_env["sdkRoot"]}"
56 "/${path}${import.substring(slashPos, import.length)}";
57 } else {
58 // Resolve 'dart:something' to be something/something.dart in the SDK.
59 var lib = import.substring(DART_PREFIX.length, import.length);
60 filePath = "${_env["sdkRoot"]}/${lib}/${lib}.dart";
61 }
62 return filePath;
63 }
64 if (import.startsWith(PACKAGE_PREFIX)) {
65 var filePath =
66 "${_env["pkgRoot"]}"
67 "/${import.substring(PACKAGE_PREFIX.length, import.length)}";
68 return filePath;
69 }
70 if (import.startsWith(FILE_PREFIX)) {
71 var filePath = fromUri(Uri.parse(import));
72 return filePath;
73 }
74 if (import.startsWith(HTTP_PREFIX)) {
75 return import;
76 }
77 // We cannot deal with anything else.
78 failed.add(import);
79 return null;
80 }
81 }
82
83 /// Converts the given hitmap to lcov format and appends the result to
84 /// env.output.
85 ///
86 /// Returns a [Future] that completes as soon as all map entries have been
87 /// emitted.
88 Future lcov(Map hitmap) {
89 var emitOne = (key) {
90 var v = hitmap[key];
91 StringBuffer entry = new StringBuffer();
92 entry.write("SF:${key}\n");
93 v.keys.toList()
94 ..sort()
95 ..forEach((k) {
96 entry.write("DA:${k},${v[k]}\n");
97 });
98 entry.write("end_of_record\n");
99 env.output.write(entry.toString());
100 return new Future.value(null);
101 };
102
103 return Future.forEach(hitmap.keys, emitOne);
104 }
105
106 /// Converts the given hitmap to a pretty-print format and appends the result
107 /// to env.output.
108 ///
109 /// Returns a [Future] that completes as soon as all map entries have been
110 /// emitted.
111 Future prettyPrint(Map hitMap, List failedLoads) {
112 var emitOne = (key) {
113 var v = hitMap[key];
114 var c = new Completer();
115 loadResource(key).then((lines) {
116 if (lines == null) {
117 failedLoads.add(key);
118 c.complete();
119 return;
120 }
121 env.output.write("${key}\n");
122 for (var line = 1; line <= lines.length; line++) {
123 String prefix = " ";
124 if (v.containsKey(line)) {
125 prefix = v[line].toString();
126 StringBuffer b = new StringBuffer();
127 for (int i = prefix.length; i < 7; i++) {
128 b.write(" ");
129 }
130 b.write(prefix);
131 prefix = b.toString();
132 }
133 env.output.write("${prefix}|${lines[line-1]}\n");
134 }
135 c.complete();
136 });
137 return c.future;
138 };
139
140 return Future.forEach(hitMap.keys, emitOne);
141 }
142
143 /// Load an import resource and return a [Future] with a [List] of its lines.
144 /// Returns [null] instead of a list if the resource could not be loaded.
145 Future<List> loadResource(String import) {
146 if (import.startsWith("http")) {
147 Completer c = new Completer();
148 HttpClient client = new HttpClient();
149 client.getUrl(Uri.parse(import))
150 .then((HttpClientRequest request) {
151 return request.close();
152 })
153 .then((HttpClientResponse response) {
154 response.transform(new StringDecoder()).toList().then((data) {
155 c.complete(data);
156 httpClient.close();
157 });
158 })
159 .catchError((e) {
160 c.complete(null);
161 });
162 return c.future;
163 } else {
164 File f = new File(import);
165 return f.readAsLines()
166 .catchError((e) {
167 return new Future.value(null);
168 });
169 }
170 }
171
172 /// Creates a single hitmap from a raw json object. Throws away all entries that
173 /// are not resolvable.
174 Map createHitmap(String rawJson, Resolver resolver) {
175 Map<String, Map<int,int>> hitMap = {};
176
177 addToMap(source, line, count) {
178 if (!hitMap[source].containsKey(line)) {
179 hitMap[source][line] = 0;
180 }
181 hitMap[source][line] += count;
182 }
183
184 JSON.decode(rawJson).forEach((Map e) {
185 String source = resolver.resolve(e["source"]);
186 if (source == null) {
187 // Couldnt resolve import, so skip this entry.
188 return;
189 }
190 if (!hitMap.containsKey(source)) {
191 hitMap[source] = {};
192 }
193 var hits = e["hits"];
194 // hits is a flat array of the following format:
195 // [ <line|linerange>, <hitcount>,...]
196 // line: number.
197 // linerange: "<line>-<line>".
198 for (var i = 0; i < hits.length; i += 2) {
199 var k = hits[i];
200 if (k is num) {
201 // Single line.
202 addToMap(source, k, hits[i+1]);
203 }
204 if (k is String) {
205 // Linerange. We expand line ranges to actual lines at this point.
206 var splitPos = k.indexOf("-");
207 int start = int.parse(k.substring(0, splitPos));
208 int end = int.parse(k.substring(splitPos + 1, k.length));
209 for (var j = start; j <= end; j++) {
210 addToMap(source, j, hits[i+1]);
211 }
212 }
213 }
214 });
215 return hitMap;
216 }
217
218 /// Merges [newMap] into [result].
219 mergeHitmaps(Map newMap, Map result) {
220 newMap.forEach((String file, Map v) {
221 if (result.containsKey(file)) {
222 v.forEach((int line, int cnt) {
223 if (result[file][line] == null) {
224 result[file][line] = cnt;
225 } else {
226 result[file][line] += cnt;
227 }
228 });
229 } else {
230 result[file] = v;
231 }
232 });
233 }
234
235 /// Given an absolute path absPath, this function returns a [List] of files
236 /// are contained by it if it is a directory, or a [List] containing the file if
237 /// it is a file.
238 List filesToProcess(String absPath) {
239 if (FileSystemEntity.isDirectorySync(absPath)) {
240 Directory d = new Directory(absPath);
241 List files = [];
242 d.listSync(recursive: true).forEach((FileSystemEntity entity) {
243 if (entity is File) {
244 files.add(entity as File);
245 }
246 });
247 return files;
248 } else if (FileSystemEntity.isFileSync(absPath)) {
249 return [ new File(absPath) ];
250 }
251 }
252
253 worker() {
254 final start = new DateTime.now().millisecondsSinceEpoch;
255 String me = currentMirrorSystem().isolate.debugName;
256
257 port.receive((Message message, reply) {
258 if (message.type == Message.SHUTDOWN) {
259 port.close();
260 }
261
262 if (message.type == Message.WORK) {
263 var env = message.payload[0];
264 List files = message.payload[1];
265 Resolver resolver = new Resolver(env);
266 var workerHitmap = {};
267 files.forEach((File fileEntry) {
268 // Read file sync, as it only contains 1 object.
269 String contents = fileEntry.readAsStringSync();
270 if (contents.length > 0) {
271 mergeHitmaps(createHitmap(contents, resolver), workerHitmap);
272 }
273 });
274 if (env["verbose"]) {
275 final end = new DateTime.now().millisecondsSinceEpoch;
276 print("worker[${me}]: Finished processing files. "
277 "Took ${end - start} ms.");
278 }
279 reply.send(new Message(Message.RESULT, [workerHitmap, resolver.failed]));
280 }
281
282 });
283 }
284
285 class Message {
286 static const int SHUTDOWN = 1;
287 static const int RESULT = 2;
288 static const int WORK = 3;
289
290 final int type;
291 final payload;
292
293 Message(this.type, this.payload);
294 }
295
296 final env = new Environment();
297
298 main() {
299 parseArgs();
300
301 List files = filesToProcess(env.input);
302 int filesPerWorker = files.length ~/ env.workers;
303 List workerPorts = [];
304 int doneCnt = 0;
305
306 List failedResolves = [];
307 List failedLoads = [];
308 Map globalHitmap = {};
309 int start = new DateTime.now().millisecondsSinceEpoch;
310
311 if (env.verbose) {
312 print("Environment:");
313 print(" # files: ${files.length}");
314 print(" # workers: ${env.workers}");
315 print(" sdk-root: ${env.sdkRoot}");
316 print(" package-root: ${env.pkgRoot}");
317 }
318
319 port.receive((Message message, reply) {
320 if (message.type == Message.RESULT) {
321 mergeHitmaps(message.payload[0], globalHitmap);
322 failedResolves.addAll(message.payload[1]);
323 doneCnt++;
324 }
325
326 // All workers are done. Process the data.
327 if (doneCnt == env.workers) {
328 workerPorts.forEach((p) => p.send(new Message(Message.SHUTDOWN, null)));
329 if (env.verbose) {
330 final end = new DateTime.now().millisecondsSinceEpoch;
331 print("Done creating a global hitmap. Took ${end - start} ms.");
332 }
333
334 Future out;
335 if (env.prettyPrint) {
336 out = prettyPrint(globalHitmap, failedLoads);
337 }
338 if (env.lcov) {
339 out = lcov(globalHitmap);
340 }
341
342 out.then((_) {
343 env.output.close().then((_) {
344 if (env.verbose) {
345 final end = new DateTime.now().millisecondsSinceEpoch;
346 print("Done flushing output. Took ${end - start} ms.");
347 }
348 });
349 port.close();
350
351 if (env.verbose) {
352 if (failedResolves.length > 0) {
353 print("Failed to resolve:");
354 failedResolves.toSet().forEach((e) {
355 print(" ${e}");
356 });
357 }
358 if (failedLoads.length > 0) {
359 print("Failed to load:");
360 failedLoads.toSet().forEach((e) {
361 print(" ${e}");
362 });
363 }
364 }
365
366 });
367 }
368 });
369
370 Map sharedEnv = {
371 "sdkRoot": env.sdkRoot,
372 "pkgRoot": env.pkgRoot,
373 "verbose": env.verbose,
374 };
375
376 // Create workers.
377 for (var i = 1; i < env.workers; i++) {
378 var p = spawnFunction(worker);
379 workerPorts.add(p);
380 var start = files.length - filesPerWorker;
381 var end = files.length;
382 var workerFiles = files.getRange(start, end).toList();
383 files.removeRange(start, end);
384 p.send(new Message(Message.WORK, [sharedEnv, workerFiles]), port);
385 }
386 // Let the last worker deal with the rest of the files (which should be only
387 // off by at max (#workers - 1).
388 var p = spawnFunction(worker);
389 workerPorts.add(p);
390 p.send(new Message(Message.WORK, [sharedEnv, files]), port);
391
392 return 0;
393 }
394
395 /// Checks the validity of the provided arguments. Does not initialize actual
396 /// processing.
397 parseArgs() {
398 var parser = new ArgParser();
399
400 parser.addOption("sdk-root", abbr: "s",
401 help: "path to the SDK root");
402 parser.addOption("package-root", abbr: "p",
403 help: "path to the package root",
404 defaultsTo: ".");
405 parser.addOption("in", abbr: "i",
406 help: "input(s): may be file or directory",
407 defaultsTo: "stdin");
408 parser.addOption("out", abbr: "o",
409 help: "output: may be file or stdout",
410 defaultsTo: "stdout");
411 parser.addOption("workers", abbr: "j",
412 help: "number of workers",
413 defaultsTo: "1");
414 parser.addFlag("pretty-print", abbr: "r",
415 help: "convert coverage data to pretty print format",
416 negatable: false);
417 parser.addFlag("lcov", abbr :"l",
418 help: "convert coverage data to lcov format",
419 negatable: false);
420 parser.addFlag("verbose", abbr :"v",
421 help: "verbose output",
422 negatable: false);
423 parser.addFlag("help", abbr: "h",
424 help: "show this help",
425 negatable: false);
426
427 var args = parser.parse(new Options().arguments);
428
429 if (args["help"]) {
430 print("Usage: coverage [OPTION...]\n");
431 print(parser.getUsage());
432 exit(0);
433 }
434
435 if (args["sdk-root"] == null) {
436 if (Platform.environment.containsKey("SDK_ROOT")) {
437 env.sdkRoot =
438 join(absolute(normalize(Platform.environment["SDK_ROOT"])), "lib");
439 } else {
440 throw "No SDK root found, please specify one using --sdk-root.";
441 }
442 } else {
443 env.sdkRoot = join(absolute(normalize(args["sdk-root"])), "lib");
444 }
445 if (!FileSystemEntity.isDirectorySync(env.sdkRoot)) {
446 throw "Provided SDK root ${args["sdk-root"]} is not a valid SDK "
447 "top-level directory";
448 }
449
450 if (args["package-root"] == null) {
451 env.pkgRoot = absolute(normalize("./packages"));
452 } else {
453 env.pkgRoot = absolute(normalize(args["package-root"]));
454 if (!FileSystemEntity.isDirectorySync(env.pkgRoot)) {
455 throw "Provided package root ${args["package-root"]} is not directory.";
456 }
457 }
458
459 if (args["in"] == "stdin") {
460 env.input = "stdin";
461 } else {
462 env.input = absolute(normalize(args["in"]));
463 if (!FileSystemEntity.isDirectorySync(env.input) &&
464 !FileSystemEntity.isFileSync(env.input)) {
465 throw "Provided input ${args["in"]} is neither a directory, nor a file.";
466 }
467 }
468
469 if (args["out"] == "stdout") {
470 env.output = stdout;
471 } else {
472 env.output = absolute(normalize(args["out"]));
473 env.output = new File(env.output).openWrite();
474 }
475
476 if (args["pretty-print"] &&
477 args["lcov"]) {
478 throw "Choose either pretty-print or lcov output";
479 }
480
481 env.prettyPrint = args["pretty-print"];
482 env.lcov = args["lcov"];
483 env.verbose = args["verbose"];
484 env.workers = int.parse("${args["workers"]}");
485 }
OLDNEW
« no previous file with comments | « runtime/vm/object.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698