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