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

Side by Side Diff: pkg/analyzer_experimental/lib/src/services/runtime/coverage/coverage_impl.dart

Issue 45573002: Rename analyzer_experimental to analyzer. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Tweaks before publishing. Created 7 years, 1 month 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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:async';
9 import 'dart:collection' show SplayTreeMap;
10 import 'dart:io';
11
12 import 'package:path/path.dart' as pathos;
13
14 import 'package:analyzer_experimental/src/generated/java_core.dart' show CharSeq uence;
15 import 'package:analyzer_experimental/src/generated/scanner.dart' show CharSeque nceReader, Scanner;
16 import 'package:analyzer_experimental/src/generated/parser.dart' show Parser;
17 import 'package:analyzer_experimental/src/generated/ast.dart';
18 import 'package:analyzer_experimental/src/generated/engine.dart' show RecordingE rrorListener;
19
20 import '../log.dart' as log;
21 import 'models.dart';
22
23 /// Run the [targetPath] with code coverage rewriting.
24 /// Redirects stdandard process streams.
25 /// On process exit dumps coverage statistics into the [outPath].
26 void runServerApplication(String targetPath, String outPath) {
27 var targetFolder = pathos.dirname(targetPath);
28 var targetName = pathos.basename(targetPath);
29 var server = new CoverageServer(targetFolder, targetPath, outPath);
30 server.start().then((port) {
31 var options = new Options();
32 var targetArgs = ['http://127.0.0.1:$port/$targetName'];
33 var dartExecutable = options.executable;
34 return Process.start(dartExecutable, targetArgs);
35 }).then((process) {
36 stdin.pipe(process.stdin);
37 process.stdout.pipe(stdout);
38 process.stderr.pipe(stderr);
39 return process.exitCode;
40 }).then(exit).catchError((e) {
41 log.severe('Error starting $targetPath. $e');
42 });
43 }
44
45
46 /// Abstract server to listen requests and serve files, may be rewriting them.
47 abstract class RewriteServer {
48 final String basePath;
49 int port;
50
51 RewriteServer(this.basePath);
52
53 /// Runs the HTTP server on the ephemeral port and returns [Future] with it.
54 Future<int> start() {
55 return HttpServer.bind('127.0.0.1', 0).then((server) {
56 port = server.port;
57 log.info('RewriteServer is listening at: $port.');
58 server.listen((request) {
59 if (request.method == 'GET') {
60 handleGetRequest(request);
61 }
62 if (request.method == 'POST') {
63 handlePostRequest(request);
64 }
65 });
66 return port;
67 });
68 }
69
70 void handlePostRequest(HttpRequest request);
71
72 void handleGetRequest(HttpRequest request) {
73 var response = request.response;
74 // Prepare path.
75 var path = getFilePath(request.uri);
76 log.info('[$path] Requested.');
77 // May be serve using just path.
78 {
79 var content = rewritePathContent(path);
80 if (content != null) {
81 log.info('[$path] Request served by path.');
82 response.write(content);
83 response.close();
84 return;
85 }
86 }
87 // Serve from file.
88 log.info('[$path] Serving file.');
89 var file = new File(path);
90 file.exists().then((found) {
91 if (found) {
92 // May be this files should be sent as is.
93 if (!shouldRewriteFile(path)) {
94 return sendFile(request, file);
95 }
96 // Rewrite content of the file.
97 return file.readAsString().then((content) {
98 log.finest('[$path] Done reading ${content.length} characters.');
99 content = rewriteFileContent(path, content);
100 log.fine('[$path] Rewritten.');
101 response.write(content);
102 return response.close();
103 });
104 } else {
105 log.severe('[$path] File not found.');
106 response.statusCode = HttpStatus.NOT_FOUND;
107 return response.close();
108 }
109 }).catchError((e) {
110 log.severe('[$path] $e.');
111 response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
112 return response.close();
113 });
114 }
115
116 String getFilePath(Uri uri) {
117 var path = uri.path;
118 path = pathos.joinAll(uri.pathSegments);
119 path = pathos.join(basePath, path);
120 return pathos.normalize(path);
121 }
122
123 Future sendFile(HttpRequest request, File file) {
124 file.fullPath().then((fullPath) {
125 return file.openRead().pipe(request.response);
126 });
127 }
128
129 bool shouldRewriteFile(String path);
130
131 /// Subclasses implement this method to rewrite the provided [code] of the
132 /// file with [path]. Returns some content or `null` if file content
133 /// should be requested.
134 String rewritePathContent(String path);
135
136 /// Subclasses implement this method to rewrite the provided [code] of the
137 /// file with [path].
138 String rewriteFileContent(String path, String code);
139 }
140
141
142 /// Here `CCC` means 'code coverage configuration'.
143 const TEST_UNIT_CCC = '''
144 class __CCC extends __cc_ut.Configuration {
145 void onDone(bool success) {
146 __cc.postStatistics();
147 super.onDone(success);
148 }
149 }''';
150
151 const TEST_UNIT_CCC_SET = '__cc_ut.unittestConfiguration = new __CCC();';
152
153
154 /// Server that rewrites Dart code so that it reports execution of statements
155 /// and other nodes.
156 class CoverageServer extends RewriteServer {
157 final appInfo = new AppInfo();
158 final String targetPath;
159 final String outPath;
160
161 CoverageServer(String basePath, this.targetPath, this.outPath)
162 : super(basePath);
163
164 void handlePostRequest(HttpRequest request) {
165 var id = 0;
166 var executedIds = new Set<int>();
167 request.listen((data) {
168 log.fine('Received statistics, ${data.length} bytes.');
169 while (true) {
170 var listIndex = id ~/ 8;
171 if (listIndex >= data.length) break;
172 var bitIndex = id % 8;
173 if ((data[listIndex] & (1 << bitIndex)) != 0) {
174 executedIds.add(id);
175 }
176 id++;
177 }
178 }).onDone(() {
179 log.fine('Received all statistics.');
180 var buffer = new StringBuffer();
181 appInfo.write(buffer, executedIds);
182 new File(outPath).writeAsString(buffer.toString()).then((_) {
183 return request.response.close();
184 }).catchError((e) {
185 log.severe('Error in receiving statistics $e.');
186 return request.response.close();
187 });
188 });
189 }
190
191 String rewritePathContent(String path) {
192 if (path.endsWith('__coverage_lib.dart')) {
193 String implPath = pathos.joinAll([
194 pathos.dirname(Platform.script),
195 '..', 'lib', 'src', 'services', 'runtime', 'coverage',
196 'coverage_lib.dart']);
197 var content = new File(implPath).readAsStringSync();
198 return content.replaceAll('0; // replaced during rewrite', '$port;');
199 }
200 return null;
201 }
202
203 bool shouldRewriteFile(String path) {
204 if (pathos.extension(path).toLowerCase() != '.dart') return false;
205 // Rewrite target itself, only to send statistics.
206 if (path == targetPath) {
207 return true;
208 }
209 // TODO(scheglov) use configuration
210 return path.contains('/packages/analyzer_experimental/');
211 }
212
213 String rewriteFileContent(String path, String code) {
214 var unit = _parseCode(code);
215 log.finest('[$path] Parsed.');
216 var injector = new CodeInjector(code);
217 // Inject imports.
218 var directives = unit.directives;
219 if (directives.isNotEmpty && directives[0] is LibraryDirective) {
220 injector.inject(directives[0].end,
221 'import "package:unittest/unittest.dart" as __cc_ut;'
222 'import "http://127.0.0.1:$port/__coverage_lib.dart" as __cc;');
223 }
224 // Inject statistics sender.
225 var isTargetScript = path == targetPath;
226 if (isTargetScript) {
227 for (var node in unit.declarations) {
228 if (node is FunctionDeclaration) {
229 var body = node.functionExpression.body;
230 if (node.name.name == 'main' && body is BlockFunctionBody) {
231 injector.inject(node.offset, TEST_UNIT_CCC);
232 injector.inject(body.offset + 1, TEST_UNIT_CCC_SET);
233 }
234 }
235 }
236 }
237 // Inject touch() invocations.
238 if (!isTargetScript) {
239 appInfo.enterUnit(path, code);
240 unit.accept(new InsertTouchInvocationsVisitor(appInfo, injector));
241 }
242 // Done.
243 return injector.getResult();
244 }
245
246 CompilationUnit _parseCode(String code) {
247 var source = null;
248 var errorListener = new RecordingErrorListener();
249 var parser = new Parser(source, errorListener);
250 var reader = new CharSequenceReader(new CharSequence(code));
251 var scanner = new Scanner(null, reader, errorListener);
252 var token = scanner.tokenize();
253 return parser.parseCompilationUnit(token);
254 }
255 }
256
257
258 /// The visitor that inserts `touch` method invocations.
259 class InsertTouchInvocationsVisitor extends GeneralizingASTVisitor {
260 final AppInfo appInfo;
261 final CodeInjector injector;
262
263 InsertTouchInvocationsVisitor(this.appInfo, this.injector);
264
265 visitClassDeclaration(ClassDeclaration node) {
266 appInfo.enter('class', node.name.name);
267 super.visitClassDeclaration(node);
268 appInfo.leave();
269 }
270
271 visitConstructorDeclaration(ConstructorDeclaration node) {
272 var className = (node.parent as ClassDeclaration).name.name;
273 var constructorName;
274 if (node.name == null) {
275 constructorName = className;
276 } else {
277 constructorName = className + '.' + node.name.name;
278 }
279 appInfo.enter('constructor', constructorName);
280 super.visitConstructorDeclaration(node);
281 appInfo.leave();
282 }
283
284 visitMethodDeclaration(MethodDeclaration node) {
285 if (node.isAbstract) {
286 super.visitMethodDeclaration(node);
287 } else {
288 var kind;
289 if (node.isGetter) {
290 kind = 'getter';
291 } else if (node.isSetter) {
292 kind = 'setter';
293 } else {
294 kind = 'method';
295 }
296 appInfo.enter(kind, node.name.name);
297 super.visitMethodDeclaration(node);
298 appInfo.leave();
299 }
300 }
301
302 visitStatement(Statement node) {
303 insertTouch(node);
304 super.visitStatement(node);
305 }
306
307 void insertTouch(Statement node) {
308 if (node is Block) return;
309 if (node.parent is LabeledStatement) return;
310 if (node.parent is! Block) return;
311 // Inject 'touch' invocation.
312 var offset = node.offset;
313 var id = appInfo.addNode(node);
314 injector.inject(offset, '__cc.touch($id);');
315 }
316 }
317
318
319 /// Helper for injecting fragments into some existing code.
320 class CodeInjector {
321 final String _code;
322 final offsetFragmentMap = new SplayTreeMap<int, String>();
323
324 CodeInjector(this._code);
325
326 void inject(int offset, String fragment) {
327 offsetFragmentMap[offset] = fragment;
328 }
329
330 String getResult() {
331 var sb = new StringBuffer();
332 var lastOffset = 0;
333 offsetFragmentMap.forEach((offset, fragment) {
334 sb.write(_code.substring(lastOffset, offset));
335 sb.write(fragment);
336 lastOffset = offset;
337 });
338 sb.write(_code.substring(lastOffset, _code.length));
339 return sb.toString();
340 }
341 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698