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

Unified Diff: observatory_pub_packages/analyzer/src/services/runtime/coverage/coverage_impl.dart

Issue 816693004: Add observatory_pub_packages snapshot to third_party (Closed) Base URL: http://dart.googlecode.com/svn/third_party/
Patch Set: Created 6 years 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: observatory_pub_packages/analyzer/src/services/runtime/coverage/coverage_impl.dart
===================================================================
--- observatory_pub_packages/analyzer/src/services/runtime/coverage/coverage_impl.dart (revision 0)
+++ observatory_pub_packages/analyzer/src/services/runtime/coverage/coverage_impl.dart (working copy)
@@ -0,0 +1,339 @@
+// 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.
+
+/// A library for code coverage support for Dart.
+library runtime.coverage.impl;
+
+import 'dart:async';
+import 'dart:collection' show SplayTreeMap;
+import 'dart:io';
+
+import 'package:path/path.dart' as pathos;
+
+import 'package:analyzer/src/generated/scanner.dart' show CharSequenceReader, Scanner;
+import 'package:analyzer/src/generated/parser.dart' show Parser;
+import 'package:analyzer/src/generated/ast.dart';
+import 'package:analyzer/src/generated/engine.dart' show RecordingErrorListener;
+
+import '../log.dart' as log;
+import 'models.dart';
+
+/// Run the [targetPath] with code coverage rewriting.
+/// Redirects stdandard process streams.
+/// On process exit dumps coverage statistics into the [outPath].
+void runServerApplication(String targetPath, String outPath) {
+ var targetFolder = pathos.dirname(targetPath);
+ var targetName = pathos.basename(targetPath);
+ var server = new CoverageServer(targetFolder, targetPath, outPath);
+ server.start().then((port) {
+ var targetArgs = ['http://127.0.0.1:$port/$targetName'];
+ var dartExecutable = Platform.executable;
+ return Process.start(dartExecutable, targetArgs);
+ }).then((process) {
+ stdin.pipe(process.stdin);
+ process.stdout.pipe(stdout);
+ process.stderr.pipe(stderr);
+ return process.exitCode;
+ }).then(exit).catchError((e) {
+ log.severe('Error starting $targetPath. $e');
+ });
+}
+
+
+/// Abstract server to listen requests and serve files, may be rewriting them.
+abstract class RewriteServer {
+ final String basePath;
+ int port;
+
+ RewriteServer(this.basePath);
+
+ /// Runs the HTTP server on the ephemeral port and returns [Future] with it.
+ Future<int> start() {
+ return HttpServer.bind('127.0.0.1', 0).then((server) {
+ port = server.port;
+ log.info('RewriteServer is listening at: $port.');
+ server.listen((request) {
+ if (request.method == 'GET') {
+ handleGetRequest(request);
+ }
+ if (request.method == 'POST') {
+ handlePostRequest(request);
+ }
+ });
+ return port;
+ });
+ }
+
+ void handlePostRequest(HttpRequest request);
+
+ void handleGetRequest(HttpRequest request) {
+ var response = request.response;
+ // Prepare path.
+ var path = getFilePath(request.uri);
+ log.info('[$path] Requested.');
+ // May be serve using just path.
+ {
+ var content = rewritePathContent(path);
+ if (content != null) {
+ log.info('[$path] Request served by path.');
+ response.write(content);
+ response.close();
+ return;
+ }
+ }
+ // Serve from file.
+ log.info('[$path] Serving file.');
+ var file = new File(path);
+ file.exists().then((found) {
+ if (found) {
+ // May be this files should be sent as is.
+ if (!shouldRewriteFile(path)) {
+ return sendFile(request, file);
+ }
+ // Rewrite content of the file.
+ return file.readAsString().then((content) {
+ log.finest('[$path] Done reading ${content.length} characters.');
+ content = rewriteFileContent(path, content);
+ log.fine('[$path] Rewritten.');
+ response.write(content);
+ return response.close();
+ });
+ } else {
+ log.severe('[$path] File not found.');
+ response.statusCode = HttpStatus.NOT_FOUND;
+ return response.close();
+ }
+ }).catchError((e) {
+ log.severe('[$path] $e.');
+ response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
+ return response.close();
+ });
+ }
+
+ String getFilePath(Uri uri) {
+ var path = uri.path;
+ path = pathos.joinAll(uri.pathSegments);
+ path = pathos.join(basePath, path);
+ return pathos.normalize(path);
+ }
+
+ Future sendFile(HttpRequest request, File file) {
+ return file.resolveSymbolicLinks().then((fullPath) {
+ return file.openRead().pipe(request.response);
+ });
+ }
+
+ bool shouldRewriteFile(String path);
+
+ /// 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);
+}
+
+
+/// Here `CCC` means 'code coverage configuration'.
+const TEST_UNIT_CCC = '''
+class __CCC extends __cc_ut.Configuration {
+ void onDone(bool success) {
+ __cc.postStatistics();
+ super.onDone(success);
+ }
+}''';
+
+const TEST_UNIT_CCC_SET = '__cc_ut.unittestConfiguration = new __CCC();';
+
+
+/// Server that rewrites Dart code so that it reports execution of statements
+/// and other nodes.
+class CoverageServer extends RewriteServer {
+ final appInfo = new AppInfo();
+ final String targetPath;
+ final String outPath;
+
+ CoverageServer(String basePath, this.targetPath, this.outPath)
+ : super(basePath);
+
+ void handlePostRequest(HttpRequest request) {
+ var id = 0;
+ var executedIds = new Set<int>();
+ request.listen((data) {
+ log.fine('Received statistics, ${data.length} bytes.');
+ while (true) {
+ var listIndex = id ~/ 8;
+ if (listIndex >= data.length) break;
+ var bitIndex = id % 8;
+ if ((data[listIndex] & (1 << bitIndex)) != 0) {
+ executedIds.add(id);
+ }
+ id++;
+ }
+ }).onDone(() {
+ log.fine('Received all statistics.');
+ var buffer = new StringBuffer();
+ appInfo.write(buffer, executedIds);
+ new File(outPath).writeAsString(buffer.toString()).then((_) {
+ return request.response.close();
+ }).catchError((e) {
+ log.severe('Error in receiving statistics $e.');
+ return request.response.close();
+ });
+ });
+ }
+
+ String rewritePathContent(String path) {
+ if (path.endsWith('__coverage_lib.dart')) {
+ String implPath = pathos.joinAll([
+ pathos.dirname(Platform.script.toFilePath()),
+ '..', 'lib', 'src', 'services', 'runtime', 'coverage',
+ 'coverage_lib.dart']);
+ var content = new File(implPath).readAsStringSync();
+ return content.replaceAll('0; // replaced during rewrite', '$port;');
+ }
+ return null;
+ }
+
+ bool shouldRewriteFile(String path) {
+ if (pathos.extension(path).toLowerCase() != '.dart') return false;
+ // Rewrite target itself, only to send statistics.
+ if (path == targetPath) {
+ return true;
+ }
+ // TODO(scheglov) use configuration
+ return path.contains('/packages/analyzer/');
+ }
+
+ String rewriteFileContent(String path, String code) {
+ var unit = _parseCode(code);
+ log.finest('[$path] Parsed.');
+ var injector = new CodeInjector(code);
+ // Inject imports.
+ var directives = unit.directives;
+ if (directives.isNotEmpty && directives[0] is LibraryDirective) {
+ injector.inject(directives[0].end,
+ 'import "package:unittest/unittest.dart" as __cc_ut;'
+ 'import "http://127.0.0.1:$port/__coverage_lib.dart" as __cc;');
+ }
+ // Inject statistics sender.
+ var isTargetScript = path == targetPath;
+ if (isTargetScript) {
+ for (var node in unit.declarations) {
+ if (node is FunctionDeclaration) {
+ var body = node.functionExpression.body;
+ if (node.name.name == 'main' && body is BlockFunctionBody) {
+ injector.inject(node.offset, TEST_UNIT_CCC);
+ injector.inject(body.offset + 1, TEST_UNIT_CCC_SET);
+ }
+ }
+ }
+ }
+ // Inject touch() invocations.
+ if (!isTargetScript) {
+ appInfo.enterUnit(path, code);
+ unit.accept(new InsertTouchInvocationsVisitor(appInfo, injector));
+ }
+ // Done.
+ return injector.getResult();
+ }
+
+ CompilationUnit _parseCode(String code) {
+ var source = null;
+ var errorListener = new RecordingErrorListener();
+ var parser = new Parser(source, errorListener);
+ var reader = new CharSequenceReader(code);
+ var scanner = new Scanner(null, reader, errorListener);
+ var token = scanner.tokenize();
+ return parser.parseCompilationUnit(token);
+ }
+}
+
+
+/// The visitor that inserts `touch` method invocations.
+class InsertTouchInvocationsVisitor extends GeneralizingAstVisitor {
+ final AppInfo appInfo;
+ final CodeInjector injector;
+
+ InsertTouchInvocationsVisitor(this.appInfo, this.injector);
+
+ visitClassDeclaration(ClassDeclaration node) {
+ appInfo.enter('class', node.name.name);
+ super.visitClassDeclaration(node);
+ appInfo.leave();
+ }
+
+ visitConstructorDeclaration(ConstructorDeclaration node) {
+ var className = (node.parent as ClassDeclaration).name.name;
+ var constructorName;
+ if (node.name == null) {
+ constructorName = className;
+ } else {
+ constructorName = className + '.' + node.name.name;
+ }
+ appInfo.enter('constructor', constructorName);
+ super.visitConstructorDeclaration(node);
+ appInfo.leave();
+ }
+
+ visitMethodDeclaration(MethodDeclaration node) {
+ if (node.isAbstract) {
+ super.visitMethodDeclaration(node);
+ } else {
+ var kind;
+ if (node.isGetter) {
+ kind = 'getter';
+ } else if (node.isSetter) {
+ kind = 'setter';
+ } else {
+ kind = 'method';
+ }
+ appInfo.enter(kind, node.name.name);
+ super.visitMethodDeclaration(node);
+ appInfo.leave();
+ }
+ }
+
+ visitStatement(Statement node) {
+ insertTouch(node);
+ super.visitStatement(node);
+ }
+
+ void insertTouch(Statement node) {
+ if (node is Block) return;
+ if (node.parent is LabeledStatement) return;
+ if (node.parent is! Block) return;
+ // Inject 'touch' invocation.
+ var offset = node.offset;
+ var id = appInfo.addNode(node);
+ injector.inject(offset, '__cc.touch($id);');
+ }
+}
+
+
+/// Helper for injecting fragments into some existing code.
+class CodeInjector {
+ final String _code;
+ final offsetFragmentMap = new SplayTreeMap<int, String>();
+
+ CodeInjector(this._code);
+
+ void inject(int offset, String fragment) {
+ offsetFragmentMap[offset] = fragment;
+ }
+
+ String getResult() {
+ var sb = new StringBuffer();
+ var lastOffset = 0;
+ offsetFragmentMap.forEach((offset, fragment) {
+ sb.write(_code.substring(lastOffset, offset));
+ sb.write(fragment);
+ lastOffset = offset;
+ });
+ sb.write(_code.substring(lastOffset, _code.length));
+ return sb.toString();
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698