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