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

Unified Diff: pkg/analyzer_experimental/lib/src/services/runtime/coverage_server_impl.dart

Issue 16580018: Code coverage server babysteps. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 6 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 side-by-side diff with in-line comments
Download patch
Index: pkg/analyzer_experimental/lib/src/services/runtime/coverage_server_impl.dart
diff --git a/pkg/analyzer_experimental/lib/src/services/runtime/coverage_server_impl.dart b/pkg/analyzer_experimental/lib/src/services/runtime/coverage_server_impl.dart
new file mode 100644
index 0000000000000000000000000000000000000000..87b9191f80e6e5631b99e2e4ba87b8c3db7eb92f
--- /dev/null
+++ b/pkg/analyzer_experimental/lib/src/services/runtime/coverage_server_impl.dart
@@ -0,0 +1,154 @@
+/// A library for code coverage support for Dart.
pquitslund 2013/06/11 17:18:56 Again. Probably want a header.
scheglov 2013/06/11 19:54:23 Done.
+library runtime.coverage.server_impl;
+
+import "dart:io";
+
+import "package:logging/logging.dart" as log;
+import "package:pathos/path.dart" as po;
+
+import 'package:analyzer_experimental/src/generated/source.dart' show Source;
+import 'package:analyzer_experimental/src/generated/scanner.dart' show StringScanner;
+import 'package:analyzer_experimental/src/generated/parser.dart' show Parser;
+import 'package:analyzer_experimental/src/generated/ast.dart';
+import 'package:analyzer_experimental/src/generated/engine.dart' show RecordingErrorListener;
+
+
+log.Logger logger = log.Logger.root;
+
+/// Abstract server that listens requests and serves files, may be rewriting them.
+abstract class RewriteServer {
+ String _basePath;
+ RewriteServer(this._basePath);
+ void start() {
+ HttpServer.bind("127.0.0.1", 3445).then((HttpServer server) {
+ logger.info('RewriteServer is listening at: ${server.port}.');
+ server.listen((HttpRequest request) {
+ var response = request.response;
+ // prepare path
pquitslund 2013/06/11 17:18:56 prepare -> Prepare (upper case)
scheglov 2013/06/11 19:54:23 Done.
+ String path = _basePath + '/' + request.uri.path;
pquitslund 2013/06/11 17:18:56 var path
scheglov 2013/06/11 19:54:23 Done.
+ path = po.normalize(path);
+ logger.info('[$path] Requested.');
+ // may be we have path content
pquitslund 2013/06/11 17:18:56 Upper case 'may'
scheglov 2013/06/11 19:54:23 Done.
+ {
+ String content = rewritePathContent(path);
+ if (content != null) {
+ logger.info('[$path] Request served by path.');
+ response.write(content);
+ response.close();
+ return;
+ }
+ }
+ // get content from file
pquitslund 2013/06/11 17:18:56 More upper case (and below). Pretty sure the styl
scheglov 2013/06/11 19:54:23 Done.
+ logger.info('[$path] Serving file.');
+ File file = new File(path);
pquitslund 2013/06/11 17:18:56 var file
scheglov 2013/06/11 19:54:23 Done.
+ file.exists().then((bool found) {
+ if (found) {
+ logger.finest('[$path] Found file.');
+ file.readAsString().then((String content) {
+ logger.finest('[$path] Got file content.');
+ var sw = new Stopwatch();
+ sw.start();
+ try {
+ content = rewriteFileContent(path, content);
+ } finally {
+ sw.stop();
+ logger.fine('[$path] Rewritten in ${sw.elapsedMilliseconds} ms.');
+ }
+ response.write(content);
+ response.close();
+ });
+ } else {
+ logger.severe('[$path] File not found.');
+ response.statusCode = HttpStatus.NOT_FOUND;
+ response.close();
+ }
+ });
+ });
+ });
+ }
+
+ /// Subclasses implement this method to rewrite the provided [code] of the file with [path].
+ /// Returns some content or `null` if file content should be requested.
+ String rewritePathContent(String path);
+
+ /// Subclasses implement this method to rewrite the provided [code] of the file with [path].
+ String rewriteFileContent(String path, String code);
+}
+
+/// Server that rewrites Dart code so that it reports execution of statements and other nodes.
+class CoverageServer extends RewriteServer {
+ CoverageServer(String basePath) : super(basePath);
+
+ String rewritePathContent(String path) {
+ if (path.endsWith('__coverage_impl.dart')) {
+ String implPath = po.joinAll([
+ po.dirname(new Options().script),
+ '..', 'lib', 'src', 'services', 'runtime', 'coverage_lib.dart']);
+ return new File(implPath).readAsStringSync();
+ }
+ return null;
+ }
+
+ String rewriteFileContent(String path, String code) {
+ if (po.extension(path).toLowerCase() != '.dart') return code;
+ if (path.contains('packages')) return code;
+ CompilationUnit unit = _parseCode(code);
pquitslund 2013/06/11 17:18:56 Again, drop the type annotation (var unit).
scheglov 2013/06/11 19:54:23 Done.
+ print(unit);
+ var injector = new StringInjector(code);
+ // inject coverage_lib.dart import
+ var directives = unit.directives;
+ if (directives.isNotEmpty && directives[0] is LibraryDirective) {
+ injector.inject(directives[0].end, 'import "__coverage_impl.dart" as __cc;');
+ } else {
+ throw new Exception('Only single library coverage is implemented.');
+ }
+ // insert touch() invocations
+ unit.accept(new InsertTouchInvocationsVisitor(injector));
+ // done
+ code = injector.code;
+ logger.finest('[$path] Rewritten content\n$code');
+ return code;
+ }
+
+ CompilationUnit _parseCode(String code) {
+ Source source = null;
pquitslund 2013/06/11 17:18:56 And here.
scheglov 2013/06/11 19:54:23 Done.
+ var errorListener = new RecordingErrorListener();
+ var parser = new Parser(source, errorListener);
+ var scanner = new StringScanner(source, code, errorListener);
+ var token = scanner.tokenize();
+ return parser.parseCompilationUnit(token);
+ }
+}
+
+/// The visitor that inserts `touch` method invocations.
+class InsertTouchInvocationsVisitor extends GeneralizingASTVisitor {
+ StringInjector injector;
+ InsertTouchInvocationsVisitor(this.injector);
+ visitStatement(Statement node) {
+ super.visitStatement(node);
+ int offset = node.end;
pquitslund 2013/06/11 17:18:56 And here.
scheglov 2013/06/11 19:54:23 Done.
+ if (node is Block) {
+ offset--;
+ }
+ if (node is Block && node.parent is BlockFunctionBody) return null;
+ injector.inject(offset, '__cc.touch(${node.offset});');
+ return null;
+ }
+}
+
+/// Helper for injecting fragments into some existing [String].
+class StringInjector {
+ String code;
+ int _lastOffset = -1;
+ int _delta = 0;
+ StringInjector(this.code);
+ void inject(int offset, String fragment) {
+ if (offset < _lastOffset) {
+ throw new ArgumentError('Only forward inserts are supported, was $_lastOffset given $offset');
+ }
+ _lastOffset = offset;
+ offset += _delta;
+ code = code.substring(0, offset) + fragment + code.substring(offset);
+ _delta += fragment.length;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698