| 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 |