OLD | NEW |
---|---|
1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 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.md file. | 3 // BSD-style license that can be found in the LICENSE.md file. |
4 | 4 |
5 library testing.analyze; | 5 library testing.analyze; |
6 | 6 |
7 import 'dart:async' show Stream, Future; | 7 import 'dart:async' show Stream, Future; |
8 | 8 |
9 import 'dart:convert' show LineSplitter, UTF8; | 9 import 'dart:convert' show LineSplitter, UTF8; |
10 | 10 |
11 import 'dart:io' show File, Process; | 11 import 'dart:io' show Directory, File, FileSystemEntity, Process, ProcessResult; |
12 | 12 |
13 import '../testing.dart' show startDart; | 13 import '../testing.dart' show startDart; |
14 | 14 |
15 import 'log.dart' show isVerbose; | 15 import 'log.dart' show isVerbose, splitLines; |
16 | 16 |
17 import 'suite.dart' show Suite; | 17 import 'suite.dart' show Suite; |
18 | 18 |
19 class Analyze extends Suite { | 19 class Analyze extends Suite { |
20 final Uri analysisOptions; | 20 final Uri analysisOptions; |
21 | 21 |
22 final List<Uri> uris; | 22 final List<Uri> uris; |
23 | 23 |
24 final List<RegExp> exclude; | 24 final List<RegExp> exclude; |
25 | 25 |
26 Analyze(this.analysisOptions, this.uris, this.exclude) | 26 final List<String> gitGrepPathspecs; |
27 | |
28 final List<String> gitGrepPatterns; | |
29 | |
30 Analyze(this.analysisOptions, this.uris, this.exclude, this.gitGrepPathspecs, | |
31 this.gitGrepPatterns) | |
27 : super("analyze", "analyze", null); | 32 : super("analyze", "analyze", null); |
28 | 33 |
29 Future<Null> run(Uri packages, List<Uri> extraUris) { | 34 Future<Null> run(Uri packages, List<Uri> extraUris) { |
30 List<Uri> allUris = new List<Uri>.from(uris); | 35 List<Uri> allUris = new List<Uri>.from(uris); |
31 if (extraUris != null) { | 36 if (extraUris != null) { |
32 allUris.addAll(extraUris); | 37 allUris.addAll(extraUris); |
33 } | 38 } |
34 return analyzeUris(analysisOptions, packages, allUris, exclude); | 39 return analyzeUris(analysisOptions, packages, allUris, exclude, |
40 gitGrepPathspecs, gitGrepPatterns); | |
35 } | 41 } |
36 | 42 |
37 static Future<Analyze> fromJsonMap( | 43 static Future<Analyze> fromJsonMap( |
38 Uri base, Map json, List<Suite> suites) async { | 44 Uri base, Map json, List<Suite> suites) async { |
39 String optionsPath = json["options"]; | 45 String optionsPath = json["options"]; |
40 Uri optionsUri = optionsPath == null ? null : base.resolve(optionsPath); | 46 Uri optionsUri = optionsPath == null ? null : base.resolve(optionsPath); |
41 | 47 |
42 List<Uri> uris = new List<Uri>.from( | 48 List<Uri> uris = new List<Uri>.from( |
43 json["uris"].map((String relative) => base.resolve(relative))); | 49 json["uris"].map((String relative) => base.resolve(relative))); |
44 | 50 |
45 List<RegExp> exclude = | 51 List<RegExp> exclude = |
46 new List<RegExp>.from(json["exclude"].map((String p) => new RegExp(p))); | 52 new List<RegExp>.from(json["exclude"].map((String p) => new RegExp(p))); |
47 | 53 |
48 return new Analyze(optionsUri, uris, exclude); | 54 Map gitGrep = json["git grep"]; |
55 List<String> gitGrepPathspecs; | |
56 List<String> gitGrepPatterns; | |
57 if (gitGrep != null) { | |
58 gitGrepPathspecs = gitGrep["pathspecs"] ?? const <String>["."]; | |
59 gitGrepPatterns = gitGrep["patterns"]; | |
60 } | |
61 | |
62 return new Analyze( | |
63 optionsUri, uris, exclude, gitGrepPathspecs, gitGrepPatterns); | |
49 } | 64 } |
50 | 65 |
51 String toString() => "Analyze($uris, $exclude)"; | 66 String toString() => "Analyze($uris, $exclude)"; |
52 } | 67 } |
53 | 68 |
54 class AnalyzerDiagnostic { | 69 class AnalyzerDiagnostic { |
55 final String kind; | 70 final String kind; |
56 | 71 |
57 final String detailedKind; | 72 final String detailedKind; |
58 | 73 |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
124 Stream<AnalyzerDiagnostic> parseAnalyzerOutput( | 139 Stream<AnalyzerDiagnostic> parseAnalyzerOutput( |
125 Stream<List<int>> stream) async* { | 140 Stream<List<int>> stream) async* { |
126 Stream<String> lines = | 141 Stream<String> lines = |
127 stream.transform(UTF8.decoder).transform(new LineSplitter()); | 142 stream.transform(UTF8.decoder).transform(new LineSplitter()); |
128 await for (String line in lines) { | 143 await for (String line in lines) { |
129 yield new AnalyzerDiagnostic.fromLine(line); | 144 yield new AnalyzerDiagnostic.fromLine(line); |
130 } | 145 } |
131 } | 146 } |
132 | 147 |
133 /// Run dartanalyzer on all tests in [uris]. | 148 /// Run dartanalyzer on all tests in [uris]. |
134 Future<Null> analyzeUris(Uri analysisOptions, Uri packages, List<Uri> uris, | 149 Future<Null> analyzeUris( |
135 List<RegExp> exclude) async { | 150 Uri analysisOptions, |
151 Uri packages, | |
152 List<Uri> uris, | |
153 List<RegExp> exclude, | |
154 List<String> gitGrepPathspecs, | |
155 List<String> gitGrepPatterns) async { | |
136 if (uris.isEmpty) return; | 156 if (uris.isEmpty) return; |
157 String topLevel; | |
158 try { | |
159 topLevel = Uri | |
160 .directory(await git("rev-parse", <String>["--show-toplevel"])) | |
161 .toFilePath(); | |
162 } catch (e) { | |
163 topLevel = Uri.base.toFilePath(); | |
164 } | |
165 | |
166 String toFilePath(Uri uri) { | |
167 String path = uri.toFilePath(); | |
168 return path.startsWith(topLevel) ? path.substring(topLevel.length) : path; | |
169 } | |
170 | |
171 Set<String> filesToAnalyze = new Set<String>(); | |
172 | |
173 for (Uri uri in uris) { | |
174 if (await new Directory.fromUri(uri).exists()) { | |
175 await for (FileSystemEntity entity in new Directory.fromUri(uri) | |
176 .list(recursive: true, followLinks: false)) { | |
177 if (entity is File && entity.path.endsWith(".dart")) { | |
178 filesToAnalyze.add(toFilePath(entity.uri)); | |
179 } | |
180 } | |
181 } else if (await new File.fromUri(uri).exists()) { | |
182 filesToAnalyze.add(toFilePath(uri)); | |
183 } else { | |
184 throw "File not found: ${uri}"; | |
185 } | |
186 } | |
187 | |
188 if (gitGrepPatterns != null) { | |
189 print("Top level git dir is $topLevel"); | |
190 List<String> arguments = <String>["-l"]; | |
191 arguments.addAll( | |
192 gitGrepPatterns.expand((String pattern) => <String>["-e", pattern])); | |
193 arguments.add("--"); | |
194 arguments.addAll(gitGrepPathspecs); | |
195 filesToAnalyze.addAll(splitLines(await git("grep", arguments)) | |
196 .map((String line) => line.trimRight())); | |
197 } | |
198 | |
137 const String analyzerPath = "pkg/analyzer_cli/bin/analyzer.dart"; | 199 const String analyzerPath = "pkg/analyzer_cli/bin/analyzer.dart"; |
138 Uri analyzer = Uri.base.resolve(analyzerPath); | 200 Uri analyzer = Uri.base.resolve(analyzerPath); |
139 if (!await new File.fromUri(analyzer).exists()) { | 201 if (!await new File.fromUri(analyzer).exists()) { |
140 throw "Couldn't find '$analyzerPath' in '${Uri.base.toFilePath()}'"; | 202 throw "Couldn't find '$analyzerPath' in '${toFilePath(Uri.base)}'"; |
141 } | 203 } |
142 List<String> arguments = <String>[ | 204 List<String> arguments = <String>[ |
143 "--packages=${packages.toFilePath()}", | 205 "--packages=${toFilePath(packages)}", |
144 "--package-warnings", | |
145 "--format=machine", | 206 "--format=machine", |
146 "--dart-sdk=${Uri.base.resolve('sdk/').toFilePath()}", | 207 "--dart-sdk=${Uri.base.resolve('sdk/').toFilePath()}", |
147 ]; | 208 ]; |
148 if (analysisOptions != null) { | 209 if (analysisOptions != null) { |
149 arguments.add("--options=${analysisOptions.toFilePath()}"); | 210 arguments.add("--options=${toFilePath(analysisOptions)}"); |
150 } | 211 } |
151 arguments.addAll(uris.map((Uri uri) => uri.toFilePath())); | 212 |
213 arguments.addAll(filesToAnalyze); | |
152 if (isVerbose) { | 214 if (isVerbose) { |
153 print("Running:\n ${analyzer.toFilePath()} ${arguments.join(' ')}"); | 215 print("Running:\n ${toFilePath(analyzer)} ${arguments.join(' ')}"); |
154 } else { | 216 } else { |
155 print("Running dartanalyzer."); | 217 print("Running dartanalyzer."); |
156 } | 218 } |
157 Stopwatch sw = new Stopwatch()..start(); | 219 Stopwatch sw = new Stopwatch()..start(); |
158 Process process = await startDart(analyzer, arguments); | 220 Process process = await startDart(analyzer, arguments); |
159 process.stdin.close(); | 221 process.stdin.close(); |
160 Future stdoutFuture = parseAnalyzerOutput(process.stdout).toList(); | 222 |
161 Future stderrFuture = parseAnalyzerOutput(process.stderr).toList(); | |
162 await process.exitCode; | |
163 List<AnalyzerDiagnostic> diagnostics = <AnalyzerDiagnostic>[]; | |
164 diagnostics.addAll(await stdoutFuture); | |
165 diagnostics.addAll(await stderrFuture); | |
166 bool hasOutput = false; | 223 bool hasOutput = false; |
167 Set<String> seen = new Set<String>(); | 224 Set<String> seen = new Set<String>(); |
168 for (AnalyzerDiagnostic diagnostic in diagnostics) { | 225 |
169 String path = diagnostic.uri?.path; | 226 processAnalyzerOutput(Stream<AnalyzerDiagnostic> diagnostics) async { |
170 if (path != null && exclude.any((RegExp r) => path.contains(r))) { | 227 await for (AnalyzerDiagnostic diagnostic in diagnostics) { |
171 continue; | 228 if (diagnostic.uri != null) { |
172 } | 229 String path = toFilePath(diagnostic.uri); |
173 String message = "$diagnostic"; | 230 if (diagnostic.code.startsWith("STRONG_MODE") && |
174 if (seen.add(message)) { | 231 (path.startsWith("pkg/compiler/") || |
175 hasOutput = true; | 232 path.startsWith("tests/compiler/dart2js/"))) { |
176 print(message); | 233 // Hack to work around dart2js not being strong-mode clean. |
234 continue; | |
235 } | |
236 if (!filesToAnalyze.contains(path)) continue; | |
237 if (exclude.any((RegExp r) => path.contains(r))) { | |
238 continue; | |
239 } | |
240 } | |
241 String message = "$diagnostic"; | |
242 if (seen.add(message)) { | |
243 hasOutput = true; | |
244 print(message); | |
245 } | |
177 } | 246 } |
178 } | 247 } |
248 | |
249 Future stderrFuture = | |
250 processAnalyzerOutput(parseAnalyzerOutput(process.stderr)); | |
251 Future stdoutFuture = | |
252 processAnalyzerOutput(parseAnalyzerOutput(process.stdout)); | |
253 await process.exitCode; | |
254 await stdoutFuture; | |
255 await stderrFuture; | |
256 sw.stop(); | |
257 print("Running analyzer took: ${sw.elapsed}."); | |
179 if (hasOutput) { | 258 if (hasOutput) { |
180 throw "Non-empty output from analyzer."; | 259 throw "Non-empty output from analyzer."; |
181 } | 260 } |
182 sw.stop(); | |
183 print("Running analyzer took: ${sw.elapsed}."); | |
184 } | 261 } |
262 | |
263 Future<String> git(String command, Iterable<String> arguments, | |
264 {String workingDirectory}) async { | |
265 ProcessResult result = await Process.run( | |
266 "git", <String>[command]..addAll(arguments), | |
Johnni Winther
2017/05/23 14:02:53
This doesn't work on Windows (missing .exe on git)
ahe
2017/05/24 09:12:53
Done.
| |
267 workingDirectory: workingDirectory); | |
268 if (result.exitCode != 0) { | |
269 throw "Non-zero exit code from git $command (${result.exitCode})\n" | |
270 "${result.stdout}\n${result.stderr}"; | |
271 } | |
272 return result.stdout; | |
273 } | |
OLD | NEW |