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

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

Issue 841193003: cleanup to tools/testing/dart (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: one last bit Created 5 years, 11 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 | Annotate | Revision Log
« no previous file with comments | « tools/testing/dart/lib/utils.dart ('k') | tools/testing/dart/path.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 multitest;
6
7 import "dart:async";
8 import "dart:io";
9
10 import "path.dart";
11 import "test_suite.dart";
12 import "utils.dart";
13
14 // Multitests are Dart test scripts containing lines of the form
15 // " [some dart code] /// [key]: [error type]"
16 //
17 // For each key in the file, a new test file is made containing all
18 // the normal lines of the file, and all of the multitest lines containing
19 // that key, in the same order as in the source file. The new test is expected
20 // to pass if the error type listed is 'ok', or to fail if there is an error
21 // type of type 'compile-time error', 'runtime error', 'static type warning', or
22 // 'dynamic type error'. The type error tests fail only in checked mode.
23 // There is also a test created from only the untagged lines of the file,
24 // with key "none", which is expected to pass. This library extracts these
25 // tests, writes them into a temporary directory, and passes them to the test
26 // runner. These tests may be referred to in the status files with the
27 // pattern [test name]/[key].
28 //
29 // For example: file I_am_a_multitest.dart
30 // aaa
31 // bbb /// 02: runtime error
32 // ccc /// 02: continued
33 // ddd /// 07: static type warning
34 // eee /// 10: ok
35 // fff
36 //
37 // should create four tests:
38 // I_am_a_multitest_none.dart
39 // aaa
40 // fff
41 //
42 // I_am_a_multitest_02.dart
43 // aaa
44 // bbb /// 02: runtime error
45 // ccc /// 02: continued
46 // fff
47 //
48 // I_am_a_multitest_07.dart
49 // aaa
50 // ddd /// 07: static type warning
51 // fff
52 //
53 // and I_am_a_multitest_10.dart
54 // aaa
55 // eee /// 10: ok
56 // fff
57 //
58 // Note that it is possible to indicate more than one acceptable outcome
59 // in the case of dynamic and static type warnings
60 // aaa
61 // ddd /// 07: static type warning, dynamic type error
62 // fff
63
64 void ExtractTestsFromMultitest(Path filePath,
65 Map<String, String> tests,
66 Map<String, Set<String>> outcomes) {
67 // Read the entire file into a byte buffer and transform it to a
68 // String. This will treat the file as ascii but the only parts
69 // we are interested in will be ascii in any case.
70 List bytes = new File(filePath.toNativePath()).readAsBytesSync();
71 String contents = decodeUtf8(bytes);
72 int first_newline = contents.indexOf('\n');
73 final String line_separator =
74 (first_newline == 0 || contents[first_newline - 1] != '\r')
75 ? '\n'
76 : '\r\n';
77 List<String> lines = contents.split(line_separator);
78 if (lines.last == '') lines.removeLast();
79 bytes = null;
80 contents = null;
81 Set<String> validMultitestOutcomes = new Set<String>.from(
82 ['ok', 'compile-time error', 'runtime error',
83 'static type warning', 'dynamic type error',
84 'checked mode compile-time error']);
85
86 List<String> testTemplate = new List<String>();
87 testTemplate.add(
88 '// Test created from multitest named ${filePath.toNativePath()}.');
89 // Create the set of multitests, which will have a new test added each
90 // time we see a multitest line with a new key.
91 Map<String, List<String>> testsAsLines = new Map<String, List<String>>();
92
93 int lineCount = 0;
94 for (String line in lines) {
95 lineCount++;
96 var annotation = new _Annotation.from(line);
97 if (annotation != null) {
98 testsAsLines.putIfAbsent(annotation.key,
99 () => new List<String>.from(testTemplate)).add(line);
100 outcomes.putIfAbsent(annotation.key,
101 () => new Set<String>());
102 if (annotation.rest == 'continued') {
103 continue;
104 } else {
105 for (String nextOutcome in annotation.outcomesList) {
106 if (validMultitestOutcomes.contains(nextOutcome)) {
107 outcomes[annotation.key].add(nextOutcome);
108 } else {
109 DebugLogger.warning(
110 "Warning: Invalid test directive '$nextOutcome' on line "
111 "${lineCount}:\n${annotation.rest} ");
112 }
113 }
114 }
115 } else {
116 testTemplate.add(line);
117 for (var test in testsAsLines.values) test.add(line);
118 }
119 }
120
121 var keysToDelete = [];
122 // Check that every key (other than the none case) has at least one outcome
123 for (var outcomeKey in outcomes.keys) {
124 if (outcomeKey != 'none' && outcomes[outcomeKey].isEmpty) {
125 DebugLogger.warning(
126 "Warning: Test ${outcomeKey} has no valid annotated outcomes.\n"
127 "Expected one of: ${validMultitestOutcomes.toString()}");
128 // If this multitest doesn't have an outcome, mark the multitest for
129 // deletion.
130 keysToDelete.add(outcomeKey);
131 }
132 }
133 // If a key/multitest was marked for deletion, do the necessary cleanup.
134 keysToDelete.forEach((key) => outcomes.remove(key));
135 keysToDelete.forEach((key) => testsAsLines.remove(key));
136
137 // Add the template, with no multitest lines, as a test with key 'none'.
138 testsAsLines['none'] = testTemplate;
139 outcomes['none'] = new Set<String>();
140
141 // Copy all the tests into the output map tests, as multiline strings.
142 for (String key in testsAsLines.keys) {
143 tests[key] = testsAsLines[key].join(line_separator) + line_separator;
144 }
145 }
146
147 // Represents a mutlitest annotation in the special /// comment.
148 class _Annotation {
149 String key;
150 String rest;
151 List<String> outcomesList;
152 _Annotation() {}
153 factory _Annotation.from(String line) {
154 // Do an early return with "null" if this is not a valid multitest
155 // annotation.
156 if (!line.contains('///')) {
157 return null;
158 }
159 var parts = line
160 .split('///')[1]
161 .split(':')
162 .map((s) => s.trim())
163 .where((s) => s.length > 0)
164 .toList();
165 if (parts.length <= 1) {
166 return null;
167 }
168
169 var annotation = new _Annotation();
170 annotation.key = parts[0];
171 annotation.rest = parts[1];
172 annotation.outcomesList = annotation.rest.split(',')
173 .map((s) => s.trim()).toList();
174 return annotation;
175 }
176 }
177
178 // Find all relative imports and copy them into the dir that contains
179 // the generated tests.
180 Set<String> _findAllRelativeImports(Path topLibrary) {
181 Set<Path> toSearch = new Set<Path>.from([topLibrary]);
182 Set<String> foundImports = new Set<String>();
183 Path libraryDir = topLibrary.directoryPath;
184 RegExp relativeImportRegExp = new RegExp(
185 '^(?:@.*\\s+)?' // Allow for a meta-data annotation.
186 '(import|part)'
187 '\\s+["\']'
188 '(?!(dart:|dart-ext:|package:|/))' // Look-ahead: not in package.
189 '([^"\']*)' // The path to the imported file.
190 '["\']');
191 while (!toSearch.isEmpty) {
192 var thisPass = toSearch;
193 toSearch = new Set<Path>();
194 for (Path filename in thisPass) {
195 File f = new File(filename.toNativePath());
196 for (String line in f.readAsLinesSync()) {
197 Match match = relativeImportRegExp.firstMatch(line);
198 if (match != null) {
199 Path relativePath = new Path(match.group(3));
200 if (foundImports.contains(relativePath.toString())) {
201 continue;
202 }
203 if (relativePath.toString().contains('..')) {
204 // This is just for safety reasons, we don't want
205 // to unintentionally clobber files relative to the destination
206 // dir when copying them ove.
207 print("relative paths containing .. are not allowed.");
208 exit(1);
209 }
210 foundImports.add(relativePath.toString());
211 toSearch.add(libraryDir.join(relativePath));
212 }
213 }
214 }
215 }
216 return foundImports;
217 }
218
219 Future doMultitest(Path filePath, String outputDir, Path suiteDir,
220 CreateTest doTest) {
221 void writeFile(String filepath, String content) {
222 final File file = new File(filepath);
223
224 if (file.existsSync()) {
225 var oldContent = file.readAsStringSync();
226 if (oldContent == content) {
227 // Don't write to the file if the content is the same
228 return;
229 }
230 }
231 file.writeAsStringSync(content);
232 }
233
234 // Each new test is a single String value in the Map tests.
235 Map<String, String> tests = new Map<String, String>();
236 Map<String, Set<String>> outcomes = new Map<String, Set<String>>();
237 ExtractTestsFromMultitest(filePath, tests, outcomes);
238
239 Path sourceDir = filePath.directoryPath;
240 Path targetDir = CreateMultitestDirectory(outputDir, suiteDir);
241 assert(targetDir != null);
242
243 // Copy all the relative imports of the multitest.
244 Set<String> importsToCopy = _findAllRelativeImports(filePath);
245 List<Future> futureCopies = [];
246 for (String relativeImport in importsToCopy) {
247 Path importPath = new Path(relativeImport);
248 // Make sure the target directory exists.
249 Path importDir = importPath.directoryPath;
250 if (!importDir.isEmpty) {
251 TestUtils.mkdirRecursive(targetDir, importDir);
252 }
253 // Copy file.
254 futureCopies.add(TestUtils.copyFile(sourceDir.join(importPath),
255 targetDir.join(importPath)));
256 }
257
258 // Wait until all imports are copied before scheduling test cases.
259 return Future.wait(futureCopies).then((_) {
260 String baseFilename = filePath.filenameWithoutExtension;
261 for (String key in tests.keys) {
262 final Path multitestFilename =
263 targetDir.append('${baseFilename}_$key.dart');
264 writeFile(multitestFilename.toNativePath(), tests[key]);
265 Set<String> outcome = outcomes[key];
266 bool hasStaticWarning = outcome.contains('static type warning');
267 bool hasRuntimeErrors = outcome.contains('runtime error');
268 bool hasCompileError = outcome.contains('compile-time error');
269 bool isNegativeIfChecked = outcome.contains('dynamic type error');
270 bool hasCompileErrorIfChecked =
271 outcome.contains('checked mode compile-time error');
272 doTest(multitestFilename,
273 filePath,
274 hasCompileError,
275 hasRuntimeErrors,
276 isNegativeIfChecked: isNegativeIfChecked,
277 hasCompileErrorIfChecked: hasCompileErrorIfChecked,
278 hasStaticWarning: hasStaticWarning,
279 multitestKey: key);
280 }
281
282 return null;
283 });
284 }
285
286
287 Path CreateMultitestDirectory(String outputDir, Path suiteDir) {
288 Directory generatedTestDir = new Directory('$outputDir/generated_tests');
289 if (!new Directory(outputDir).existsSync()) {
290 new Directory(outputDir).createSync();
291 }
292 if (!generatedTestDir.existsSync()) {
293 generatedTestDir.createSync();
294 }
295 var split = suiteDir.segments();
296 if (split.last == 'src') {
297 // TODO(sigmund): remove this once all tests are migrated to use
298 // TestSuite.forDirectory.
299 split.removeLast();
300 }
301 String path = '${generatedTestDir.path}/${split.last}';
302 Directory dir = new Directory(path);
303 if (!dir.existsSync()) {
304 dir.createSync();
305 }
306 return new Path(new File(path).absolute.path);
307 }
OLDNEW
« no previous file with comments | « tools/testing/dart/lib/utils.dart ('k') | tools/testing/dart/path.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698