| // 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 'package:status_file/expectation.dart'; | 
 | import 'package:status_file/status_file.dart'; | 
 |  | 
 | import 'configuration.dart'; | 
 | import 'environment.dart'; | 
 |  | 
 | /// Tracks the [Expectation]s associated with a set of file paths. | 
 | /// | 
 | /// For any given file path, returns the expected test results for that file. | 
 | /// A set can be loaded from a collection of status files. A file path may | 
 | /// exist in multiple files (or even multiple sections within the file). When | 
 | /// that happens, all of the expectations of every entry are unioned together | 
 | /// and the test is considered to pass if the outcome is any of those | 
 | /// expectations. | 
 | class ExpectationSet { | 
 |   static final _passSet = {Expectation.pass}; | 
 |  | 
 |   /// A cache of path component glob strings (like "b*r") that we've previously | 
 |   /// converted to regexes. This ensures we collapse multiple globs from the | 
 |   /// same string to the same map key in [_PathNode.regExpChildren]. | 
 |   final Map<String, RegExp> _globCache = {}; | 
 |  | 
 |   /// The root of the expectation tree. | 
 |   final _PathNode _tree = _PathNode(); | 
 |  | 
 |   /// Reads the expectations defined by the status files at [statusFilePaths] | 
 |   /// when in [configuration]. | 
 |   ExpectationSet.read( | 
 |       List<String> statusFilePaths, TestConfiguration configuration) { | 
 |     try { | 
 |       var environment = ConfigurationEnvironment(configuration); | 
 |       for (var path in statusFilePaths) { | 
 |         var file = StatusFile.read(path); | 
 |         file.validate(environment); | 
 |         for (var section in file.sections) { | 
 |           if (section.isEnabled(environment)) { | 
 |             for (var entry in section.entries) { | 
 |               addEntry(entry); | 
 |             } | 
 |           } | 
 |         } | 
 |       } | 
 |     } on SyntaxError catch (error) { | 
 |       stderr.writeln(error.toString()); | 
 |       exit(1); | 
 |     } | 
 |   } | 
 |  | 
 |   /// Add [entry] to the set of expectations. | 
 |   void addEntry(StatusEntry entry) { | 
 |     var tree = _tree; | 
 |     for (var part in entry.path.split('/')) { | 
 |       if (part.contains("*")) { | 
 |         var regExp = _globCache.putIfAbsent(part, () { | 
 |           return RegExp("^${part.replaceAll("*", ".*")}" r"$"); | 
 |         }); | 
 |         tree = tree.regExpChildren.putIfAbsent(regExp, _PathNode.new); | 
 |       } else { | 
 |         tree = tree.stringChildren.putIfAbsent(part, _PathNode.new); | 
 |       } | 
 |     } | 
 |  | 
 |     tree.expectations.addAll(entry.expectations); | 
 |   } | 
 |  | 
 |   /// Get the expectations for the test at [path]. | 
 |   /// | 
 |   /// For every (key, expectation) pair, matches the key with the file name. | 
 |   /// Returns 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 path) { | 
 |     var result = <Expectation>{}; | 
 |     _tree.walk(path.split('/'), 0, result); | 
 |  | 
 |     // If no status files modified the expectation, default to the test passing. | 
 |     if (result.isEmpty) return _passSet; | 
 |  | 
 |     return result; | 
 |   } | 
 | } | 
 |  | 
 | /// A single file system path component in the tree of expectations. | 
 | /// | 
 | /// Given a list of status file entry paths (which may contain globs at various | 
 | /// parts), we parse it into a tree of nodes. Then, later, when looking up the | 
 | /// status for a single test, this lets us quickly consider only the status | 
 | /// file entries that relate to that test. | 
 | class _PathNode { | 
 |   /// The non-glob child directory and file paths within this directory. | 
 |   final Map<String, _PathNode> stringChildren = {}; | 
 |  | 
 |   /// The glob child directory and file paths within this directory. | 
 |   final Map<RegExp, _PathNode> regExpChildren = {}; | 
 |  | 
 |   /// The test expectations that any test within this directory should | 
 |   /// include. | 
 |   final Set<Expectation> expectations = {}; | 
 |  | 
 |   /// Walks the list of path [parts], starting at [index] adding any | 
 |   /// expectations to [result] from this node and any of its matching children. | 
 |   /// | 
 |   /// We need to include all matching children because multiple children may | 
 |   /// match a single test, as in: | 
 |   /// | 
 |   ///     foo/bar/baz: Timeout | 
 |   ///     foo/b*r/baz: Skip | 
 |   ///     foo/*ar/baz: SkipByDesign | 
 |   /// | 
 |   /// Assuming this node is for "foo" and we are walking ["bar", "baz"], all | 
 |   /// three of the above should match and the resulting expectation set should | 
 |   /// include all three. | 
 |   /// | 
 |   /// Also if a status file entry is a prefix of the test's path, that matches | 
 |   /// too: | 
 |   /// | 
 |   ///     foo/bar: Skip | 
 |   /// | 
 |   /// If the test path is "foo/baz/baz", the above entry will match it. | 
 |   void walk(List<String> parts, int index, Set<Expectation> result) { | 
 |     // We've reached this node itself, so add its expectations. | 
 |     result.addAll(expectations); | 
 |  | 
 |     // If this is a leaf node, stop traversing. | 
 |     if (index >= parts.length) return; | 
 |  | 
 |     var part = parts[index]; | 
 |  | 
 |     // Look for a non-glob child directory. | 
 |     stringChildren[part]?.walk(parts, index + 1, result); | 
 |  | 
 |     // Look for any matching glob directories. | 
 |     for (var child in regExpChildren.entries) { | 
 |       if (child.key.hasMatch(part)) { | 
 |         child.value.walk(parts, index + 1, result); | 
 |       } | 
 |     } | 
 |   } | 
 | } |