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

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

Powered by Google App Engine
This is Rietveld 408576698