OLD | NEW |
1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2017, 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 import 'dart:io'; | 5 import 'dart:io'; |
6 | 6 |
| 7 import 'package:path/path.dart' as p; |
| 8 |
7 import 'environment.dart'; | 9 import 'environment.dart'; |
8 import 'expectation.dart'; | 10 import 'expectation.dart'; |
9 import 'path.dart'; | 11 import 'src/expression.dart'; |
10 import 'status_expression.dart'; | |
11 | 12 |
12 /// Matches the header that begins a new section, like: | 13 /// Matches the header that begins a new section, like: |
13 /// | 14 /// |
14 /// [ $compiler == dart2js && $minified ] | 15 /// [ $compiler == dart2js && $minified ] |
15 final _sectionPattern = new RegExp(r"^\[(.+?)\]"); | 16 final _sectionPattern = new RegExp(r"^\[(.+?)\]"); |
16 | 17 |
17 /// Matches an entry that defines the status for a path in the current section, | 18 /// Matches an entry that defines the status for a path in the current section, |
18 /// like: | 19 /// like: |
19 /// | 20 /// |
20 /// some/path/to/some_test: Pass || Fail | 21 /// some/path/to/some_test: Pass || Fail |
(...skipping 18 matching lines...) Expand all Loading... |
39 /// comma-separated list of [Expectation]s. The path may point to an individual | 40 /// comma-separated list of [Expectation]s. The path may point to an individual |
40 /// file, or a directory, in which case it applies to all files under that path. | 41 /// file, or a directory, in which case it applies to all files under that path. |
41 /// | 42 /// |
42 /// Entries may also appear before any section header, in which case they | 43 /// Entries may also appear before any section header, in which case they |
43 /// always apply. | 44 /// always apply. |
44 class StatusFile { | 45 class StatusFile { |
45 final String _path; | 46 final String _path; |
46 final List<StatusSection> sections = []; | 47 final List<StatusSection> sections = []; |
47 | 48 |
48 /// Parses the status file at [_path]. | 49 /// Parses the status file at [_path]. |
| 50 /// |
| 51 /// Throws a [SyntaxError] if the file could not be parsed. |
49 StatusFile.read(this._path) { | 52 StatusFile.read(this._path) { |
50 var lines = new File(_path).readAsLinesSync(); | 53 var lines = new File(_path).readAsLinesSync(); |
51 | 54 |
52 // The current section whose rules are being parsed. | 55 // The current section whose rules are being parsed. |
53 StatusSection section; | 56 StatusSection section; |
54 | 57 |
55 var lineNumber = 0; | 58 var lineNumber = 0; |
56 | 59 |
57 for (var line in lines) { | 60 for (var line in lines) { |
58 lineNumber++; | 61 lineNumber++; |
59 | 62 |
60 fail(String message, [List<String> errors]) { | 63 fail(String message, [List<String> errors]) { |
61 print('$message in "$_shortPath" line $lineNumber:\n$line'); | 64 throw new SyntaxError(_shortPath, lineNumber, line, message, errors); |
62 | |
63 if (errors != null) { | |
64 for (var error in errors) { | |
65 print("- ${error.replaceAll('\n', '\n ')}"); | |
66 } | |
67 } | |
68 exit(1); | |
69 } | 65 } |
70 | 66 |
71 // Strip off the comment and whitespace. | 67 // Strip off the comment and whitespace. |
72 var source = line; | 68 var source = line; |
73 var comment = ""; | 69 var comment = ""; |
74 var hashIndex = line.indexOf('#'); | 70 var hashIndex = line.indexOf('#'); |
75 if (hashIndex >= 0) { | 71 if (hashIndex >= 0) { |
76 source = line.substring(0, hashIndex); | 72 source = line.substring(0, hashIndex); |
77 comment = line.substring(hashIndex); | 73 comment = line.substring(hashIndex); |
78 } | 74 } |
79 source = source.trim(); | 75 source = source.trim(); |
80 | 76 |
81 // Ignore empty (or comment-only) lines. | 77 // Ignore empty (or comment-only) lines. |
82 if (source.isEmpty) continue; | 78 if (source.isEmpty) continue; |
83 | 79 |
84 // See if we are starting a new section. | 80 // See if we are starting a new section. |
85 var match = _sectionPattern.firstMatch(source); | 81 var match = _sectionPattern.firstMatch(source); |
86 if (match != null) { | 82 if (match != null) { |
87 try { | 83 try { |
88 var condition = Expression.parse(match[1].trim()); | 84 section = new StatusSection(Expression.parse(match[1].trim())); |
89 | |
90 var errors = <String>[]; | |
91 condition.validate(errors); | |
92 | |
93 if (errors.isNotEmpty) { | |
94 var s = errors.length > 1 ? "s" : ""; | |
95 fail('Validation error$s', errors); | |
96 } | |
97 | |
98 section = new StatusSection(condition); | |
99 sections.add(section); | 85 sections.add(section); |
100 } on FormatException { | 86 } on FormatException { |
101 fail("Status expression syntax error"); | 87 fail("Status expression syntax error"); |
102 } | 88 } |
103 continue; | 89 continue; |
104 } | 90 } |
105 | 91 |
106 // Otherwise, it should be a new entry under the current section. | 92 // Otherwise, it should be a new entry under the current section. |
107 match = _entryPattern.firstMatch(source); | 93 match = _entryPattern.firstMatch(source); |
108 if (match != null) { | 94 if (match != null) { |
(...skipping 19 matching lines...) Expand all Loading... |
128 } | 114 } |
129 | 115 |
130 section.entries.add(new StatusEntry(path, expectations, issue)); | 116 section.entries.add(new StatusEntry(path, expectations, issue)); |
131 continue; | 117 continue; |
132 } | 118 } |
133 | 119 |
134 fail("Unrecognized input"); | 120 fail("Unrecognized input"); |
135 } | 121 } |
136 } | 122 } |
137 | 123 |
| 124 /// Validates that the variables and values used in all of the section |
| 125 /// condition expressions are defined in [environment]. |
| 126 /// |
| 127 /// Throws a [SyntaxError] on the first found error. |
| 128 void validate(Environment environment) { |
| 129 // TODO(rnystrom): It would be more useful if it reported all of the errors |
| 130 // instead of stopping on the first. |
| 131 for (var section in sections) { |
| 132 if (section._condition == null) continue; |
| 133 |
| 134 var errors = <String>[]; |
| 135 section._condition.validate(environment, errors); |
| 136 |
| 137 if (errors.isNotEmpty) { |
| 138 var s = errors.length > 1 ? "s" : ""; |
| 139 throw new SyntaxError(_shortPath, section.lineNumber, |
| 140 "[ ${section._condition} ]", 'Validation error$s', errors); |
| 141 } |
| 142 } |
| 143 } |
| 144 |
138 /// Gets the path to this status file relative to the Dart repo root. | 145 /// Gets the path to this status file relative to the Dart repo root. |
139 String get _shortPath { | 146 String get _shortPath { |
140 var repoRoot = new Path(Platform.script | 147 var repoRoot = p.join(p.dirname(p.fromUri(Platform.script)), "../../../"); |
141 .toFilePath(windows: Platform.operatingSystem == "windows")) | 148 return p.normalize(p.relative(_path, from: repoRoot)); |
142 .join(new Path("../../../../")); | |
143 return new Path(_path).relativeTo(repoRoot).toString(); | |
144 } | 149 } |
145 | 150 |
146 /// Returns the issue number embedded in [comment] or `null` if there is none. | 151 /// Returns the issue number embedded in [comment] or `null` if there is none. |
147 int _issueNumber(String comment) { | 152 int _issueNumber(String comment) { |
148 var match = _issuePattern.firstMatch(comment); | 153 var match = _issuePattern.firstMatch(comment); |
149 if (match == null) return null; | 154 if (match == null) return null; |
150 | 155 |
151 return int.parse(match[1]); | 156 return int.parse(match[1]); |
152 } | 157 } |
153 | 158 |
154 String toString() { | 159 String toString() { |
155 var buffer = new StringBuffer(); | 160 var buffer = new StringBuffer(); |
156 for (var section in sections) { | 161 for (var section in sections) { |
157 buffer.writeln("[${section._condition}]"); | 162 buffer.writeln("[ ${section._condition} ]"); |
158 | 163 |
159 for (var entry in section.entries) { | 164 for (var entry in section.entries) { |
160 buffer.write("${entry.path}: ${entry.expectations.join(', ')}"); | 165 buffer.write("${entry.path}: ${entry.expectations.join(', ')}"); |
161 if (entry.issue != null) buffer.write(" # Issue ${entry.issue}"); | 166 if (entry.issue != null) buffer.write(" # Issue ${entry.issue}"); |
162 buffer.writeln(); | 167 buffer.writeln(); |
163 } | 168 } |
164 | 169 |
165 buffer.writeln(); | 170 buffer.writeln(); |
166 } | 171 } |
167 | 172 |
(...skipping 21 matching lines...) Expand all Loading... |
189 } | 194 } |
190 | 195 |
191 /// Describes the test status of the file or files at a given path. | 196 /// Describes the test status of the file or files at a given path. |
192 class StatusEntry { | 197 class StatusEntry { |
193 final String path; | 198 final String path; |
194 final List<Expectation> expectations; | 199 final List<Expectation> expectations; |
195 final int issue; | 200 final int issue; |
196 | 201 |
197 StatusEntry(this.path, this.expectations, this.issue); | 202 StatusEntry(this.path, this.expectations, this.issue); |
198 } | 203 } |
| 204 |
| 205 /// Error thrown when a parse or validation error occurs in a [StatusFile]. |
| 206 class SyntaxError implements Exception { |
| 207 final String file; |
| 208 final int lineNumber; |
| 209 final String line; |
| 210 final String message; |
| 211 final List<String> errors; |
| 212 |
| 213 SyntaxError(this.file, this.lineNumber, this.line, this.message, this.errors); |
| 214 |
| 215 String toString() { |
| 216 var buffer = new StringBuffer(); |
| 217 buffer.writeln('$message in "$file" line $lineNumber:'); |
| 218 buffer.writeln(line); |
| 219 |
| 220 if (errors != null) { |
| 221 for (var error in errors) { |
| 222 buffer.writeln("- ${error.replaceAll('\n', '\n ')}"); |
| 223 } |
| 224 } |
| 225 |
| 226 return buffer.toString().trimRight(); |
| 227 } |
| 228 } |
OLD | NEW |