| 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>'
|
| + : 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'};
|
|
|