Chromium Code Reviews| 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) { |
|
Bill Hesse
2017/05/15 16:15:09
We do a lot of work in ExpectationSet to produce a
|
| + 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); |
| +} |