| 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 /** | 5 /** |
| 6 * Classes and methods for executing tests. | 6 * Classes and methods for executing tests. |
| 7 * | 7 * |
| 8 * This module includes: | 8 * This module includes: |
| 9 * - Managing parallel execution of tests, including timeout checks. | 9 * - Managing parallel execution of tests, including timeout checks. |
| 10 * - Evaluating the output of each test as pass/fail/crash/timeout. | 10 * - Evaluating the output of each test as pass/fail/crash/timeout. |
| 11 */ | 11 */ |
| 12 #library("test_runner"); | 12 #library("test_runner"); |
| 13 | 13 |
| 14 #import("dart:io"); | 14 #import("dart:io"); |
| 15 #import("dart:isolate"); | 15 #import("dart:isolate"); |
| 16 #import("status_file_parser.dart"); | 16 #import("status_file_parser.dart"); |
| 17 #import("test_progress.dart"); | 17 #import("test_progress.dart"); |
| 18 #import("test_suite.dart"); | 18 #import("test_suite.dart"); |
| 19 | 19 |
| 20 const int NO_TIMEOUT = 0; | 20 const int NO_TIMEOUT = 0; |
| 21 const int SLOW_TIMEOUT_MULTIPLIER = 4; | 21 const int SLOW_TIMEOUT_MULTIPLIER = 4; |
| 22 | 22 |
| 23 typedef void TestCaseEvent(TestCase testCase); | 23 typedef void TestCaseEvent(TestCase testCase); |
| 24 typedef void ExitCodeEvent(int exitCode); | 24 typedef void ExitCodeEvent(int exitCode); |
| 25 typedef bool EnqueueMoreWork(ProcessQueue queue); | 25 typedef void EnqueueMoreWork(ProcessQueue queue); |
| 26 | 26 |
| 27 /** A command executed as a step in a test case. */ | 27 /** A command executed as a step in a test case. */ |
| 28 class Command { | 28 class Command { |
| 29 /** Path to the executable of this command. */ | 29 /** Path to the executable of this command. */ |
| 30 String executable; | 30 String executable; |
| 31 | 31 |
| 32 /** Command line arguments to the executable. */ | 32 /** Command line arguments to the executable. */ |
| 33 List<String> arguments; | 33 List<String> arguments; |
| 34 | 34 |
| 35 /** The actual command line that will be executed. */ | 35 /** The actual command line that will be executed. */ |
| (...skipping 566 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 602 } | 602 } |
| 603 } | 603 } |
| 604 | 604 |
| 605 /** | 605 /** |
| 606 * Process exit handler called at the end of every command. It internally | 606 * Process exit handler called at the end of every command. It internally |
| 607 * treats all but the last command as compilation steps. The last command is | 607 * treats all but the last command as compilation steps. The last command is |
| 608 * the actual test and its output is analyzed in [testComplete]. | 608 * the actual test and its output is analyzed in [testComplete]. |
| 609 */ | 609 */ |
| 610 void stepExitHandler(int exitCode) { | 610 void stepExitHandler(int exitCode) { |
| 611 process.close(); | 611 process.close(); |
| 612 process = null; |
| 612 int totalSteps = testCase.commands.length; | 613 int totalSteps = testCase.commands.length; |
| 613 String suffix =' (step $currentStep of $totalSteps)'; | 614 String suffix =' (step $currentStep of $totalSteps)'; |
| 614 if (currentStep == totalSteps) { // done with test command | 615 if (timedOut) { |
| 616 // Test timed out before it could complete. |
| 617 testComplete(0, true); |
| 618 } else if (currentStep == totalSteps) { |
| 619 // Done with all test commands. |
| 615 testComplete(exitCode, false); | 620 testComplete(exitCode, false); |
| 616 } else if (exitCode != 0) { | 621 } else if (exitCode != 0) { |
| 622 // One of the steps failed. |
| 617 stderr.add('test.dart: Compilation failed$suffix, exit code $exitCode\n'); | 623 stderr.add('test.dart: Compilation failed$suffix, exit code $exitCode\n'); |
| 618 testComplete(exitCode, true); | 624 testComplete(exitCode, true); |
| 619 } else { | 625 } else { |
| 626 // One compilation step successfully completed, move on to the |
| 627 // next step. |
| 620 stderr.add('test.dart: Compilation finished $suffix\n'); | 628 stderr.add('test.dart: Compilation finished $suffix\n'); |
| 621 stdout.add('test.dart: Compilation finished $suffix\n'); | 629 stdout.add('test.dart: Compilation finished $suffix\n'); |
| 622 if (currentStep == totalSteps - 1 && testCase.usesWebDriver && | 630 if (currentStep == totalSteps - 1 && testCase.usesWebDriver && |
| 623 !testCase.configuration['noBatch']) { | 631 !testCase.configuration['noBatch']) { |
| 624 // Note: processQueue will always be non-null for runtime == ie, ff, | 632 // Note: processQueue will always be non-null for runtime == ie, ff, |
| 625 // safari, chrome, opera. (It is only null for runtime == vm) | 633 // safari, chrome, opera. (It is only null for runtime == vm) |
| 626 // This RunningProcess object is done, and hands over control to | 634 // This RunningProcess object is done, and hands over control to |
| 627 // BatchRunner.startTest(), which handles reporting, etc. | 635 // BatchRunner.startTest(), which handles reporting, etc. |
| 628 timeoutTimer.cancel(); | 636 timeoutTimer.cancel(); |
| 629 processQueue._getBatchRunner(testCase).startTest(testCase); | 637 processQueue._getBatchRunner(testCase).startTest(testCase); |
| (...skipping 19 matching lines...) Expand all Loading... |
| 649 void start() { | 657 void start() { |
| 650 Expect.isFalse(testCase.expectedOutcomes.contains(SKIP)); | 658 Expect.isFalse(testCase.expectedOutcomes.contains(SKIP)); |
| 651 stdout = new List<String>(); | 659 stdout = new List<String>(); |
| 652 stderr = new List<String>(); | 660 stderr = new List<String>(); |
| 653 currentStep = 0; | 661 currentStep = 0; |
| 654 startTime = new Date.now(); | 662 startTime = new Date.now(); |
| 655 runCommand(testCase.commands[currentStep++], stepExitHandler); | 663 runCommand(testCase.commands[currentStep++], stepExitHandler); |
| 656 } | 664 } |
| 657 | 665 |
| 658 void runCommand(Command command, void exitHandler(int exitCode)) { | 666 void runCommand(Command command, void exitHandler(int exitCode)) { |
| 659 process = Process.start(command.executable, command.arguments); | 667 Future processFuture = Process.start(command.executable, command.arguments); |
| 660 process.onExit = exitHandler; | 668 processFuture.then((Process p) { |
| 661 process.onError = (e) { | 669 process = p; |
| 662 print("Error starting process:"); | 670 process.onExit = exitHandler; |
| 671 var stdoutStringStream = new StringInputStream(process.stdout); |
| 672 var stderrStringStream = new StringInputStream(process.stderr); |
| 673 stdoutStringStream.onLine = |
| 674 makeReadHandler(stdoutStringStream, stdout); |
| 675 stderrStringStream.onLine = |
| 676 makeReadHandler(stderrStringStream, stderr); |
| 677 if (timeoutTimer == null) { |
| 678 // Create one timeout timer when starting test case, remove it at end. |
| 679 timeoutTimer = new Timer(1000 * testCase.timeout, timeoutHandler); |
| 680 } |
| 681 // If the timeout fired in between two commands, kill the just |
| 682 // started process immediately. |
| 683 if (timedOut) process.kill(); |
| 684 }); |
| 685 processFuture.handleException((e) { |
| 686 print("Process error:"); |
| 663 print(" Command: $command"); | 687 print(" Command: $command"); |
| 664 print(" Error: $e"); | 688 print(" Error: $e"); |
| 665 testComplete(-1, false); | 689 testComplete(-1, false); |
| 666 }; | 690 return true; |
| 667 InputStream stdoutStream = process.stdout; | 691 }); |
| 668 InputStream stderrStream = process.stderr; | |
| 669 StringInputStream stdoutStringStream = new StringInputStream(stdoutStream); | |
| 670 StringInputStream stderrStringStream = new StringInputStream(stderrStream); | |
| 671 stdoutStringStream.onLine = | |
| 672 makeReadHandler(stdoutStringStream, stdout); | |
| 673 stderrStringStream.onLine = | |
| 674 makeReadHandler(stderrStringStream, stderr); | |
| 675 if (timeoutTimer == null) { | |
| 676 // Create one timeout timer when starting test case, remove it at end. | |
| 677 timeoutTimer = new Timer(1000 * testCase.timeout, timeoutHandler); | |
| 678 } | |
| 679 } | 692 } |
| 680 | 693 |
| 681 void timeoutHandler(Timer unusedTimer) { | 694 void timeoutHandler(Timer unusedTimer) { |
| 682 timedOut = true; | 695 timedOut = true; |
| 683 process.kill(); | 696 if (process != null) process.kill(); |
| 684 } | 697 } |
| 685 } | 698 } |
| 686 | 699 |
| 687 /** | 700 /** |
| 688 * This class holds a value, that can be changed. It is used when | 701 * This class holds a value, that can be changed. It is used when |
| 689 * closures need a shared value, that they can all change and read. | 702 * closures need a shared value, that they can all change and read. |
| 690 */ | 703 */ |
| 691 class MutableValue<T> { | 704 class MutableValue<T> { |
| 692 MutableValue(T this.value); | 705 MutableValue(T this.value); |
| 693 T value; | 706 T value; |
| (...skipping 200 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 894 line = _stdoutStream.readLine(); | 907 line = _stdoutStream.readLine(); |
| 895 } | 908 } |
| 896 line = _stderrStream.readLine(); | 909 line = _stderrStream.readLine(); |
| 897 while (line != null) { | 910 while (line != null) { |
| 898 _testStderr.add(line); | 911 _testStderr.add(line); |
| 899 line = _stderrStream.readLine(); | 912 line = _stderrStream.readLine(); |
| 900 } | 913 } |
| 901 _stderrDrained = true; | 914 _stderrDrained = true; |
| 902 _stdoutDrained = true; | 915 _stdoutDrained = true; |
| 903 _process.close(); | 916 _process.close(); |
| 904 _startProcess(() { _reportResult(); }); | 917 _startProcess(_reportResult); |
| 905 } else { // No active test case running. | 918 } else { // No active test case running. |
| 906 _process.close(); | 919 _process.close(); |
| 907 _process = null; | 920 _process = null; |
| 908 } | 921 } |
| 909 } | 922 } |
| 910 return handler; | 923 return handler; |
| 911 } | 924 } |
| 912 | 925 |
| 913 void _timeoutHandler(ignore) { | 926 void _timeoutHandler(ignore) { |
| 914 _process.onExit = makeExitHandler(">>> TEST TIMEOUT"); | 927 _process.onExit = makeExitHandler(">>> TEST TIMEOUT"); |
| 915 _process.kill(); | 928 _process.kill(); |
| 916 } | 929 } |
| 917 | 930 |
| 918 void _startProcess(then) { | 931 _startProcess(callback) { |
| 919 _process = Process.start(_executable, _batchArguments); | 932 Future processFuture = Process.start(_executable, _batchArguments); |
| 920 _stdoutStream = new StringInputStream(_process.stdout); | 933 processFuture.then((Process p) { |
| 921 _stderrStream = new StringInputStream(_process.stderr); | 934 _process = p; |
| 922 _process.onExit = makeExitHandler(">>> TEST CRASH"); | 935 _stdoutStream = new StringInputStream(_process.stdout); |
| 923 _process.onError = (e) { | 936 _stderrStream = new StringInputStream(_process.stderr); |
| 924 print("Error starting process:"); | 937 _process.onExit = makeExitHandler(">>> TEST CRASH"); |
| 938 callback(); |
| 939 }); |
| 940 processFuture.handleException((e) { |
| 941 print("Process error:"); |
| 925 print(" Command: $_executable ${Strings.join(_batchArguments, ' ')}"); | 942 print(" Command: $_executable ${Strings.join(_batchArguments, ' ')}"); |
| 926 print(" Error: $e"); | 943 print(" Error: $e"); |
| 927 // If there is an error starting a batch process, chances are that | 944 // If there is an error starting a batch process, chances are that |
| 928 // it will always fail. So rather than re-trying a 1000+ times, we | 945 // it will always fail. So rather than re-trying a 1000+ times, we |
| 929 // exit. | 946 // exit. |
| 930 exit(1); | 947 exit(1); |
| 931 }; | 948 return true; |
| 932 _process.onStart = then; | 949 }); |
| 933 } | 950 } |
| 934 } | 951 } |
| 935 | 952 |
| 936 /** | 953 /** |
| 937 * ProcessQueue is the master control class, responsible for running all | 954 * ProcessQueue is the master control class, responsible for running all |
| 938 * the tests in all the TestSuites that have been registered. It includes | 955 * the tests in all the TestSuites that have been registered. It includes |
| 939 * a rate-limited queue to run a limited number of tests in parallel, | 956 * a rate-limited queue to run a limited number of tests in parallel, |
| 940 * a ProgressIndicator which prints output when tests are started and | 957 * a ProgressIndicator which prints output when tests are started and |
| 941 * and completed, and a summary report when all tests are completed, | 958 * and completed, and a summary report when all tests are completed, |
| 942 * and counters to determine when all of the tests in all of the test suites | 959 * and counters to determine when all of the tests in all of the test suites |
| (...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1071 if (!_isSeleniumAvailable && !_startingServer) { | 1088 if (!_isSeleniumAvailable && !_startingServer) { |
| 1072 _startingServer = true; | 1089 _startingServer = true; |
| 1073 | 1090 |
| 1074 // Check to see if the jar was already running before the program started. | 1091 // Check to see if the jar was already running before the program started. |
| 1075 String cmd = 'ps'; | 1092 String cmd = 'ps'; |
| 1076 var arg = ['aux']; | 1093 var arg = ['aux']; |
| 1077 if (Platform.operatingSystem == 'windows') { | 1094 if (Platform.operatingSystem == 'windows') { |
| 1078 cmd = 'tasklist'; | 1095 cmd = 'tasklist'; |
| 1079 arg.add('/v'); | 1096 arg.add('/v'); |
| 1080 } | 1097 } |
| 1081 Process p = Process.start(cmd, arg); | 1098 |
| 1082 final StringInputStream stdoutStringStream = | 1099 Future processFuture = Process.start(cmd, arg); |
| 1083 new StringInputStream(p.stdout); | 1100 processFuture.then((Process p) { |
| 1084 p.onError = (e) { | 1101 final StringInputStream stdoutStringStream = |
| 1102 new StringInputStream(p.stdout); |
| 1103 stdoutStringStream.onLine = () { |
| 1104 var line = stdoutStringStream.readLine(); |
| 1105 while (null != line) { |
| 1106 var regexp = const RegExp(r".*selenium-server-standalone.*"); |
| 1107 if (regexp.hasMatch(line)) { |
| 1108 _seleniumAlreadyRunning = true; |
| 1109 resumeTesting(); |
| 1110 } |
| 1111 line = stdoutStringStream.readLine(); |
| 1112 } |
| 1113 if (!_isSeleniumAvailable) { |
| 1114 _startSeleniumServer(); |
| 1115 } |
| 1116 }; |
| 1117 }); |
| 1118 processFuture.handleException((e) { |
| 1085 print("Error starting process:"); | 1119 print("Error starting process:"); |
| 1086 print(" Command: $cmd ${Strings.join(arg, ' ')}"); | 1120 print(" Command: $cmd ${Strings.join(arg, ' ')}"); |
| 1087 print(" Error: $e"); | 1121 print(" Error: $e"); |
| 1088 // TODO(ahe): How to report this as a test failure? | 1122 // TODO(ahe): How to report this as a test failure? |
| 1089 exit(1); | 1123 exit(1); |
| 1090 }; | 1124 return true; |
| 1091 stdoutStringStream.onLine = () { | 1125 }); |
| 1092 var line = stdoutStringStream.readLine(); | |
| 1093 while (null != line) { | |
| 1094 if (const RegExp(r".*selenium-server-standalone.*").hasMatch(line)) { | |
| 1095 _seleniumAlreadyRunning = true; | |
| 1096 resumeTesting(); | |
| 1097 } | |
| 1098 line = stdoutStringStream.readLine(); | |
| 1099 } | |
| 1100 if (!_isSeleniumAvailable) { | |
| 1101 _startSeleniumServer(); | |
| 1102 } | |
| 1103 }; | |
| 1104 } | 1126 } |
| 1105 } | 1127 } |
| 1106 | 1128 |
| 1107 void _runTest(TestCase test) { | 1129 void _runTest(TestCase test) { |
| 1108 if (test.usesWebDriver) { | 1130 if (test.usesWebDriver) { |
| 1109 browserUsed = test.configuration['browser']; | 1131 browserUsed = test.configuration['browser']; |
| 1110 if (_needsSelenium) _ensureSeleniumServerRunning(); | 1132 if (_needsSelenium) _ensureSeleniumServerRunning(); |
| 1111 } | 1133 } |
| 1112 _progress.testAdded(); | 1134 _progress.testAdded(); |
| 1113 _tests.add(test); | 1135 _tests.add(test); |
| (...skipping 28 matching lines...) Expand all Loading... |
| 1142 void _startSeleniumServer() { | 1164 void _startSeleniumServer() { |
| 1143 // Get the absolute path to the Selenium jar. | 1165 // Get the absolute path to the Selenium jar. |
| 1144 String filePath = TestUtils.testScriptPath; | 1166 String filePath = TestUtils.testScriptPath; |
| 1145 String pathSep = Platform.pathSeparator; | 1167 String pathSep = Platform.pathSeparator; |
| 1146 int index = filePath.lastIndexOf(pathSep); | 1168 int index = filePath.lastIndexOf(pathSep); |
| 1147 filePath = '${filePath.substring(0, index)}${pathSep}testing${pathSep}'; | 1169 filePath = '${filePath.substring(0, index)}${pathSep}testing${pathSep}'; |
| 1148 var lister = new Directory(filePath).list(); | 1170 var lister = new Directory(filePath).list(); |
| 1149 lister.onFile = (String file) { | 1171 lister.onFile = (String file) { |
| 1150 if (const RegExp(r"selenium-server-standalone-.*\.jar").hasMatch(file) | 1172 if (const RegExp(r"selenium-server-standalone-.*\.jar").hasMatch(file) |
| 1151 && _seleniumServer == null) { | 1173 && _seleniumServer == null) { |
| 1152 _seleniumServer = Process.start('java', ['-jar', file]); | 1174 Future processFuture = Process.start('java', ['-jar', file]); |
| 1153 _seleniumServer.onError = (e) { | 1175 processFuture.then((Process server) { |
| 1154 print("Error starting process:"); | 1176 _seleniumServer = server; |
| 1177 // Heads up: there seems to an obscure data race of some form in |
| 1178 // the VM between launching the server process and launching the test |
| 1179 // tasks that disappears when you read IO (which is convenient, since |
| 1180 // that is our condition for knowing that the server is ready). |
| 1181 StringInputStream stdoutStringStream = |
| 1182 new StringInputStream(_seleniumServer.stdout); |
| 1183 StringInputStream stderrStringStream = |
| 1184 new StringInputStream(_seleniumServer.stderr); |
| 1185 stdoutStringStream.onLine = |
| 1186 makeSeleniumServerHandler(stdoutStringStream); |
| 1187 stderrStringStream.onLine = |
| 1188 makeSeleniumServerHandler(stderrStringStream); |
| 1189 }); |
| 1190 processFuture.handleException((e) { |
| 1191 print("Process error:"); |
| 1155 print(" Command: java -jar $file"); | 1192 print(" Command: java -jar $file"); |
| 1156 print(" Error: $e"); | 1193 print(" Error: $e"); |
| 1157 // TODO(ahe): How to report this as a test failure? | 1194 // TODO(ahe): How to report this as a test failure? |
| 1158 exit(1); | 1195 exit(1); |
| 1159 }; | 1196 return true; |
| 1160 // Heads up: there seems to an obscure data race of some form in | 1197 }); |
| 1161 // the VM between launching the server process and launching the test | |
| 1162 // tasks that disappears when you read IO (which is convenient, since | |
| 1163 // that is our condition for knowing that the server is ready). | |
| 1164 StringInputStream stdoutStringStream = | |
| 1165 new StringInputStream(_seleniumServer.stdout); | |
| 1166 StringInputStream stderrStringStream = | |
| 1167 new StringInputStream(_seleniumServer.stderr); | |
| 1168 stdoutStringStream.onLine = | |
| 1169 makeSeleniumServerHandler(stdoutStringStream); | |
| 1170 stderrStringStream.onLine = | |
| 1171 makeSeleniumServerHandler(stderrStringStream); | |
| 1172 } | 1198 } |
| 1173 }; | 1199 }; |
| 1174 } | 1200 } |
| 1175 | 1201 |
| 1176 Future _terminateBatchRunners() { | 1202 Future _terminateBatchRunners() { |
| 1177 var futures = new List(); | 1203 var futures = new List(); |
| 1178 for (var runners in _batchProcesses.getValues()) { | 1204 for (var runners in _batchProcesses.getValues()) { |
| 1179 for (var runner in runners) { | 1205 for (var runner in runners) { |
| 1180 futures.add(runner.terminate()); | 1206 futures.add(runner.terminate()); |
| 1181 } | 1207 } |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1247 // the developer doesn't waste his or her time trying to fix a bunch of | 1273 // the developer doesn't waste his or her time trying to fix a bunch of |
| 1248 // tests that appear to be broken but were actually just flakes that | 1274 // tests that appear to be broken but were actually just flakes that |
| 1249 // didn't get retried because there had already been one failure. | 1275 // didn't get retried because there had already been one failure. |
| 1250 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests; | 1276 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests; |
| 1251 new RunningProcess(test, allowRetry, this).start(); | 1277 new RunningProcess(test, allowRetry, this).start(); |
| 1252 } | 1278 } |
| 1253 _numProcesses++; | 1279 _numProcesses++; |
| 1254 } | 1280 } |
| 1255 } | 1281 } |
| 1256 } | 1282 } |
| OLD | NEW |