Chromium Code Reviews| Index: pkg/compiler/tool/coverage_log_server.dart |
| diff --git a/pkg/compiler/tool/coverage_log_server.dart b/pkg/compiler/tool/coverage_log_server.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..d2e860e46ad259a112835451f74dfc1f766b0f25 |
| --- /dev/null |
| +++ b/pkg/compiler/tool/coverage_log_server.dart |
| @@ -0,0 +1,190 @@ |
| +// Copyright (c) 2015, 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. |
| + |
| +/// A tool to gather coverage data from an app generated with dart2js. |
| +/// This tool starts a server that answers to mainly 2 requests: |
| +/// * a GET request to retrieve the application |
| +/// * POST requests to record coverage data. |
| +/// |
| +/// It is intended to be used as follows: |
| +/// * generate an app by running dart2js with the environment boolean |
| +/// -DinstrumentForCoverage=true provided to the vm, and the --dump-info |
| +/// flag provided to dart2js. |
| +/// * start this server, and proxy requests from your normal frontend |
| +/// server to this one. |
| +library compiler.tool.coverage_log_server; |
| + |
| +import 'dart:convert'; |
| +import 'dart:io'; |
| +import 'dart:async'; |
| +import 'package:path/path.dart' as path; |
| +import 'package:args/args.dart'; |
| +import 'package:shelf/shelf.dart' as shelf; |
| +import 'package:shelf/shelf_io.dart' as shelf; |
| + |
| +const _DEFAULT_OUT_TEMPLATE = '<dart2js-out-file>.coverage.json'; |
| + |
| +main(argv) async { |
| + var parser = new ArgParser() |
| + ..addOption('port', abbr: 'p', help: 'port number', defaultsTo: "8080") |
| + ..addOption('host', help: 'host name (use 0.0.0.0 for all interfaces)', |
| + defaultsTo: 'localhost') |
| + ..addFlag('help', abbr: 'h', help: 'show this help message', |
| + negatable: false) |
| + ..addOption('uri-prefix', |
| + help: 'uri path prefix that will hit this server. This will be injected' |
| + ' into the .js file', |
| + defaultsTo: '') |
| + ..addOption('out', abbr: 'o', help: 'output log file', |
| + defaultsTo: _DEFAULT_OUT_TEMPLATE); |
| + var args = parser.parse(argv); |
| + if (args['help'] == true || args.rest.isEmpty) { |
| + print('usage: dart coverage_logging.dart [options] ' |
| + '<dart2js-out-file> [<html-file>]'); |
| + print(parser.usage); |
| + exit(1); |
| + } |
| + |
| + var jsPath = args.rest[0]; |
| + var htmlPath = null; |
| + if (args.rest.length > 1) { |
| + htmlPath = args.rest[1]; |
| + } |
| + var outPath = args['out']; |
| + if (outPath == _DEFAULT_OUT_TEMPLATE) outPath = '$jsPath.coverage.json'; |
| + var server = new _Server(args['host'], int.parse(args['port']), jsPath, |
| + htmlPath, outPath, args['uri-prefix']); |
| + await server.run(); |
| +} |
| + |
| +class _Server { |
| + /// Server hostname, typically `localhost`, but can be `0.0.0.0`. |
| + final String hostname; |
| + |
| + /// Port the server will listen to. |
| + final int port; |
| + |
| + /// JS file (previously generated by dart2js) to serve. |
| + final String jsPath; |
| + |
| + /// HTML file to serve, if any. |
| + final String htmlPath; |
| + |
| + /// Contents of jsPath, adjusted to use the appropriate server url. |
| + String jsCode; |
| + |
| + /// Location where we'll dump the coverage data. |
| + final String outPath; |
| + |
| + /// Uri prefix used on all requests to this server. This will be injected into |
| + /// the .js file. |
| + final String prefix; |
| + |
| + // TODO(sigmund): add support to load also simple HTML files to test small |
| + // simple apps. |
| + |
| + /// Data received so far. The data is just an array of pairs, showing the |
| + /// hashCode and name of the element used. This can be later cross-checked |
| + /// against dump-info data. |
| + Map data = {}; |
| + |
| + String get _serializedData => new JsonEncoder.withIndent(' ').convert(data); |
| + |
| + _Server(this.hostname, this.port, String jsPath, this.htmlPath, |
| + this.outPath, String prefix) |
| + : jsPath = jsPath, |
| + jsCode = _adjustRequestUrl(new File(jsPath).readAsStringSync(), prefix), |
| + prefix = _normalize(prefix); |
| + |
| + run() async { |
| + await shelf.serve(_handler, hostname, port); |
| + var urlBase = "http://$hostname:$port${prefix == '' ? '/' : '/$prefix/'}"; |
| + var htmlFilename = htmlPath == null ? '' : path.basename(htmlPath); |
| + print("Server is listening\n" |
| + " - html page: $urlBase$htmlFilename\n" |
| + " - js code: $urlBase${path.basename(jsPath)}\n" |
| + " - coverage reporting: ${urlBase}coverage\n"); |
| + } |
| + |
| + _expectedPath(String tail) => prefix == '' ? tail : '$prefix/$tail'; |
| + |
| + _handler(shelf.Request request) async { |
| + var urlPath = request.url.path; |
| + print('received request: $urlPath'); |
| + var baseJsName = path.basename(jsPath); |
| + var baseHtmlName = htmlPath == null ? '' : path.basename(htmlPath); |
| + |
| + // Serve an HTML file at the default prefix, or a path matching the HTML |
| + // file name |
| + if (urlPath == prefix || urlPath == '$prefix/' |
| + || urlPath == _expectedPath(baseHtmlName)) { |
| + var contents = htmlPath == null |
| + ? '<html><script src="$baseJsName"></script>' |
|
Johnni Winther
2015/08/19 10:38:22
Is the missing </html> deliberate?
Siggi Cherem (dart-lang)
2015/08/19 15:45:19
yeah, but it is equivalent (HTML parsing spec says
|
| + : await new File(htmlPath).readAsString(); |
| + return new shelf.Response.ok(contents, headers: HTML_HEADERS); |
| + } |
| + |
| + if (urlPath == _expectedPath(baseJsName)) { |
| + return new shelf.Response.ok(jsCode, headers: JS_HEADERS); |
| + } |
| + |
| + // Handle POST requests to record coverage data, and GET requests to display |
| + // the currently coverage resutls. |
| + if (urlPath == _expectedPath('coverage')) { |
| + if (request.method == 'GET') { |
| + return new shelf.Response.ok(_serializedData, headers: TEXT_HEADERS); |
| + } |
| + |
| + if (request.method == 'POST') { |
| + _record(JSON.decode(await request.readAsString())); |
| + return new shelf.Response.ok("Thanks!"); |
| + } |
| + } |
| + |
| + // Any other request is not supported. |
| + return new shelf.Response.notFound('Not found: "$urlPath"'); |
| + } |
| + |
| + _record(List entries) { |
| + for (var entry in entries) { |
| + var id = entry[0]; |
| + data.putIfAbsent('$id', () => {'name': entry[1], 'count': 0}); |
| + data['$id']['count']++; |
| + } |
| + _enqueueSave(); |
| + } |
| + |
| + bool _savePending = false; |
| + int _total = 0; |
| + _enqueueSave() async { |
| + if (!_savePending) { |
| + _savePending = true; |
| + await new Future.delayed(new Duration(seconds: 3)); |
| + await new File(outPath).writeAsString(_serializedData); |
| + var diff = data.length - _total; |
| + print(diff ? ' - no new element covered' |
| + : ' - $diff new elements covered'); |
| + _savePending = false; |
| + _total = data.length; |
| + } |
| + } |
| +} |
| + |
| +/// Removes leading and trailing slashes of [uriPath]. |
| +_normalize(String uriPath) { |
| + if (uriPath.startsWith('/')) uriPath = uriPath.substring(1); |
| + if (uriPath.endsWith('/')) uriPath = uriPath.substring(0, uriPath.length - 1); |
| + return uriPath; |
| +} |
| + |
| +_adjustRequestUrl(String code, String prefix) { |
| + var newUrl = prefix == '' ? 'coverage' : '$prefix/coverage'; |
| + return code.replaceFirst( |
| + '"/coverage_uri_to_amend_by_server"', |
| + '"/$newUrl" /*url-prefix updated!*/'); |
| +} |
| + |
| +const HTML_HEADERS = const {'content-type': 'text/html'}; |
| +const JS_HEADERS = const {'content-type': 'text/javascript'}; |
| +const TEXT_HEADERS = const {'content-type': 'text/plain'}; |