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

Side by Side Diff: dart/tools/status_clean.dart

Issue 143453012: Added tools/status_clean.dart script (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge
Patch Set: Created 6 years, 10 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
« no previous file with comments | « no previous file | dart/tools/testing/dart/multitest.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2014, 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 library status_clean;
6
7 import "dart:async";
8 import "dart:convert" show JSON, UTF8;
9 import "dart:io";
10 import "testing/dart/multitest.dart";
11 import "testing/dart/status_file_parser.dart";
12 import "testing/dart/test_suite.dart"
13 show multiHtmlTestGroupRegExp, multiTestRegExp, multiHtmlTestRegExp,
14 TestUtils;
15 import "testing/dart/utils.dart" show Path;
16
17 // [STATUS_TUPLES] is a list of (suite-name, directory, status-file)-tuples.
18 final STATUS_TUPLES = [
19 ["corelib", "tests/corelib", "tests/corelib/corelib.status"],
20 ["html", "tests/html", "tests/html/html.status"],
21 ["isolate", "tests/isolate", "tests/isolate/isolate.status"],
22 ["json", "tests/json", "tests/json/json.status"],
23 ["language", "tests/language", "tests/language/language.status"],
24 ["language", "tests/language", "tests/language/language_analyzer2.status"],
25 ["language","tests/language", "tests/language/language_analyzer.status"],
26 ["language","tests/language", "tests/language/language_dart2js.status"],
27 ["lib", "tests/lib", "tests/lib/lib.status"],
28 ["standalone", "tests/standalone", "tests/standalone/standalone.status"],
29 ["pkg", "pkg", "pkg/pkg.status"],
30 ["pkgbuild", ".", "pkg/pkgbuild.status"],
31 ["utils", "tests/utils", "tests/utils/utils.status"],
32 ["samples", "samples", "samples/samples.status"],
33 ["analyze_library", "sdk", "tests/lib/analyzer/analyze_library.status"],
34 ["dart2js_extra", "tests/compiler/dart2js_extra",
35 "tests/compiler/dart2js_extra/dart2js_extra.status"],
36 ["dart2js_native", "tests/compiler/dart2js_native",
37 "tests/compiler/dart2js_native/dart2js_native.status"],
38 ["dart2js", "tests/compiler/dart2js",
39 "tests/compiler/dart2js/dart2js.status"],
40 ["pub", "sdk/lib/_internal/pub", "sdk/lib/_internal/pub/pub.status"],
41 ["benchmark_smoke", "tests/benchmark_smoke",
42 "tests/benchmark_smoke/benchmark_smoke.status"],
43 ["co19", "tests/co19/src", "tests/co19/co19-analyzer2.status"],
44 ["co19", "tests/co19/src", "tests/co19/co19-analyzer.status"],
45 ["co19", "tests/co19/src", "tests/co19/co19-dart2dart.status"],
46 ["co19", "tests/co19/src", "tests/co19/co19-dart2js.status"],
47 ["co19", "tests/co19/src", "tests/co19/co19-co19.status"],
48 ["co19", "tests/co19/src", "tests/co19/co19-dartium.status"],
49 ["co19", "tests/co19/src", "tests/co19/co19-runtime.status"],
50 ];
51
52 void main(List<String> args) {
53 usage() {
54 print("Usage: ${Platform.executable} <deflake|remove-nonexistent-tests>");
55 exit(1);
56 }
57
58 if (args.length == 0) usage();
59
60 if (args[0] == 'deflake') {
61 run(new StatusFileDeflaker());
62 } else if (args[0] == 'remove-nonexistent-tests') {
63 run(new StatusFileNonExistentTestRemover());
64 } else {
65 usage();
66 }
67 }
68
69 run(StatusFileProcessor processor) {
70 Future.forEach(STATUS_TUPLES, (List tuple) {
71 String suiteName = tuple[0];
72 String directory = tuple[1];
73 String filePath = tuple[2];
74 print("Processing $filePath");
75 return processor.run(suiteName, directory, filePath);
76 });
77 }
78
79 abstract class StatusFileProcessor {
80 Future run(String suiteName, String directory, String filePath);
81
82 Future<List<Section>> _readSections(String filePath) {
83 File file = new File(filePath);
84
85 if (file.existsSync()) {
86 var completer = new Completer();
87 List<Section> sections = new List<Section>();
88
89 ReadConfigurationInto(new Path(file.path), sections, () {
90 completer.complete(sections);
91 });
92 return completer.future;
93 }
94 return new Future.value([]);
95 }
96 }
97
98 class StatusFileNonExistentTestRemover extends StatusFileProcessor {
99 final MultiTestDetector multiTestDetector = new MultiTestDetector();
100 final TestFileLister testFileLister = new TestFileLister();
101
102 Future run(String suiteName, String directory, String filePath) {
103 return _readSections(filePath).then((List<Section> sections) {
104 Set<int> invalidLines = _analyzeStatusFile(directory, filePath, sections);
105 if (invalidLines.length > 0) {
106 return _writeFixedStatusFile(filePath, invalidLines);
107 }
108 return new Future.value();
109 });
110 }
111
112 bool _testExists(String filePath,
113 List<String> testFiles,
114 String directory,
115 TestRule rule) {
116 // TODO: Unify this regular expression matching with status_file_parser.dart
117 List<RegExp> getRuleRegex(String name) {
118 return name.split("/")
119 .map((name) => new RegExp(name.replaceAll('*', '.*')))
120 .toList();
121 }
122 bool matchRegexp(List<RegExp> patterns, String str) {
123 var parts = str.split("/");
124 if (patterns.length > parts.length) {
125 return false;
126 }
127 // NOTE: patterns.length <= parts.length
128 for (var i = 0; i < patterns.length; i++) {
129 if (!patterns[i].hasMatch(parts[i])) {
130 return false;
131 }
132 }
133 return true;
134 }
135
136 var rulePattern = getRuleRegex(rule.name);
137 return testFiles.any((String file) {
138 // TODO: Use test_suite.dart's [buildTestCaseDisplayName] instead.
139 var filePath = new Path(file).relativeTo(new Path(directory));
140 String baseTestName = _concat("${filePath.directoryPath}",
141 "${filePath.filenameWithoutExtension}");
142
143 List<String> testNames = [];
144 for (var name in multiTestDetector.getMultitestNames(file)) {
145 testNames.add(_concat(baseTestName, name));
146 }
147
148 // If it is not a multitest the testname is [baseTestName]
149 if (testNames.isEmpty) {
150 testNames.add(baseTestName);
151 }
152
153 return testNames.any(
154 (String testName) => matchRegexp(rulePattern, testName));
155 });
156 }
157
158 Set<int> _analyzeStatusFile(String directory,
159 String filePath,
160 List<Section> sections) {
161 var invalidLines = new Set<int>();
162 var dartFiles = testFileLister.listTestFiles(directory);
163 for (var section in sections) {
164 for (var rule in section.testRules) {
165 if (!_testExists(filePath, dartFiles, directory, rule)) {
166 print("Invalid rule: ${rule.name} in file "
167 "$filePath:${rule.lineNumber}");
168 invalidLines.add(rule.lineNumber);
169 }
170 }
171 }
172 return invalidLines;
173 }
174
175 _writeFixedStatusFile(String statusFilePath, Set<int> invalidLines) {
176 var lines = new File(statusFilePath).readAsLinesSync();
177 var outputLines = <String>[];
178 for (int i = 0; i < lines.length; i++) {
179 // The status file parser numbers lines starting with 1, not 0.
180 if (!invalidLines.contains(i + 1)) {
181 outputLines.add(lines[i]);
182 }
183 }
184 var outputFile = new File("$statusFilePath.fixed");
185 outputFile.writeAsStringSync(outputLines.join("\n"));
186 }
187
188 String _concat(String base, String part) {
189 if (base == "") return part;
190 if (part == "") return base;
191 return "$base/$part";
192 }
193 }
194
195 class StatusFileDeflaker extends StatusFileProcessor {
196 TestOutcomeFetcher _testOutcomeFetcher = new TestOutcomeFetcher();
197
198 Future run(String suiteName, String directory, String filePath) {
199 return _readSections(filePath).then((List<Section> sections) {
200 return _generatedDeflakedLines(suiteName, sections)
201 .then((Map<int, String> fixedLines) {
202 if (fixedLines.length > 0) {
203 return _writeFixedStatusFile(filePath, fixedLines);
204 }
205 });
206 });
207 }
208
209 Future _generatedDeflakedLines(String suiteName,
210 List<Section> sections) {
211 var fixedLines = new Map<int, String>();
212 return Future.forEach(sections, (Section section) {
213 return Future.forEach(section.testRules, (rule) {
214 return _maybeFixStatusfileLine(suiteName, section, rule, fixedLines);
215 });
216 }).then((_) => fixedLines);
217 }
218
219 Future _maybeFixStatusfileLine(String suiteName,
220 Section section,
221 TestRule rule,
222 Map<int, String> fixedLines) {
223 print("Processing ${section.statusFile.location}: ${rule.lineNumber}");
224 // None of our status file lines have expressions, so we pass {} here.
225 var notedOutcomes = rule.expression
226 .evaluate({})
227 .map((name) => Expectation.byName(name))
228 .where((Expectation expectation) => !expectation.isMetaExpectation)
229 .toSet();
230
231 if (notedOutcomes.isEmpty) return new Future.value();
232
233 // TODO: [rule.name] is actually a pattern not just a testname. We should
234 // find all possible testnames this rule matches against and unify the
235 // outcomes of these tests.
236 return _testOutcomeFetcher.outcomesOf(suiteName, section, rule.name)
237 .then((Set<Expectation> actualOutcomes) {
238
239 var outcomesThatNeverHappened = new Set<Expectation>();
240 for (Expectation notedOutcome in notedOutcomes) {
241 bool found = false;
242 for (Expectation actualOutcome in actualOutcomes) {
243 if (actualOutcome.canBeOutcomeOf(notedOutcome)) {
244 found = true;
245 break;
246 }
247 }
248 if (!found) {
249 outcomesThatNeverHappened.add(notedOutcome);
250 }
251 }
252
253 if (outcomesThatNeverHappened.length > 0 && actualOutcomes.length > 0) {
254 // Print the change to stdout.
255 print("${rule.name} "
256 "(${section.statusFile.location}:${rule.lineNumber}):");
257 print(" Actual outcomes: ${actualOutcomes.toList()}");
258 print(" Outcomes in status file: ${notedOutcomes.toList()}");
259 print(" Outcomes in status file that never happened : "
260 "${outcomesThatNeverHappened.toList()}\n");
261
262 // Build the fixed status file line.
263 fixedLines[rule.lineNumber] =
264 '${rule.name}: ${actualOutcomes.join(', ')} '
265 '# before: ${notedOutcomes.join(', ')} / '
266 'never happened: ${outcomesThatNeverHappened.join(', ')}';
267 }
268 });
269 }
270
271 _writeFixedStatusFile(String filePath, Map<int, String> fixedLines) {
272 var lines = new File(filePath).readAsLinesSync();
273 var outputLines = <String>[];
274 for (int i = 0; i < lines.length; i++) {
275 if (fixedLines.containsKey(i + 1)) {
276 outputLines.add(fixedLines[i + 1]);
277 } else {
278 outputLines.add(lines[i]);
279 }
280 }
281 var output = outputLines.join("\n");
282 var outputFile = new File("$filePath.deflaked");
283 outputFile.writeAsStringSync(output);
284 }
285 }
286
287 class MultiTestDetector {
288 final multiTestsCache = new Map<String,List<String>>();
289 final multiHtmlTestsCache = new Map<String,List<String>>();
290
291
292 List<String> getMultitestNames(String file) {
293 List<String> names = [];
294 names.addAll(getStandardMultitestNames(file));
295 names.addAll(getHtmlMultitestNames(file));
296 return names;
297 }
298
299 List<String> getStandardMultitestNames(String file) {
300 return multiTestsCache.putIfAbsent(file, () {
301 try {
302 var tests = new Map<String, String>();
303 var outcomes = new Map<String, Set<String>>();
304 if (multiTestRegExp.hasMatch(new File(file).readAsStringSync())) {
305 ExtractTestsFromMultitest(new Path(file), tests, outcomes);
306 }
307 return tests.keys.toList();
308 } catch (error) {
309 print("WARNING: Couldn't determine multitests in file ${file}: $error");
310 return [];
311 }
312 });
313 }
314
315 List<String> getHtmlMultitestNames(String file) {
316 return multiHtmlTestsCache.putIfAbsent(file, () {
317 try {
318 List<String> subtestNames = [];
319 var content = new File(file).readAsStringSync();
320
321 if (multiHtmlTestRegExp.hasMatch(content)) {
322 var matchesIter = multiHtmlTestGroupRegExp.allMatches(content).iterato r;
323 while(matchesIter.moveNext()) {
324 String fullMatch = matchesIter.current.group(0);
325 subtestNames.add(fullMatch.substring(fullMatch.indexOf("'") + 1));
326 }
327 }
328 return subtestNames;
329 } catch (error) {
330 print("WARNING: Couldn't determine multitests in file ${file}: $error");
331 }
332 return [];
333 });
334 }
335 }
336
337 class TestFileLister {
338 final Map<String, List<String>> _filesCache = {};
339
340 List<String> listTestFiles(String directory) {
341 return _filesCache.putIfAbsent(directory, () {
342 var dir = new Directory(directory);
343 // Cannot test for _test.dart because co19 tests don't have that ending.
344 var dartFiles = dir.listSync(recursive: true)
345 .where((fe) => fe is File)
346 .where((file) => file.path.endsWith(".dart") ||
347 file.path.endsWith("_test.html"))
348 .map((file) => file.path)
349 .toList();
350 return dartFiles;
351 });
352 }
353 }
354
355
356 /*
357 * [TestOutcomeFetcher] will fetch test results from a server using a REST-like
358 * interface.
359 */
360 class TestOutcomeFetcher {
361 static String SERVER = '108.170.219.8';
362 static int PORT = 4540;
363
364 HttpClient _client = new HttpClient();
365
366 Future<Set<Expectation>> outcomesOf(
367 String suiteName, Section section, String testName) {
368 var pathComponents = ['json', 'test-outcomes', 'outcomes',
369 Uri.encodeComponent("$suiteName/$testName")];
370 var path = pathComponents.join('/') + '/';
371 var url = new Uri(scheme: 'http', host: SERVER, port: PORT, path: path);
372
373 return _client.getUrl(url)
374 .then((HttpClientRequest request) => request.close())
375 .then((HttpClientResponse response) {
376 return response.transform(UTF8.decoder).transform(JSON.decoder).first
377 .then((List testResults) {
378 var setOfActualOutcomes = new Set<Expectation>();
379
380 try {
381 for (var result in testResults) {
382 var config = result['configuration'];
383 var testResult = result['test_result'];
384 var outcome = testResult['outcome'];
385
386 // These variables are derived variables and will be set in
387 // tools/testing/dart/test_options.dart.
388 // [Mostly due to the fact that we don't have an unary !
389 // operator in status file expressions.]
390 config['unchecked'] = !config['checked'];
391 config['unminified'] = !config['minified'];
392 config['nocsp'] = !config['csp'];
393 config['browser'] =
394 TestUtils.isBrowserRuntime(config['runtime']);
395 config['analyzer'] =
396 TestUtils.isCommandLineAnalyzer(config['compiler']);
397 config['jscl'] =
398 TestUtils.isJsCommandLineRuntime(config['runtime']);
399
400 if (section.condition == null ||
401 section.condition.evaluate(config)) {
402 setOfActualOutcomes.add(Expectation.byName(outcome));
403 }
404 }
405 return setOfActualOutcomes;
406 } catch (error) {
407 print("Warning: Error occured while processing testoutcomes"
408 ": $error");
409 return [];
410 }
411 }).catchError((error) {
412 print("Warning: Error occured while fetching testoutcomes: $error" );
413 return [];
414 });
415 });
416 }
417 }
OLDNEW
« no previous file with comments | « no previous file | dart/tools/testing/dart/multitest.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698