| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 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 | 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 #library("test_runner"); | |
| 6 | |
| 7 #import("dart:io"); | |
| 8 #import("status_file_parser.dart"); | |
| 9 #import("test_progress.dart"); | |
| 10 #import("test_suite.dart"); | |
| 11 | |
| 12 /** | 5 /** |
| 13 * Classes and methods for executing tests. | 6 * Classes and methods for executing tests. |
| 14 * | 7 * |
| 15 * This module includes: | 8 * This module includes: |
| 16 * - Managing parallel execution of tests, including timeout checks. | 9 * - Managing parallel execution of tests, including timeout checks. |
| 17 * - Evaluating the output of each test as pass/fail/crash/timeout. | 10 * - Evaluating the output of each test as pass/fail/crash/timeout. |
| 18 */ | 11 */ |
| 12 #library("test_runner"); |
| 13 |
| 14 #import("dart:io"); |
| 15 #import("status_file_parser.dart"); |
| 16 #import("test_progress.dart"); |
| 17 #import("test_suite.dart"); |
| 19 | 18 |
| 20 final int NO_TIMEOUT = 0; | 19 final int NO_TIMEOUT = 0; |
| 21 | 20 |
| 22 | 21 /** |
| 22 * TestCase contains all the information needed to run a test and evaluate |
| 23 * its output. Running a test involves starting a separate process, with |
| 24 * the executable and arguments given by the TestCase, and recording its |
| 25 * stdout and stderr output streams, and its exit code. TestCase only |
| 26 * contains static information about the test; actually running the test is |
| 27 * performed by [ProcessQueue] using a [RunningProcess] object. |
| 28 * |
| 29 * The output information is stored in a [TestOutput] instance contained |
| 30 * in the TestCase. The TestOutput instance is responsible for evaluating |
| 31 * if the test has passed, failed, crashed, or timed out, and the TestCase |
| 32 * has information about what the expected result of the test should be. |
| 33 * |
| 34 * The TestCase has a callback function, [completedHandler], that is run when |
| 35 * the test is completed. |
| 36 */ |
| 23 class TestCase { | 37 class TestCase { |
| 24 String executablePath; | 38 String executablePath; |
| 25 List<String> arguments; | 39 List<String> arguments; |
| 26 Map configuration; | 40 Map configuration; |
| 27 String commandLine; | 41 String commandLine; |
| 28 String displayName; | 42 String displayName; |
| 29 TestOutput output; | 43 TestOutput output; |
| 30 bool isNegative; | 44 bool isNegative; |
| 31 Set<String> expectedOutcomes; | 45 Set<String> expectedOutcomes; |
| 32 Function completedHandler; | 46 Function completedHandler; |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 82 final mode = configuration['mode']; | 96 final mode = configuration['mode']; |
| 83 final arch = configuration['arch']; | 97 final arch = configuration['arch']; |
| 84 return "$component ${mode}_$arch"; | 98 return "$component ${mode}_$arch"; |
| 85 } | 99 } |
| 86 | 100 |
| 87 void completed() { completedHandler(this); } | 101 void completed() { completedHandler(this); } |
| 88 } | 102 } |
| 89 | 103 |
| 90 | 104 |
| 91 /** | 105 /** |
| 92 * BrowserTestCase has an extra compilation command that is run by | 106 * BrowserTestCase has an extra compilation command that is run in a separate |
| 93 * RunningProcess.start(), and it checks conditions on the test output | 107 * process, before the regular test is run as in the base class [TestCase]. |
| 94 * in TestOutput.didFail(). | 108 * If the compilation command fails, then the rest of the test is not run. |
| 95 */ | 109 */ |
| 96 class BrowserTestCase extends TestCase { | 110 class BrowserTestCase extends TestCase { |
| 111 /** |
| 112 * The executable that is run in a new process in the compilation phase. |
| 113 */ |
| 97 String compilerPath; | 114 String compilerPath; |
| 115 /** |
| 116 * The arguments for the compilation command. |
| 117 */ |
| 98 List<String> compilerArguments; | 118 List<String> compilerArguments; |
| 99 /** | 119 /** |
| 100 * Indicates if this test is a rerun, to compensate for flaky browser tests. | 120 * Indicates if this test is a rerun, to compensate for flaky browser tests. |
| 101 */ | 121 */ |
| 102 bool isRerun; | 122 bool isRerun; |
| 103 | 123 |
| 104 BrowserTestCase(displayName, | 124 BrowserTestCase(displayName, |
| 105 this.compilerPath, | 125 this.compilerPath, |
| 106 this.compilerArguments, | 126 this.compilerArguments, |
| 107 executablePath, | 127 executablePath, |
| 108 arguments, | 128 arguments, |
| 109 configuration, | 129 configuration, |
| 110 completedHandler, | 130 completedHandler, |
| 111 expectedOutcomes, | 131 expectedOutcomes, |
| 112 [isNegative = false]) : super(displayName, | 132 [isNegative = false]) : super(displayName, |
| 113 executablePath, | 133 executablePath, |
| 114 arguments, | 134 arguments, |
| 115 configuration, | 135 configuration, |
| 116 completedHandler, | 136 completedHandler, |
| 117 expectedOutcomes, | 137 expectedOutcomes, |
| 118 isNegative) { | 138 isNegative) { |
| 119 if (compilerPath != null) { | 139 if (compilerPath != null) { |
| 120 commandLine = 'execution command: $commandLine'; | 140 commandLine = 'execution command: $commandLine'; |
| 121 String compilationCommand = | 141 String compilationCommand = |
| 122 '$compilerPath ${Strings.join(compilerArguments, " ")}'; | 142 '$compilerPath ${Strings.join(compilerArguments, " ")}'; |
| 123 commandLine = 'compilation command: $compilationCommand\n$commandLine'; | 143 commandLine = 'compilation command: $compilationCommand\n$commandLine'; |
| 124 } | 144 } |
| 125 isRerun = false; | 145 isRerun = false; |
| 126 } | 146 } |
| 127 } | 147 } |
| 128 | 148 |
| 129 | 149 |
| 150 /** |
| 151 * TestOutput records the output of a completed test: the process's exit code, |
| 152 * the standard output and standard error, whether the process timed out, and |
| 153 * the time the process took to run. It also contains a pointer to the |
| 154 * [TestCase] this is the output of. |
| 155 */ |
| 130 class TestOutput { | 156 class TestOutput { |
| 131 // The TestCase this is the output from. | |
| 132 TestCase testCase; | 157 TestCase testCase; |
| 133 int exitCode; | 158 int exitCode; |
| 134 bool timedOut; | 159 bool timedOut; |
| 135 bool failed = false; | 160 bool failed = false; |
| 136 List<String> stdout; | 161 List<String> stdout; |
| 137 List<String> stderr; | 162 List<String> stderr; |
| 138 Duration time; | 163 Duration time; |
| 139 | 164 |
| 140 TestOutput(this.testCase, this.exitCode, this.timedOut, this.stdout, | 165 TestOutput(this.testCase, this.exitCode, this.timedOut, this.stdout, |
| 141 this.stderr, this.time) { | 166 this.stderr, this.time) { |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 186 return testCase.isNegative; | 211 return testCase.isNegative; |
| 187 } | 212 } |
| 188 } | 213 } |
| 189 return true; | 214 return true; |
| 190 } | 215 } |
| 191 | 216 |
| 192 // Reverse result of a negative test. | 217 // Reverse result of a negative test. |
| 193 bool get hasFailed() => (testCase.isNegative ? !didFail : didFail); | 218 bool get hasFailed() => (testCase.isNegative ? !didFail : didFail); |
| 194 } | 219 } |
| 195 | 220 |
| 196 | 221 /** |
| 222 * A RunningProcess actually runs a test, getting the command lines from |
| 223 * its [TestCase], starting the test process (and first, a compilation |
| 224 * process if the TestCase is a [BrowserTestCase]), creating a timeout |
| 225 * timer, and recording the results in a new [TestOutput] object, which it |
| 226 * attaches to the TestCase. The lifetime of the RunningProcess is limited |
| 227 * to the time it takes to start the process, run the process, and record |
| 228 * the result; there are no pointers to it, so it should be available to |
| 229 * be garbage collected as soon as it is done. |
| 230 */ |
| 197 class RunningProcess { | 231 class RunningProcess { |
| 198 Process process; | 232 Process process; |
| 199 TestCase testCase; | 233 TestCase testCase; |
| 200 bool timedOut = false; | 234 bool timedOut = false; |
| 201 Date startTime; | 235 Date startTime; |
| 202 Timer timeoutTimer; | 236 Timer timeoutTimer; |
| 203 List<String> stdout; | 237 List<String> stdout; |
| 204 List<String> stderr; | 238 List<String> stderr; |
| 205 List<Function> handlers; | 239 List<Function> handlers; |
| 206 | 240 |
| 207 RunningProcess(this.testCase); | 241 RunningProcess(TestCase this.testCase); |
| 208 | 242 |
| 209 void exitHandler(int exitCode) { | 243 void exitHandler(int exitCode) { |
| 210 new TestOutput(testCase, exitCode, timedOut, stdout, | 244 new TestOutput(testCase, exitCode, timedOut, stdout, |
| 211 stderr, new Date.now().difference(startTime)); | 245 stderr, new Date.now().difference(startTime)); |
| 212 process.close(); | 246 process.close(); |
| 213 timeoutTimer.cancel(); | 247 timeoutTimer.cancel(); |
| 214 if (testCase.output.unexpectedOutput && testCase.configuration['verbose']) { | 248 if (testCase.output.unexpectedOutput && testCase.configuration['verbose']) { |
| 215 print(testCase.output.stdout); | 249 print(testCase.output.stdout); |
| 216 print(testCase.output.stderr); | 250 print(testCase.output.stderr); |
| 217 } | 251 } |
| 218 if (testCase is BrowserTestCase && testCase.output.unexpectedOutput && | 252 if (testCase is BrowserTestCase && testCase.output.unexpectedOutput && |
| 219 !testCase.isRerun) { | 253 !testCase.isRerun) { |
| 220 // Selenium tests can be flaky. Try rerunning. | 254 // Selenium tests can be flaky. Try rerunning. |
| 221 testCase.isRerun = true; | 255 testCase.isRerun = true; |
| 222 this.timedOut = false; | 256 this.timedOut = false; |
| 223 this.start(); | 257 this.start(); |
| 224 } else { | 258 } else { |
| 225 testCase.completed(); | 259 testCase.completed(); |
| 226 } | 260 } |
| 227 } | 261 } |
| 228 | 262 |
| 229 void compilerExitHandler(int exitCode) { | 263 void compilerExitHandler(int exitCode) { |
| 230 if (exitCode != 0) { | 264 if (exitCode != 0) { |
| 231 stderr.add('test.dart: Compilation step failed (exit code $exitCode)\n'); | 265 stderr.add('test.dart: Compilation step failed (exit code $exitCode)\n'); |
| 232 exitHandler(exitCode); | 266 exitHandler(exitCode); |
| 233 } else { | 267 } else { |
| 234 process.close(); | 268 process.close(); |
| 235 stderr.add('test.dart: Compilation finished, starting execution\n'); | 269 stderr.add('test.dart: Compilation finished, starting execution\n'); |
| 236 stdout.add('test.dart: Compilation finished, starting execution\n'); | 270 stdout.add('test.dart: Compilation finished, starting execution\n'); |
| 237 runCommand(testCase.executablePath, testCase.arguments, exitHandler); | 271 runCommand(testCase.executablePath, testCase.arguments, exitHandler); |
| 238 } | 272 } |
| 239 } | 273 } |
| 240 | 274 |
| 241 Function makeReadHandler(StringInputStream source, List<String> destination) { | 275 Function makeReadHandler(StringInputStream source, List<String> destination) { |
| 242 return () { | 276 return () { |
| 243 if (source.closed) return; // TODO(whesse): Remove when bug is fixed. | 277 if (source.closed) return; // TODO(whesse): Remove when bug is fixed. |
| 244 var line = source.readLine(); | 278 var line = source.readLine(); |
| 245 while (null != line) { | 279 while (null != line) { |
| 246 destination.add(line); | 280 destination.add(line); |
| 247 line = source.readLine(); | 281 line = source.readLine(); |
| 248 } | 282 } |
| 249 }; | 283 }; |
| 250 } | 284 } |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 310 bool get active() => _currentTest != null; | 344 bool get active() => _currentTest != null; |
| 311 | 345 |
| 312 void startTest(TestCase testCase) { | 346 void startTest(TestCase testCase) { |
| 313 _currentTest = testCase; | 347 _currentTest = testCase; |
| 314 if (_process === null) { | 348 if (_process === null) { |
| 315 // Start process if not yet started. | 349 // Start process if not yet started. |
| 316 _executable = testCase.executablePath; | 350 _executable = testCase.executablePath; |
| 317 _startProcess(() { | 351 _startProcess(() { |
| 318 doStartTest(testCase); | 352 doStartTest(testCase); |
| 319 }); | 353 }); |
| 320 } else if (testCase.executablePath != _executable) { | 354 } else if (testCase.executablePath != _executable) { |
| 321 // Restart this runner with the right executable for this test | 355 // Restart this runner with the right executable for this test |
| 322 // if needed. | 356 // if needed. |
| 323 _executable = testCase.executablePath; | 357 _executable = testCase.executablePath; |
| 324 _process.exitHandler = (exitCode) { | 358 _process.exitHandler = (exitCode) { |
| 325 _process.close(); | 359 _process.close(); |
| 326 _startProcess(() { | 360 _startProcess(() { |
| 327 doStartTest(testCase); | 361 doStartTest(testCase); |
| 328 }); | 362 }); |
| 329 }; | 363 }; |
| 330 _process.kill(); | 364 _process.kill(); |
| 331 } else { | 365 } else { |
| 332 doStartTest(testCase); | 366 doStartTest(testCase); |
| 333 } | 367 } |
| 334 } | 368 } |
| 335 | 369 |
| 336 void terminate() { | 370 void terminate() { |
| 337 if (_process !== null) { | 371 if (_process !== null) { |
| 338 _process.exitHandler = (exitCode) { | 372 _process.exitHandler = (exitCode) { |
| 339 _process.close(); | 373 _process.close(); |
| 340 }; | 374 }; |
| 341 _process.kill(); | 375 _process.kill(); |
| 342 } | 376 } |
| 343 } | 377 } |
| 344 | 378 |
| 345 void doStartTest(TestCase testCase) { | 379 void doStartTest(TestCase testCase) { |
| 346 _startTime = new Date.now(); | 380 _startTime = new Date.now(); |
| 347 _testStdout = new List<String>(); | 381 _testStdout = new List<String>(); |
| 348 _testStderr = new List<String>(); | 382 _testStderr = new List<String>(); |
| 349 _stdoutStream.lineHandler = _readOutput(_stdoutStream, _testStdout); | 383 _stdoutStream.lineHandler = _readOutput(_stdoutStream, _testStdout); |
| 350 _stderrStream.lineHandler = _readOutput(_stderrStream, _testStderr); | 384 _stderrStream.lineHandler = _readOutput(_stderrStream, _testStderr); |
| 351 _timer = new Timer(_timeoutHandler(testCase), testCase.timeout * 1000); | 385 _timer = new Timer(_timeoutHandler(testCase), testCase.timeout * 1000); |
| 352 _process.stdin.write(_createArgumentsLine(testCase.arguments).charCodes()); | 386 _process.stdin.write(_createArgumentsLine(testCase.arguments).charCodes()); |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 403 _startProcess(() { | 437 _startProcess(() { |
| 404 _reportResult(">>> TEST CRASH"); | 438 _reportResult(">>> TEST CRASH"); |
| 405 }); | 439 }); |
| 406 } | 440 } |
| 407 | 441 |
| 408 Function _timeoutHandler(TestCase test) { | 442 Function _timeoutHandler(TestCase test) { |
| 409 return (ignore) { | 443 return (ignore) { |
| 410 _process.exitHandler = (exitCode) { | 444 _process.exitHandler = (exitCode) { |
| 411 _process.close(); | 445 _process.close(); |
| 412 _startProcess(() { | 446 _startProcess(() { |
| 413 _reportResult(">>> TEST TIMEOUT"); | 447 _reportResult(">>> TEST TIMEOUT"); |
| 414 }); | 448 }); |
| 415 }; | 449 }; |
| 416 _process.kill(); | 450 _process.kill(); |
| 417 }; | 451 }; |
| 418 } | 452 } |
| 419 | 453 |
| 420 void _startProcess(then) { | 454 void _startProcess(then) { |
| 421 _process = new Process.start(_executable, ['-batch']); | 455 _process = new Process.start(_executable, ['-batch']); |
| 422 _stdoutStream = new StringInputStream(_process.stdout); | 456 _stdoutStream = new StringInputStream(_process.stdout); |
| 423 _stderrStream = new StringInputStream(_process.stderr); | 457 _stderrStream = new StringInputStream(_process.stderr); |
| 424 _testStdout = new List<String>(); | 458 _testStdout = new List<String>(); |
| 425 _testStderr = new List<String>(); | 459 _testStderr = new List<String>(); |
| 426 _stdoutStream.lineHandler = _readOutput(_stdoutStream, _testStdout); | 460 _stdoutStream.lineHandler = _readOutput(_stdoutStream, _testStdout); |
| 427 _stderrStream.lineHandler = _readOutput(_stderrStream, _testStderr); | 461 _stderrStream.lineHandler = _readOutput(_stderrStream, _testStderr); |
| 428 _process.exitHandler = _exitHandler; | 462 _process.exitHandler = _exitHandler; |
| 429 _process.startHandler = then; | 463 _process.startHandler = then; |
| 430 } | 464 } |
| 431 } | 465 } |
| 432 | 466 |
| 433 | 467 |
| 468 /** |
| 469 * ProcessQueue is the master control class, responsible for running all |
| 470 * the tests in all the TestSuites that have been registered. It includes |
| 471 * a rate-limited queue to run a limited number of tests in parallel, |
| 472 * a ProgressIndicator which prints output when tests are started and |
| 473 * and completed, and a summary report when all tests are completed, |
| 474 * and counters to determine when all of the tests in all of the test suites |
| 475 * have completed. |
| 476 * |
| 477 * Because multiple configurations may be run on each test suite, the |
| 478 * ProcessQueue contains a cache in which a test suite may record information |
| 479 * about its list of tests, and may retrieve that information when it is called |
| 480 * upon to enqueue its tests again. |
| 481 */ |
| 434 class ProcessQueue { | 482 class ProcessQueue { |
| 435 int _numProcesses = 0; | 483 int _numProcesses = 0; |
| 436 int _activeTestListers = 0; | 484 int _activeTestListers = 0; |
| 437 int _maxProcesses; | 485 int _maxProcesses; |
| 438 bool _verbose; | 486 bool _verbose; |
| 439 bool _listTests; | 487 bool _listTests; |
| 440 bool _keepGeneratedTests; | 488 bool _keepGeneratedTests; |
| 441 Function _enqueueMoreWork; | 489 Function _enqueueMoreWork; |
| 442 Queue<TestCase> _tests; | 490 Queue<TestCase> _tests; |
| 443 ProgressIndicator _progress; | 491 ProgressIndicator _progress; |
| 444 String _temporaryDirectory; | 492 String _temporaryDirectory; |
| 445 // For dartc batch processing we keep a list of batch processes. | 493 // For dartc batch processing we keep a list of batch processes. |
| 446 List<DartcBatchRunnerProcess> _batchProcesses; | 494 List<DartcBatchRunnerProcess> _batchProcesses; |
| 495 |
| 447 // Cache information about test cases per test suite. For multiple | 496 // Cache information about test cases per test suite. For multiple |
| 448 // configurations there is no need to repeatedly search the file | 497 // configurations there is no need to repeatedly search the file |
| 449 // system, generate tests, and search test files for options. | 498 // system, generate tests, and search test files for options. |
| 450 Map<String, List<TestInformation>> _testCache; | 499 Map<String, List<TestInformation>> _testCache; |
| 451 | 500 |
| 452 ProcessQueue(int this._maxProcesses, | 501 ProcessQueue(int this._maxProcesses, |
| 453 String progress, | 502 String progress, |
| 454 Date startTime, | 503 Date startTime, |
| 455 bool printTiming, | 504 bool printTiming, |
| 456 Function this._enqueueMoreWork, | 505 Function this._enqueueMoreWork, |
| 457 [bool this._verbose = false, | 506 [bool this._verbose = false, |
| 458 bool this._listTests = false, | 507 bool this._listTests = false, |
| 459 bool this._keepGeneratedTests = false]) | 508 bool this._keepGeneratedTests = false]) |
| 460 : _tests = new Queue<TestCase>(), | 509 : _tests = new Queue<TestCase>(), |
| 461 _progress = new ProgressIndicator.fromName(progress, | 510 _progress = new ProgressIndicator.fromName(progress, |
| 462 startTime, | 511 startTime, |
| 463 printTiming), | 512 printTiming), |
| 464 _batchProcesses = new List<DartcBatchRunnerProcess>(), | 513 _batchProcesses = new List<DartcBatchRunnerProcess>(), |
| 465 _testCache = new Map<String, List<TestInformation>>() { | 514 _testCache = new Map<String, List<TestInformation>>() { |
| 466 if (!_enqueueMoreWork(this)) _progress.allDone(); | 515 if (!_enqueueMoreWork(this)) _progress.allDone(); |
| 467 } | 516 } |
| 468 | 517 |
| 518 /** |
| 519 * Registers a TestSuite so that all of its tests will be run. |
| 520 */ |
| 469 void addTestSuite(TestSuite testSuite) { | 521 void addTestSuite(TestSuite testSuite) { |
| 470 _activeTestListers++; | 522 _activeTestListers++; |
| 471 testSuite.forEachTest(_runTest, _testCache, globalTemporaryDirectory, | 523 testSuite.forEachTest(_runTest, _testCache, globalTemporaryDirectory, |
| 472 _testListerDone); | 524 _testListerDone); |
| 473 } | 525 } |
| 474 | 526 |
| 475 void _testListerDone() { | 527 void _testListerDone() { |
| 476 _activeTestListers--; | 528 _activeTestListers--; |
| 477 _checkDone(); | 529 _checkDone(); |
| 478 } | 530 } |
| (...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 577 test.displayName != 'dartc/junit_tests') { | 629 test.displayName != 'dartc/junit_tests') { |
| 578 _ensureDartcBatchRunnersStarted(test.executablePath); | 630 _ensureDartcBatchRunnersStarted(test.executablePath); |
| 579 _getDartcBatchRunnerProcess().startTest(test); | 631 _getDartcBatchRunnerProcess().startTest(test); |
| 580 } else { | 632 } else { |
| 581 new RunningProcess(test).start(); | 633 new RunningProcess(test).start(); |
| 582 } | 634 } |
| 583 _numProcesses++; | 635 _numProcesses++; |
| 584 } | 636 } |
| 585 } | 637 } |
| 586 } | 638 } |
| OLD | NEW |