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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: dart/tools/status_clean.dart
diff --git a/dart/tools/status_clean.dart b/dart/tools/status_clean.dart
new file mode 100644
index 0000000000000000000000000000000000000000..7a56bf83b1c05f786c397c4c117e240a01cb7d89
--- /dev/null
+++ b/dart/tools/status_clean.dart
@@ -0,0 +1,424 @@
+// 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.
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library StatusFileParserTest;
ricow1 2014/02/11 14:33:52 I find that name a little odd
kustermann 2014/02/14 11:52:07 Done.
+
+import "dart:async";
+import "dart:convert" show JSON, UTF8;
+import "dart:io";
+import "testing/dart/status_file_parser.dart";
+import "testing/dart/multitest.dart";
ricow1 2014/02/11 14:33:52 m<s
kustermann 2014/02/14 11:52:07 Done.
+import "testing/dart/utils.dart" show Path;
+import "testing/dart/test_suite.dart"
ricow1 2014/02/11 14:33:52 t<u
kustermann 2014/02/14 11:52:07 Done.
+ show multiHtmlTestGroupRegExp, multiTestRegExp, multiHtmlTestRegExp,
+ TestUtils;
+
+// [STATUS_TUPLES] is a list of (suite-name, directory, status-file)-tuples.
+final STATUS_TUPLES = [
+ ["corelib", "tests/corelib", "tests/corelib/corelib.status"],
+ ["html", "tests/html", "tests/html/html.status"],
+ ["isolate", "tests/isolate", "tests/isolate/isolate.status"],
+ ["json", "tests/json", "tests/json/json.status"],
+ ["language", "tests/language", "tests/language/language.status"],
+ ["language", "tests/language", "tests/language/language_analyzer2.status"],
+ ["language","tests/language", "tests/language/language_analyzer.status"],
+ ["language","tests/language", "tests/language/language_dart2js.status"],
+ ["lib", "tests/lib", "tests/lib/lib.status"],
+ ["standalone", "tests/standalone", "tests/standalone/standalone.status"],
+ ["pkg", "pkg", "pkg/pkg.status"],
+
+ ["pkgbuild", ".", "pkg/pkgbuild.status"],
+ ["utils", "tests/utils", "tests/utils/utils.status"],
+ ["samples", "samples", "samples/samples.status"],
+ ["analyze_library", "sdk", "tests/lib/analyzer/analyze_library.status"],
+
+ ["dart2js_extra", "tests/compiler/dart2js_extra",
+ "tests/compiler/dart2js_extra/dart2js_extra.status"],
+ ["dart2js_native", "tests/compiler/dart2js_native",
+ "tests/compiler/dart2js_native/dart2js_native.status"],
+ ["dart2js", "tests/compiler/dart2js",
+ "tests/compiler/dart2js/dart2js.status"],
+
+ ["pub", "sdk/lib/_internal/pub", "sdk/lib/_internal/pub/pub.status"],
+ ["benchmark_smoke", "tests/benchmark_smoke",
+ "tests/benchmark_smoke/benchmark_smoke.status"],
+
+ ["co19", "tests/co19/src", "tests/co19/co19-analyzer2.status"],
+ ["co19", "tests/co19/src", "tests/co19/co19-analyzer.status"],
+ ["co19", "tests/co19/src", "tests/co19/co19-dart2dart.status"],
+ ["co19", "tests/co19/src", "tests/co19/co19-dart2js.status"],
+ ["co19", "tests/co19/src", "tests/co19/co19-co19.status"],
+ ["co19", "tests/co19/src", "tests/co19/co19-dartium.status"],
+ ["co19", "tests/co19/src", "tests/co19/co19-runtime.status"],
+];
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
+
+void main(List<String> args) {
+ usage() {
+ 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
+ exit(1);
+ }
+
+ if (args.length == 0) usage();
+
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
+ if (args[0] == 'deflake') {
+ var statusFileDeflaker = new StatusFileDeflaker();
+ Future.forEach(STATUS_TUPLES, (List tuple) {
+ String suiteName = tuple[0];
+ String filePath = tuple[2];
+ print("Processing $filePath");
+ return statusFileDeflaker.deflakeStatusFile(suiteName, filePath);
+ });
+ } else if (args[0] == 'fix') {
+ var invalidTestFixer = new StatusFileNonExistentTestRemover();
+ Future.forEach(STATUS_TUPLES, (List tuple) {
+ String directory = tuple[1];
+ String filePath = tuple[2];
+ print("Processing $filePath");
+ return invalidTestFixer
+ .removeNonExistentTestsFromStatusFile(directory, filePath);
+ });
+ } else {
+ usage();
+ }
+}
+
+abstract class StatusFileProcessor {
+ Future<List<Section>> _readSections(String filePath) {
+ File file = new File(filePath);
+
+ if (file.existsSync()) {
+ var completer = new Completer();
+ List<Section> sections = new List<Section>();
+
+ ReadConfigurationInto(new Path(file.path), sections, () {
+ completer.complete(sections);
+ });
+ return completer.future;
+ }
+ return new Future.value([]);
+ }
+}
+
+class StatusFileNonExistentTestRemover extends StatusFileProcessor {
+ final MultiTestDetector multiTestDetector = new MultiTestDetector();
+ final TestFileLister testFileLister = new TestFileLister();
+
+ Future removeNonExistentTestsFromStatusFile(String directory,
+ String filePath) {
+ return _readSections(filePath).then((List<Section> sections) {
+ Set<int> invalidLines = _analyzeStatusFile(directory, filePath, sections);
+ if (invalidLines.length > 0) {
+ return _writeFixedStatusFile(filePath, invalidLines);
+ }
+ return new Future.value();
+ });
+ }
+
+ 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.
+ List<String> dartFiles,
+ String directory,
+ TestRule rule) {
+ List<RegExp> getRuleRegex(String name) {
+ return name.split("/")
+ .map((name) => new RegExp(name.replaceAll('*', '.*')))
+ .toList();
+ }
+ bool matchRegexp(List<RegExp> patterns, String str) {
+ var parts = str.split("/");
+ if (patterns.length > parts.length) {
+ return false;
+ }
+ // NOTE: patterns.length <= parts.length
+ for (var i = 0; i < patterns.length; i++) {
+ if (!patterns[i].hasMatch(parts[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ 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
+ return true;
+ }
+
+ var rulePattern = getRuleRegex(rule.name);
+ return dartFiles.any((String file) {
+ var relative = new Path(file).relativeTo(new Path(directory)).toString();
+ for (int splitIndex = 0; splitIndex < rulePattern.length; splitIndex++) {
+ // Construct a pattern for the file name.
+ var filePattern = new List();
+ for (var i = 0; i <= splitIndex; i++) {
+ filePattern.add(rulePattern[i]);
+ }
+
+ // Construct a pattern for the multitest name.
+ var multitestPattern = new List();
+ for (var i = splitIndex + 1; i < rulePattern.length; i++) {
+ multitestPattern.add(rulePattern[i]);
+ }
+
+ if (matchRegexp(filePattern, relative)) {
Bill Hesse 2014/02/13 17:05:08 There is a strange semantics here, that filePatter
+ // Could be a normal test
+ if (multitestPattern.length == 0) {
+ return true;
+ }
+
+ // Could be a real multitest.
+ if (multiTestDetector.getMultitestNamesFromFile(file).any(
+ (name) => matchRegexp(multitestPattern, name))) {
+ return true;
+ }
+
+ // Could be a multi html test.
+ if (multiTestDetector.getMultiHtmlTests(file).any(
+ (name) => matchRegexp(multitestPattern, name))) {
+ return true;
+ }
+ }
+ }
+ return false;
+ });
+ }
+
+ Set<int> _analyzeStatusFile(String directory,
+ String filePath,
+ List<Section> sections) {
+ var invalidLines = new Set<int>();
+ var dartFiles = testFileLister.listTestFiles(directory);
+ for (var section in sections) {
+ for (var rule in section.testRules) {
+ if (!_doesTestExist(filePath, dartFiles, directory, rule)) {
+ print("Invalid rule: ${rule.name} in file $filePath:${rule.lineNr}");
+ invalidLines.add(rule.lineNr);
+ }
+ }
+ }
+ return invalidLines;
+ }
+
+ _writeFixedStatusFile(String filePath, Set<int> invalidLines) {
Bill Hesse 2014/02/13 17:05:08 statusFilePath
kustermann 2014/02/14 11:52:07 Done.
+ var lines = new File(filePath).readAsLinesSync();
+ var outputLines = <String>[];
+ for (int i = 0; i < lines.length; i++) {
+ 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.
+ outputLines.add(lines[i]);
+ }
+ }
+ var outputFile = new File("$filePath.fixed");
+ 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
+ }
+}
+
+class StatusFileDeflaker extends StatusFileProcessor {
+ TestOutcomeFetcher _testOutcomeFetcher = new TestOutcomeFetcher();
+
+ Future deflakeStatusFile(String suiteName, String filePath) {
+ return _readSections(filePath).then((List<Section> sections) {
+ 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
+ return _generatedDeflakedLines(suiteName, sections)
+ .then((Map<int, String> fixedLines) {
+ if (fixedLines.length > 0) {
+ return _writeFixedStatusFile(filePath, fixedLines);
+ }
+ });
+ });
+ }
+
+ Future _generatedDeflakedLines(String suiteName,
+ List<Section> sections) {
+ 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.
+ var fixedLines = new Map<int, String>();
+ Future.forEach(sections, (Section section) {
+ return Future.forEach(section.testRules, (rule) {
+ return _maybeFixStatusfileLine(suiteName, section, rule, fixedLines);
+ });
+ }).then((_) => completer.complete(fixedLines));
+ return completer.future;
+ }
+
+ Future _maybeFixStatusfileLine(String suiteName,
+ Section section,
+ TestRule rule,
+ Map<int, String> fixedLines) {
+ print("Processing ${section.statusFile.location}: ${rule.lineNr}");
+ var notedOutcomes = rule.expression
+ .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.
+ .map((name) => Expectation.byName(name))
+ .where((Expectation expectation) => !expectation.isMetaExpectation)
+ .toSet();
+
+ if (notedOutcomes.isEmpty) return new Future.value();
+
+ return _testOutcomeFetcher.outcomesOf(suiteName, section, rule.name)
+ .then((Set<Expectation> actualOutcomes) {
+
+ var outcomesThatNeverHappend = new Set<Expectation>();
Bill Hesse 2014/02/13 17:05:08 Happened
kustermann 2014/02/14 11:52:07 Done.
+ for (Expectation notedOutcome in notedOutcomes) {
+ bool found = false;
+ for (Expectation actualOutcome in actualOutcomes) {
+ if (actualOutcome.canBeOutcomeOf(notedOutcome)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ outcomesThatNeverHappend.add(notedOutcome);
+ }
+ }
+
+ if (outcomesThatNeverHappend.length > 0 && actualOutcomes.length > 0) {
+ // Print the change to stdout.
+ print("${rule.name} (${section.statusFile.location}:${rule.lineNr}):");
+ print(" Actual outcomes: ${actualOutcomes.toList()}");
+ print(" Outcomes in status file: ${notedOutcomes.toList()}");
+ print(" Outcomes in status file that never happened : "
+ "${outcomesThatNeverHappend.toList()}\n");
+
+ // Build the fixed status file line.
+ fixedLines[rule.lineNr] =
+ '${rule.name}: ${actualOutcomes.join(', ')} '
+ '# before: ${notedOutcomes.join(', ')} / '
+ 'never happened: ${outcomesThatNeverHappend.join(', ')}';
+ }
+ });
+ }
+
+ _writeFixedStatusFile(String filePath, Map<int, String> fixedLines) {
+ var lines = new File(filePath).readAsLinesSync();
+ var outputLines = <String>[];
+ for (int i = 0; i < lines.length; i++) {
+ if (fixedLines.containsKey(i + 1)) {
+ outputLines.add(fixedLines[i + 1]);
+ } else {
+ outputLines.add(lines[i]);
+ }
+ }
+ var output = outputLines.join("\n");
+ var outputFile = new File("$filePath.deflaked");
+ outputFile.writeAsStringSync(output);
+ }
+}
+
+class MultiTestDetector {
+ final multiTestsCache = new Map<String,List<String>>();
+ final multiHtmlTestsCache = new Map<String,List<String>>();
+
+ List<String> getMultitestNamesFromFile(String file) {
+ if (multiTestsCache.containsKey(file)) return multiTestsCache[file];
+
+ var tests = new Map<String, String>();
+ var outcomes = new Map<String, Set<String>>();
+ if (multiTestRegExp.hasMatch(new File(file).readAsStringSync())) {
+ ExtractTestsFromMultitest(new Path(file), tests, outcomes);
+ }
+ multiTestsCache[file] = tests.keys.toList();
+ return multiTestsCache[file];
+ }
+
+ List<String> getMultiHtmlTests(String file) {
+ if (multiHtmlTestsCache.containsKey(file)) return multiHtmlTestsCache[file];
+
Bill Hesse 2014/02/13 17:05:08 return multiHtmlTestsCache.putIfAbsent(file, () {
kustermann 2014/02/14 11:52:07 Done.
+ try {
+ List<String> subtestNames = [];
+ var content = new File(file).readAsStringSync();
+
+ if (multiHtmlTestRegExp.hasMatch(content)) {
+ var matchesIter = multiHtmlTestGroupRegExp.allMatches(content).iterator;
+ while(matchesIter.moveNext()) {
+ String fullMatch = matchesIter.current.group(0);
+ subtestNames.add(fullMatch.substring(fullMatch.indexOf("'") + 1));
+ }
+ }
+ multiHtmlTestsCache[file] = subtestNames;
+ return subtestNames;
+ } catch (e) {
+ print("WARNING: couldn't determine html multitests in file ${file}");
+ }
+ return [];
+ }
+}
+
+class TestFileLister {
+ final Map<String, List<String>> _filesCache = {};
+
Bill Hesse 2014/02/13 17:05:08 putIfAbsent
kustermann 2014/02/14 11:52:07 Done.
+ List<String> listTestFiles(String directory) {
+ if (_filesCache.containsKey(directory)) {
+ return _filesCache[directory];
+ }
+
+ var dir = new Directory(directory);
+ // Cannot test for _test.dart because co19 tests don't have that ending.
+ var dartFiles = dir.listSync(recursive: true)
+ .where((fe) => fe is File)
+ .where((file) => file.path.endsWith(".dart") ||
+ file.path.endsWith("_test.html"))
+ .map((file) => file.path)
+ .toList();
+ _filesCache[directory] = dartFiles;
+ return dartFiles;
+ }
+}
+
+
+/*
+ * [TestOutcomeFetcher] will fetch test results from a server using a REST-like
+ * interface.
+ */
+class TestOutcomeFetcher {
+ 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
+ static int PORT = 4540;
+
+ HttpClient _client = new HttpClient();
+
+ Future<Set<Expectation>> outcomesOf(
+ String suiteName, Section section, String testName) {
+ var completer = new Completer();
+ var pathComponents = ['json', 'test-outcomes', 'outcomes',
+ Uri.encodeComponent("$suiteName/$testName")];
+ var path = pathComponents.join('/') + '/';
+ var url = new Uri(scheme: 'http', host: SERVER, port: PORT, path: path);
+
+ _client.getUrl(url)
+ .then((HttpClientRequest request) => request.close())
+ .then((HttpClientResponse response) {
+ response
+ .transform(UTF8.decoder)
+ .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.
+ var setOfActualOutcomes = new Set<Expectation>();
+
+ try {
+ for (var result in testResults) {
+ var config = result['configuration'];
+ var testResult = result['test_result'];
+ var outcome = testResult['outcome'];
+
+ 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
+ config['unminified'] = !config['minified'];
+ config['nocsp'] = !config['csp'];
+ config['browser'] =
+ TestUtils.isBrowserRuntime(config['runtime']);
+ config['analyzer'] =
+ TestUtils.isCommandLineAnalyzer(config['compiler']);
+ config['jscl'] =
+ TestUtils.isJsCommandLineRuntime(config['runtime']);
+
+ if (section.condition == null ||
+ section.condition.evaluate(config)) {
+ setOfActualOutcomes.add(Expectation.byName(outcome));
+ }
+ }
+ completer.complete(setOfActualOutcomes);
+ } catch (error) {
+ print("Warning: Error occured while processing testoutcomes"
+ ": $error");
+ completer.complete([]);
+ }
+ }, onError: (error) {
+ print("Warning: Error occured while fetching testoutcomes: $error");
+ completer.complete([]);
+ }, cancelOnError: true);
+ });
+ return completer.future;
+ }
+}
« 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