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

Unified Diff: lib/src/runner/parse_metadata.dart

Issue 1092153003: Support an @OnPlatform annotation. (Closed) Base URL: git@github.com:dart-lang/test@master
Patch Set: Code review changes Created 5 years, 8 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 | « lib/src/runner/loader.dart ('k') | lib/src/utils.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: lib/src/runner/parse_metadata.dart
diff --git a/lib/src/runner/parse_metadata.dart b/lib/src/runner/parse_metadata.dart
index e4e4079608b8ee72a583eb3d7d9ad909a2aaf381..7f735deb037a3706e08678e85f6f1b7f68354d3d 100644
--- a/lib/src/runner/parse_metadata.dart
+++ b/lib/src/runner/parse_metadata.dart
@@ -12,8 +12,10 @@ import 'package:path/path.dart' as p;
import 'package:source_span/source_span.dart';
import '../backend/metadata.dart';
+import '../backend/platform_selector.dart';
import '../frontend/timeout.dart';
import '../util/dart.dart';
+import '../utils.dart';
/// Parse the test metadata for the test file at [path].
///
@@ -52,64 +54,47 @@ class _Parser {
var timeout;
var testOn;
var skip;
+ var onPlatform;
for (var annotation in _annotations) {
- // The annotation syntax is ambiguous between named constructors and
- // prefixed annotations, so we need to resolve that ambiguity using the
- // known prefixes. The analyzer parses "@x.y()" as prefix "x", annotation
- // "y", and named constructor null. It parses "@x.y.z()" as prefix "x",
- // annotation "y", and named constructor "z".
- var name;
- var constructorName;
- var identifier = annotation.name;
- if (identifier is PrefixedIdentifier &&
- !_prefixes.contains(identifier.prefix.name) &&
- annotation.constructorName == null) {
- name = identifier.prefix.name;
- constructorName = identifier.identifier.name;
- } else {
- name = identifier is PrefixedIdentifier
- ? identifier.identifier.name
- : identifier.name;
- if (annotation.constructorName != null) {
- constructorName = annotation.constructorName.name;
- }
- }
+ var pair = _resolveConstructor(
+ annotation.name, annotation.constructorName);
+ var name = pair.first;
+ var constructorName = pair.last;
if (name == 'TestOn') {
- _assertSingleAnnotation(testOn, 'TestOn', annotation);
+ _assertSingle(testOn, 'TestOn', annotation);
testOn = _parseTestOn(annotation, constructorName);
} else if (name == 'Timeout') {
- _assertSingleAnnotation(timeout, 'Timeout', annotation);
+ _assertSingle(timeout, 'Timeout', annotation);
timeout = _parseTimeout(annotation, constructorName);
} else if (name == 'Skip') {
- _assertSingleAnnotation(skip, 'Skip', annotation);
+ _assertSingle(skip, 'Skip', annotation);
skip = _parseSkip(annotation, constructorName);
+ } else if (name == 'OnPlatform') {
+ _assertSingle(onPlatform, 'OnPlatform', annotation);
+ onPlatform = _parseOnPlatform(annotation, constructorName);
}
}
- try {
- return new Metadata.parse(
- testOn: testOn == null ? null : testOn.stringValue,
- timeout: timeout,
- skip: skip);
- } on SourceSpanFormatException catch (error) {
- var file = new SourceFile(new File(_path).readAsStringSync(),
- url: p.toUri(_path));
- var span = contextualizeSpan(error.span, testOn, file);
- if (span == null) rethrow;
- throw new SourceSpanFormatException(error.message, span);
- }
+ return new Metadata(
+ testOn: testOn,
+ timeout: timeout,
+ skip: skip != null,
+ skipReason: skip is String ? skip : null,
+ onPlatform: onPlatform);
}
/// Parses a `@TestOn` annotation.
///
/// [annotation] is the annotation. [constructorName] is the name of the named
/// constructor for the annotation, if any.
- StringLiteral _parseTestOn(Annotation annotation, String constructorName) {
+ PlatformSelector _parseTestOn(Annotation annotation, String constructorName) {
_assertConstructorName(constructorName, 'TestOn', annotation);
_assertArguments(annotation.arguments, 'TestOn', annotation, positional: 1);
- return _parseString(annotation.arguments.arguments.first);
+ var literal = _parseString(annotation.arguments.arguments.first);
+ return _contextualize(literal,
+ () => new PlatformSelector.parse(literal.stringValue));
}
/// Parses a `@Timeout` annotation.
@@ -131,6 +116,22 @@ class _Parser {
return new Timeout.factor(_parseNum(args.first));
}
+ /// Parses a `Timeout` constructor.
+ Timeout _parseTimeoutConstructor(InstanceCreationExpression constructor) {
+ var name = _parseConstructor(constructor, 'Timeout',
+ validNames: [null, 'factor']);
+
+ var description = 'Timeout';
+ if (name != null) description += '.$name';
+
+ _assertArguments(constructor.argumentList, description, constructor,
+ positional: 1);
+
+ var args = constructor.argumentList.arguments;
+ if (name == null) return new Timeout(_parseDuration(args.first));
+ return new Timeout.factor(_parseNum(args.first));
+ }
+
/// Parses a `@Skip` annotation.
///
/// [annotation] is the annotation. [constructorName] is the name of the named
@@ -145,6 +146,70 @@ class _Parser {
return args.isEmpty ? true : _parseString(args.first).stringValue;
}
+ /// Parses a `Skip` constructor.
+ ///
+ /// Returns either `true` or a reason string.
+ _parseSkipConstructor(InstanceCreationExpression constructor) {
+ _parseConstructor(constructor, 'Skip');
+ _assertArguments(constructor.argumentList, 'Skip', constructor,
+ optional: 1);
+
+ var args = constructor.argumentList.arguments;
+ return args.isEmpty ? true : _parseString(args.first).stringValue;
+ }
+
+ /// Parses an `@OnPlatform` annotation.
+ ///
+ /// [annotation] is the annotation. [constructorName] is the name of the named
+ /// constructor for the annotation, if any.
+ Map<PlatformSelector, Metadata> _parseOnPlatform(Annotation annotation,
+ String constructorName) {
+ _assertConstructorName(constructorName, 'OnPlatform', annotation);
+ _assertArguments(annotation.arguments, 'OnPlatform', annotation,
+ positional: 1);
+
+ return _parseMap(annotation.arguments.arguments.first, key: (key) {
+ var selector = _parseString(key);
+ return _contextualize(selector,
+ () => new PlatformSelector.parse(selector.stringValue));
+ }, value: (value) {
+ var expressions = [];
+ if (value is ListLiteral) {
+ expressions = _parseList(value);
+ } else if (value is InstanceCreationExpression) {
+ expressions = [value];
+ } else {
+ throw new SourceSpanFormatException(
+ 'Expected a Timeout, Skip, or List of those.',
+ _spanFor(value));
+ }
+
+ var timeout;
+ var skip;
+ for (var expression in expressions) {
+ var className = expression is InstanceCreationExpression
+ ? _resolveConstructor(
+ expression.constructorName.type.name,
+ expression.constructorName.name).first
+ : null;
+
+ if (className == 'Timeout') {
+ _assertSingle(timeout, 'Timeout', expression);
+ timeout = _parseTimeoutConstructor(expression);
+ } else if (className == 'Skip') {
+ _assertSingle(skip, 'Skip', expression);
+ skip = _parseSkipConstructor(expression);
+ } else {
+ throw new SourceSpanFormatException(
+ 'Expected a Timeout or Skip.',
+ _spanFor(expression));
+ }
+ }
+
+ return new Metadata.parse(timeout: timeout, skip: skip);
+ });
+ }
+
/// Parses a `const Duration` expression.
Duration _parseDuration(Expression expression) {
_parseConstructor(expression, 'Duration');
@@ -173,11 +238,38 @@ class _Parser {
///
/// [name] is the name of the annotation and [node] is its location, used for
/// error reporting.
- void _assertSingleAnnotation(Object existing, String name, AstNode node) {
+ void _assertSingle(Object existing, String name, AstNode node) {
if (existing == null) return;
throw new SourceSpanFormatException(
- "Only a single $name annotation may be used for a given test file.",
- _spanFor(node));
+ "Only a single $name may be used.", _spanFor(node));
+ }
+
+ /// Resolves a constructor name from its type [identifier] and its
+ /// [constructorName].
+ ///
+ /// Since the parsed file isn't fully resolved, this is necessary to
+ /// disambiguate between prefixed names and named constructors.
+ Pair<String, String> _resolveConstructor(Identifier identifier,
+ SimpleIdentifier constructorName) {
+ // The syntax is ambiguous between named constructors and prefixed
+ // annotations, so we need to resolve that ambiguity using the known
+ // prefixes. The analyzer parses "new x.y()" as prefix "x", annotation "y",
+ // and named constructor null. It parses "new x.y.z()" as prefix "x",
+ // annotation "y", and named constructor "z".
+ var className;
+ var namedConstructor;
+ if (identifier is PrefixedIdentifier &&
+ !_prefixes.contains(identifier.prefix.name) &&
+ constructorName == null) {
+ className = identifier.prefix.name;
+ namedConstructor = identifier.identifier.name;
+ } else {
+ className = identifier is PrefixedIdentifier
+ ? identifier.identifier.name
+ : identifier.name;
+ if (constructorName != null) namedConstructor = constructorName.name;
+ }
+ return new Pair(className, namedConstructor);
}
/// Asserts that [constructorName] is a valid constructor name for an AST
@@ -220,7 +312,13 @@ class _Parser {
}
var constructor = expression as InstanceCreationExpression;
- if (constructor.constructorName.type.name.name != className) {
+ var pair = _resolveConstructor(
+ constructor.constructorName.type.name,
+ constructor.constructorName.name);
+ var actualClassName = pair.first;
+ var constructorName = pair.last;
+
+ if (actualClassName != className) {
throw new SourceSpanFormatException(
"Expected a $className.", _spanFor(constructor));
}
@@ -230,12 +328,9 @@ class _Parser {
"$className must use a const constructor.", _spanFor(constructor));
}
- var name = constructor.constructorName == null
- ? null
- : constructor.constructorName.name;
- _assertConstructorName(name, className, expression,
+ _assertConstructorName(constructorName, className, expression,
validNames: validNames);
- return name;
+ return constructorName;
}
/// Assert that [arguments] is a valid argument list.
@@ -312,6 +407,47 @@ class _Parser {
return namedValues;
}
+ /// Parses a Map literal.
+ ///
+ /// By default, returns [Expression] keys and values. These can be overridden
+ /// with the [key] and [value] parameters.
+ Map _parseMap(Expression expression, {key(Expression expression),
+ value(Expression expression)}) {
+ if (key == null) key = (expression) => expression;
+ if (value == null) value = (expression) => expression;
+
+ if (expression is! MapLiteral) {
+ throw new SourceSpanFormatException(
+ "Expected a Map.", _spanFor(expression));
+ }
+
+ var map = expression as MapLiteral;
+ if (map.constKeyword == null) {
+ throw new SourceSpanFormatException(
+ "Map literals must be const.", _spanFor(map));
+ }
+
+ return new Map.fromIterable(map.entries,
+ key: (entry) => key(entry.key),
+ value: (entry) => value(entry.value));
+ }
+
+ /// Parses a List literal.
+ List<Expression> _parseList(Expression expression) {
+ if (expression is! ListLiteral) {
+ throw new SourceSpanFormatException(
+ "Expected a List.", _spanFor(expression));
+ }
+
+ var list = expression as ListLiteral;
+ if (list.constKeyword == null) {
+ throw new SourceSpanFormatException(
+ "List literals must be const.", _spanFor(list));
+ }
+
+ return list.elements;
+ }
+
/// Parses a constant number literal.
num _parseNum(Expression expression) {
if (expression is IntegerLiteral) return expression.value;
@@ -342,4 +478,18 @@ class _Parser {
return new SourceFile(contents, url: p.toUri(_path))
.span(node.offset, node.end);
}
+
+ /// Runs [fn] and contextualizes any [SourceSpanFormatException]s that occur
+ /// in it relative to [literal].
+ _contextualize(StringLiteral literal, fn()) {
+ try {
+ return fn();
+ } on SourceSpanFormatException catch (error) {
+ var file = new SourceFile(new File(_path).readAsStringSync(),
+ url: p.toUri(_path));
+ var span = contextualizeSpan(error.span, literal, file);
+ if (span == null) rethrow;
+ throw new SourceSpanFormatException(error.message, span);
+ }
+ }
}
« no previous file with comments | « lib/src/runner/loader.dart ('k') | lib/src/utils.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698