| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 /// A library for code coverage support for Dart. | |
| 6 library runtime.coverage_impl; | |
| 7 | |
| 8 import "dart:io"; | |
| 9 | |
| 10 import "package:logging/logging.dart" as log; | |
| 11 import "package:pathos/path.dart" as po; | |
| 12 | |
| 13 import 'package:analyzer_experimental/src/generated/source.dart' show Source; | |
| 14 import 'package:analyzer_experimental/src/generated/scanner.dart' show StringSca
nner; | |
| 15 import 'package:analyzer_experimental/src/generated/parser.dart' show Parser; | |
| 16 import 'package:analyzer_experimental/src/generated/ast.dart'; | |
| 17 import 'package:analyzer_experimental/src/generated/engine.dart' show RecordingE
rrorListener; | |
| 18 | |
| 19 | |
| 20 log.Logger logger = log.Logger.root; | |
| 21 | |
| 22 /// Abstract server that listens requests and serves files, may be rewriting the
m. | |
| 23 abstract class RewriteServer { | |
| 24 String _basePath; | |
| 25 RewriteServer(this._basePath); | |
| 26 void start() { | |
| 27 HttpServer.bind("127.0.0.1", 3445).then((HttpServer server) { | |
| 28 logger.info('RewriteServer is listening at: ${server.port}.'); | |
| 29 server.listen((HttpRequest request) { | |
| 30 var response = request.response; | |
| 31 // Prepare path. | |
| 32 var path = _basePath + '/' + request.uri.path; | |
| 33 path = po.normalize(path); | |
| 34 logger.info('[$path] Requested.'); | |
| 35 // May be serve using just path. | |
| 36 { | |
| 37 String content = rewritePathContent(path); | |
| 38 if (content != null) { | |
| 39 logger.info('[$path] Request served by path.'); | |
| 40 response.write(content); | |
| 41 response.close(); | |
| 42 return; | |
| 43 } | |
| 44 } | |
| 45 // Serve from file. | |
| 46 logger.info('[$path] Serving file.'); | |
| 47 var file = new File(path); | |
| 48 file.exists().then((bool found) { | |
| 49 if (found) { | |
| 50 logger.finest('[$path] Found file.'); | |
| 51 file.readAsString().then((String content) { | |
| 52 logger.finest('[$path] Got file content.'); | |
| 53 var sw = new Stopwatch(); | |
| 54 sw.start(); | |
| 55 try { | |
| 56 content = rewriteFileContent(path, content); | |
| 57 } finally { | |
| 58 sw.stop(); | |
| 59 logger.fine('[$path] Rewritten in ${sw.elapsedMilliseconds} ms.'
); | |
| 60 } | |
| 61 response.write(content); | |
| 62 response.close(); | |
| 63 }); | |
| 64 } else { | |
| 65 logger.severe('[$path] File not found.'); | |
| 66 response.statusCode = HttpStatus.NOT_FOUND; | |
| 67 response.close(); | |
| 68 } | |
| 69 }); | |
| 70 }); | |
| 71 }); | |
| 72 } | |
| 73 | |
| 74 /// Subclasses implement this method to rewrite the provided [code] of the fil
e with [path]. | |
| 75 /// Returns some content or `null` if file content should be requested. | |
| 76 String rewritePathContent(String path); | |
| 77 | |
| 78 /// Subclasses implement this method to rewrite the provided [code] of the fil
e with [path]. | |
| 79 String rewriteFileContent(String path, String code); | |
| 80 } | |
| 81 | |
| 82 /// Server that rewrites Dart code so that it reports execution of statements an
d other nodes. | |
| 83 class CoverageServer extends RewriteServer { | |
| 84 CoverageServer(String basePath) : super(basePath); | |
| 85 | |
| 86 String rewritePathContent(String path) { | |
| 87 if (path.endsWith('__coverage_impl.dart')) { | |
| 88 String implPath = po.joinAll([ | |
| 89 po.dirname(new Options().script), | |
| 90 '..', 'lib', 'src', 'services', 'runtime', 'coverage_lib.dart']); | |
| 91 return new File(implPath).readAsStringSync(); | |
| 92 } | |
| 93 return null; | |
| 94 } | |
| 95 | |
| 96 String rewriteFileContent(String path, String code) { | |
| 97 if (po.extension(path).toLowerCase() != '.dart') return code; | |
| 98 if (path.contains('packages')) return code; | |
| 99 var unit = _parseCode(code); | |
| 100 var injector = new StringInjector(code); | |
| 101 // Inject coverage library import. | |
| 102 var directives = unit.directives; | |
| 103 if (directives.isNotEmpty && directives[0] is LibraryDirective) { | |
| 104 injector.inject(directives[0].end, 'import "__coverage_impl.dart" as __cc;
'); | |
| 105 } else { | |
| 106 throw new Exception('Only single library coverage is implemented.'); | |
| 107 } | |
| 108 // Insert touch() invocations. | |
| 109 unit.accept(new InsertTouchInvocationsVisitor(injector)); | |
| 110 // Done. | |
| 111 code = injector.code; | |
| 112 logger.finest('[$path] Rewritten content\n$code'); | |
| 113 return code; | |
| 114 } | |
| 115 | |
| 116 CompilationUnit _parseCode(String code) { | |
| 117 var source = null; | |
| 118 var errorListener = new RecordingErrorListener(); | |
| 119 var parser = new Parser(source, errorListener); | |
| 120 var scanner = new StringScanner(source, code, errorListener); | |
| 121 var token = scanner.tokenize(); | |
| 122 return parser.parseCompilationUnit(token); | |
| 123 } | |
| 124 } | |
| 125 | |
| 126 /// The visitor that inserts `touch` method invocations. | |
| 127 class InsertTouchInvocationsVisitor extends GeneralizingASTVisitor { | |
| 128 StringInjector injector; | |
| 129 InsertTouchInvocationsVisitor(this.injector); | |
| 130 visitStatement(Statement node) { | |
| 131 super.visitStatement(node); | |
| 132 var offset = node.end; | |
| 133 if (node is Block) { | |
| 134 offset--; | |
| 135 } | |
| 136 if (node is Block && node.parent is BlockFunctionBody) return null; | |
| 137 injector.inject(offset, '__cc.touch(${node.offset});'); | |
| 138 return null; | |
| 139 } | |
| 140 } | |
| 141 | |
| 142 /// Helper for injecting fragments into some existing [String]. | |
| 143 class StringInjector { | |
| 144 String code; | |
| 145 int _lastOffset = -1; | |
| 146 int _delta = 0; | |
| 147 StringInjector(this.code); | |
| 148 void inject(int offset, String fragment) { | |
| 149 if (offset < _lastOffset) { | |
| 150 throw new ArgumentError('Only forward inserts are supported, was $_lastOff
set given $offset'); | |
| 151 } | |
| 152 _lastOffset = offset; | |
| 153 offset += _delta; | |
| 154 code = code.substring(0, offset) + fragment + code.substring(offset); | |
| 155 _delta += fragment.length; | |
| 156 } | |
| 157 } | |
| OLD | NEW |