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