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

Side by Side Diff: lib/src/runner/parse_metadata.dart

Issue 1086213002: Support a @Timeout annotation. (Closed) Base URL: git@github.com:dart-lang/test@master
Patch Set: CHANGELOG + README 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
« no previous file with comments | « lib/src/runner/loader.dart ('k') | lib/src/runner/vm/isolate_listener.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 '../frontend/timeout.dart';
15 import '../util/dart.dart'; 16 import '../util/dart.dart';
16 17
18 /// The valid argument names for [new Duration].
19 const _durationArgs = const [
20 "days",
21 "hours",
22 "minutes",
23 "seconds",
24 "milliseconds",
25 "microseconds"
26 ];
27
17 /// Parse the test metadata for the test file at [path]. 28 /// Parse the test metadata for the test file at [path].
18 /// 29 ///
19 /// Throws an [AnalysisError] if parsing fails or a [FormatException] if the 30 /// Throws an [AnalysisError] if parsing fails or a [FormatException] if the
20 /// test annotations are incorrect. 31 /// test annotations are incorrect.
21 Metadata parseMetadata(String path) { 32 Metadata parseMetadata(String path) {
33 var timeout;
22 var testOn; 34 var testOn;
23 35
24 var contents = new File(path).readAsStringSync(); 36 var contents = new File(path).readAsStringSync();
25 var directives = parseDirectives(contents, name: path).directives; 37 var directives = parseDirectives(contents, name: path).directives;
26 var annotations = directives.isEmpty ? [] : directives.first.metadata; 38 var annotations = directives.isEmpty ? [] : directives.first.metadata;
27 39
28 // We explicitly *don't* just look for "package:test" imports here, 40 // We explicitly *don't* just look for "package:test" imports here,
29 // because it could be re-exported from another library. 41 // because it could be re-exported from another library.
30 var prefixes = directives.map((directive) { 42 var prefixes = directives.map((directive) {
31 if (directive is! ImportDirective) return null; 43 if (directive is! ImportDirective) return null;
(...skipping 17 matching lines...) Expand all
49 constructorName = identifier.identifier.name; 61 constructorName = identifier.identifier.name;
50 } else { 62 } else {
51 name = identifier is PrefixedIdentifier 63 name = identifier is PrefixedIdentifier
52 ? identifier.identifier.name 64 ? identifier.identifier.name
53 : identifier.name; 65 : identifier.name;
54 if (annotation.constructorName != null) { 66 if (annotation.constructorName != null) {
55 constructorName = annotation.constructorName.name; 67 constructorName = annotation.constructorName.name;
56 } 68 }
57 } 69 }
58 70
59 if (name != 'TestOn') continue; 71 if (name == 'TestOn') {
60 if (constructorName != null) { 72 if (testOn != null) {
61 throw new SourceSpanFormatException( 73 throw new SourceSpanFormatException(
62 'TestOn doesn\'t have a constructor named "$constructorName".', 74 "Only a single TestOn annotation may be used for a given test file." ,
63 _spanFor(identifier.identifier, path)); 75 _spanFor(annotation, path));
64 } 76 }
65 77 testOn = _parseTestOn(annotation, constructorName, path);
66 if (annotation.arguments == null) { 78 } else if (name == 'Timeout') {
67 throw new SourceSpanFormatException( 79 if (timeout != null) {
68 'TestOn takes one argument.', _spanFor(annotation, path)); 80 throw new SourceSpanFormatException(
69 } 81 "Only a single Timeout annotation may be used for a given test file. ",
70 82 _spanFor(annotation, path));
71 var args = annotation.arguments.arguments; 83 }
72 if (args.isEmpty) { 84 timeout = _parseTimeout(annotation, constructorName, path);
73 throw new SourceSpanFormatException( 85 }
74 'TestOn takes one argument.', _spanFor(annotation.arguments, path));
75 }
76
77 if (args.first is NamedExpression) {
78 throw new SourceSpanFormatException(
79 "TestOn doesn't take named parameters.", _spanFor(args.first, path));
80 }
81
82 if (args.length > 1) {
83 throw new SourceSpanFormatException(
84 "TestOn takes only one argument.",
85 _spanFor(annotation.arguments, path));
86 }
87
88 if (args.first is! StringLiteral) {
89 throw new SourceSpanFormatException(
90 "TestOn takes a String.", _spanFor(args.first, path));
91 }
92
93 if (testOn != null) {
94 throw new SourceSpanFormatException(
95 "Only a single TestOn annotation may be used for a given test file.",
96 _spanFor(annotation, path));
97 }
98
99 testOn = args.first;
100 } 86 }
101 87
102 try { 88 try {
103 return new Metadata.parse( 89 return new Metadata.parse(
104 testOn: testOn == null ? null : testOn.stringValue); 90 testOn: testOn == null ? null : testOn.stringValue,
91 timeout: timeout);
105 } on SourceSpanFormatException catch (error) { 92 } on SourceSpanFormatException catch (error) {
106 var file = new SourceFile(new File(path).readAsStringSync(), 93 var file = new SourceFile(new File(path).readAsStringSync(),
107 url: p.toUri(path)); 94 url: p.toUri(path));
108 var span = contextualizeSpan(error.span, testOn, file); 95 var span = contextualizeSpan(error.span, testOn, file);
109 if (span == null) rethrow; 96 if (span == null) rethrow;
110 throw new SourceSpanFormatException(error.message, span); 97 throw new SourceSpanFormatException(error.message, span);
111 } 98 }
112 } 99 }
113 100
101 /// Parses a `@TestOn` annotation.
102 ///
103 /// [annotation] is the annotation. [constructorName] is the name of the named
104 /// constructor for the annotation, if any. [path] is the path to the file from
105 /// which the annotation was parsed.
106 StringLiteral _parseTestOn(Annotation annotation, String constructorName,
107 String path) {
108 if (constructorName != null) {
109 throw new SourceSpanFormatException(
110 'TestOn doesn\'t have a constructor named "$constructorName".',
111 _spanFor(annotation, path));
112 }
113
114 if (annotation.arguments == null) {
115 throw new SourceSpanFormatException(
116 'TestOn takes one argument.', _spanFor(annotation, path));
117 }
118
119 var args = annotation.arguments.arguments;
120 if (args.isEmpty) {
121 throw new SourceSpanFormatException(
122 'TestOn takes one argument.', _spanFor(annotation.arguments, path));
123 }
124
125 if (args.first is NamedExpression) {
126 throw new SourceSpanFormatException(
127 "TestOn doesn't take named parameters.", _spanFor(args.first, path));
128 }
129
130 if (args.length > 1) {
131 throw new SourceSpanFormatException(
132 "TestOn takes only one argument.",
133 _spanFor(annotation.arguments, path));
134 }
135
136 if (args.first is! StringLiteral) {
137 throw new SourceSpanFormatException(
138 "TestOn takes a String.", _spanFor(args.first, path));
139 }
140
141 return args.first;
142 }
143
144 /// Parses a `@Timeout` annotation.
145 ///
146 /// [annotation] is the annotation. [constructorName] is the name of the named
147 /// constructor for the annotation, if any. [path] is the path to the file from
148 /// which the annotation was parsed.
149 Timeout _parseTimeout(Annotation annotation, String constructorName,
150 String path) {
151 if (constructorName != null && constructorName != 'factor') {
152 throw new SourceSpanFormatException(
153 'Timeout doesn\'t have a constructor named "$constructorName".',
154 _spanFor(annotation, path));
155 }
156
157 var description = 'Timeout';
158 if (constructorName != null) description += '.$constructorName';
159
160 if (annotation.arguments == null) {
161 throw new SourceSpanFormatException(
162 '$description takes one argument.', _spanFor(annotation, path));
163 }
164
165 var args = annotation.arguments.arguments;
166 if (args.isEmpty) {
167 throw new SourceSpanFormatException(
168 '$description takes one argument.',
169 _spanFor(annotation.arguments, path));
170 }
171
172 if (args.first is NamedExpression) {
173 throw new SourceSpanFormatException(
174 "$description doesn't take named parameters.",
175 _spanFor(args.first, path));
176 }
177
178 if (args.length > 1) {
179 throw new SourceSpanFormatException(
180 "$description takes only one argument.",
181 _spanFor(annotation.arguments, path));
182 }
183
184 if (constructorName == null) {
185 return new Timeout(_parseDuration(args.first, path));
186 } else {
187 return new Timeout.factor(_parseNum(args.first, path));
188 }
189 }
190
191 /// Parses a `const Duration` expression.
192 Duration _parseDuration(Expression expression, String path) {
193 if (expression is! InstanceCreationExpression) {
194 throw new SourceSpanFormatException(
195 "Expected a Duration.",
196 _spanFor(expression, path));
197 }
198
199 var constructor = expression as InstanceCreationExpression;
200 if (constructor.constructorName.type.name.name != 'Duration') {
201 throw new SourceSpanFormatException(
202 "Expected a Duration.",
203 _spanFor(constructor, path));
204 }
205
206 if (constructor.keyword.lexeme != "const") {
207 throw new SourceSpanFormatException(
208 "Duration must use a const constructor.",
209 _spanFor(constructor, path));
210 }
211
212 if (constructor.constructorName.name != null) {
213 throw new SourceSpanFormatException(
214 "Duration doesn't have a constructor named "
215 '"${constructor.constructorName}".',
216 _spanFor(constructor.constructorName, path));
217 }
218
219 var values = {};
220 var args = constructor.argumentList.arguments;
221 for (var argument in args) {
222 if (argument is! NamedExpression) {
223 throw new SourceSpanFormatException(
224 "Duration doesn't take positional arguments.",
225 _spanFor(argument, path));
226 }
227
228 var name = argument.name.label.name;
229 if (!_durationArgs.contains(name)) {
230 throw new SourceSpanFormatException(
231 'Duration doesn\'t take an argument named "$name".',
232 _spanFor(argument, path));
233 }
234
235 if (values.containsKey(name)) {
236 throw new SourceSpanFormatException(
237 'An argument named "$name" was already passed.',
238 _spanFor(argument, path));
239 }
240
241 values[name] = _parseInt(argument.expression, path);
242 }
243
244 return new Duration(
245 days: values["days"] == null ? 0 : values["days"],
246 hours: values["hours"] == null ? 0 : values["hours"],
247 minutes: values["minutes"] == null ? 0 : values["minutes"],
248 seconds: values["seconds"] == null ? 0 : values["seconds"],
249 milliseconds: values["milliseconds"] == null ? 0 : values["milliseconds"],
250 microseconds:
251 values["microseconds"] == null ? 0 : values["microseconds"]);
252 }
253
254 /// Parses a constant number literal.
255 num _parseNum(Expression expression, String path) {
256 if (expression is IntegerLiteral) return expression.value;
257 if (expression is DoubleLiteral) return expression.value;
258 throw new SourceSpanFormatException(
259 "Expected a number.", _spanFor(expression, path));
260 }
261
262 /// Parses a constant int literal.
263 int _parseInt(Expression expression, String path) {
264 if (expression is IntegerLiteral) return expression.value;
265 throw new SourceSpanFormatException(
266 "Expected an integer.", _spanFor(expression, path));
267 }
268
114 /// Creates a [SourceSpan] for [node]. 269 /// Creates a [SourceSpan] for [node].
115 SourceSpan _spanFor(AstNode node, String path) => 270 SourceSpan _spanFor(AstNode node, String path) =>
116 // Load a SourceFile from scratch here since we're only ever going to emit 271 // Load a SourceFile from scratch here since we're only ever going to emit
117 // one error per file anyway. 272 // one error per file anyway.
118 new SourceFile(new File(path).readAsStringSync(), url: p.toUri(path)) 273 new SourceFile(new File(path).readAsStringSync(), url: p.toUri(path))
119 .span(node.offset, node.end); 274 .span(node.offset, node.end);
OLDNEW
« no previous file with comments | « lib/src/runner/loader.dart ('k') | lib/src/runner/vm/isolate_listener.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698