| Index: tools/full-coverage.dart
|
| diff --git a/tools/full-coverage.dart b/tools/full-coverage.dart
|
| deleted file mode 100644
|
| index d7ff76f005668bd81aec804d45b25131e9a60723..0000000000000000000000000000000000000000
|
| --- a/tools/full-coverage.dart
|
| +++ /dev/null
|
| @@ -1,507 +0,0 @@
|
| -// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
| -// for details. All rights reserved. Use of this source code is governed by a
|
| -// BSD-style license that can be found in the LICENSE file.
|
| -
|
| -import "dart:async";
|
| -import "dart:convert";
|
| -import "dart:io";
|
| -import "dart:isolate";
|
| -
|
| -import "package:args/args.dart";
|
| -import "package:path/path.dart";
|
| -
|
| -/// [Environment] stores gathered arguments information.
|
| -class Environment {
|
| - String sdkRoot;
|
| - String pkgRoot;
|
| - var input;
|
| - var output;
|
| - int workers;
|
| - bool prettyPrint = false;
|
| - bool lcov = false;
|
| - bool expectMarkers;
|
| - bool verbose;
|
| -}
|
| -
|
| -/// [Resolver] resolves imports with respect to a given environment.
|
| -class Resolver {
|
| - static const DART_PREFIX = "dart:";
|
| - static const PACKAGE_PREFIX = "package:";
|
| - static const FILE_PREFIX = "file://";
|
| - static const HTTP_PREFIX = "http://";
|
| -
|
| - Map _env;
|
| - List failed = [];
|
| -
|
| - Resolver(this._env);
|
| -
|
| - /// Returns the absolute path wrt. to the given environment or null, if the
|
| - /// import could not be resolved.
|
| - resolve(String import) {
|
| - if (import.startsWith(DART_PREFIX)) {
|
| - if (_env["sdkRoot"] == null) {
|
| - // No sdk-root given, do not resolve dart: URIs.
|
| - return null;
|
| - }
|
| - var slashPos = import.indexOf("/");
|
| - var filePath;
|
| - if (slashPos != -1) {
|
| - var path = import.substring(DART_PREFIX.length, slashPos);
|
| - // Drop patch files, since we don't have their source in the compiled
|
| - // SDK.
|
| - if (path.endsWith("-patch")) {
|
| - failed.add(import);
|
| - return null;
|
| - }
|
| - // Canonicalize path. For instance: _collection-dev => _collection_dev.
|
| - path = path.replaceAll("-", "_");
|
| - filePath = "${_env["sdkRoot"]}"
|
| - "/${path}${import.substring(slashPos, import.length)}";
|
| - } else {
|
| - // Resolve 'dart:something' to be something/something.dart in the SDK.
|
| - var lib = import.substring(DART_PREFIX.length, import.length);
|
| - filePath = "${_env["sdkRoot"]}/${lib}/${lib}.dart";
|
| - }
|
| - return filePath;
|
| - }
|
| - if (import.startsWith(PACKAGE_PREFIX)) {
|
| - if (_env["pkgRoot"] == null) {
|
| - // No package-root given, do not resolve package: URIs.
|
| - return null;
|
| - }
|
| - var filePath =
|
| - "${_env["pkgRoot"]}"
|
| - "/${import.substring(PACKAGE_PREFIX.length, import.length)}";
|
| - return filePath;
|
| - }
|
| - if (import.startsWith(FILE_PREFIX)) {
|
| - var filePath = fromUri(Uri.parse(import));
|
| - return filePath;
|
| - }
|
| - if (import.startsWith(HTTP_PREFIX)) {
|
| - return import;
|
| - }
|
| - // We cannot deal with anything else.
|
| - failed.add(import);
|
| - return null;
|
| - }
|
| -}
|
| -
|
| -/// Converts the given hitmap to lcov format and appends the result to
|
| -/// env.output.
|
| -///
|
| -/// Returns a [Future] that completes as soon as all map entries have been
|
| -/// emitted.
|
| -Future lcov(Map hitmap) {
|
| - var emitOne = (key) {
|
| - var v = hitmap[key];
|
| - StringBuffer entry = new StringBuffer();
|
| - entry.write("SF:${key}\n");
|
| - v.keys.toList()
|
| - ..sort()
|
| - ..forEach((k) {
|
| - entry.write("DA:${k},${v[k]}\n");
|
| - });
|
| - entry.write("end_of_record\n");
|
| - env.output.write(entry.toString());
|
| - return new Future.value(null);
|
| - };
|
| -
|
| - return Future.forEach(hitmap.keys, emitOne);
|
| -}
|
| -
|
| -/// Converts the given hitmap to a pretty-print format and appends the result
|
| -/// to env.output.
|
| -///
|
| -/// Returns a [Future] that completes as soon as all map entries have been
|
| -/// emitted.
|
| -Future prettyPrint(Map hitMap, List failedLoads) {
|
| - var emitOne = (key) {
|
| - var v = hitMap[key];
|
| - var c = new Completer();
|
| - loadResource(key).then((lines) {
|
| - if (lines == null) {
|
| - failedLoads.add(key);
|
| - c.complete();
|
| - return;
|
| - }
|
| - env.output.write("${key}\n");
|
| - for (var line = 1; line <= lines.length; line++) {
|
| - String prefix = " ";
|
| - if (v.containsKey(line)) {
|
| - prefix = v[line].toString();
|
| - StringBuffer b = new StringBuffer();
|
| - for (int i = prefix.length; i < 7; i++) {
|
| - b.write(" ");
|
| - }
|
| - b.write(prefix);
|
| - prefix = b.toString();
|
| - }
|
| - env.output.write("${prefix}|${lines[line-1]}\n");
|
| - }
|
| - c.complete();
|
| - });
|
| - return c.future;
|
| - };
|
| -
|
| - return Future.forEach(hitMap.keys, emitOne);
|
| -}
|
| -
|
| -/// Load an import resource and return a [Future] with a [List] of its lines.
|
| -/// Returns [null] instead of a list if the resource could not be loaded.
|
| -Future<List> loadResource(String import) {
|
| - if (import.startsWith("http")) {
|
| - Completer c = new Completer();
|
| - HttpClient client = new HttpClient();
|
| - client.getUrl(Uri.parse(import))
|
| - .then((HttpClientRequest request) {
|
| - return request.close();
|
| - })
|
| - .then((HttpClientResponse response) {
|
| - response.transform(new StringDecoder()).toList().then((data) {
|
| - c.complete(data);
|
| - httpClient.close();
|
| - });
|
| - })
|
| - .catchError((e) {
|
| - c.complete(null);
|
| - });
|
| - return c.future;
|
| - } else {
|
| - File f = new File(import);
|
| - return f.readAsLines()
|
| - .catchError((e) {
|
| - return new Future.value(null);
|
| - });
|
| - }
|
| -}
|
| -
|
| -/// Creates a single hitmap from a raw json object. Throws away all entries that
|
| -/// are not resolvable.
|
| -Map createHitmap(String rawJson, Resolver resolver) {
|
| - Map<String, Map<int,int>> hitMap = {};
|
| -
|
| - addToMap(source, line, count) {
|
| - if (!hitMap[source].containsKey(line)) {
|
| - hitMap[source][line] = 0;
|
| - }
|
| - hitMap[source][line] += count;
|
| - }
|
| -
|
| - JSON.decode(rawJson)['coverage'].forEach((Map e) {
|
| - String source = resolver.resolve(e["source"]);
|
| - if (source == null) {
|
| - // Couldnt resolve import, so skip this entry.
|
| - return;
|
| - }
|
| - if (!hitMap.containsKey(source)) {
|
| - hitMap[source] = {};
|
| - }
|
| - var hits = e["hits"];
|
| - // hits is a flat array of the following format:
|
| - // [ <line|linerange>, <hitcount>,...]
|
| - // line: number.
|
| - // linerange: "<line>-<line>".
|
| - for (var i = 0; i < hits.length; i += 2) {
|
| - var k = hits[i];
|
| - if (k is num) {
|
| - // Single line.
|
| - addToMap(source, k, hits[i+1]);
|
| - }
|
| - if (k is String) {
|
| - // Linerange. We expand line ranges to actual lines at this point.
|
| - var splitPos = k.indexOf("-");
|
| - int start = int.parse(k.substring(0, splitPos));
|
| - int end = int.parse(k.substring(splitPos + 1, k.length));
|
| - for (var j = start; j <= end; j++) {
|
| - addToMap(source, j, hits[i+1]);
|
| - }
|
| - }
|
| - }
|
| - });
|
| - return hitMap;
|
| -}
|
| -
|
| -/// Merges [newMap] into [result].
|
| -mergeHitmaps(Map newMap, Map result) {
|
| - newMap.forEach((String file, Map v) {
|
| - if (result.containsKey(file)) {
|
| - v.forEach((int line, int cnt) {
|
| - if (result[file][line] == null) {
|
| - result[file][line] = cnt;
|
| - } else {
|
| - result[file][line] += cnt;
|
| - }
|
| - });
|
| - } else {
|
| - result[file] = v;
|
| - }
|
| - });
|
| -}
|
| -
|
| -/// Given an absolute path absPath, this function returns a [List] of files
|
| -/// are contained by it if it is a directory, or a [List] containing the file if
|
| -/// it is a file.
|
| -List filesToProcess(String absPath) {
|
| - var filePattern = new RegExp(r"^dart-cov-\d+-\d+.json$");
|
| - if (FileSystemEntity.isDirectorySync(absPath)) {
|
| - return new Directory(absPath).listSync(recursive: true)
|
| - .where((entity) => entity is File &&
|
| - filePattern.hasMatch(basename(entity.path)))
|
| - .toList();
|
| - }
|
| -
|
| - return [new File(absPath)];
|
| -}
|
| -
|
| -worker(WorkMessage msg) {
|
| - final start = new DateTime.now().millisecondsSinceEpoch;
|
| -
|
| - var env = msg.environment;
|
| - List files = msg.files;
|
| - Resolver resolver = new Resolver(env);
|
| - var workerHitmap = {};
|
| - files.forEach((File fileEntry) {
|
| - // Read file sync, as it only contains 1 object.
|
| - String contents = fileEntry.readAsStringSync();
|
| - if (contents.length > 0) {
|
| - mergeHitmaps(createHitmap(contents, resolver), workerHitmap);
|
| - }
|
| - });
|
| -
|
| - if (env["verbose"]) {
|
| - final end = new DateTime.now().millisecondsSinceEpoch;
|
| - print("${msg.workerName}: Finished processing ${files.length} files. "
|
| - "Took ${end - start} ms.");
|
| - }
|
| -
|
| - msg.replyPort.send(new ResultMessage(workerHitmap, resolver.failed));
|
| -}
|
| -
|
| -class WorkMessage {
|
| - final String workerName;
|
| - final Map environment;
|
| - final List files;
|
| - final SendPort replyPort;
|
| - WorkMessage(this.workerName, this.environment, this.files, this.replyPort);
|
| -}
|
| -
|
| -class ResultMessage {
|
| - final hitmap;
|
| - final failedResolves;
|
| - ResultMessage(this.hitmap, this.failedResolves);
|
| -}
|
| -
|
| -final env = new Environment();
|
| -
|
| -List<List> split(List list, int nBuckets) {
|
| - var buckets = new List(nBuckets);
|
| - var bucketSize = list.length ~/ nBuckets;
|
| - var leftover = list.length % nBuckets;
|
| - var taken = 0;
|
| - var start = 0;
|
| - for (int i = 0; i < nBuckets; i++) {
|
| - var end = (i < leftover) ? (start + bucketSize + 1) : (start + bucketSize);
|
| - buckets[i] = list.sublist(start, end);
|
| - taken += buckets[i].length;
|
| - start = end;
|
| - }
|
| - if (taken != list.length) throw "Error splitting";
|
| - return buckets;
|
| -}
|
| -
|
| -Future<ResultMessage> spawnWorker(name, environment, files) {
|
| - RawReceivePort port = new RawReceivePort();
|
| - var completer = new Completer();
|
| - port.handler = ((ResultMessage msg) {
|
| - completer.complete(msg);
|
| - port.close();
|
| - });
|
| - var msg = new WorkMessage(name, environment, files, port.sendPort);
|
| - Isolate.spawn(worker, msg);
|
| - return completer.future;
|
| -}
|
| -
|
| -main(List<String> arguments) {
|
| - parseArgs(arguments);
|
| -
|
| - List files = filesToProcess(env.input);
|
| -
|
| - List failedResolves = [];
|
| - List failedLoads = [];
|
| - Map globalHitmap = {};
|
| - int start = new DateTime.now().millisecondsSinceEpoch;
|
| -
|
| - if (env.verbose) {
|
| - print("Environment:");
|
| - print(" # files: ${files.length}");
|
| - print(" # workers: ${env.workers}");
|
| - print(" sdk-root: ${env.sdkRoot}");
|
| - print(" package-root: ${env.pkgRoot}");
|
| - }
|
| -
|
| - Map sharedEnv = {
|
| - "sdkRoot": env.sdkRoot,
|
| - "pkgRoot": env.pkgRoot,
|
| - "verbose": env.verbose,
|
| - };
|
| -
|
| - // Create workers.
|
| - int workerId = 0;
|
| - var results = split(files, env.workers).map((workerFiles) {
|
| - var result = spawnWorker("Worker ${workerId++}", sharedEnv, workerFiles);
|
| - return result.then((ResultMessage message) {
|
| - mergeHitmaps(message.hitmap, globalHitmap);
|
| - failedResolves.addAll(message.failedResolves);
|
| - });
|
| - });
|
| -
|
| - Future.wait(results).then((ignore) {
|
| - // All workers are done. Process the data.
|
| - if (env.verbose) {
|
| - final end = new DateTime.now().millisecondsSinceEpoch;
|
| - print("Done creating a global hitmap. Took ${end - start} ms.");
|
| - }
|
| -
|
| - Future out;
|
| - if (env.prettyPrint) {
|
| - out = prettyPrint(globalHitmap, failedLoads);
|
| - }
|
| - if (env.lcov) {
|
| - out = lcov(globalHitmap);
|
| - }
|
| -
|
| - out.then((_) {
|
| - env.output.close().then((_) {
|
| - if (env.verbose) {
|
| - final end = new DateTime.now().millisecondsSinceEpoch;
|
| - print("Done flushing output. Took ${end - start} ms.");
|
| - }
|
| - });
|
| -
|
| - if (env.verbose) {
|
| - if (failedResolves.length > 0) {
|
| - print("Failed to resolve:");
|
| - failedResolves.toSet().forEach((e) {
|
| - print(" ${e}");
|
| - });
|
| - }
|
| - if (failedLoads.length > 0) {
|
| - print("Failed to load:");
|
| - failedLoads.toSet().forEach((e) {
|
| - print(" ${e}");
|
| - });
|
| - }
|
| - }
|
| - });
|
| - });
|
| -}
|
| -
|
| -/// Checks the validity of the provided arguments. Does not initialize actual
|
| -/// processing.
|
| -parseArgs(List<String> arguments) {
|
| - var parser = new ArgParser();
|
| -
|
| - parser.addOption("sdk-root", abbr: "s",
|
| - help: "path to the SDK root");
|
| - parser.addOption("package-root", abbr: "p",
|
| - help: "override path to the package root "
|
| - "(default: inherited from dart)");
|
| - parser.addOption("in", abbr: "i",
|
| - help: "input(s): may be file or directory");
|
| - parser.addOption("out", abbr: "o",
|
| - help: "output: may be file or stdout",
|
| - defaultsTo: "stdout");
|
| - parser.addOption("workers", abbr: "j",
|
| - help: "number of workers",
|
| - defaultsTo: "1");
|
| - parser.addFlag("pretty-print", abbr: "r",
|
| - help: "convert coverage data to pretty print format",
|
| - negatable: false);
|
| - parser.addFlag("lcov", abbr :"l",
|
| - help: "convert coverage data to lcov format",
|
| - negatable: false);
|
| - parser.addFlag("verbose", abbr :"v",
|
| - help: "verbose output",
|
| - negatable: false);
|
| - parser.addFlag("help", abbr: "h",
|
| - help: "show this help",
|
| - negatable: false);
|
| -
|
| - var args = parser.parse(arguments);
|
| -
|
| - printUsage() {
|
| - print("Usage: dart full-coverage.dart [OPTION...]\n");
|
| - print(parser.getUsage());
|
| - }
|
| -
|
| - fail(String msg) {
|
| - print("\n$msg\n");
|
| - printUsage();
|
| - exit(1);
|
| - }
|
| -
|
| - if (args["help"]) {
|
| - printUsage();
|
| - exit(0);
|
| - }
|
| -
|
| - env.sdkRoot = args["sdk-root"];
|
| - if (env.sdkRoot == null) {
|
| - if (Platform.environment.containsKey("SDK_ROOT")) {
|
| - env.sdkRoot =
|
| - join(absolute(normalize(Platform.environment["SDK_ROOT"])), "lib");
|
| - }
|
| - } else {
|
| - env.sdkRoot = join(absolute(normalize(env.sdkRoot)), "lib");
|
| - }
|
| - if ((env.sdkRoot != null) && !FileSystemEntity.isDirectorySync(env.sdkRoot)) {
|
| - fail("Provided SDK root '${args["sdk-root"]}' is not a valid SDK "
|
| - "top-level directory");
|
| - }
|
| -
|
| - env.pkgRoot = args["package-root"];
|
| - if (env.pkgRoot != null) {
|
| - var pkgRootUri = Uri.parse(env.pkgRoot);
|
| - if (pkgRootUri.scheme == "file") {
|
| - if (!FileSystemEntity.isDirectorySync(pkgRootUri.toFilePath())) {
|
| - fail("Provided package root '${args["package-root"]}' is not directory.");
|
| - }
|
| - }
|
| - } else {
|
| - env.pkgRoot = Platform.packageRoot;
|
| - }
|
| -
|
| - if (args["in"] == null) {
|
| - fail("No input files given.");
|
| - } else {
|
| - env.input = absolute(normalize(args["in"]));
|
| - if (!FileSystemEntity.isDirectorySync(env.input) &&
|
| - !FileSystemEntity.isFileSync(env.input)) {
|
| - fail("Provided input '${args["in"]}' is neither a directory, nor a file.");
|
| - }
|
| - }
|
| -
|
| - if (args["out"] == "stdout") {
|
| - env.output = stdout;
|
| - } else {
|
| - env.output = absolute(normalize(args["out"]));
|
| - env.output = new File(env.output).openWrite();
|
| - }
|
| -
|
| - env.lcov = args["lcov"];
|
| - if (args["pretty-print"] && env.lcov) {
|
| - fail("Choose one of pretty-print or lcov output");
|
| - } else if (!env.lcov) {
|
| - // Use pretty-print either explicitly or by default.
|
| - env.prettyPrint = true;
|
| - }
|
| -
|
| - try {
|
| - env.workers = int.parse("${args["workers"]}");
|
| - } catch (e) {
|
| - fail("Invalid worker count: $e");
|
| - }
|
| -
|
| - env.verbose = args["verbose"];
|
| -}
|
|
|