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

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

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

Powered by Google App Engine
This is Rietveld 408576698