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

Unified Diff: tools/testing/dart/status_file_parser.dart

Issue 2888213002: Revert "Re-apply status file parser changes from 0b7728da1bef08c1c1e092005d9fd8c8bff5fa6c." (Closed)
Patch Set: Created 3 years, 7 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
« no previous file with comments | « tools/testing/dart/status_file.dart ('k') | tools/testing/dart/summary_report.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/testing/dart/status_file_parser.dart
diff --git a/tools/testing/dart/status_file_parser.dart b/tools/testing/dart/status_file_parser.dart
new file mode 100644
index 0000000000000000000000000000000000000000..49fdc9bf2420589b36a85d69b631d4bf38546e46
--- /dev/null
+++ b/tools/testing/dart/status_file_parser.dart
@@ -0,0 +1,368 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// 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 status_file_parser;
+
+import "dart:async";
+import "dart:convert" show LineSplitter, UTF8;
+import "dart:io";
+
+import "path.dart";
+import "status_expression.dart";
+
+typedef void Action();
+
+class Expectation {
+ // Possible outcomes of running a test.
+ static Expectation PASS = byName('Pass');
+ static Expectation CRASH = byName('Crash');
+ static Expectation TIMEOUT = byName('Timeout');
+ static Expectation FAIL = byName('Fail');
+
+ // Special 'FAIL' cases
+ static Expectation RUNTIME_ERROR = byName('RuntimeError');
+ static Expectation COMPILETIME_ERROR = byName('CompileTimeError');
+ static Expectation MISSING_RUNTIME_ERROR = byName('MissingRuntimeError');
+ static Expectation MISSING_COMPILETIME_ERROR =
+ byName('MissingCompileTimeError');
+ static Expectation STATIC_WARNING = byName('StaticWarning');
+ static Expectation MISSING_STATIC_WARNING = byName('MissingStaticWarning');
+ static Expectation PUB_GET_ERROR = byName('PubGetError');
+ static Expectation NON_UTF8_ERROR = byName('NonUtf8Output');
+
+ // Special 'CRASH' cases
+ static Expectation DARTK_CRASH = byName('DartkCrash');
+
+ // Special 'TIMEOUT' cases
+ static Expectation DARTK_TIMEOUT = byName('DartkTimeout');
+
+ // Special 'COMPILETIME_ERROR'
+ static Expectation DARTK_COMPILETIME_ERROR = byName('DartkCompileTimeError');
+
+ // "meta expectations"
+ static Expectation OK = byName('Ok');
+ static Expectation SLOW = byName('Slow');
+ static Expectation SKIP = byName('Skip');
+ static Expectation SKIP_SLOW = byName('SkipSlow');
+ static Expectation SKIP_BY_DESIGN = byName('SkipByDesign');
+
+ // Can be returned by the test runner to say the result should be ignored,
+ // and assumed to meet the expectations, due to an infrastructure failure.
+ // Do not place in status files.
+ static Expectation IGNORE = byName('Ignore');
+
+ static Expectation byName(String name) {
+ _initialize();
+ name = name.toLowerCase();
+ if (!_AllExpectations.containsKey(name)) {
+ throw new Exception("Expectation.byName(name='$name'): Invalid name.");
+ }
+ return _AllExpectations[name];
+ }
+
+ // Keep a map of all possible Expectation objects, initialized lazily.
+ static Map<String, Expectation> _AllExpectations;
+ static void _initialize() {
+ if (_AllExpectations == null) {
+ _AllExpectations = new Map<String, Expectation>();
+
+ Expectation build(String prettyName,
+ {Expectation group, bool isMetaExpectation: false}) {
+ var expectation = new Expectation._(prettyName,
+ group: group, isMetaExpectation: isMetaExpectation);
+ assert(!_AllExpectations.containsKey(expectation.name));
+ return _AllExpectations[expectation.name] = expectation;
+ }
+
+ var fail = build("Fail");
+ var crash = build("Crash");
+ var timeout = build("Timeout");
+ build("Pass");
+
+ var compileError = build("CompileTimeError", group: fail);
+ build("MissingCompileTimeError", group: fail);
+ build("MissingRuntimeError", group: fail);
+ build("RuntimeError", group: fail);
+ build("NonUtf8Output", group: fail);
+
+ // Dartk sub expectations
+ build("DartkCrash", group: crash);
+ build("DartkTimeout", group: timeout);
+ build("DartkCompileTimeError", group: compileError);
+
+ build("MissingStaticWarning", group: fail);
+ build("StaticWarning", group: fail);
+
+ build("PubGetError", group: fail);
+
+ var skip = build("Skip", isMetaExpectation: true);
+ build("SkipByDesign", isMetaExpectation: true);
+ build("SkipSlow", group: skip, isMetaExpectation: true);
+ build("Ok", isMetaExpectation: true);
+ build("Slow", isMetaExpectation: true);
+ build("Ignore");
+ }
+ }
+
+ final String prettyName;
+ final String name;
+ final Expectation group;
+ // Indicates whether this expectation cannot be a test outcome (i.e. it is a
+ // "meta marker").
+ final bool isMetaExpectation;
+
+ Expectation._(this.prettyName,
+ {Expectation this.group: null, bool this.isMetaExpectation: false})
+ : name = prettyName.toLowerCase();
+
+ bool canBeOutcomeOf(Expectation expectation) {
+ Expectation outcome = this;
+ if (outcome == IGNORE) return true;
+ while (outcome != null) {
+ if (outcome == expectation) {
+ return true;
+ }
+ outcome = outcome.group;
+ }
+ return false;
+ }
+
+ String toString() => prettyName;
+}
+
+final RegExp SplitComment = new RegExp("^([^#]*)(#.*)?\$");
+final RegExp HeaderPattern = new RegExp(r"^\[([^\]]+)\]");
+final RegExp RulePattern = new RegExp(r"\s*([^: ]*)\s*:(.*)");
+final RegExp IssueNumberPattern = new RegExp("[Ii]ssue ([0-9]+)");
+
+class StatusFile {
+ final Path location;
+
+ StatusFile(this.location);
+}
+
+// TODO(whesse): Implement configuration_info library that contains data
+// structures for test configuration, including Section.
+class Section {
+ final StatusFile statusFile;
+
+ final BooleanExpression condition;
+ final List<TestRule> testRules;
+ final int lineNumber;
+
+ Section.always(this.statusFile, this.lineNumber)
+ : condition = null,
+ testRules = new List<TestRule>();
+ Section(this.statusFile, this.condition, this.lineNumber)
+ : testRules = new List<TestRule>();
+
+ bool isEnabled(environment) =>
+ condition == null || condition.evaluate(environment);
+
+ String toString() {
+ return "Section: $condition";
+ }
+}
+
+Future<TestExpectations> ReadTestExpectations(
+ List<String> statusFilePaths, Map<String, String> environment) {
+ var testExpectations = new TestExpectations();
+ return Future.wait(statusFilePaths.map((String statusFile) {
+ return ReadTestExpectationsInto(testExpectations, statusFile, environment);
+ })).then((_) => testExpectations);
+}
+
+Future ReadTestExpectationsInto(TestExpectations expectations,
+ String statusFilePath, Map<String, String> environment) {
+ var completer = new Completer<Null>();
+ var sections = <Section>[];
+
+ void sectionsRead() {
+ for (Section section in sections) {
+ if (section.isEnabled(environment)) {
+ for (var rule in section.testRules) {
+ expectations.addRule(rule, environment);
+ }
+ }
+ }
+ completer.complete();
+ }
+
+ ReadConfigurationInto(new Path(statusFilePath), sections, sectionsRead);
+ return completer.future;
+}
+
+void ReadConfigurationInto(Path path, List<Section> sections, Action onDone) {
+ StatusFile statusFile = new StatusFile(path);
+ File file = new File(path.toNativePath());
+ if (!file.existsSync()) {
+ throw new Exception('Cannot find test status file $path');
+ }
+ int lineNumber = 0;
+ Stream<String> lines =
+ file.openRead().transform(UTF8.decoder).transform(new LineSplitter());
+
+ Section currentSection = new Section.always(statusFile, -1);
+ sections.add(currentSection);
+
+ lines.listen((String line) {
+ lineNumber++;
+ Match match = SplitComment.firstMatch(line);
+ line = (match == null) ? "" : match[1];
+ line = line.trim();
+ if (line.isEmpty) return;
+
+ // Extract the comment to get the issue number if needed.
+ String comment = (match == null || match[2] == null) ? "" : match[2];
+
+ match = HeaderPattern.firstMatch(line);
+ if (match != null) {
+ String condition_string = match[1].trim();
+ List<String> tokens = new Tokenizer(condition_string).tokenize();
+ ExpressionParser parser = new ExpressionParser(new Scanner(tokens));
+ currentSection =
+ new Section(statusFile, parser.parseBooleanExpression(), lineNumber);
+ sections.add(currentSection);
+ return;
+ }
+
+ match = RulePattern.firstMatch(line);
+ if (match != null) {
+ String name = match[1].trim();
+ // TODO(whesse): Handle test names ending in a wildcard (*).
+ String expression_string = match[2].trim();
+ List<String> tokens = new Tokenizer(expression_string).tokenize();
+ SetExpression expression =
+ new ExpressionParser(new Scanner(tokens)).parseSetExpression();
+
+ // Look for issue number in comment.
+ String issueString = null;
+ match = IssueNumberPattern.firstMatch(comment);
+ if (match != null) {
+ issueString = match[1];
+ if (issueString == null) issueString = match[2];
+ }
+ int issue = issueString != null ? int.parse(issueString) : null;
+ currentSection.testRules
+ .add(new TestRule(name, expression, issue, lineNumber));
+ return;
+ }
+
+ print("unmatched line: $line");
+ }, onDone: onDone);
+}
+
+class TestRule {
+ String name;
+ SetExpression expression;
+ int issue;
+ int lineNumber;
+
+ TestRule(this.name, this.expression, this.issue, this.lineNumber);
+
+ bool get hasIssue => issue != null;
+
+ String toString() => 'TestRule($name, $expression, $issue)';
+}
+
+class TestExpectations {
+ // Only create one copy of each Set<Expectation>.
+ // We just use .toString as a key, so we may make a few
+ // sets that only differ in their toString element order.
+ static Map<String, Set<Expectation>> _cachedSets = {};
+
+ Map<String, Set<Expectation>> _map;
+ bool _preprocessed = false;
+ Map<String, RegExp> _regExpCache;
+ Map<String, List<RegExp>> _keyToRegExps;
+
+ /**
+ * Create a TestExpectations object. See the [expectations] method
+ * for an explanation of matching.
+ */
+ TestExpectations() : _map = {};
+
+ /**
+ * Add a rule to the expectations.
+ */
+ void addRule(TestRule testRule, environment) {
+ // Once we have started using the expectations we cannot add more
+ // rules.
+ if (_preprocessed) {
+ throw "TestExpectations.addRule: cannot add more rules";
+ }
+ var names = testRule.expression.evaluate(environment);
+ var expectations = names.map((name) => Expectation.byName(name));
+ _map
+ .putIfAbsent(testRule.name, () => new Set<Expectation>())
+ .addAll(expectations);
+ }
+
+ /**
+ * Compute the expectations for a test based on the filename.
+ *
+ * For every (key, expectation) pair. Match the key with the file
+ * name. Return the union of the expectations for all the keys
+ * that match.
+ *
+ * Normal matching splits the key and the filename into path
+ * components and checks that the anchored regular expression
+ * "^$keyComponent\$" matches the corresponding filename component.
+ */
+ Set<Expectation> expectations(String filename) {
+ var result = new Set<Expectation>();
+ var splitFilename = filename.split('/');
+
+ // Create mapping from keys to list of RegExps once and for all.
+ _preprocessForMatching();
+
+ _map.forEach((key, Set<Expectation> expectations) {
+ List<RegExp> regExps = _keyToRegExps[key];
+ if (regExps.length > splitFilename.length) return;
+ for (var i = 0; i < regExps.length; i++) {
+ if (!regExps[i].hasMatch(splitFilename[i])) return;
+ }
+ // If all components of the status file key matches the filename
+ // add the expectations to the result.
+ result.addAll(expectations);
+ });
+
+ // If no expectations were found the expectation is that the test
+ // passes.
+ if (result.isEmpty) {
+ result.add(Expectation.PASS);
+ }
+ return _cachedSets.putIfAbsent(result.toString(), () => result);
+ }
+
+ // Preprocess the expectations for matching against
+ // filenames. Generate lists of regular expressions once and for all
+ // for each key.
+ void _preprocessForMatching() {
+ if (_preprocessed) return;
+
+ _keyToRegExps = {};
+ _regExpCache = {};
+
+ _map.forEach((key, expectations) {
+ if (_keyToRegExps[key] != null) return;
+ var splitKey = key.split('/');
+ var regExps = new List<RegExp>(splitKey.length);
+ for (var i = 0; i < splitKey.length; i++) {
+ var component = splitKey[i];
+ var regExp = _regExpCache[component];
+ if (regExp == null) {
+ var pattern = "^${splitKey[i]}\$".replaceAll('*', '.*');
+ regExp = new RegExp(pattern);
+ _regExpCache[component] = regExp;
+ }
+ regExps[i] = regExp;
+ }
+ _keyToRegExps[key] = regExps;
+ });
+
+ _regExpCache = null;
+ _preprocessed = true;
+ }
+}
« no previous file with comments | « tools/testing/dart/status_file.dart ('k') | tools/testing/dart/summary_report.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698