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

Side by Side 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: 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 unified diff | Download patch
OLDNEW
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library test.runner.parse_metadata; 5 library test.runner.parse_metadata;
6 6
7 import 'dart:io'; 7 import 'dart:io';
8 8
9 import 'package:analyzer/analyzer.dart'; 9 import 'package:analyzer/analyzer.dart';
10 import 'package:analyzer/src/generated/ast.dart'; 10 import 'package:analyzer/src/generated/ast.dart';
11 import 'package:path/path.dart' as p; 11 import 'package:path/path.dart' as p;
12 import 'package:source_span/source_span.dart'; 12 import 'package:source_span/source_span.dart';
13 13
14 import '../backend/metadata.dart'; 14 import '../backend/metadata.dart';
15 import '../backend/platform_selector.dart';
15 import '../frontend/timeout.dart'; 16 import '../frontend/timeout.dart';
16 import '../util/dart.dart'; 17 import '../util/dart.dart';
18 import '../utils.dart';
17 19
18 /// Parse the test metadata for the test file at [path]. 20 /// Parse the test metadata for the test file at [path].
19 /// 21 ///
20 /// Throws an [AnalysisError] if parsing fails or a [FormatException] if the 22 /// Throws an [AnalysisError] if parsing fails or a [FormatException] if the
21 /// test annotations are incorrect. 23 /// test annotations are incorrect.
22 Metadata parseMetadata(String path) => new _Parser(path).parse(); 24 Metadata parseMetadata(String path) => new _Parser(path).parse();
23 25
24 /// A parser for test suite metadata. 26 /// A parser for test suite metadata.
25 class _Parser { 27 class _Parser {
26 /// The path to the test suite. 28 /// The path to the test suite.
(...skipping 18 matching lines...) Expand all
45 if (directive.prefix == null) return null; 47 if (directive.prefix == null) return null;
46 return directive.prefix.name; 48 return directive.prefix.name;
47 }).where((prefix) => prefix != null).toSet(); 49 }).where((prefix) => prefix != null).toSet();
48 } 50 }
49 51
50 /// Parses the metadata. 52 /// Parses the metadata.
51 Metadata parse() { 53 Metadata parse() {
52 var timeout; 54 var timeout;
53 var testOn; 55 var testOn;
54 var skip; 56 var skip;
57 var onPlatform;
55 58
56 for (var annotation in _annotations) { 59 for (var annotation in _annotations) {
57 // The annotation syntax is ambiguous between named constructors and 60 var pair = _resolveConstructor(
58 // prefixed annotations, so we need to resolve that ambiguity using the 61 annotation.name, annotation.constructorName);
59 // known prefixes. The analyzer parses "@x.y()" as prefix "x", annotation 62 var name = pair.first;
60 // "y", and named constructor null. It parses "@x.y.z()" as prefix "x", 63 var constructorName = pair.last;
61 // annotation "y", and named constructor "z".
62 var name;
63 var constructorName;
64 var identifier = annotation.name;
65 if (identifier is PrefixedIdentifier &&
66 !_prefixes.contains(identifier.prefix.name) &&
67 annotation.constructorName == null) {
68 name = identifier.prefix.name;
69 constructorName = identifier.identifier.name;
70 } else {
71 name = identifier is PrefixedIdentifier
72 ? identifier.identifier.name
73 : identifier.name;
74 if (annotation.constructorName != null) {
75 constructorName = annotation.constructorName.name;
76 }
77 }
78 64
79 if (name == 'TestOn') { 65 if (name == 'TestOn') {
80 _assertSingleAnnotation(testOn, 'TestOn', annotation); 66 _assertSingle(testOn, 'TestOn', annotation);
81 testOn = _parseTestOn(annotation, constructorName); 67 testOn = _parseTestOn(annotation, constructorName);
82 } else if (name == 'Timeout') { 68 } else if (name == 'Timeout') {
83 _assertSingleAnnotation(timeout, 'Timeout', annotation); 69 _assertSingle(timeout, 'Timeout', annotation);
84 timeout = _parseTimeout(annotation, constructorName); 70 timeout = _parseTimeout(annotation, constructorName);
85 } else if (name == 'Skip') { 71 } else if (name == 'Skip') {
86 _assertSingleAnnotation(skip, 'Skip', annotation); 72 _assertSingle(skip, 'Skip', annotation);
87 skip = _parseSkip(annotation, constructorName); 73 skip = _parseSkip(annotation, constructorName);
74 } else if (name == 'OnPlatform') {
75 _assertSingle(onPlatform, 'OnPlatform', annotation);
76 onPlatform = _parseOnPlatform(annotation, constructorName);
88 } 77 }
89 } 78 }
90 79
91 try { 80 return new Metadata(
92 return new Metadata.parse( 81 testOn: testOn,
93 testOn: testOn == null ? null : testOn.stringValue, 82 timeout: timeout,
94 timeout: timeout, 83 skip: skip != null,
95 skip: skip); 84 skipReason: skip is String ? skip : null,
96 } on SourceSpanFormatException catch (error) { 85 onPlatform: onPlatform);
97 var file = new SourceFile(new File(_path).readAsStringSync(),
98 url: p.toUri(_path));
99 var span = contextualizeSpan(error.span, testOn, file);
100 if (span == null) rethrow;
101 throw new SourceSpanFormatException(error.message, span);
102 }
103 } 86 }
104 87
105 /// Parses a `@TestOn` annotation. 88 /// Parses a `@TestOn` annotation.
106 /// 89 ///
107 /// [annotation] is the annotation. [constructorName] is the name of the named 90 /// [annotation] is the annotation. [constructorName] is the name of the named
108 /// constructor for the annotation, if any. 91 /// constructor for the annotation, if any.
109 StringLiteral _parseTestOn(Annotation annotation, String constructorName) { 92 PlatformSelector _parseTestOn(Annotation annotation, String constructorName) {
110 _assertConstructorName(constructorName, 'TestOn', annotation); 93 _assertConstructorName(constructorName, 'TestOn', annotation);
111 _assertArguments(annotation.arguments, 'TestOn', annotation, positional: 1); 94 _assertArguments(annotation.arguments, 'TestOn', annotation, positional: 1);
112 return _parseString(annotation.arguments.arguments.first); 95 var literal = _parseString(annotation.arguments.arguments.first);
96 return _contextualize(literal,
97 () => new PlatformSelector.parse(literal.stringValue));
113 } 98 }
114 99
115 /// Parses a `@Timeout` annotation. 100 /// Parses a `@Timeout` annotation.
116 /// 101 ///
117 /// [annotation] is the annotation. [constructorName] is the name of the named 102 /// [annotation] is the annotation. [constructorName] is the name of the named
118 /// constructor for the annotation, if any. 103 /// constructor for the annotation, if any.
119 Timeout _parseTimeout(Annotation annotation, String constructorName) { 104 Timeout _parseTimeout(Annotation annotation, String constructorName) {
120 _assertConstructorName(constructorName, 'Timeout', annotation, 105 _assertConstructorName(constructorName, 'Timeout', annotation,
121 validNames: [null, 'factor']); 106 validNames: [null, 'factor']);
122 107
123 var description = 'Timeout'; 108 var description = 'Timeout';
124 if (constructorName != null) description += '.$constructorName'; 109 if (constructorName != null) description += '.$constructorName';
125 110
126 _assertArguments(annotation.arguments, description, annotation, 111 _assertArguments(annotation.arguments, description, annotation,
127 positional: 1); 112 positional: 1);
128 113
129 var args = annotation.arguments.arguments; 114 var args = annotation.arguments.arguments;
130 if (constructorName == null) return new Timeout(_parseDuration(args.first)); 115 if (constructorName == null) return new Timeout(_parseDuration(args.first));
131 return new Timeout.factor(_parseNum(args.first)); 116 return new Timeout.factor(_parseNum(args.first));
132 } 117 }
133 118
119 /// Parses a `Timeout` constructor.
120 Timeout _parseTimeoutConstructor(InstanceCreationExpression constructor) {
121 var name = _parseConstructor(constructor, 'Timeout',
122 validNames: [null, 'factor']);
123
124 var description = 'Timeout';
125 if (name != null) description += '.$name';
126
127 _assertArguments(constructor.argumentList, description, constructor,
128 positional: 1);
129
130 var args = constructor.argumentList.arguments;
131 if (name == null) return new Timeout(_parseDuration(args.first));
132 return new Timeout.factor(_parseNum(args.first));
133 }
134
134 /// Parses a `@Skip` annotation. 135 /// Parses a `@Skip` annotation.
135 /// 136 ///
136 /// [annotation] is the annotation. [constructorName] is the name of the named 137 /// [annotation] is the annotation. [constructorName] is the name of the named
137 /// constructor for the annotation, if any. 138 /// constructor for the annotation, if any.
138 /// 139 ///
139 /// Returns either `true` or a reason string. 140 /// Returns either `true` or a reason string.
140 _parseSkip(Annotation annotation, String constructorName) { 141 _parseSkip(Annotation annotation, String constructorName) {
141 _assertConstructorName(constructorName, 'Skip', annotation); 142 _assertConstructorName(constructorName, 'Skip', annotation);
142 _assertArguments(annotation.arguments, 'Skip', annotation, optional: 1); 143 _assertArguments(annotation.arguments, 'Skip', annotation, optional: 1);
143 144
144 var args = annotation.arguments.arguments; 145 var args = annotation.arguments.arguments;
145 return args.isEmpty ? true : _parseString(args.first).stringValue; 146 return args.isEmpty ? true : _parseString(args.first).stringValue;
146 } 147 }
147 148
149 /// Parses a `Skip` constructor.
150 ///
151 /// Returns either `true` or a reason string.
152 _parseSkipConstructor(InstanceCreationExpression constructor) {
153 _parseConstructor(constructor, 'Skip');
154 _assertArguments(constructor.argumentList, 'Skip', constructor,
155 optional: 1);
156
157 var args = constructor.argumentList.arguments;
158 return args.isEmpty ? true : _parseString(args.first).stringValue;
159 }
160
161 /// Parses an `@OnPlatform` annotation.
162 ///
163 /// [annotation] is the annotation. [constructorName] is the name of the named
164 /// constructor for the annotation, if any.
165 Map<PlatformSelector, Metadata> _parseOnPlatform(Annotation annotation,
166 String constructorName) {
167 _assertConstructorName(constructorName, 'OnPlatform', annotation);
168 _assertArguments(annotation.arguments, 'OnPlatform', annotation,
169 positional: 1);
170
171 return _parseMap(annotation.arguments.arguments.first, key: (key) {
172 var selector = _parseString(key);
173 return _contextualize(selector,
174 () => new PlatformSelector.parse(selector.stringValue));
175 }, value: (value) {
176 var expressions = [];
177 if (value is ListLiteral) {
178 expressions = _parseList(value);
179 } else if (value is InstanceCreationExpression) {
180 expressions = [value];
181 } else {
182 throw new SourceSpanFormatException(
183 'Expected a Timeout, Skip, or List of those.',
184 _spanFor(value));
185 }
186
187 var timeout;
188 var skip;
189 for (var expression in expressions) {
190 var className = expression is InstanceCreationExpression
191 ? _resolveConstructor(
192 expression.constructorName.type.name,
193 expression.constructorName.name).first
194 : null;
195
196 if (className == 'Timeout') {
197 _assertSingle(timeout, 'Timeout', expression);
198 timeout = _parseTimeoutConstructor(expression);
199 } else if (className == 'Skip') {
200 _assertSingle(skip, 'Skip', expression);
201 skip = _parseSkipConstructor(expression);
202 } else {
203 throw new SourceSpanFormatException(
204 'Expected a Timeout or Skip.',
205 _spanFor(expression));
206 }
207 }
208
209 return new Metadata.parse(timeout: timeout, skip: skip);
210 });
211 }
212
148 /// Parses a `const Duration` expression. 213 /// Parses a `const Duration` expression.
149 Duration _parseDuration(Expression expression) { 214 Duration _parseDuration(Expression expression) {
150 _parseConstructor(expression, 'Duration'); 215 _parseConstructor(expression, 'Duration');
151 216
152 var constructor = expression as InstanceCreationExpression; 217 var constructor = expression as InstanceCreationExpression;
153 var values = _assertArguments( 218 var values = _assertArguments(
154 constructor.argumentList, 'Duration', constructor, named: [ 219 constructor.argumentList, 'Duration', constructor, named: [
155 'days', 'hours', 'minutes', 'seconds', 'milliseconds', 'microseconds' 220 'days', 'hours', 'minutes', 'seconds', 'milliseconds', 'microseconds'
156 ]); 221 ]);
157 222
158 for (var key in values.keys.toList()) { 223 for (var key in values.keys.toList()) {
159 if (values.containsKey(key)) values[key] = _parseInt(values[key]); 224 if (values.containsKey(key)) values[key] = _parseInt(values[key]);
160 } 225 }
161 226
162 return new Duration( 227 return new Duration(
163 days: values["days"] == null ? 0 : values["days"], 228 days: values["days"] == null ? 0 : values["days"],
164 hours: values["hours"] == null ? 0 : values["hours"], 229 hours: values["hours"] == null ? 0 : values["hours"],
165 minutes: values["minutes"] == null ? 0 : values["minutes"], 230 minutes: values["minutes"] == null ? 0 : values["minutes"],
166 seconds: values["seconds"] == null ? 0 : values["seconds"], 231 seconds: values["seconds"] == null ? 0 : values["seconds"],
167 milliseconds: values["milliseconds"] == null ? 0 : values["milliseconds" ], 232 milliseconds: values["milliseconds"] == null ? 0 : values["milliseconds" ],
168 microseconds: 233 microseconds:
169 values["microseconds"] == null ? 0 : values["microseconds"]); 234 values["microseconds"] == null ? 0 : values["microseconds"]);
170 } 235 }
171 236
172 /// Asserts that [existing] is null. 237 /// Asserts that [existing] is null.
173 /// 238 ///
174 /// [name] is the name of the annotation and [node] is its location, used for 239 /// [name] is the name of the annotation and [node] is its location, used for
175 /// error reporting. 240 /// error reporting.
176 void _assertSingleAnnotation(Object existing, String name, AstNode node) { 241 void _assertSingle(Object existing, String name, AstNode node) {
177 if (existing == null) return; 242 if (existing == null) return;
178 throw new SourceSpanFormatException( 243 throw new SourceSpanFormatException(
179 "Only a single $name annotation may be used for a given test file.", 244 "Only a single $name may be used.", _spanFor(node));
180 _spanFor(node)); 245 }
246
247 /// Resolves a constructor name from its type [identifier] and its
248 /// [constructorName].
249 ///
250 /// Since the parsed file isn't fully resolved, this is necessary to
251 /// disambiguate between prefixed names and named constructors.
252 Pair<String, String> _resolveConstructor(Identifier identifier,
253 SimpleIdentifier constructorName) {
254 // The syntax is ambiguous between named constructors and prefixed
255 // annotations, so we need to resolve that ambiguity using the known
256 // prefixes. The analyzer parses "new x.y()" as prefix "x", annotation "y",
257 // and named constructor null. It parses "new x.y.z()" as prefix "x",
258 // annotation "y", and named constructor "z".
259 var className;
260 var namedConstructor;
261 if (identifier is PrefixedIdentifier &&
262 !_prefixes.contains(identifier.prefix.name) &&
263 constructorName == null) {
264 className = identifier.prefix.name;
265 namedConstructor = identifier.identifier.name;
266 } else {
267 className = identifier is PrefixedIdentifier
268 ? identifier.identifier.name
269 : identifier.name;
270 if (constructorName != null) namedConstructor = constructorName.name;
271 }
272 return new Pair(className, namedConstructor);
181 } 273 }
182 274
183 /// Asserts that [constructorName] is a valid constructor name for an AST 275 /// Asserts that [constructorName] is a valid constructor name for an AST
184 /// node. 276 /// node.
185 /// 277 ///
186 /// [nodeName] is the name of the class being constructed, and [node] is the 278 /// [nodeName] is the name of the class being constructed, and [node] is the
187 /// AST node for that class. [validNames], if passed, is the set of valid 279 /// AST node for that class. [validNames], if passed, is the set of valid
188 /// constructor names; if an unnamed constructor is valid, it should include 280 /// constructor names; if an unnamed constructor is valid, it should include
189 /// `null`. By default, only an unnamed constructor is allowed. 281 /// `null`. By default, only an unnamed constructor is allowed.
190 void _assertConstructorName(String constructorName, String nodeName, 282 void _assertConstructorName(String constructorName, String nodeName,
(...skipping 22 matching lines...) Expand all
213 String _parseConstructor(Expression expression, String className, 305 String _parseConstructor(Expression expression, String className,
214 {Iterable<String> validNames}) { 306 {Iterable<String> validNames}) {
215 if (validNames == null) validNames = [null]; 307 if (validNames == null) validNames = [null];
216 308
217 if (expression is! InstanceCreationExpression) { 309 if (expression is! InstanceCreationExpression) {
218 throw new SourceSpanFormatException( 310 throw new SourceSpanFormatException(
219 "Expected a $className.", _spanFor(expression)); 311 "Expected a $className.", _spanFor(expression));
220 } 312 }
221 313
222 var constructor = expression as InstanceCreationExpression; 314 var constructor = expression as InstanceCreationExpression;
223 if (constructor.constructorName.type.name.name != className) { 315 var pair = _resolveConstructor(
316 expression.constructorName.type.name,
317 expression.constructorName.name);
318 var actualClassName = pair.first;
319 var constructorName = pair.last;
320
321 if (actualClassName != className) {
224 throw new SourceSpanFormatException( 322 throw new SourceSpanFormatException(
225 "Expected a $className.", _spanFor(constructor)); 323 "Expected a $className.", _spanFor(constructor));
226 } 324 }
227 325
228 if (constructor.keyword.lexeme != "const") { 326 if (constructor.keyword.lexeme != "const") {
229 throw new SourceSpanFormatException( 327 throw new SourceSpanFormatException(
230 "$className must use a const constructor.", _spanFor(constructor)); 328 "$className must use a const constructor.", _spanFor(constructor));
231 } 329 }
232 330
233 var name = constructor.constructorName == null 331 _assertConstructorName(constructorName, className, expression,
234 ? null
235 : constructor.constructorName.name;
236 _assertConstructorName(name, className, expression,
237 validNames: validNames); 332 validNames: validNames);
238 return name; 333 return constructorName;
239 } 334 }
240 335
241 /// Assert that [arguments] is a valid argument list. 336 /// Assert that [arguments] is a valid argument list.
242 /// 337 ///
243 /// [name] describes the function and [node] is its AST node. [positional] is 338 /// [name] describes the function and [node] is its AST node. [positional] is
244 /// the number of required positional arguments, [optional] the number of 339 /// the number of required positional arguments, [optional] the number of
245 /// optional positional arguments, and [named] the set of valid argument 340 /// optional positional arguments, and [named] the set of valid argument
246 /// names. 341 /// names.
247 /// 342 ///
248 /// The set of parsed named arguments is returned. 343 /// The set of parsed named arguments is returned.
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
305 buffer.write("${positional + optional} argument"); 400 buffer.write("${positional + optional} argument");
306 if (positional > 1) buffer.write("s"); 401 if (positional > 1) buffer.write("s");
307 buffer.write("."); 402 buffer.write(".");
308 throw new SourceSpanFormatException( 403 throw new SourceSpanFormatException(
309 buffer.toString(), _spanFor(arguments)); 404 buffer.toString(), _spanFor(arguments));
310 } 405 }
311 406
312 return namedValues; 407 return namedValues;
313 } 408 }
314 409
410 /// Parses a Map literal.
411 ///
412 /// By default, returns [Expression] keys and values. These can be overridden
413 /// with the [key] and [value] parameters.
414 Map _parseMap(Expression expression, {key(Expression expression),
415 value(Expression expression)}) {
416 if (key == null) key = (expression) => expression;
417 if (value == null) value = (expression) => expression;
418
419 if (expression is! MapLiteral) {
420 throw new SourceSpanFormatException(
421 "Expected a Map.", _spanFor(expression));
422 }
423
424 var map = expression as MapLiteral;
425 if (map.constKeyword == null) {
426 throw new SourceSpanFormatException(
427 "Map literals must be const.", _spanFor(map));
428 }
429
430 return new Map.fromIterable(map.entries,
431 key: (entry) => key(entry.key),
432 value: (entry) => value(entry.value));
433 }
434
435 /// Parses a List literal.
436 List<Expression> _parseList(Expression expression) {
437 if (expression is! ListLiteral) {
438 throw new SourceSpanFormatException(
439 "Expected a List.", _spanFor(expression));
440 }
441
442 var list = expression as ListLiteral;
443 if (list.constKeyword == null) {
444 throw new SourceSpanFormatException(
445 "List literals must be const.", _spanFor(list));
446 }
447
448 return list.elements;
449 }
450
315 /// Parses a constant number literal. 451 /// Parses a constant number literal.
316 num _parseNum(Expression expression) { 452 num _parseNum(Expression expression) {
317 if (expression is IntegerLiteral) return expression.value; 453 if (expression is IntegerLiteral) return expression.value;
318 if (expression is DoubleLiteral) return expression.value; 454 if (expression is DoubleLiteral) return expression.value;
319 throw new SourceSpanFormatException( 455 throw new SourceSpanFormatException(
320 "Expected a number.", _spanFor(expression)); 456 "Expected a number.", _spanFor(expression));
321 } 457 }
322 458
323 /// Parses a constant int literal. 459 /// Parses a constant int literal.
324 int _parseInt(Expression expression) { 460 int _parseInt(Expression expression) {
(...skipping 10 matching lines...) Expand all
335 } 471 }
336 472
337 /// Creates a [SourceSpan] for [node]. 473 /// Creates a [SourceSpan] for [node].
338 SourceSpan _spanFor(AstNode node) { 474 SourceSpan _spanFor(AstNode node) {
339 // Load a SourceFile from scratch here since we're only ever going to emit 475 // Load a SourceFile from scratch here since we're only ever going to emit
340 // one error per file anyway. 476 // one error per file anyway.
341 var contents = new File(_path).readAsStringSync(); 477 var contents = new File(_path).readAsStringSync();
342 return new SourceFile(contents, url: p.toUri(_path)) 478 return new SourceFile(contents, url: p.toUri(_path))
343 .span(node.offset, node.end); 479 .span(node.offset, node.end);
344 } 480 }
481
482 /// Runs [fn] and contextualizes any [SourceSpanFormatException]s that occur
483 /// in it relative to [literal].
484 _contextualize(StringLiteral literal, fn()) {
485 try {
486 return fn();
487 } on SourceSpanFormatException catch (error) {
488 var file = new SourceFile(new File(_path).readAsStringSync(),
489 url: p.toUri(_path));
490 var span = contextualizeSpan(error.span, literal, file);
491 if (span == null) rethrow;
492 throw new SourceSpanFormatException(error.message, span);
493 }
494 }
345 } 495 }
OLDNEW
« no previous file with comments | « lib/src/runner/loader.dart ('k') | lib/src/utils.dart » ('j') | lib/src/utils.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698