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