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

Side by Side Diff: tools/testing/dart/status_file_parser.dart

Issue 2875203005: Refactor and clean up the status file parsing code. (Closed)
Patch Set: Dartfmt. Created 3 years, 7 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 | « tools/testing/dart/status_file.dart ('k') | tools/testing/dart/summary_report.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
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.
4
5 library status_file_parser;
6
7 import "dart:async";
8 import "dart:convert" show LineSplitter, UTF8;
9 import "dart:io";
10
11 import "path.dart";
12 import "status_expression.dart";
13
14 typedef void Action();
15
16 class Expectation {
17 // Possible outcomes of running a test.
18 static Expectation PASS = byName('Pass');
19 static Expectation CRASH = byName('Crash');
20 static Expectation TIMEOUT = byName('Timeout');
21 static Expectation FAIL = byName('Fail');
22
23 // Special 'FAIL' cases
24 static Expectation RUNTIME_ERROR = byName('RuntimeError');
25 static Expectation COMPILETIME_ERROR = byName('CompileTimeError');
26 static Expectation MISSING_RUNTIME_ERROR = byName('MissingRuntimeError');
27 static Expectation MISSING_COMPILETIME_ERROR =
28 byName('MissingCompileTimeError');
29 static Expectation STATIC_WARNING = byName('StaticWarning');
30 static Expectation MISSING_STATIC_WARNING = byName('MissingStaticWarning');
31 static Expectation PUB_GET_ERROR = byName('PubGetError');
32 static Expectation NON_UTF8_ERROR = byName('NonUtf8Output');
33
34 // Special 'CRASH' cases
35 static Expectation DARTK_CRASH = byName('DartkCrash');
36
37 // Special 'TIMEOUT' cases
38 static Expectation DARTK_TIMEOUT = byName('DartkTimeout');
39
40 // Special 'COMPILETIME_ERROR'
41 static Expectation DARTK_COMPILETIME_ERROR = byName('DartkCompileTimeError');
42
43 // "meta expectations"
44 static Expectation OK = byName('Ok');
45 static Expectation SLOW = byName('Slow');
46 static Expectation SKIP = byName('Skip');
47 static Expectation SKIP_SLOW = byName('SkipSlow');
48 static Expectation SKIP_BY_DESIGN = byName('SkipByDesign');
49
50 // Can be returned by the test runner to say the result should be ignored,
51 // and assumed to meet the expectations, due to an infrastructure failure.
52 // Do not place in status files.
53 static Expectation IGNORE = byName('Ignore');
54
55 static Expectation byName(String name) {
56 _initialize();
57 name = name.toLowerCase();
58 if (!_AllExpectations.containsKey(name)) {
59 throw new Exception("Expectation.byName(name='$name'): Invalid name.");
60 }
61 return _AllExpectations[name];
62 }
63
64 // Keep a map of all possible Expectation objects, initialized lazily.
65 static Map<String, Expectation> _AllExpectations;
66 static void _initialize() {
67 if (_AllExpectations == null) {
68 _AllExpectations = new Map<String, Expectation>();
69
70 Expectation build(String prettyName,
71 {Expectation group, bool isMetaExpectation: false}) {
72 var expectation = new Expectation._(prettyName,
73 group: group, isMetaExpectation: isMetaExpectation);
74 assert(!_AllExpectations.containsKey(expectation.name));
75 return _AllExpectations[expectation.name] = expectation;
76 }
77
78 var fail = build("Fail");
79 var crash = build("Crash");
80 var timeout = build("Timeout");
81 build("Pass");
82
83 var compileError = build("CompileTimeError", group: fail);
84 build("MissingCompileTimeError", group: fail);
85 build("MissingRuntimeError", group: fail);
86 build("RuntimeError", group: fail);
87 build("NonUtf8Output", group: fail);
88
89 // Dartk sub expectations
90 build("DartkCrash", group: crash);
91 build("DartkTimeout", group: timeout);
92 build("DartkCompileTimeError", group: compileError);
93
94 build("MissingStaticWarning", group: fail);
95 build("StaticWarning", group: fail);
96
97 build("PubGetError", group: fail);
98
99 var skip = build("Skip", isMetaExpectation: true);
100 build("SkipByDesign", isMetaExpectation: true);
101 build("SkipSlow", group: skip, isMetaExpectation: true);
102 build("Ok", isMetaExpectation: true);
103 build("Slow", isMetaExpectation: true);
104 build("Ignore");
105 }
106 }
107
108 final String prettyName;
109 final String name;
110 final Expectation group;
111 // Indicates whether this expectation cannot be a test outcome (i.e. it is a
112 // "meta marker").
113 final bool isMetaExpectation;
114
115 Expectation._(this.prettyName,
116 {Expectation this.group: null, bool this.isMetaExpectation: false})
117 : name = prettyName.toLowerCase();
118
119 bool canBeOutcomeOf(Expectation expectation) {
120 Expectation outcome = this;
121 if (outcome == IGNORE) return true;
122 while (outcome != null) {
123 if (outcome == expectation) {
124 return true;
125 }
126 outcome = outcome.group;
127 }
128 return false;
129 }
130
131 String toString() => prettyName;
132 }
133
134 final RegExp SplitComment = new RegExp("^([^#]*)(#.*)?\$");
135 final RegExp HeaderPattern = new RegExp(r"^\[([^\]]+)\]");
136 final RegExp RulePattern = new RegExp(r"\s*([^: ]*)\s*:(.*)");
137 final RegExp IssueNumberPattern = new RegExp("[Ii]ssue ([0-9]+)");
138
139 class StatusFile {
140 final Path location;
141
142 StatusFile(this.location);
143 }
144
145 // TODO(whesse): Implement configuration_info library that contains data
146 // structures for test configuration, including Section.
147 class Section {
148 final StatusFile statusFile;
149
150 final BooleanExpression condition;
151 final List<TestRule> testRules;
152 final int lineNumber;
153
154 Section.always(this.statusFile, this.lineNumber)
155 : condition = null,
156 testRules = new List<TestRule>();
157 Section(this.statusFile, this.condition, this.lineNumber)
158 : testRules = new List<TestRule>();
159
160 bool isEnabled(environment) =>
161 condition == null || condition.evaluate(environment);
162
163 String toString() {
164 return "Section: $condition";
165 }
166 }
167
168 Future<TestExpectations> ReadTestExpectations(
169 List<String> statusFilePaths, Map<String, String> environment) {
170 var testExpectations = new TestExpectations();
171 return Future.wait(statusFilePaths.map((String statusFile) {
172 return ReadTestExpectationsInto(testExpectations, statusFile, environment);
173 })).then((_) => testExpectations);
174 }
175
176 Future ReadTestExpectationsInto(TestExpectations expectations,
177 String statusFilePath, Map<String, String> environment) {
178 var completer = new Completer<Null>();
179 var sections = <Section>[];
180
181 void sectionsRead() {
182 for (Section section in sections) {
183 if (section.isEnabled(environment)) {
184 for (var rule in section.testRules) {
185 expectations.addRule(rule, environment);
186 }
187 }
188 }
189 completer.complete();
190 }
191
192 ReadConfigurationInto(new Path(statusFilePath), sections, sectionsRead);
193 return completer.future;
194 }
195
196 void ReadConfigurationInto(Path path, List<Section> sections, Action onDone) {
197 StatusFile statusFile = new StatusFile(path);
198 File file = new File(path.toNativePath());
199 if (!file.existsSync()) {
200 throw new Exception('Cannot find test status file $path');
201 }
202 int lineNumber = 0;
203 Stream<String> lines =
204 file.openRead().transform(UTF8.decoder).transform(new LineSplitter());
205
206 Section currentSection = new Section.always(statusFile, -1);
207 sections.add(currentSection);
208
209 lines.listen((String line) {
210 lineNumber++;
211 Match match = SplitComment.firstMatch(line);
212 line = (match == null) ? "" : match[1];
213 line = line.trim();
214 if (line.isEmpty) return;
215
216 // Extract the comment to get the issue number if needed.
217 String comment = (match == null || match[2] == null) ? "" : match[2];
218
219 match = HeaderPattern.firstMatch(line);
220 if (match != null) {
221 String condition_string = match[1].trim();
222 List<String> tokens = new Tokenizer(condition_string).tokenize();
223 ExpressionParser parser = new ExpressionParser(new Scanner(tokens));
224 currentSection =
225 new Section(statusFile, parser.parseBooleanExpression(), lineNumber);
226 sections.add(currentSection);
227 return;
228 }
229
230 match = RulePattern.firstMatch(line);
231 if (match != null) {
232 String name = match[1].trim();
233 // TODO(whesse): Handle test names ending in a wildcard (*).
234 String expression_string = match[2].trim();
235 List<String> tokens = new Tokenizer(expression_string).tokenize();
236 SetExpression expression =
237 new ExpressionParser(new Scanner(tokens)).parseSetExpression();
238
239 // Look for issue number in comment.
240 String issueString = null;
241 match = IssueNumberPattern.firstMatch(comment);
242 if (match != null) {
243 issueString = match[1];
244 if (issueString == null) issueString = match[2];
245 }
246 int issue = issueString != null ? int.parse(issueString) : null;
247 currentSection.testRules
248 .add(new TestRule(name, expression, issue, lineNumber));
249 return;
250 }
251
252 print("unmatched line: $line");
253 }, onDone: onDone);
254 }
255
256 class TestRule {
257 String name;
258 SetExpression expression;
259 int issue;
260 int lineNumber;
261
262 TestRule(this.name, this.expression, this.issue, this.lineNumber);
263
264 bool get hasIssue => issue != null;
265
266 String toString() => 'TestRule($name, $expression, $issue)';
267 }
268
269 class TestExpectations {
270 // Only create one copy of each Set<Expectation>.
271 // We just use .toString as a key, so we may make a few
272 // sets that only differ in their toString element order.
273 static Map<String, Set<Expectation>> _cachedSets = {};
274
275 Map<String, Set<Expectation>> _map;
276 bool _preprocessed = false;
277 Map<String, RegExp> _regExpCache;
278 Map<String, List<RegExp>> _keyToRegExps;
279
280 /**
281 * Create a TestExpectations object. See the [expectations] method
282 * for an explanation of matching.
283 */
284 TestExpectations() : _map = {};
285
286 /**
287 * Add a rule to the expectations.
288 */
289 void addRule(TestRule testRule, environment) {
290 // Once we have started using the expectations we cannot add more
291 // rules.
292 if (_preprocessed) {
293 throw "TestExpectations.addRule: cannot add more rules";
294 }
295 var names = testRule.expression.evaluate(environment);
296 var expectations = names.map((name) => Expectation.byName(name));
297 _map
298 .putIfAbsent(testRule.name, () => new Set<Expectation>())
299 .addAll(expectations);
300 }
301
302 /**
303 * Compute the expectations for a test based on the filename.
304 *
305 * For every (key, expectation) pair. Match the key with the file
306 * name. Return the union of the expectations for all the keys
307 * that match.
308 *
309 * Normal matching splits the key and the filename into path
310 * components and checks that the anchored regular expression
311 * "^$keyComponent\$" matches the corresponding filename component.
312 */
313 Set<Expectation> expectations(String filename) {
314 var result = new Set<Expectation>();
315 var splitFilename = filename.split('/');
316
317 // Create mapping from keys to list of RegExps once and for all.
318 _preprocessForMatching();
319
320 _map.forEach((key, Set<Expectation> expectations) {
321 List<RegExp> regExps = _keyToRegExps[key];
322 if (regExps.length > splitFilename.length) return;
323 for (var i = 0; i < regExps.length; i++) {
324 if (!regExps[i].hasMatch(splitFilename[i])) return;
325 }
326 // If all components of the status file key matches the filename
327 // add the expectations to the result.
328 result.addAll(expectations);
329 });
330
331 // If no expectations were found the expectation is that the test
332 // passes.
333 if (result.isEmpty) {
334 result.add(Expectation.PASS);
335 }
336 return _cachedSets.putIfAbsent(result.toString(), () => result);
337 }
338
339 // Preprocess the expectations for matching against
340 // filenames. Generate lists of regular expressions once and for all
341 // for each key.
342 void _preprocessForMatching() {
343 if (_preprocessed) return;
344
345 _keyToRegExps = {};
346 _regExpCache = {};
347
348 _map.forEach((key, expectations) {
349 if (_keyToRegExps[key] != null) return;
350 var splitKey = key.split('/');
351 var regExps = new List<RegExp>(splitKey.length);
352 for (var i = 0; i < splitKey.length; i++) {
353 var component = splitKey[i];
354 var regExp = _regExpCache[component];
355 if (regExp == null) {
356 var pattern = "^${splitKey[i]}\$".replaceAll('*', '.*');
357 regExp = new RegExp(pattern);
358 _regExpCache[component] = regExp;
359 }
360 regExps[i] = regExp;
361 }
362 _keyToRegExps[key] = regExps;
363 });
364
365 _regExpCache = null;
366 _preprocessed = true;
367 }
368 }
OLDNEW
« no previous file with comments | « tools/testing/dart/status_file.dart ('k') | tools/testing/dart/summary_report.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698