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

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
OLDNEW
(Empty)
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
ricow1 2014/02/11 14:33:52 2014
kustermann 2014/02/14 11:52:07 Done.
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 StatusFileParserTest;
ricow1 2014/02/11 14:33:52 I find that name a little odd
kustermann 2014/02/14 11:52:07 Done.
6
7 import "dart:async";
8 import "dart:convert" show JSON, UTF8;
9 import "dart:io";
10 import "testing/dart/status_file_parser.dart";
11 import "testing/dart/multitest.dart";
ricow1 2014/02/11 14:33:52 m<s
kustermann 2014/02/14 11:52:07 Done.
12 import "testing/dart/utils.dart" show Path;
13 import "testing/dart/test_suite.dart"
ricow1 2014/02/11 14:33:52 t<u
kustermann 2014/02/14 11:52:07 Done.
14 show multiHtmlTestGroupRegExp, multiTestRegExp, multiHtmlTestRegExp,
15 TestUtils;
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
31 ["pkgbuild", ".", "pkg/pkgbuild.status"],
32 ["utils", "tests/utils", "tests/utils/utils.status"],
33 ["samples", "samples", "samples/samples.status"],
34 ["analyze_library", "sdk", "tests/lib/analyzer/analyze_library.status"],
35
36 ["dart2js_extra", "tests/compiler/dart2js_extra",
37 "tests/compiler/dart2js_extra/dart2js_extra.status"],
38 ["dart2js_native", "tests/compiler/dart2js_native",
39 "tests/compiler/dart2js_native/dart2js_native.status"],
40 ["dart2js", "tests/compiler/dart2js",
41 "tests/compiler/dart2js/dart2js.status"],
42
43 ["pub", "sdk/lib/_internal/pub", "sdk/lib/_internal/pub/pub.status"],
44 ["benchmark_smoke", "tests/benchmark_smoke",
45 "tests/benchmark_smoke/benchmark_smoke.status"],
46
47 ["co19", "tests/co19/src", "tests/co19/co19-analyzer2.status"],
48 ["co19", "tests/co19/src", "tests/co19/co19-analyzer.status"],
49 ["co19", "tests/co19/src", "tests/co19/co19-dart2dart.status"],
50 ["co19", "tests/co19/src", "tests/co19/co19-dart2js.status"],
51 ["co19", "tests/co19/src", "tests/co19/co19-co19.status"],
52 ["co19", "tests/co19/src", "tests/co19/co19-dartium.status"],
53 ["co19", "tests/co19/src", "tests/co19/co19-runtime.status"],
54 ];
ricow1 2014/02/11 14:33:52 grouping (i.e., blank lines in the above seems ran
kustermann 2014/02/14 11:52:07 The really bad thing about this is, that this info
55
56 void main(List<String> args) {
57 usage() {
58 print("Usage: ${Platform.executable} <deflake|fix>");
ricow1 2014/02/11 14:33:52 fix is not a very descriptive word here
kustermann 2014/02/14 11:52:07 I'll change it to "remove-nonexistent-tests. Feel
59 exit(1);
60 }
61
62 if (args.length == 0) usage();
63
Bill Hesse 2014/02/13 17:05:08 You could extract the function to run (statusFileD
kustermann 2014/02/14 11:52:07 I thought about it and left it like that because i
64 if (args[0] == 'deflake') {
65 var statusFileDeflaker = new StatusFileDeflaker();
66 Future.forEach(STATUS_TUPLES, (List tuple) {
67 String suiteName = tuple[0];
68 String filePath = tuple[2];
69 print("Processing $filePath");
70 return statusFileDeflaker.deflakeStatusFile(suiteName, filePath);
71 });
72 } else if (args[0] == 'fix') {
73 var invalidTestFixer = new StatusFileNonExistentTestRemover();
74 Future.forEach(STATUS_TUPLES, (List tuple) {
75 String directory = tuple[1];
76 String filePath = tuple[2];
77 print("Processing $filePath");
78 return invalidTestFixer
79 .removeNonExistentTestsFromStatusFile(directory, filePath);
80 });
81 } else {
82 usage();
83 }
84 }
85
86 abstract class StatusFileProcessor {
87 Future<List<Section>> _readSections(String filePath) {
88 File file = new File(filePath);
89
90 if (file.existsSync()) {
91 var completer = new Completer();
92 List<Section> sections = new List<Section>();
93
94 ReadConfigurationInto(new Path(file.path), sections, () {
95 completer.complete(sections);
96 });
97 return completer.future;
98 }
99 return new Future.value([]);
100 }
101 }
102
103 class StatusFileNonExistentTestRemover extends StatusFileProcessor {
104 final MultiTestDetector multiTestDetector = new MultiTestDetector();
105 final TestFileLister testFileLister = new TestFileLister();
106
107 Future removeNonExistentTestsFromStatusFile(String directory,
108 String filePath) {
109 return _readSections(filePath).then((List<Section> sections) {
110 Set<int> invalidLines = _analyzeStatusFile(directory, filePath, sections);
111 if (invalidLines.length > 0) {
112 return _writeFixedStatusFile(filePath, invalidLines);
113 }
114 return new Future.value();
115 });
116 }
117
118 bool _doesTestExist(String filePath,
Bill Hesse 2014/02/13 17:05:08 _testExists is a shorter name.
kustermann 2014/02/14 11:52:07 Done.
119 List<String> dartFiles,
120 String directory,
121 TestRule rule) {
122 List<RegExp> getRuleRegex(String name) {
123 return name.split("/")
124 .map((name) => new RegExp(name.replaceAll('*', '.*')))
125 .toList();
126 }
127 bool matchRegexp(List<RegExp> patterns, String str) {
128 var parts = str.split("/");
129 if (patterns.length > parts.length) {
130 return false;
131 }
132 // NOTE: patterns.length <= parts.length
133 for (var i = 0; i < patterns.length; i++) {
134 if (!patterns[i].hasMatch(parts[i])) {
135 return false;
136 }
137 }
138 return true;
139 }
140
141 if (rule.name.contains("packages") && filePath.contains("pkg.status")) {
Bill Hesse 2014/02/13 17:05:08 Add a comment, that fix option does nothing to pkg
kustermann 2014/02/14 11:52:07 Well, that's not what it's doing. It's processing
142 return true;
143 }
144
145 var rulePattern = getRuleRegex(rule.name);
146 return dartFiles.any((String file) {
147 var relative = new Path(file).relativeTo(new Path(directory)).toString();
148 for (int splitIndex = 0; splitIndex < rulePattern.length; splitIndex++) {
149 // Construct a pattern for the file name.
150 var filePattern = new List();
151 for (var i = 0; i <= splitIndex; i++) {
152 filePattern.add(rulePattern[i]);
153 }
154
155 // Construct a pattern for the multitest name.
156 var multitestPattern = new List();
157 for (var i = splitIndex + 1; i < rulePattern.length; i++) {
158 multitestPattern.add(rulePattern[i]);
159 }
160
161 if (matchRegexp(filePattern, relative)) {
Bill Hesse 2014/02/13 17:05:08 There is a strange semantics here, that filePatter
162 // Could be a normal test
163 if (multitestPattern.length == 0) {
164 return true;
165 }
166
167 // Could be a real multitest.
168 if (multiTestDetector.getMultitestNamesFromFile(file).any(
169 (name) => matchRegexp(multitestPattern, name))) {
170 return true;
171 }
172
173 // Could be a multi html test.
174 if (multiTestDetector.getMultiHtmlTests(file).any(
175 (name) => matchRegexp(multitestPattern, name))) {
176 return true;
177 }
178 }
179 }
180 return false;
181 });
182 }
183
184 Set<int> _analyzeStatusFile(String directory,
185 String filePath,
186 List<Section> sections) {
187 var invalidLines = new Set<int>();
188 var dartFiles = testFileLister.listTestFiles(directory);
189 for (var section in sections) {
190 for (var rule in section.testRules) {
191 if (!_doesTestExist(filePath, dartFiles, directory, rule)) {
192 print("Invalid rule: ${rule.name} in file $filePath:${rule.lineNr}");
193 invalidLines.add(rule.lineNr);
194 }
195 }
196 }
197 return invalidLines;
198 }
199
200 _writeFixedStatusFile(String filePath, Set<int> invalidLines) {
Bill Hesse 2014/02/13 17:05:08 statusFilePath
kustermann 2014/02/14 11:52:07 Done.
201 var lines = new File(filePath).readAsLinesSync();
202 var outputLines = <String>[];
203 for (int i = 0; i < lines.length; i++) {
204 if (!invalidLines.contains(i + 1)) {
Bill Hesse 2014/02/13 17:05:08 // The status file parser numbers lines starting w
kustermann 2014/02/14 11:52:07 Done.
205 outputLines.add(lines[i]);
206 }
207 }
208 var outputFile = new File("$filePath.fixed");
209 outputFile.writeAsStringSync(outputLines.join("\n"));
Bill Hesse 2014/02/13 17:05:08 Writing the output can happen asynchronously, whil
kustermann 2014/02/14 11:52:07 But then there is a future and nobody waits for it
210 }
211 }
212
213 class StatusFileDeflaker extends StatusFileProcessor {
214 TestOutcomeFetcher _testOutcomeFetcher = new TestOutcomeFetcher();
215
216 Future deflakeStatusFile(String suiteName, String filePath) {
217 return _readSections(filePath).then((List<Section> sections) {
218 var fixedLines = new Map<int, String>();
Bill Hesse 2014/02/13 17:05:08 Dead variable, hidden by parameter name in closure
kustermann 2014/02/14 11:52:07 Dead and left-over variable :) [I moved it into _g
219 return _generatedDeflakedLines(suiteName, sections)
220 .then((Map<int, String> fixedLines) {
221 if (fixedLines.length > 0) {
222 return _writeFixedStatusFile(filePath, fixedLines);
223 }
224 });
225 });
226 }
227
228 Future _generatedDeflakedLines(String suiteName,
229 List<Section> sections) {
230 var completer = new Completer();
Bill Hesse 2014/02/13 17:05:08 Completer not needed. Just return Future.forEac
kustermann 2014/02/14 11:52:07 Done.
231 var fixedLines = new Map<int, String>();
232 Future.forEach(sections, (Section section) {
233 return Future.forEach(section.testRules, (rule) {
234 return _maybeFixStatusfileLine(suiteName, section, rule, fixedLines);
235 });
236 }).then((_) => completer.complete(fixedLines));
237 return completer.future;
238 }
239
240 Future _maybeFixStatusfileLine(String suiteName,
241 Section section,
242 TestRule rule,
243 Map<int, String> fixedLines) {
244 print("Processing ${section.statusFile.location}: ${rule.lineNr}");
245 var notedOutcomes = rule.expression
246 .evaluate({})
Bill Hesse 2014/02/13 17:05:08 Comment that none of our status files have express
kustermann 2014/02/14 11:52:07 Done.
247 .map((name) => Expectation.byName(name))
248 .where((Expectation expectation) => !expectation.isMetaExpectation)
249 .toSet();
250
251 if (notedOutcomes.isEmpty) return new Future.value();
252
253 return _testOutcomeFetcher.outcomesOf(suiteName, section, rule.name)
254 .then((Set<Expectation> actualOutcomes) {
255
256 var outcomesThatNeverHappend = new Set<Expectation>();
Bill Hesse 2014/02/13 17:05:08 Happened
kustermann 2014/02/14 11:52:07 Done.
257 for (Expectation notedOutcome in notedOutcomes) {
258 bool found = false;
259 for (Expectation actualOutcome in actualOutcomes) {
260 if (actualOutcome.canBeOutcomeOf(notedOutcome)) {
261 found = true;
262 break;
263 }
264 }
265 if (!found) {
266 outcomesThatNeverHappend.add(notedOutcome);
267 }
268 }
269
270 if (outcomesThatNeverHappend.length > 0 && actualOutcomes.length > 0) {
271 // Print the change to stdout.
272 print("${rule.name} (${section.statusFile.location}:${rule.lineNr}):");
273 print(" Actual outcomes: ${actualOutcomes.toList()}");
274 print(" Outcomes in status file: ${notedOutcomes.toList()}");
275 print(" Outcomes in status file that never happened : "
276 "${outcomesThatNeverHappend.toList()}\n");
277
278 // Build the fixed status file line.
279 fixedLines[rule.lineNr] =
280 '${rule.name}: ${actualOutcomes.join(', ')} '
281 '# before: ${notedOutcomes.join(', ')} / '
282 'never happened: ${outcomesThatNeverHappend.join(', ')}';
283 }
284 });
285 }
286
287 _writeFixedStatusFile(String filePath, Map<int, String> fixedLines) {
288 var lines = new File(filePath).readAsLinesSync();
289 var outputLines = <String>[];
290 for (int i = 0; i < lines.length; i++) {
291 if (fixedLines.containsKey(i + 1)) {
292 outputLines.add(fixedLines[i + 1]);
293 } else {
294 outputLines.add(lines[i]);
295 }
296 }
297 var output = outputLines.join("\n");
298 var outputFile = new File("$filePath.deflaked");
299 outputFile.writeAsStringSync(output);
300 }
301 }
302
303 class MultiTestDetector {
304 final multiTestsCache = new Map<String,List<String>>();
305 final multiHtmlTestsCache = new Map<String,List<String>>();
306
307 List<String> getMultitestNamesFromFile(String file) {
308 if (multiTestsCache.containsKey(file)) return multiTestsCache[file];
309
310 var tests = new Map<String, String>();
311 var outcomes = new Map<String, Set<String>>();
312 if (multiTestRegExp.hasMatch(new File(file).readAsStringSync())) {
313 ExtractTestsFromMultitest(new Path(file), tests, outcomes);
314 }
315 multiTestsCache[file] = tests.keys.toList();
316 return multiTestsCache[file];
317 }
318
319 List<String> getMultiHtmlTests(String file) {
320 if (multiHtmlTestsCache.containsKey(file)) return multiHtmlTestsCache[file];
321
Bill Hesse 2014/02/13 17:05:08 return multiHtmlTestsCache.putIfAbsent(file, () {
kustermann 2014/02/14 11:52:07 Done.
322 try {
323 List<String> subtestNames = [];
324 var content = new File(file).readAsStringSync();
325
326 if (multiHtmlTestRegExp.hasMatch(content)) {
327 var matchesIter = multiHtmlTestGroupRegExp.allMatches(content).iterator;
328 while(matchesIter.moveNext()) {
329 String fullMatch = matchesIter.current.group(0);
330 subtestNames.add(fullMatch.substring(fullMatch.indexOf("'") + 1));
331 }
332 }
333 multiHtmlTestsCache[file] = subtestNames;
334 return subtestNames;
335 } catch (e) {
336 print("WARNING: couldn't determine html multitests in file ${file}");
337 }
338 return [];
339 }
340 }
341
342 class TestFileLister {
343 final Map<String, List<String>> _filesCache = {};
344
Bill Hesse 2014/02/13 17:05:08 putIfAbsent
kustermann 2014/02/14 11:52:07 Done.
345 List<String> listTestFiles(String directory) {
346 if (_filesCache.containsKey(directory)) {
347 return _filesCache[directory];
348 }
349
350 var dir = new Directory(directory);
351 // Cannot test for _test.dart because co19 tests don't have that ending.
352 var dartFiles = dir.listSync(recursive: true)
353 .where((fe) => fe is File)
354 .where((file) => file.path.endsWith(".dart") ||
355 file.path.endsWith("_test.html"))
356 .map((file) => file.path)
357 .toList();
358 _filesCache[directory] = dartFiles;
359 return dartFiles;
360 }
361 }
362
363
364 /*
365 * [TestOutcomeFetcher] will fetch test results from a server using a REST-like
366 * interface.
367 */
368 class TestOutcomeFetcher {
369 static String SERVER = '108.170.219.8';
Bill Hesse 2014/02/13 17:05:08 Is there anything better than hardcoding these val
kustermann 2014/02/14 11:52:07 No. We don't have a DNS name for it. I could make
370 static int PORT = 4540;
371
372 HttpClient _client = new HttpClient();
373
374 Future<Set<Expectation>> outcomesOf(
375 String suiteName, Section section, String testName) {
376 var completer = new Completer();
377 var pathComponents = ['json', 'test-outcomes', 'outcomes',
378 Uri.encodeComponent("$suiteName/$testName")];
379 var path = pathComponents.join('/') + '/';
380 var url = new Uri(scheme: 'http', host: SERVER, port: PORT, path: path);
381
382 _client.getUrl(url)
383 .then((HttpClientRequest request) => request.close())
384 .then((HttpClientResponse response) {
385 response
386 .transform(UTF8.decoder)
387 .transform(JSON.decoder).listen((List testResults) {
Bill Hesse 2014/02/13 17:05:08 Is this .listen really a .first? If so, then you
kustermann 2014/02/14 11:52:07 Good point.
388 var setOfActualOutcomes = new Set<Expectation>();
389
390 try {
391 for (var result in testResults) {
392 var config = result['configuration'];
393 var testResult = result['test_result'];
394 var outcome = testResult['outcome'];
395
396 config['unchecked'] = !config['checked'];
Bill Hesse 2014/02/13 17:05:08 Is there a way to keep these in sync with what the
kustermann 2014/02/14 11:52:07 Added comment. I don't want to make this CL bigger
397 config['unminified'] = !config['minified'];
398 config['nocsp'] = !config['csp'];
399 config['browser'] =
400 TestUtils.isBrowserRuntime(config['runtime']);
401 config['analyzer'] =
402 TestUtils.isCommandLineAnalyzer(config['compiler']);
403 config['jscl'] =
404 TestUtils.isJsCommandLineRuntime(config['runtime']);
405
406 if (section.condition == null ||
407 section.condition.evaluate(config)) {
408 setOfActualOutcomes.add(Expectation.byName(outcome));
409 }
410 }
411 completer.complete(setOfActualOutcomes);
412 } catch (error) {
413 print("Warning: Error occured while processing testoutcomes"
414 ": $error");
415 completer.complete([]);
416 }
417 }, onError: (error) {
418 print("Warning: Error occured while fetching testoutcomes: $error");
419 completer.complete([]);
420 }, cancelOnError: true);
421 });
422 return completer.future;
423 }
424 }
OLDNEW
« no previous file with comments | « no previous file | dart/tools/testing/dart/multitest.dart » ('j') | dart/tools/testing/dart/status_file_parser.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698