| Index: tools/testing/dart/status_file.dart
|
| diff --git a/tools/testing/dart/status_file.dart b/tools/testing/dart/status_file.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..cd2d65f2f9511fadf5a356abb930d42fae257000
|
| --- /dev/null
|
| +++ b/tools/testing/dart/status_file.dart
|
| @@ -0,0 +1,164 @@
|
| +// Copyright (c) 2017, 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.
|
| +
|
| +import 'dart:io';
|
| +
|
| +import 'expectation.dart';
|
| +import 'status_expression.dart';
|
| +
|
| +/// Splits out a trailing line comment
|
| +final _commentPattern = new RegExp("^([^#]*)(#.*)?\$");
|
| +
|
| +/// Matches the header that begins a new section, like:
|
| +///
|
| +/// [ $compiler == dart2js && $minified ]
|
| +final _sectionPattern = new RegExp(r"^\[([^\]]+)\]");
|
| +
|
| +/// Matches an entry that defines the status for a path in the current section,
|
| +/// like:
|
| +///
|
| +/// some/path/to/some_test: Pass || Fail
|
| +final _entryPattern = new RegExp(r"\s*([^: ]*)\s*:(.*)");
|
| +
|
| +/// Matches an issue number in a comment, like:
|
| +///
|
| +/// blah_test: Fail # Issue 1234
|
| +/// ^^^^
|
| +final _issuePattern = new RegExp("[Ii]ssue ([0-9]+)");
|
| +
|
| +/// A parsed status file, which describes how a collection of tests are
|
| +/// expected to behave under various configurations and conditions.
|
| +///
|
| +/// Each status file is made of a series of sections. Each section begins with
|
| +/// a header, followed by a series of entries. A header is enclosed in square
|
| +/// brackets and contains a Boolean expression. That expression is evaluated in
|
| +/// an environment. If it evaluates to true, then the entries after the header
|
| +/// take effect.
|
| +///
|
| +/// Each entry is a glob-like file path followed by a colon and then a
|
| +/// comma-separated list of [Expectation]s. The path may point to an individual
|
| +/// file, or a directory, in which case it applies to all files under that path.
|
| +///
|
| +/// Entries may also appear before any section header, in which case they
|
| +/// always apply.
|
| +class StatusFile {
|
| + final List<StatusSection> sections = [];
|
| +
|
| + /// Parses the status file at [path].
|
| + StatusFile.read(String path) {
|
| + var lines = new File(path).readAsLinesSync();
|
| +
|
| + // The current section whose rules are being parsed.
|
| + StatusSection section;
|
| +
|
| + var lineNumber = 0;
|
| + for (var line in lines) {
|
| + lineNumber++;
|
| +
|
| + // Strip off the comment and whitespace.
|
| + var match = _commentPattern.firstMatch(line);
|
| + var source = "";
|
| + var comment = "";
|
| + if (match != null) {
|
| + source = match[1].trim();
|
| + comment = match[2] ?? "";
|
| + }
|
| +
|
| + // Ignore empty (or comment-only) lines.
|
| + if (source.isEmpty) continue;
|
| +
|
| + // See if we are starting a new section.
|
| + match = _sectionPattern.firstMatch(source);
|
| + if (match != null) {
|
| + var condition = Expression.parse(match[1].trim());
|
| + section = new StatusSection(condition);
|
| + sections.add(section);
|
| + continue;
|
| + }
|
| +
|
| + // Otherwise, it should be a new entry under the current section.
|
| + match = _entryPattern.firstMatch(source);
|
| + if (match != null) {
|
| + var path = match[1].trim();
|
| + // TODO(whesse): Handle test names ending in a wildcard (*).
|
| + var expectations = _parseExpectations(match[2]);
|
| + var issue = _issueNumber(comment);
|
| +
|
| + // If we haven't found a section header yet, create an implicit section
|
| + // that matches everything.
|
| + if (section == null) {
|
| + section = new StatusSection(null);
|
| + sections.add(section);
|
| + }
|
| +
|
| + section.entries.add(new StatusEntry(path, expectations, issue));
|
| + continue;
|
| + }
|
| +
|
| + throw new FormatException(
|
| + "Could not parse line $lineNumber of status file '$path':\n$line");
|
| + }
|
| + }
|
| +
|
| + /// Parses a comma-separated list of expectation names from [text].
|
| + List<Expectation> _parseExpectations(String text) {
|
| + return text
|
| + .split(",")
|
| + .map((name) => Expectation.find(name.trim()))
|
| + .toList();
|
| + }
|
| +
|
| + /// Returns the issue number embedded in [comment] or `null` if there is none.
|
| + int _issueNumber(String comment) {
|
| + var match = _issuePattern.firstMatch(comment);
|
| + if (match == null) return null;
|
| +
|
| + return int.parse(match[1], onError: (_) => null);
|
| + }
|
| +
|
| + String toString() {
|
| + var buffer = new StringBuffer();
|
| + for (var section in sections) {
|
| + buffer.writeln("[${section._condition}]");
|
| +
|
| + for (var entry in section.entries) {
|
| + buffer.write("${entry.path}: ${entry.expectations.join(', ')}");
|
| + if (entry.issue != null) buffer.write(" # Issue ${entry.issue}");
|
| + buffer.writeln();
|
| + }
|
| +
|
| + buffer.writeln();
|
| + }
|
| +
|
| + return buffer.toString();
|
| + }
|
| +}
|
| +
|
| +/// One section in a status file.
|
| +///
|
| +/// Contains the condition from the header that begins the section, then all of
|
| +/// the entries within the section.
|
| +class StatusSection {
|
| + /// The expression that determines when this section is applied.
|
| + ///
|
| + /// May be `null` for paths that appear before any section header in the file.
|
| + /// In that case, the section always applies.
|
| + final Expression _condition;
|
| + final List<StatusEntry> entries = [];
|
| +
|
| + /// Returns true if this section should apply in the given [environment].
|
| + bool isEnabled(Map<String, dynamic> environment) =>
|
| + _condition == null || _condition.evaluate(environment);
|
| +
|
| + StatusSection(this._condition);
|
| +}
|
| +
|
| +/// Describes the test status of the file or files at a given path.
|
| +class StatusEntry {
|
| + final String path;
|
| + final List<Expectation> expectations;
|
| + final int issue;
|
| +
|
| + StatusEntry(this.path, this.expectations, this.issue);
|
| +}
|
|
|