Index: pkg/analyzer_experimental/lib/src/services/runtime/coverage/coverage_impl.dart |
diff --git a/pkg/analyzer_experimental/lib/src/services/runtime/coverage/coverage_impl.dart b/pkg/analyzer_experimental/lib/src/services/runtime/coverage/coverage_impl.dart |
deleted file mode 100644 |
index 445aa8317da1d29826b66f4567fa178867c0acc7..0000000000000000000000000000000000000000 |
--- a/pkg/analyzer_experimental/lib/src/services/runtime/coverage/coverage_impl.dart |
+++ /dev/null |
@@ -1,341 +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. |
- |
-/// 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_experimental/src/generated/java_core.dart' show CharSequence; |
-import 'package:analyzer_experimental/src/generated/scanner.dart' show CharSequenceReader, Scanner; |
-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; |
- |
-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 options = new Options(); |
- var targetArgs = ['http://127.0.0.1:$port/$targetName']; |
- var dartExecutable = options.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) { |
- file.fullPath().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), |
- '..', '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_experimental/'); |
- } |
- |
- 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(new CharSequence(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(); |
- } |
-} |