From 6d60de76f4355de3d7963bafa71212b356e78070 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum <nweiz@google.com> Date: Thu, 18 Feb 2016 16:29:33 -0800 Subject: [PATCH] Make PlatformSelector use boolean_selector. R=kevmoo@google.com Review URL: https://codereview.chromium.org//1717483002 . --- README.md | 17 +- lib/src/backend/metadata.dart | 2 +- lib/src/backend/platform_selector.dart | 98 +++---- lib/src/backend/platform_selector/ast.dart | 137 --------- .../backend/platform_selector/evaluator.dart | 47 ---- lib/src/backend/platform_selector/parser.dart | 105 ------- .../backend/platform_selector/scanner.dart | 146 ---------- lib/src/backend/platform_selector/token.dart | 70 ----- .../backend/platform_selector/visitor.dart | 44 --- pubspec.yaml | 1 + test/backend/platform_selector/ast_test.dart | 82 ------ .../platform_selector/evaluate_test.dart | 142 ---------- .../platform_selector/parser_test.dart | 266 ------------------ .../platform_selector/scanner_test.dart | 266 ------------------ 14 files changed, 45 insertions(+), 1378 deletions(-) delete mode 100644 lib/src/backend/platform_selector/ast.dart delete mode 100644 lib/src/backend/platform_selector/evaluator.dart delete mode 100644 lib/src/backend/platform_selector/parser.dart delete mode 100644 lib/src/backend/platform_selector/scanner.dart delete mode 100644 lib/src/backend/platform_selector/token.dart delete mode 100644 lib/src/backend/platform_selector/visitor.dart delete mode 100644 test/backend/platform_selector/ast_test.dart delete mode 100644 test/backend/platform_selector/evaluate_test.dart delete mode 100644 test/backend/platform_selector/parser_test.dart delete mode 100644 test/backend/platform_selector/scanner_test.dart diff --git a/README.md b/README.md index 13381113..6035cb91 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ * [Writing Tests](#writing-tests) * [Running Tests](#running-tests) * [Restricting Tests to Certain Platforms](#restricting-tests-to-certain-platforms) - * [Platform Selector Syntax](#platform-selector-syntax) + * [Platform Selectors](#platform-selectors) * [Running Tests on Dartium](#running-tests-on-dartium) * [Asynchronous Tests](#asynchronous-tests) * [Running Tests With Custom HTML](#running-tests-with-custom-html) @@ -176,13 +176,16 @@ specifies exactly which platforms a test can run on. It can be as simple as the name of a platform, or a more complex Dart-like boolean expression involving these platform names. -### Platform Selector Syntax +### Platform Selectors -Platform selectors can contain identifiers, parentheses, and operators. When -loading a test, each identifier is set to `true` or `false` based on the current -platform, and the test is only loaded if the platform selector returns `true`. -The operators `||`, `&&`, `!`, and `? :` all work just like they do in Dart. The -valid identifiers are: +Platform selectors use the [boolean selector syntax][] defined in the +[`boolean_selector` package][boolean_selector], which is a subset of Dart's +expression syntax that only supports boolean operations. The following +identifiers are defined: + +[boolean selector syntax]: https://github.com/dart-lang/boolean_selector/blob/master/README.md + +[boolean_selector]: https://pub.dartlang.org/packages/boolean_selector * `vm`: Whether the test is running on the command-line Dart VM. diff --git a/lib/src/backend/metadata.dart b/lib/src/backend/metadata.dart index 6a5133c4..54eb64ed 100644 --- a/lib/src/backend/metadata.dart +++ b/lib/src/backend/metadata.dart @@ -252,7 +252,7 @@ class Metadata { /// is merged as well. Metadata merge(Metadata other) => new Metadata( - testOn: testOn.intersect(other.testOn), + testOn: testOn.intersection(other.testOn), timeout: timeout.merge(other.timeout), skip: skip || other.skip, skipReason: other.skipReason == null ? skipReason : other.skipReason, diff --git a/lib/src/backend/platform_selector.dart b/lib/src/backend/platform_selector.dart index 841fbdc7..47adc1f9 100644 --- a/lib/src/backend/platform_selector.dart +++ b/lib/src/backend/platform_selector.dart @@ -2,13 +2,10 @@ // 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 'package:boolean_selector/boolean_selector.dart'; import 'package:source_span/source_span.dart'; import 'operating_system.dart'; -import 'platform_selector/ast.dart'; -import 'platform_selector/evaluator.dart'; -import 'platform_selector/parser.dart'; -import 'platform_selector/visitor.dart'; import 'test_platform.dart'; /// The set of all valid variable names. @@ -20,82 +17,53 @@ final _validVariables = /// An expression for selecting certain platforms, including operating systems /// and browsers. /// -/// The syntax is mostly Dart's expression syntax restricted to boolean -/// operations. See [the README][] for full details. +/// This uses the [boolean selector][] syntax. /// -/// [the README]: https://github.com/dart-lang/test/#platform-selector-syntax -abstract class PlatformSelector { +/// [boolean selector]: https://pub.dartlang.org/packages/boolean_selector +class PlatformSelector { /// A selector that declares that a test can be run on all platforms. - /// - /// This isn't representable in the platform selector syntax but it is the - /// default selector. - static const all = const _AllPlatforms(); + static const all = const PlatformSelector._(BooleanSelector.all); + + /// The boolean selector used to implement this selector. + final BooleanSelector _inner; /// Parses [selector]. /// /// This will throw a [SourceSpanFormatException] if the selector is /// malformed or if it uses an undefined variable. - factory PlatformSelector.parse(String selector) => - new _PlatformSelector.parse(selector); + PlatformSelector.parse(String selector) + : _inner = new BooleanSelector.parse(selector) { + _inner.validate(_validVariables.contains); + } + + const PlatformSelector._(this._inner); /// Returns whether the selector matches the given [platform] and [os]. /// /// [os] defaults to [OperatingSystem.none]. - bool evaluate(TestPlatform platform, {OperatingSystem os}); + bool evaluate(TestPlatform platform, {OperatingSystem os}) { + os ??= OperatingSystem.none; + + return _inner.evaluate((variable) { + if (variable == platform.identifier) return true; + if (variable == os.name) return true; + switch (variable) { + case "dart-vm": return platform.isDartVM; + case "browser": return platform.isBrowser; + case "js": return platform.isJS; + case "blink": return platform.isBlink; + case "posix": return os.isPosix; + default: return false; + } + }); + } /// Returns a new [PlatformSelector] that matches only platforms matched by /// both [this] and [other]. - PlatformSelector intersect(PlatformSelector other); -} - -/// The concrete implementation of a [PlatformSelector] parsed from a string. -/// -/// This is separate from [PlatformSelector] so that [_AllPlatforms] can -/// implement [PlatformSelector] without having to implement private members. -class _PlatformSelector implements PlatformSelector{ - /// The parsed AST. - final Node _selector; - - _PlatformSelector.parse(String selector) - : _selector = new Parser(selector).parse() { - _selector.accept(const _VariableValidator()); - } - - _PlatformSelector(this._selector); - - bool evaluate(TestPlatform platform, {OperatingSystem os}) => - _selector.accept(new Evaluator(platform, os: os)); - - PlatformSelector intersect(PlatformSelector other) { + PlatformSelector intersection(PlatformSelector other) { if (other == PlatformSelector.all) return this; - return new _PlatformSelector(new AndNode( - _selector, (other as _PlatformSelector)._selector)); + return new PlatformSelector._(_inner.intersection(other._inner)); } - String toString() => _selector.toString(); -} - -/// A selector that matches all platforms. -class _AllPlatforms implements PlatformSelector { - const _AllPlatforms(); - - bool evaluate(TestPlatform platform, {OperatingSystem os}) => true; - - PlatformSelector intersect(PlatformSelector other) => other; - - String toString() => "*"; -} - -/// An AST visitor that ensures that all variables are valid. -/// -/// This isn't done when evaluating to ensure that errors are eagerly detected, -/// and it isn't done when parsing to avoid coupling the syntax too tightly to -/// the semantics. -class _VariableValidator extends RecursiveVisitor { - const _VariableValidator(); - - void visitVariable(VariableNode node) { - if (_validVariables.contains(node.name)) return; - throw new SourceSpanFormatException("Undefined variable.", node.span); - } + String toString() => _inner.toString(); } diff --git a/lib/src/backend/platform_selector/ast.dart b/lib/src/backend/platform_selector/ast.dart deleted file mode 100644 index 981514f3..00000000 --- a/lib/src/backend/platform_selector/ast.dart +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) 2015, 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 'package:source_span/source_span.dart'; - -import 'visitor.dart'; - -/// The superclass of nodes in the platform selector abstract syntax tree. -abstract class Node { - /// The span indicating where this node came from. - /// - /// This is a [FileSpan] because the nodes are parsed from a single continuous - /// string, but the string itself isn't actually a file. It might come from a - /// statically-parsed annotation or from a parameter. - /// - /// This may be `null` for nodes without source information. - FileSpan get span; - - /// Calls the appropriate [Visitor] method on [this] and returns the result. - accept(Visitor visitor); -} - -/// A single variable. -class VariableNode implements Node { - final FileSpan span; - - /// The variable name. - final String name; - - VariableNode(this.name, [this.span]); - - accept(Visitor visitor) => visitor.visitVariable(this); - - String toString() => name; -} - -/// A negation expression. -class NotNode implements Node { - final FileSpan span; - - /// The expression being negated. - final Node child; - - NotNode(this.child, [this.span]); - - accept(Visitor visitor) => visitor.visitNot(this); - - String toString() => child is VariableNode || child is NotNode - ? "!$child" - : "!($child)"; -} - -/// An or expression. -class OrNode implements Node { - FileSpan get span => _expandSafe(left.span, right.span); - - /// The left-hand branch of the expression. - final Node left; - - /// The right-hand branch of the expression. - final Node right; - - OrNode(this.left, this.right); - - accept(Visitor visitor) => visitor.visitOr(this); - - String toString() { - var string1 = left is AndNode || left is ConditionalNode - ? "($left)" - : left; - var string2 = right is AndNode || right is ConditionalNode - ? "($right)" - : right; - - return "$string1 || $string2"; - } -} - -/// An and expression. -class AndNode implements Node { - FileSpan get span => _expandSafe(left.span, right.span); - - /// The left-hand branch of the expression. - final Node left; - - /// The right-hand branch of the expression. - final Node right; - - AndNode(this.left, this.right); - - accept(Visitor visitor) => visitor.visitAnd(this); - - String toString() { - var string1 = left is OrNode || left is ConditionalNode - ? "($left)" - : left; - var string2 = right is OrNode || right is ConditionalNode - ? "($right)" - : right; - - return "$string1 && $string2"; - } -} - -/// A ternary conditional expression. -class ConditionalNode implements Node { - FileSpan get span => _expandSafe(condition.span, whenFalse.span); - - /// The condition expression to check. - final Node condition; - - /// The branch to run if the condition is true. - final Node whenTrue; - - /// The branch to run if the condition is false. - final Node whenFalse; - - ConditionalNode(this.condition, this.whenTrue, this.whenFalse); - - accept(Visitor visitor) => visitor.visitConditional(this); - - String toString() { - var conditionString = - condition is ConditionalNode ? "($condition)" : condition; - var trueString = whenTrue is ConditionalNode ? "($whenTrue)" : whenTrue; - return "$conditionString ? $trueString : $whenFalse"; - } -} - -/// Like [FileSpan.expand], except if [start] and [end] are `null` or from -/// different files it returns `null` rather than throwing an error. -FileSpan _expandSafe(FileSpan start, FileSpan end) { - if (start == null || end == null) return null; - if (start.file != end.file) return null; - return start.expand(end); -} diff --git a/lib/src/backend/platform_selector/evaluator.dart b/lib/src/backend/platform_selector/evaluator.dart deleted file mode 100644 index 9d04d6dc..00000000 --- a/lib/src/backend/platform_selector/evaluator.dart +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2015, 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 '../operating_system.dart'; -import '../test_platform.dart'; -import 'ast.dart'; -import 'visitor.dart'; - -/// A visitor for evaluating platform selectors against a specific -/// [TestPlatform] and [OperatingSystem]. -class Evaluator implements Visitor<bool> { - /// The platform to test against. - final TestPlatform _platform; - - /// The operating system to test against. - final OperatingSystem _os; - - Evaluator(this._platform, {OperatingSystem os}) - : _os = os == null ? OperatingSystem.none : os; - - bool visitVariable(VariableNode node) { - if (node.name == _platform.identifier) return true; - if (node.name == _os.name) return true; - - switch (node.name) { - case "dart-vm": return _platform.isDartVM; - case "browser": return _platform.isBrowser; - case "js": return _platform.isJS; - case "blink": return _platform.isBlink; - case "posix": return _os.isPosix; - default: return false; - } - } - - bool visitNot(NotNode node) => !node.child.accept(this); - - bool visitOr(OrNode node) => - node.left.accept(this) || node.right.accept(this); - - bool visitAnd(AndNode node) => - node.left.accept(this) && node.right.accept(this); - - bool visitConditional(ConditionalNode node) => node.condition.accept(this) - ? node.whenTrue.accept(this) - : node.whenFalse.accept(this); -} diff --git a/lib/src/backend/platform_selector/parser.dart b/lib/src/backend/platform_selector/parser.dart deleted file mode 100644 index 4a08025a..00000000 --- a/lib/src/backend/platform_selector/parser.dart +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) 2015, 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 'package:source_span/source_span.dart'; - -import 'ast.dart'; -import 'scanner.dart'; -import 'token.dart'; - -/// A class for parsing a platform selector. -/// -/// Platform selectors use a stripped-down version of the Dart expression -/// syntax that only contains variables, parentheses, and boolean operators. -/// Variables may also contain dashes, contrary to Dart's syntax; this allows -/// consistency with command-line arguments. -class Parser { - /// The scanner that tokenizes the selector. - final Scanner _scanner; - - Parser(String selector) - : _scanner = new Scanner(selector); - - /// Parses the selector. - /// - /// This must only be called once per parser. - Node parse() { - var selector = _conditional(); - - if (_scanner.peek().type != TokenType.endOfFile) { - throw new SourceSpanFormatException( - "Expected end of input.", _scanner.peek().span); - } - - return selector; - } - - /// Parses a conditional: - /// - /// conditionalExpression: - /// logicalOrExpression ("?" conditionalExpression ":" - /// conditionalExpression)? - Node _conditional() { - var condition = _or(); - if (!_scanner.scan(TokenType.questionMark)) return condition; - - var whenTrue = _conditional(); - if (!_scanner.scan(TokenType.colon)) { - throw new SourceSpanFormatException( - 'Expected ":".', _scanner.peek().span); - } - - var whenFalse = _conditional(); - return new ConditionalNode(condition, whenTrue, whenFalse); - } - - /// Parses a logical or: - /// - /// logicalOrExpression: - /// logicalAndExpression ("||" logicalOrExpression)? - Node _or() { - var left = _and(); - if (!_scanner.scan(TokenType.or)) return left; - return new OrNode(left, _or()); - } - - /// Parses a logical and: - /// - /// logicalAndExpression: - /// simpleExpression ("&&" logicalAndExpression)? - Node _and() { - var left = _simpleExpression(); - if (!_scanner.scan(TokenType.and)) return left; - return new AndNode(left, _and()); - } - - /// Parses a simple expression: - /// - /// simpleExpression: - /// "!" simpleExpression | - /// "(" conditionalExpression ")" | - /// IDENTIFIER - Node _simpleExpression() { - var token = _scanner.next(); - switch (token.type) { - case TokenType.not: - var child = _simpleExpression(); - return new NotNode(child, token.span.expand(child.span)); - - case TokenType.leftParen: - var child = _conditional(); - if (!_scanner.scan(TokenType.rightParen)) { - throw new SourceSpanFormatException( - 'Expected ")".', _scanner.peek().span); - } - return child; - - case TokenType.identifier: - return new VariableNode(token.name, token.span); - - default: - throw new SourceSpanFormatException("Expected expression.", token.span); - } - } -} diff --git a/lib/src/backend/platform_selector/scanner.dart b/lib/src/backend/platform_selector/scanner.dart deleted file mode 100644 index 44dddca4..00000000 --- a/lib/src/backend/platform_selector/scanner.dart +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (c) 2015, 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 'package:string_scanner/string_scanner.dart'; - -import '../../utils.dart'; -import 'token.dart'; - -/// A regular expression matching both whitespace and single-line comments. -/// -/// This will only match if consumes at least one character. -final _whitespaceAndSingleLineComments = - new RegExp(r"([ \t\n]+|//[^\n]*(\n|$))+"); - -/// A regular expression matching the body of a multi-line comment, after `/*` -/// but before `*/` or a nested `/*`. -/// -/// This will only match if it consumes at least one character. -final _multiLineCommentBody = new RegExp(r"([^/*]|/[^*]|\*[^/])+"); - -/// A scanner that converts a platform selector string into a stream of -/// tokens. -class Scanner { - /// The underlying string scanner. - final SpanScanner _scanner; - - /// The next token to emit. - Token _next; - - /// Whether the scanner has emitted a [TokenType.endOfFile] token. - bool _endOfFileEmitted = false; - - Scanner(String selector) - : _scanner = new SpanScanner(selector); - - /// Returns the next token that will be returned by [next]. - /// - /// Throws a [StateError] if a [TokenType.endOfFile] token has already been - /// consumed. - Token peek() { - if (_next == null) _next = _getNext(); - return _next; - } - - /// Consumes and returns the next token in the stream. - /// - /// Throws a [StateError] if a [TokenType.endOfFile] token has already been - /// consumed. - Token next() { - var token = _next == null ? _getNext() : _next; - _endOfFileEmitted = token.type == TokenType.endOfFile; - _next = null; - return token; - } - - /// If the next token matches [type], consumes it and returns `true`; - /// otherwise, returns `false`. - /// - /// Throws a [StateError] if a [TokenType.endOfFile] token has already been - /// consumed. - bool scan(TokenType type) { - if (peek().type != type) return false; - next(); - return true; - } - - /// Scan and return the next token in the stream. - Token _getNext() { - if (_endOfFileEmitted) throw new StateError("No more tokens."); - - _consumeWhitespace(); - if (_scanner.isDone) { - return new Token( - TokenType.endOfFile, _scanner.spanFrom(_scanner.state)); - } - - switch (_scanner.peekChar()) { - case 0x28 /* ( */: return _scanOperator(TokenType.leftParen); - case 0x29 /* ) */: return _scanOperator(TokenType.rightParen); - case 0x3F /* ? */: return _scanOperator(TokenType.questionMark); - case 0x3A /* : */: return _scanOperator(TokenType.colon); - case 0x21 /* ! */: return _scanOperator(TokenType.not); - case 0x7C /* | */: return _scanOr(); - case 0x26 /* & */: return _scanAnd(); - default: return _scanIdentifier(); - } - } - - /// Scans a single-character operator and returns a token of type [type]. - /// - /// This assumes that the caller has already verified that the next character - /// is correct for the given operator. - Token _scanOperator(TokenType type) { - var start = _scanner.state; - _scanner.readChar(); - return new Token(type, _scanner.spanFrom(start)); - } - - /// Scans a `||` operator and returns the appropriate token. - /// - /// This validates that the next two characters are `||`. - Token _scanOr() { - var start = _scanner.state; - _scanner.expect("||"); - return new Token(TokenType.or, _scanner.spanFrom(start)); - } - - /// Scans a `&&` operator and returns the appropriate token. - /// - /// This validates that the next two characters are `&&`. - Token _scanAnd() { - var start = _scanner.state; - _scanner.expect("&&"); - return new Token(TokenType.and, _scanner.spanFrom(start)); - } - - /// Scans and returns an identifier token. - Token _scanIdentifier() { - _scanner.expect(hyphenatedIdentifier, name: "expression"); - return new IdentifierToken(_scanner.lastMatch[0], _scanner.lastSpan); - } - - /// Consumes all whitespace and comments immediately following the cursor's - /// current position. - void _consumeWhitespace() { - while (_scanner.scan(_whitespaceAndSingleLineComments) || - _multiLineComment()) { - // Do nothing. - } - } - - /// Consumes a single multi-line comment. - /// - /// Returns whether or not a comment was consumed. - bool _multiLineComment() { - if (!_scanner.scan("/*")) return false; - - while (_scanner.scan(_multiLineCommentBody) || _multiLineComment()) { - // Do nothing. - } - _scanner.expect("*/"); - - return true; - } -} diff --git a/lib/src/backend/platform_selector/token.dart b/lib/src/backend/platform_selector/token.dart deleted file mode 100644 index 759999f5..00000000 --- a/lib/src/backend/platform_selector/token.dart +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2015, 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 'package:source_span/source_span.dart'; - -/// A token in a platform selector. -class Token { - /// The type of the token. - final TokenType type; - - /// The span indicating where this token came from. - /// - /// This is a [FileSpan] because the tokens are parsed from a single - /// continuous string, but the string itself isn't actually a file. It might - /// come from a statically-parsed annotation or from a parameter. - final FileSpan span; - - Token(this.type, this.span); -} - -/// A token representing an identifier. -class IdentifierToken implements Token { - final type = TokenType.identifier; - final FileSpan span; - - /// The name of the identifier. - final String name; - - IdentifierToken(this.name, this.span); - - String toString() => 'identifier "$name"'; -} - -/// An enumeration of types of tokens. -class TokenType { - /// A `(` character. - static const leftParen = const TokenType._("left paren"); - - /// A `)` character. - static const rightParen = const TokenType._("right paren"); - - /// A `||` sequence. - static const or = const TokenType._("or"); - - /// A `&&` sequence. - static const and = const TokenType._("and"); - - /// A `!` character. - static const not = const TokenType._("not"); - - /// A `?` character. - static const questionMark = const TokenType._("question mark"); - - /// A `:` character. - static const colon = const TokenType._("colon"); - - /// A named identifier. - static const identifier = const TokenType._("identifier"); - - /// The end of the selector. - static const endOfFile = const TokenType._("end of file"); - - /// The name of the token type. - final String name; - - const TokenType._(this.name); - - String toString() => name; -} diff --git a/lib/src/backend/platform_selector/visitor.dart b/lib/src/backend/platform_selector/visitor.dart deleted file mode 100644 index 3687f1d8..00000000 --- a/lib/src/backend/platform_selector/visitor.dart +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2015, 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 'ast.dart'; - -/// The interface for visitors of the platform selector AST. -abstract class Visitor<T> { - T visitVariable(VariableNode node); - T visitNot(NotNode node); - T visitOr(OrNode node); - T visitAnd(AndNode node); - T visitConditional(ConditionalNode node); -} - -/// An abstract superclass for side-effect-based visitors. -/// -/// The default implementations of this visitor's methods just traverse the AST -/// and do nothing with it. -abstract class RecursiveVisitor implements Visitor { - const RecursiveVisitor(); - - void visitVariable(VariableNode node) {} - - void visitNot(NotNode node) { - node.child.accept(this); - } - - void visitOr(OrNode node) { - node.left.accept(this); - node.right.accept(this); - } - - void visitAnd(AndNode node) { - node.left.accept(this); - node.right.accept(this); - } - - void visitConditional(ConditionalNode node) { - node.condition.accept(this); - node.whenTrue.accept(this); - node.whenFalse.accept(this); - } -} diff --git a/pubspec.yaml b/pubspec.yaml index c2fed2ec..a4e6781e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,6 +10,7 @@ dependencies: args: '^0.13.1' async: '^1.8.0' barback: '>=0.14.0 <0.16.0' + boolean_selector: '^1.0.0' collection: '^1.1.0' crypto: '^0.9.0' glob: '^1.0.0' diff --git a/test/backend/platform_selector/ast_test.dart b/test/backend/platform_selector/ast_test.dart deleted file mode 100644 index 876f7dc9..00000000 --- a/test/backend/platform_selector/ast_test.dart +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) 2015, 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 'package:test/test.dart'; -import 'package:test/src/backend/platform_selector/parser.dart'; - -void main() { - group("toString() for", () { - test("a variable is its name", () { - _expectToString("foo"); - _expectToString("a-b"); - }); - - group("not", () { - test("doesn't parenthesize a variable", () => _expectToString("!a")); - test("doesn't parenthesize a nested not", () => _expectToString("!!a")); - test("parenthesizes an or", () => _expectToString("!(a || b)")); - test("parenthesizes an and", () => _expectToString("!(a && b)")); - test("parenthesizes a condition", () => _expectToString("!(a ? b : c)")); - }); - - group("or", () { - test("doesn't parenthesize variables", () => _expectToString("a || b")); - test("doesn't parenthesize nots", () => _expectToString("!a || !b")); - - test("doesn't parenthesize ors", () { - _expectToString("a || b || c || d"); - _expectToString("((a || b) || c) || d", "a || b || c || d"); - }); - - test("parenthesizes ands", () => - _expectToString("a && b || c && d", "(a && b) || (c && d)")); - - test("parenthesizes conditions", () => - _expectToString("(a ? b : c) || (e ? f : g)")); - }); - - group("and", () { - test("doesn't parenthesize variables", () => _expectToString("a && b")); - test("doesn't parenthesize nots", () => _expectToString("!a && !b")); - - test("parenthesizes ors", () => - _expectToString("(a || b) && (c || d)", "(a || b) && (c || d)")); - - test("doesn't parenthesize ands", () { - _expectToString("a && b && c && d"); - _expectToString("((a && b) && c) && d", "a && b && c && d"); - }); - - test("parenthesizes conditions", () => - _expectToString("(a ? b : c) && (e ? f : g)")); - }); - - group("conditional", () { - test("doesn't parenthesize variables", () => - _expectToString("a ? b : c")); - - test("doesn't parenthesize nots", () => _expectToString("!a ? !b : !c")); - - test("doesn't parenthesize ors", () => - _expectToString("a || b ? c || d : e || f")); - - test("doesn't parenthesize ands", () => - _expectToString("a && b ? c && d : e && f")); - - test("parenthesizes non-trailing conditions", () { - _expectToString("(a ? b : c) ? (e ? f : g) : h ? i : j"); - _expectToString("(a ? b : c) ? (e ? f : g) : (h ? i : j)", - "(a ? b : c) ? (e ? f : g) : h ? i : j"); - }); - }); - }); -} - -void _expectToString(String selector, [String result]) { - if (result == null) result = selector; - expect(_toString(selector), equals(result), - reason: 'Expected toString of "$selector" to be "$result".'); -} - -String _toString(String selector) => new Parser(selector).parse().toString(); diff --git a/test/backend/platform_selector/evaluate_test.dart b/test/backend/platform_selector/evaluate_test.dart deleted file mode 100644 index 4f81d1b9..00000000 --- a/test/backend/platform_selector/evaluate_test.dart +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) 2015, 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. - -@TestOn("vm") - -import 'dart:io'; - -import 'package:test/test.dart'; -import 'package:test/src/backend/operating_system.dart'; -import 'package:test/src/backend/platform_selector.dart'; -import 'package:test/src/backend/test_platform.dart'; - -void main() { - test("new PlatformSelector.parse() disallows invalid variables", () { - expect(() => new PlatformSelector.parse("undefined"), - throwsFormatException); - }); - - group("operator:", () { - test("conditional", () { - _expectEval("vm ? vm : browser", true); - _expectEval("vm ? browser : vm", false); - _expectEval("browser ? vm : browser", false); - _expectEval("browser ? browser : vm", true); - }); - - test("or", () { - _expectEval("vm || vm", true); - _expectEval("vm || browser", true); - _expectEval("browser || vm", true); - _expectEval("browser || browser", false); - }); - - test("and", () { - _expectEval("vm && vm", true); - _expectEval("vm && browser", false); - _expectEval("browser && vm", false); - _expectEval("browser && browser", false); - }); - - test("not", () { - _expectEval("!vm", false); - _expectEval("!browser", true); - }); - }); - - group("baseline variable:", () { - test("vm", () { - _expectEval("vm", true, platform: TestPlatform.vm); - _expectEval("vm", false, platform: TestPlatform.chrome); - }); - - test("chrome", () { - _expectEval("chrome", true, platform: TestPlatform.chrome); - _expectEval("chrome", false, platform: TestPlatform.vm); - }); - - test("windows", () { - _expectEval("windows", true, os: OperatingSystem.windows); - _expectEval("windows", false, os: OperatingSystem.linux); - _expectEval("windows", false, os: OperatingSystem.none); - }); - - test("mac-os", () { - _expectEval("mac-os", true, os: OperatingSystem.macOS); - _expectEval("mac-os", false, os: OperatingSystem.linux); - _expectEval("mac-os", false, os: OperatingSystem.none); - }); - - test("linux", () { - _expectEval("linux", true, os: OperatingSystem.linux); - _expectEval("linux", false, os: OperatingSystem.android); - _expectEval("linux", false, os: OperatingSystem.none); - }); - - test("android", () { - _expectEval("android", true, os: OperatingSystem.android); - _expectEval("android", false, os: OperatingSystem.linux); - _expectEval("android", false, os: OperatingSystem.none); - }); - }); - - group("derived variable:", () { - test("dart-vm", () { - _expectEval("dart-vm", true, platform: TestPlatform.vm); - _expectEval("dart-vm", false, platform: TestPlatform.chrome); - }); - - test("browser", () { - _expectEval("browser", true, platform: TestPlatform.chrome); - _expectEval("browser", false, platform: TestPlatform.vm); - }); - - test("js", () { - _expectEval("js", true, platform: TestPlatform.chrome); - _expectEval("js", false, platform: TestPlatform.vm); - }); - - test("blink", () { - _expectEval("blink", true, platform: TestPlatform.chrome); - _expectEval("blink", false, platform: TestPlatform.vm); - }); - - test("posix", () { - _expectEval("posix", false, os: OperatingSystem.windows); - _expectEval("posix", true, os: OperatingSystem.macOS); - _expectEval("posix", true, os: OperatingSystem.linux); - _expectEval("posix", true, os: OperatingSystem.android); - _expectEval("posix", false, os: OperatingSystem.none); - }); - }); -} - -/// Asserts that [expression] evaluates to [result] on [platform] and [os]. -/// -/// [platform] defaults to [TestPlatform.vm]; [os] defaults to the current -/// operating system. -void _expectEval(String expression, bool result, {TestPlatform platform, - OperatingSystem os}) { - - var reason = 'Expected "$expression" to evaluate to $result'; - if (platform != null && os != null) { - reason += ' on $platform and $os.'; - } else if (platform != null || os != null) { - reason += ' on ${platform == null ? os : platform}'; - } - - expect(_eval(expression, platform: platform, os: os), equals(result), - reason: '$reason.'); -} - -/// Returns the result of evaluating [expression] on [platform] and [os]. -/// -/// [platform] defaults to [TestPlatform.vm]; [os] defaults to the current -/// operating system. -bool _eval(String expression, {TestPlatform platform, OperatingSystem os}) { - if (platform == null) platform = TestPlatform.vm; - if (os == null) os = OperatingSystem.findByIoName(Platform.operatingSystem); - var selector = new PlatformSelector.parse(expression); - return selector.evaluate(platform, os: os); -} diff --git a/test/backend/platform_selector/parser_test.dart b/test/backend/platform_selector/parser_test.dart deleted file mode 100644 index 04f356c6..00000000 --- a/test/backend/platform_selector/parser_test.dart +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright (c) 2015, 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 'package:test/test.dart'; -import 'package:test/src/backend/platform_selector/ast.dart'; -import 'package:test/src/backend/platform_selector/parser.dart'; - -/// A matcher that asserts that a value is a [ConditionalNode]. -Matcher _isConditionalNode = new isInstanceOf<ConditionalNode>(); - -/// A matcher that asserts that a value is an [OrNode]. -Matcher _isOrNode = new isInstanceOf<OrNode>(); - -/// A matcher that asserts that a value is an [AndNode]. -Matcher _isAndNode = new isInstanceOf<AndNode>(); - -/// A matcher that asserts that a value is a [NotNode]. -Matcher _isNotNode = new isInstanceOf<NotNode>(); - -void main() { - group("parses a conditional expression", () { - test("with identifiers", () { - var node = _parse(" a ? b : c "); - expect(node.toString(), equals("a ? b : c")); - - expect(node.span.text, equals("a ? b : c")); - expect(node.span.start.offset, equals(2)); - expect(node.span.end.offset, equals(11)); - }); - - test("with nested ors", () { - // Should parse as "(a || b) ? (c || d) : (e || f)". - // Should not parse as "a || (b ? (c || d) : (e || f))". - // Should not parse as "((a || b) ? (c || d) : e) || f". - // Should not parse as "a || (b ? (c || d) : e) || f". - _expectToString("a || b ? c || d : e || f", - "a || b ? c || d : e || f"); - }); - - test("with a conditional expression as branch 1", () { - // Should parse as "a ? (b ? c : d) : e". - var node = _parse("a ? b ? c : d : e"); - expect(node, _isConditionalNode); - expect(node.condition, _isVar("a")); - expect(node.whenFalse, _isVar("e")); - - expect(node.whenTrue, _isConditionalNode); - expect(node.whenTrue.condition, _isVar("b")); - expect(node.whenTrue.whenTrue, _isVar("c")); - expect(node.whenTrue.whenFalse, _isVar("d")); - }); - - test("with a conditional expression as branch 2", () { - // Should parse as "a ? b : (c ? d : e)". - // Should not parse as "(a ? b : c) ? d : e". - var node = _parse("a ? b : c ? d : e"); - expect(node, _isConditionalNode); - expect(node.condition, _isVar("a")); - expect(node.whenTrue, _isVar("b")); - - expect(node.whenFalse, _isConditionalNode); - expect(node.whenFalse.condition, _isVar("c")); - expect(node.whenFalse.whenTrue, _isVar("d")); - expect(node.whenFalse.whenFalse, _isVar("e")); - }); - - group("which must have", () { - test("an expression after the ?", () { - expect(() => _parse("a ?"), throwsFormatException); - expect(() => _parse("a ? && b"), throwsFormatException); - }); - - test("a :", () { - expect(() => _parse("a ? b"), throwsFormatException); - expect(() => _parse("a ? b && c"), throwsFormatException); - }); - - test("an expression after the :", () { - expect(() => _parse("a ? b :"), throwsFormatException); - expect(() => _parse("a ? b : && c"), throwsFormatException); - }); - }); - }); - - group("parses an or expression", () { - test("with identifiers", () { - var node = _parse(" a || b "); - expect(node, _isOrNode); - expect(node.left, _isVar("a")); - expect(node.right, _isVar("b")); - - expect(node.span.text, equals("a || b")); - expect(node.span.start.offset, equals(2)); - expect(node.span.end.offset, equals(8)); - }); - - test("with nested ands", () { - // Should parse as "(a && b) || (c && d)". - // Should not parse as "a && (b || c) && d". - var node = _parse("a && b || c && d"); - expect(node, _isOrNode); - - expect(node.left, _isAndNode); - expect(node.left.left, _isVar("a")); - expect(node.left.right, _isVar("b")); - - expect(node.right, _isAndNode); - expect(node.right.left, _isVar("c")); - expect(node.right.right, _isVar("d")); - }); - - test("with trailing ors", () { - // Should parse as "a || (b || (c || d))", although it doesn't affect the - // semantics. - var node = _parse("a || b || c || d"); - - for (var variable in ["a", "b", "c"]) { - expect(node, _isOrNode); - expect(node.left, _isVar(variable)); - node = node.right; - } - expect(node, _isVar("d")); - }); - - test("which must have an expression after the ||", () { - expect(() => _parse("a ||"), throwsFormatException); - expect(() => _parse("a || && b"), throwsFormatException); - }); - }); - - group("parses an and expression", () { - test("with identifiers", () { - var node = _parse(" a && b "); - expect(node, _isAndNode); - expect(node.left, _isVar("a")); - expect(node.right, _isVar("b")); - - expect(node.span.text, equals("a && b")); - expect(node.span.start.offset, equals(2)); - expect(node.span.end.offset, equals(8)); - }); - - test("with nested nots", () { - // Should parse as "(!a) && (!b)", obviously. - // Should not parse as "!(a && (!b))". - var node = _parse("!a && !b"); - expect(node, _isAndNode); - - expect(node.left, _isNotNode); - expect(node.left.child, _isVar("a")); - - expect(node.right, _isNotNode); - expect(node.right.child, _isVar("b")); - }); - - test("with trailing ands", () { - // Should parse as "a && (b && (c && d))", although it doesn't affect the - // semantics since . - var node = _parse("a && b && c && d"); - - for (var variable in ["a", "b", "c"]) { - expect(node, _isAndNode); - expect(node.left, _isVar(variable)); - node = node.right; - } - expect(node, _isVar("d")); - }); - - test("which must have an expression after the &&", () { - expect(() => _parse("a &&"), throwsFormatException); - expect(() => _parse("a && && b"), throwsFormatException); - }); - }); - - group("parses a not expression", () { - test("with an identifier", () { - var node = _parse(" ! a "); - expect(node, _isNotNode); - expect(node.child, _isVar("a")); - - expect(node.span.text, equals("! a")); - expect(node.span.start.offset, equals(2)); - expect(node.span.end.offset, equals(5)); - }); - - test("with a parenthesized expression", () { - var node = _parse("!(a || b)"); - expect(node, _isNotNode); - - expect(node.child, _isOrNode); - expect(node.child.left, _isVar("a")); - expect(node.child.right, _isVar("b")); - }); - - test("with a nested not", () { - var node = _parse("!!a"); - expect(node, _isNotNode); - expect(node.child, _isNotNode); - expect(node.child.child, _isVar("a")); - }); - - test("which must have an expression after the !", () { - expect(() => _parse("!"), throwsFormatException); - expect(() => _parse("! && a"), throwsFormatException); - }); - }); - - group("parses a parenthesized expression", () { - test("with an identifier", () { - var node = _parse("(a)"); - expect(node, _isVar("a")); - }); - - test("controls precedence", () { - // Without parentheses, this would parse as "(a || b) ? c : d". - var node = _parse("a || (b ? c : d)"); - - expect(node, _isOrNode); - expect(node.left, _isVar("a")); - - expect(node.right, _isConditionalNode); - expect(node.right.condition, _isVar("b")); - expect(node.right.whenTrue, _isVar("c")); - expect(node.right.whenFalse, _isVar("d")); - }); - - group("which must have", () { - test("an expression within the ()", () { - expect(() => _parse("()"), throwsFormatException); - expect(() => _parse("( && a )"), throwsFormatException); - }); - - test("a matching )", () { - expect(() => _parse("( a"), throwsFormatException); - }); - }); - }); - - group("disallows", () { - test("an empty selector", () { - expect(() => _parse(""), throwsFormatException); - }); - - test("too many expressions", () { - expect(() => _parse("a b"), throwsFormatException); - }); - }); -} - -/// Parses [selector] and returns its root node. -Node _parse(String selector) => new Parser(selector).parse(); - -/// A matcher that asserts that a value is a [VariableNode] with the given -/// [name]. -Matcher _isVar(String name) => predicate( - (value) => value is VariableNode && value.name == name, - 'is a variable named "$name"'); - -void _expectToString(String selector, [String result]) { - if (result == null) result = selector; - expect(_toString(selector), equals(result), - reason: 'Expected toString of "$selector" to be "$result".'); -} - -String _toString(String selector) => new Parser(selector).parse().toString(); diff --git a/test/backend/platform_selector/scanner_test.dart b/test/backend/platform_selector/scanner_test.dart deleted file mode 100644 index 6ba9aeb5..00000000 --- a/test/backend/platform_selector/scanner_test.dart +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright (c) 2015, 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 'package:test/test.dart'; -import 'package:test/src/backend/platform_selector/scanner.dart'; -import 'package:test/src/backend/platform_selector/token.dart'; - -void main() { - group("peek()", () { - test("returns the next token without consuming it", () { - var scanner = new Scanner("( )"); - expect(scanner.peek().type, equals(TokenType.leftParen)); - expect(scanner.peek().type, equals(TokenType.leftParen)); - expect(scanner.peek().type, equals(TokenType.leftParen)); - }); - - test("returns an end-of-file token at the end of a file", () { - var scanner = new Scanner("( )"); - scanner.next(); - scanner.next(); - - var token = scanner.peek(); - expect(token.type, equals(TokenType.endOfFile)); - expect(token.span.start.offset, equals(3)); - expect(token.span.end.offset, equals(3)); - }); - - test("throws a StateError if called after end-of-file was consumed", () { - var scanner = new Scanner("( )"); - scanner.next(); - scanner.next(); - scanner.next(); - expect(() => scanner.peek(), throwsStateError); - }); - }); - - group("next()", () { - test("consumes and returns the next token", () { - var scanner = new Scanner("( )"); - expect(scanner.next().type, equals(TokenType.leftParen)); - expect(scanner.peek().type, equals(TokenType.rightParen)); - expect(scanner.next().type, equals(TokenType.rightParen)); - }); - - test("returns an end-of-file token at the end of a file", () { - var scanner = new Scanner("( )"); - scanner.next(); - scanner.next(); - - var token = scanner.next(); - expect(token.type, equals(TokenType.endOfFile)); - expect(token.span.start.offset, equals(3)); - expect(token.span.end.offset, equals(3)); - }); - - test("throws a StateError if called after end-of-file was consumed", () { - var scanner = new Scanner("( )"); - scanner.next(); - scanner.next(); - scanner.next(); - expect(() => scanner.next(), throwsStateError); - }); - }); - - group("scan()", () { - test("consumes a matching token and returns true", () { - var scanner = new Scanner("( )"); - expect(scanner.scan(TokenType.leftParen), isTrue); - expect(scanner.peek().type, equals(TokenType.rightParen)); - }); - - test("doesn't consume a matching token and returns false", () { - var scanner = new Scanner("( )"); - expect(scanner.scan(TokenType.questionMark), isFalse); - expect(scanner.peek().type, equals(TokenType.leftParen)); - }); - - test("throws a StateError called after end-of-file was consumed", () { - var scanner = new Scanner("( )"); - scanner.next(); - scanner.next(); - scanner.next(); - expect(() => scanner.scan(TokenType.endOfFile), throwsStateError); - }); - }); - - group("scans a simple token:", () { - test("left paren", () => _expectSimpleScan("(", TokenType.leftParen)); - test("right paren", () => _expectSimpleScan(")", TokenType.rightParen)); - test("or", () => _expectSimpleScan("||", TokenType.or)); - test("and", () => _expectSimpleScan("&&", TokenType.and)); - test("not", () => _expectSimpleScan("!", TokenType.not)); - test("question mark", () => _expectSimpleScan("?", TokenType.questionMark)); - test("colon", () => _expectSimpleScan(":", TokenType.colon)); - }); - - group("scans an identifier that", () { - test("is simple", () { - var token = _scan(" foo "); - expect(token.name, equals("foo")); - expect(token.span.text, equals("foo")); - expect(token.span.start.offset, equals(3)); - expect(token.span.end.offset, equals(6)); - }); - - test("is a single character", () { - var token = _scan("f"); - expect(token.name, equals("f")); - }); - - test("has a leading underscore", () { - var token = _scan("_foo"); - expect(token.name, equals("_foo")); - }); - - test("has a leading dash", () { - var token = _scan("-foo"); - expect(token.name, equals("-foo")); - }); - - test("contains an underscore", () { - var token = _scan("foo_bar"); - expect(token.name, equals("foo_bar")); - }); - - test("contains a dash", () { - var token = _scan("foo-bar"); - expect(token.name, equals("foo-bar")); - }); - - test("is capitalized", () { - var token = _scan("FOO"); - expect(token.name, equals("FOO")); - }); - - test("contains numbers", () { - var token = _scan("foo123"); - expect(token.name, equals("foo123")); - }); - }); - - test("scans an empty selector", () { - expect(_scan("").type, equals(TokenType.endOfFile)); - }); - - test("scans multiple tokens", () { - var scanner = new Scanner("(foo && bar)"); - - var token = scanner.next(); - expect(token.type, equals(TokenType.leftParen)); - expect(token.span.start.offset, equals(0)); - expect(token.span.end.offset, equals(1)); - - token = scanner.next(); - expect(token.type, equals(TokenType.identifier)); - expect(token.name, equals("foo")); - expect(token.span.start.offset, equals(1)); - expect(token.span.end.offset, equals(4)); - - token = scanner.next(); - expect(token.type, equals(TokenType.and)); - expect(token.span.start.offset, equals(5)); - expect(token.span.end.offset, equals(7)); - - token = scanner.next(); - expect(token.type, equals(TokenType.identifier)); - expect(token.name, equals("bar")); - expect(token.span.start.offset, equals(8)); - expect(token.span.end.offset, equals(11)); - - token = scanner.next(); - expect(token.type, equals(TokenType.rightParen)); - expect(token.span.start.offset, equals(11)); - expect(token.span.end.offset, equals(12)); - - token = scanner.next(); - expect(token.type, equals(TokenType.endOfFile)); - expect(token.span.start.offset, equals(12)); - expect(token.span.end.offset, equals(12)); - }); - - group("ignores", () { - test("a single-line comment", () { - var scanner = new Scanner("( // &&\n// ||\n)"); - expect(scanner.next().type, equals(TokenType.leftParen)); - expect(scanner.next().type, equals(TokenType.rightParen)); - expect(scanner.next().type, equals(TokenType.endOfFile)); - }); - - test("a single-line comment without a trailing newline", () { - var scanner = new Scanner("( // &&"); - expect(scanner.next().type, equals(TokenType.leftParen)); - expect(scanner.next().type, equals(TokenType.endOfFile)); - }); - - test("a multi-line comment", () { - var scanner = new Scanner("( /* && * /\n|| */\n)"); - expect(scanner.next().type, equals(TokenType.leftParen)); - expect(scanner.next().type, equals(TokenType.rightParen)); - expect(scanner.next().type, equals(TokenType.endOfFile)); - }); - - test("a multi-line nested comment", () { - var scanner = new Scanner("(/* && /* ? /* || */ : */ ! */)"); - expect(scanner.next().type, equals(TokenType.leftParen)); - expect(scanner.next().type, equals(TokenType.rightParen)); - expect(scanner.next().type, equals(TokenType.endOfFile)); - }); - - test("Dart's notion of whitespace", () { - var scanner = new Scanner("( \t \n)"); - expect(scanner.next().type, equals(TokenType.leftParen)); - expect(scanner.next().type, equals(TokenType.rightParen)); - expect(scanner.next().type, equals(TokenType.endOfFile)); - }); - }); - - group("disallows", () { - test("a single |", () { - expect(() => _scan("|"), throwsFormatException); - }); - - test('"| |"', () { - expect(() => _scan("| |"), throwsFormatException); - }); - - test("a single &", () { - expect(() => _scan("&"), throwsFormatException); - }); - - test('"& &"', () { - expect(() => _scan("& &"), throwsFormatException); - }); - - test("an unknown operator", () { - expect(() => _scan("=="), throwsFormatException); - }); - - test("unicode", () { - expect(() => _scan("öh"), throwsFormatException); - }); - - test("an unclosed multi-line comment", () { - expect(() => _scan("/*"), throwsFormatException); - }); - - test("an unopened multi-line comment", () { - expect(() => _scan("*/"), throwsFormatException); - }); - }); -} - -/// Asserts that the first token scanned from [selector] has type [type], -/// and that that token's span is exactly [selector]. -void _expectSimpleScan(String selector, TokenType type) { - // Complicate the selector to test that the span covers it correctly. - var token = _scan(" $selector "); - expect(token.type, equals(type)); - expect(token.span.text, equals(selector)); - expect(token.span.start.offset, equals(3)); - expect(token.span.end.offset, equals(3 + selector.length)); -} - -/// Scans a single token from [selector]. -Token _scan(String selector) => new Scanner(selector).next(); -- GitLab