OLD | NEW |
---|---|
(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 // The DartWrapTask generates a Dart wrapper for a test file, that has a | |
Siggi Cherem (dart-lang)
2012/08/29 20:47:35
use /** */ so we make all these comments valid dar
Siggi Cherem (dart-lang)
2012/08/30 16:46:40
note that this and the comment below are still pen
gram
2012/08/30 17:34:05
Done.
| |
6 // test Configuration customized for the options specified by the user. | |
7 class DartWrapTask extends PipelineTask { | |
8 String sourceFileTemplate; | |
9 String tempDartFileTemplate; | |
10 | |
11 DartWrapTask(this.sourceFileTemplate, this.tempDartFileTemplate); | |
12 | |
13 void execute(Path testfile, List stdout, List stderr, bool verboseLogging, | |
14 Function exitHandler) { | |
15 // Get the source test file and canonicalize the path. | |
16 var sourceName = makePathAbsolute(expandMacros(sourceFileTemplate, testfile) ); | |
Siggi Cherem (dart-lang)
2012/08/29 20:47:35
80 col
gram
2012/08/30 17:34:05
Done.
| |
17 // Get the destination file. | |
18 var destFile = expandMacros(tempDartFileTemplate, testfile); | |
19 | |
20 // Working buffer for the Dart wrapper. | |
21 StringBuffer sbuf = new StringBuffer(); | |
22 | |
23 // Add the common header stuff. | |
24 var p = new Path(sourceName); | |
25 sbuf.add(directives(p.filenameWithoutExtension, | |
26 config.unittestPath, | |
27 sourceName)); | |
28 | |
29 // Add the test configuration and determine the action function. | |
30 var action; | |
31 if (config.listTests) { | |
32 action = 'listTests'; | |
33 sbuf.add(barebonesConfig()); | |
34 sbuf.add(listTestsFunction); | |
35 sbuf.add(formatListMessageFunction(config.listFormat)); | |
36 } else if (config.listGroups) { | |
37 sbuf.add(barebonesConfig()); | |
38 sbuf.add(listGroupsFunction); | |
39 sbuf.add(formatListMessageFunction(config.listFormat)); | |
40 action = 'listGroups'; | |
41 } else { | |
42 if (config.runInBrowser) { | |
43 sbuf.add(browserTestPrintFunction); | |
44 } else { | |
45 sbuf.add(nonBrowserTestPrintFunction); | |
46 } | |
47 | |
48 if (config.includeTime) { | |
49 sbuf.add(elapsedFunction); | |
50 } else { | |
51 sbuf.add(dummyElapsedFunction); | |
52 } | |
53 sbuf.add(dumpTestResultFunction); | |
54 sbuf.add(testConfig(sourceName, | |
55 config.runInBrowser, | |
56 config.runIsolated, | |
57 config.immediateOutput, | |
58 config.produceSummary)); | |
59 // Add the isolate stuff, if applicable. | |
60 if (config.runIsolated) { | |
61 sbuf.add(runIsolateTestsFunction); | |
62 action = 'runIsolateTests'; | |
63 } else { | |
64 sbuf.add(runTestsFunction); | |
65 action = 'runTests'; | |
66 } | |
67 sbuf.add(formatMessageFunction(config.passFormat, | |
68 config.failFormat, | |
69 config.errorFormat)); | |
70 } | |
71 | |
72 // Add the filter, if applicable. | |
73 if (config.filtering) { | |
74 if (config.includeFilter.length > 0) { | |
75 sbuf.add(filterTestFunction(config.includeFilter, 'true')); | |
76 } else { | |
77 sbuf.add(filterTestFunction(config.excludeFilter, 'false')); | |
78 } | |
79 } | |
80 | |
81 // Add the common trailer stuff. | |
82 sbuf.add(dartMain(sourceName, action, config.filtering)); | |
83 | |
84 // Save the Dart file. | |
85 createFile(destFile, sbuf.toString()); | |
86 exitHandler(0); | |
87 } | |
88 | |
89 String directives(String library, String unittest, String sourceName) { | |
90 return """ | |
91 #library('$library'); | |
92 #import('dart:isolate'); | |
93 #import('$unittest', prefix:'unittest'); | |
94 #import('$sourceName', prefix: 'test'); | |
95 """; | |
96 } | |
97 | |
98 // For 'printing' when we are in the browser, we add text elements | |
99 // to a DOM element with id 'console'. | |
100 final String browserTestPrintFunction = """ | |
101 #import('dart:html'); | |
102 void tprint(msg) { | |
103 var pre = query('#console'); | |
104 pre.elements.add(new Text('###\$msg\\n')); | |
105 } | |
106 """; | |
107 | |
108 // For printing when not in the browser we can just use Dart's print(). | |
109 final String nonBrowserTestPrintFunction = """ | |
110 void tprint(msg) { | |
111 print('###\$msg'); | |
112 } | |
113 """; | |
114 | |
115 // The core skeleton for a config. Most of the guts is in the | |
116 // parameter [body]. | |
117 String configuration(String body) { | |
118 return """ | |
119 class TestRunnerConfiguration extends unittest.Configuration { | |
120 get name => 'Test runner configuration'; | |
121 get autoStart => false; | |
122 $body | |
123 } | |
124 """; | |
125 } | |
126 | |
127 // A function to give us the elapsed time for a test. | |
128 final String elapsedFunction = """ | |
129 String elapsed(TestCase t) { | |
130 double duration = t.runningTime.inMilliseconds; | |
131 duration /= 1000; | |
132 return '\${duration.toStringAsFixed(3)}s '; | |
133 } | |
134 """; | |
135 | |
136 // A dummy version of the elapsed function for when the user | |
137 // doesn't want test times included. | |
138 final String dummyElapsedFunction = """ | |
139 String elapsed(TestCase t) { | |
140 return ''; | |
141 } | |
142 """; | |
143 | |
144 // A function to print the results of a test. | |
145 final String dumpTestResultFunction = """ | |
146 void dumpTestResult(source, TestCase t) { | |
147 var groupName = '', testName = ''; | |
148 var idx = t.description.lastIndexOf('###'); | |
149 if (idx >= 0) { | |
150 groupName = t.description.substring(0, idx).replaceAll('###', ' '); | |
151 testName = t.description.substring(idx+3); | |
152 } else { | |
153 testName = t.description; | |
154 } | |
155 var stack = (t.stackTrace == null) ? '' : '\${t.stackTrace} '; | |
156 var message = (t.message.length > 0) ? '\$t.message ' : ''; | |
157 var duration = elapsed(t); | |
158 tprint(formatMessage(source, '\$groupName ', '\$testName ', | |
159 duration, t.result, message, stack)); | |
160 } | |
161 """; | |
162 | |
163 // A barebones config, used for listing tests, not running them. | |
164 String barebonesConfig() { | |
165 return configuration(''); | |
166 } | |
167 | |
168 // A much more complex config, used for running tests. | |
169 String testConfig(String sourceName, | |
170 bool runInBrowser, | |
171 bool runIsolated, | |
172 bool immediateOutput, | |
173 bool summary) { | |
174 StringBuffer sbuf = new StringBuffer(); | |
175 | |
176 if (runIsolated) { | |
177 // Add a constructor that saves the isolate port. | |
178 sbuf.add(" var port;\n TestRunnerConfiguration(this.port);\n"); | |
179 } | |
180 | |
181 if (immediateOutput) { | |
182 // Output test results in onTestResult instead of onDone. | |
183 sbuf.add(""" | |
184 void onTestResult(TestCase testCase) { | |
185 dumpTestResult('${sourceName} ', testCase); | |
186 } | |
187 | |
188 void onDone(int passed, int failed, int errors, List<TestCase> results, | |
189 String uncaughtError) { | |
190 """); | |
191 } else { | |
192 // Not immediate; we output test results in onDone. | |
193 sbuf.add(""" | |
194 void onDone(int passed, int failed, int errors, List<TestCase> results, | |
195 String uncaughtError) { | |
196 // Print each result. | |
197 for (final testCase in results) { | |
198 dumpTestResult('${sourceName} ', testCase); | |
199 } | |
200 """); | |
201 } | |
202 | |
203 if (summary) { | |
204 // Add the code to produce the summary for the test file. | |
205 sbuf.add(""" | |
206 tprint(''); | |
207 var success = false; | |
208 if (passed == 0 && failed == 0 && errors == 0) { | |
209 tprint('$sourceName: No tests found.'); | |
210 } else if (failed == 0 && errors == 0 && uncaughtError == null) { | |
211 tprint('$sourceName: All \$passed tests passed.'); | |
212 success = true; | |
213 } else { | |
214 if (uncaughtError != null) { | |
215 tprint('$sourceName: Top-level uncaught error: \$uncaughtError'); | |
216 } | |
217 tprint('$sourceName: \$passed PASSED, \$failed FAILED, \$errors ERRORS'); | |
218 } | |
219 """); | |
220 } | |
221 | |
222 if (runIsolated) { | |
223 // Add the code to tell the master whether we succeeded or not. | |
224 sbuf.add(""" | |
225 var success = (passed > 0 && failed == 0 && errors == 0 && | |
226 uncaughtError == null); | |
227 port.send(success); | |
228 """); | |
229 } else if (runInBrowser) { | |
230 // Add the code to tell DRT we are done and it can exit. | |
231 sbuf.add(" window.postMessage('done', '*');\n"); | |
232 } | |
233 sbuf.add(" }\n"); | |
234 return configuration(sbuf.toString()); | |
235 } | |
236 | |
237 // A simple format function for listing tests. | |
238 String formatListMessageFunction(String format) { | |
239 return """ | |
240 String formatMessage(filename, groupname, [ testname = '']) { | |
241 return '${format}'. | |
242 replaceAll('${Macros.testfile}', filename). | |
243 replaceAll('${Macros.testGroup}', groupname). | |
244 replaceAll('${Macros.testDescription}', testname); | |
245 } | |
246 """; | |
247 } | |
248 | |
249 // A richer format function for test results. | |
250 String formatMessageFunction( | |
251 String passFormat, String failFormat, String errorFormat) { | |
252 return """ | |
253 String formatMessage(filename, groupname, | |
254 [ testname = '', testTime = '', result = '', | |
255 message = '', stack = '' ]) { | |
256 var format = '$errorFormat'; | |
257 if (result == 'pass') format = '$passFormat'; | |
258 else if (result == 'fail') format = '$failFormat'; | |
259 return format. | |
260 replaceAll('${Macros.testTime}', testTime). | |
261 replaceAll('${Macros.testfile}', filename). | |
262 replaceAll('${Macros.testGroup}', groupname). | |
263 replaceAll('${Macros.testDescription}', testname). | |
264 replaceAll('${Macros.testMessage}', message). | |
265 replaceAll('${Macros.testStacktrace}', stack); | |
266 } | |
267 """; | |
268 } | |
269 | |
270 // A function to list the test groups. | |
271 final String listGroupsFunction = """ | |
272 listGroups(testfile) { | |
273 List tests = unittest.testCases; | |
274 Map groups = {}; | |
275 for (var t in tests) { | |
276 var groupName, testName = ''; | |
277 var idx = t.description.lastIndexOf('###'); | |
278 if (idx >= 0) { | |
279 groupName = t.description.substring(0, idx).replaceAll('###', ' '); | |
280 if (!groups.containsKey(groupName)) { | |
281 groups[groupName] = ''; | |
282 } | |
283 } | |
284 } | |
285 for (var g in groups.getKeys()) { | |
286 var msg = formatMessage('\$testfile ', '\$g '); | |
287 print('###\$msg'); | |
288 } | |
289 } | |
290 """; | |
291 | |
292 // A function to list the tests. | |
293 final String listTestsFunction = """ | |
294 listTests(testfile) { | |
295 List tests = unittest.testCases; | |
296 for (var t in tests) { | |
297 var groupName, testName = ''; | |
298 var idx = t.description.lastIndexOf('###'); | |
299 if (idx >= 0) { | |
300 groupName = t.description.substring(0, idx).replaceAll('###', ' '); | |
301 testName = t.description.substring(idx+3); | |
302 } else { | |
303 groupName = ''; | |
304 testName = t.description; | |
305 } | |
306 var msg = formatMessage('\$testfile ', '\$groupName ', | |
307 '\$testName '); | |
308 print('###\$msg'); | |
309 } | |
310 } | |
311 """; | |
312 | |
313 // A function to filter the tests. | |
314 String filterTestFunction(List filters, String filterReturnValue) { | |
315 StringBuffer sbuf = new StringBuffer(); | |
316 sbuf.add('filterTest(t) {\n'); | |
317 if (filters != null) { | |
318 sbuf.add(' var name = t.description.replaceAll("###", " ");\n'); | |
319 for (var f in filters) { | |
320 sbuf.add(' if (name.indexOf("$f")>=0) return $filterReturnValue;\n'); | |
321 } | |
322 sbuf.add(' return !$filterReturnValue;\n'); | |
323 } else { | |
324 // TODO - is this right? Should it be filterReturnValue? | |
325 sbuf.add(' return true;\n'); | |
326 } | |
327 sbuf.add('}\n'); | |
328 return sbuf.toString(); | |
329 } | |
330 | |
331 // Code to support running single tests as master/slave in isolates. | |
332 final String runIsolateTestsFunction = """ | |
333 runSlaveTest() { | |
334 port.receive((testName, sendport) { | |
335 unittest.configure(new TestRunnerConfiguration(sendport)); | |
336 unittest.group('', test.main); | |
337 unittest.filterTests(testName); | |
338 unittest.runTests(); | |
339 }); | |
340 } | |
341 | |
342 var testNum; | |
343 var failed; | |
344 | |
345 runMasterTest() { | |
346 var tests = unittest.testCases; | |
347 SendPort sport = spawnFunction(runSlaveTest); | |
348 sport.call(tests[testNum].description).then((result) { | |
349 if (!result) failed = true; | |
350 ++testNum; | |
351 if (testNum < tests.length) { | |
352 runMasterTest(); | |
353 } else if (failed) { | |
354 throw new Exception("Some tests failed."); | |
355 } | |
356 }); | |
357 } | |
358 | |
359 runIsolateTests(f) { | |
360 testNum = 0; | |
361 failed = false; | |
362 runMasterTest(); | |
363 } | |
364 """; | |
365 | |
366 // Code for running all tests in the normal (non-isolate) way. | |
367 final String runTestsFunction = "runTests(f) { unittest.runTests(); }\n"; | |
368 | |
369 // The main function, that creates the config, filters the tests if | |
370 // necessary, then performs the action (list/run/run-isolated). | |
371 String dartMain(String sourceName, String action, bool filter) { | |
372 return """ | |
373 main() { | |
374 unittest.groupSep = '###'; | |
375 unittest.configure(new TestRunnerConfiguration()); | |
376 unittest.group('', test.main); | |
377 ${filter?'unittest.filterTests(filterTest);':''} | |
378 $action('$sourceName'); | |
379 } | |
380 """; | |
381 } | |
382 | |
383 } | |
OLD | NEW |