Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(16)

Side by Side Diff: tools/testing/dart/test_runner.dart

Issue 11091070: Change Process.start to return a future that completes with a (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 8 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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
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 void processErrorHandler(e) {
660 process.onExit = exitHandler; 668 print("Process error:");
661 process.onError = (e) {
662 print("Error starting process:");
663 print(" Command: $command"); 669 print(" Command: $command");
664 print(" Error: $e"); 670 print(" Error: $e");
665 testComplete(-1, false); 671 testComplete(-1, false);
666 };
667 InputStream stdoutStream = process.stdout;
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 } 672 }
673 Future processFuture = Process.start(command.executable, command.arguments);
ahe 2012/10/11 18:39:50 Consider using cascading here.
Mads Ager (google) 2012/10/12 08:44:46 I did and for this code I found it too confusing b
674 processFuture.then((p) {
675 process = p;
676 process.onExit = exitHandler;
677 process.onError = processErrorHandler;
678 InputStream stdoutStream = process.stdout;
679 InputStream stderrStream = process.stderr;
680 StringInputStream stdoutStringStream =
681 new StringInputStream(stdoutStream);
Emily Fortuna 2012/10/11 18:23:08 consider: var stdoutStringStream = new StringInput
Mads Ager (google) 2012/10/12 08:44:46 Agreed! I kept it as it was but I will be more tha
682 StringInputStream stderrStringStream =
683 new StringInputStream(stderrStream);
684 stdoutStringStream.onLine =
685 makeReadHandler(stdoutStringStream, stdout);
686 stderrStringStream.onLine =
687 makeReadHandler(stderrStringStream, stderr);
688 if (timeoutTimer == null) {
689 // Create one timeout timer when starting test case, remove it at end.
690 timeoutTimer = new Timer(1000 * testCase.timeout, timeoutHandler);
691 }
692 // If the timeout fired in between two commands, kill the just
693 // started process immediately.
694 if (timedOut) process.kill();
ricow1 2012/10/11 17:27:34 Why not just do p.kill() at the start of this clos
Mads Ager (google) 2012/10/12 08:44:46 Because we need the exit handler set up so that we
695 });
696 processFuture.handleException((e) {
ahe 2012/10/11 18:39:50 Shouldn't this API be the same as process.onError
Mads Ager (google) 2012/10/12 08:44:46 That would be nice, yes. I'll eliminate onError on
697 processErrorHandler(e);
698 return true;
699 });
679 } 700 }
680 701
681 void timeoutHandler(Timer unusedTimer) { 702 void timeoutHandler(Timer unusedTimer) {
682 timedOut = true; 703 timedOut = true;
683 process.kill(); 704 if (process != null) process.kill();
684 } 705 }
685 } 706 }
686 707
687 /** 708 /**
688 * This class holds a value, that can be changed. It is used when 709 * 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. 710 * closures need a shared value, that they can all change and read.
690 */ 711 */
691 class MutableValue<T> { 712 class MutableValue<T> {
692 MutableValue(T this.value); 713 MutableValue(T this.value);
693 T value; 714 T value;
(...skipping 200 matching lines...) Expand 10 before | Expand all | Expand 10 after
894 line = _stdoutStream.readLine(); 915 line = _stdoutStream.readLine();
895 } 916 }
896 line = _stderrStream.readLine(); 917 line = _stderrStream.readLine();
897 while (line != null) { 918 while (line != null) {
898 _testStderr.add(line); 919 _testStderr.add(line);
899 line = _stderrStream.readLine(); 920 line = _stderrStream.readLine();
900 } 921 }
901 _stderrDrained = true; 922 _stderrDrained = true;
902 _stdoutDrained = true; 923 _stdoutDrained = true;
903 _process.close(); 924 _process.close();
904 _startProcess(() { _reportResult(); }); 925 _startProcess(() { _reportResult(); });
ricow1 2012/10/11 17:27:34 why not just pass _reportResult in here instead of
Mads Ager (google) 2012/10/12 08:44:46 Done.
905 } else { // No active test case running. 926 } else { // No active test case running.
906 _process.close(); 927 _process.close();
907 _process = null; 928 _process = null;
908 } 929 }
909 } 930 }
910 return handler; 931 return handler;
911 } 932 }
912 933
913 void _timeoutHandler(ignore) { 934 void _timeoutHandler(ignore) {
914 _process.onExit = makeExitHandler(">>> TEST TIMEOUT"); 935 _process.onExit = makeExitHandler(">>> TEST TIMEOUT");
915 _process.kill(); 936 _process.kill();
916 } 937 }
917 938
918 void _startProcess(then) { 939 void _processErrorHandler(e) {
919 _process = Process.start(_executable, _batchArguments); 940 print("Process error:");
920 _stdoutStream = new StringInputStream(_process.stdout); 941 print(" Command: $_executable ${Strings.join(_batchArguments, ' ')}");
921 _stderrStream = new StringInputStream(_process.stderr); 942 print(" Error: $e");
922 _process.onExit = makeExitHandler(">>> TEST CRASH"); 943 // If there is an error starting a batch process, chances are that
923 _process.onError = (e) { 944 // it will always fail. So rather than re-trying a 1000+ times, we
924 print("Error starting process:"); 945 // exit.
ahe 2012/10/11 18:39:50 +1000!
925 print(" Command: $_executable ${Strings.join(_batchArguments, ' ')}"); 946 exit(1);
926 print(" Error: $e"); 947 }
927 // If there is an error starting a batch process, chances are that 948
928 // it will always fail. So rather than re-trying a 1000+ times, we 949 _startProcess(then) {
ricow1 2012/10/11 17:27:34 should we change "then" to another name here to no
Emily Fortuna 2012/10/11 18:23:08 +1
Mads Ager (google) 2012/10/12 08:44:46 Done.
929 // exit. 950 Future processFuture = Process.start(_executable, _batchArguments);
930 exit(1); 951 processFuture.then((p) {
931 }; 952 _process = p;
932 _process.onStart = then; 953 _stdoutStream = new StringInputStream(_process.stdout);
954 _stderrStream = new StringInputStream(_process.stderr);
955 _process.onExit = makeExitHandler(">>> TEST CRASH");
956 _process.onError = _processErrorHandler;
957 then();
958 });
959 processFuture.handleException((e) {
960 _processErrorHandler(e);
ahe 2012/10/11 18:39:50 Aren't you getting tired of wrapping this? ;-)
961 return true;
962 });
933 } 963 }
934 } 964 }
935 965
936 /** 966 /**
937 * ProcessQueue is the master control class, responsible for running all 967 * ProcessQueue is the master control class, responsible for running all
938 * the tests in all the TestSuites that have been registered. It includes 968 * 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, 969 * a rate-limited queue to run a limited number of tests in parallel,
940 * a ProgressIndicator which prints output when tests are started and 970 * a ProgressIndicator which prints output when tests are started and
941 * and completed, and a summary report when all tests are completed, 971 * 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 972 * 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
1071 if (!_isSeleniumAvailable && !_startingServer) { 1101 if (!_isSeleniumAvailable && !_startingServer) {
1072 _startingServer = true; 1102 _startingServer = true;
1073 1103
1074 // Check to see if the jar was already running before the program started. 1104 // Check to see if the jar was already running before the program started.
1075 String cmd = 'ps'; 1105 String cmd = 'ps';
1076 var arg = ['aux']; 1106 var arg = ['aux'];
1077 if (Platform.operatingSystem == 'windows') { 1107 if (Platform.operatingSystem == 'windows') {
1078 cmd = 'tasklist'; 1108 cmd = 'tasklist';
1079 arg.add('/v'); 1109 arg.add('/v');
1080 } 1110 }
1081 Process p = Process.start(cmd, arg); 1111
1082 final StringInputStream stdoutStringStream = 1112 processErrorHandler(e) {
1083 new StringInputStream(p.stdout);
1084 p.onError = (e) {
1085 print("Error starting process:"); 1113 print("Error starting process:");
1086 print(" Command: $cmd ${Strings.join(arg, ' ')}"); 1114 print(" Command: $cmd ${Strings.join(arg, ' ')}");
1087 print(" Error: $e"); 1115 print(" Error: $e");
1088 // TODO(ahe): How to report this as a test failure? 1116 // TODO(ahe): How to report this as a test failure?
1089 exit(1); 1117 exit(1);
1090 }; 1118 }
1091 stdoutStringStream.onLine = () { 1119
1092 var line = stdoutStringStream.readLine(); 1120 Future processFuture = Process.start(cmd, arg);
1093 while (null != line) { 1121 processFuture.then((p) {
ahe 2012/10/11 18:39:50 A type for p would be nice.
Mads Ager (google) 2012/10/12 08:44:46 Done.
1094 if (const RegExp(r".*selenium-server-standalone.*").hasMatch(line)) { 1122 final StringInputStream stdoutStringStream =
1095 _seleniumAlreadyRunning = true; 1123 new StringInputStream(p.stdout);
1096 resumeTesting(); 1124 p.onError = processErrorHandler;
1125 stdoutStringStream.onLine = () {
1126 var line = stdoutStringStream.readLine();
1127 while (null != line) {
1128 var regexp = const RegExp(r".*selenium-server-standalone.*");
1129 if (regexp.hasMatch(line)) {
1130 _seleniumAlreadyRunning = true;
1131 resumeTesting();
1132 }
1133 line = stdoutStringStream.readLine();
1097 } 1134 }
1098 line = stdoutStringStream.readLine(); 1135 if (!_isSeleniumAvailable) {
1099 } 1136 _startSeleniumServer();
1100 if (!_isSeleniumAvailable) { 1137 }
1101 _startSeleniumServer(); 1138 };
1102 } 1139 });
1103 }; 1140 processFuture.handleException((e) {
ahe 2012/10/11 18:39:50 Boring! ;-)
Mads Ager (google) 2012/10/12 08:44:46 Yawn! ;-)
1141 processErrorHandler(e);
1142 return true;
1143 });
1104 } 1144 }
1105 } 1145 }
1106 1146
1107 void _runTest(TestCase test) { 1147 void _runTest(TestCase test) {
1108 if (test.usesWebDriver) { 1148 if (test.usesWebDriver) {
1109 browserUsed = test.configuration['browser']; 1149 browserUsed = test.configuration['browser'];
Mads Ager (google) 2012/10/11 16:00:54 This is not right. browserUsed is supposed to be a
Emily Fortuna 2012/10/11 18:23:08 Right. I've put in this CL: https://codereview.chr
Mads Ager (google) 2012/10/12 08:44:46 Thank you!
1110 if (_needsSelenium) _ensureSeleniumServerRunning(); 1150 if (_needsSelenium) _ensureSeleniumServerRunning();
1111 } 1151 }
1112 _progress.testAdded(); 1152 _progress.testAdded();
1113 _tests.add(test); 1153 _tests.add(test);
1114 _tryRunTest(); 1154 _tryRunTest();
1115 } 1155 }
1116 1156
1117 /** 1157 /**
1118 * Monitor the output of the Selenium server, to know when we are ready to 1158 * Monitor the output of the Selenium server, to know when we are ready to
1119 * begin running tests. 1159 * begin running tests.
(...skipping 22 matching lines...) Expand all
1142 void _startSeleniumServer() { 1182 void _startSeleniumServer() {
1143 // Get the absolute path to the Selenium jar. 1183 // Get the absolute path to the Selenium jar.
1144 String filePath = TestUtils.testScriptPath; 1184 String filePath = TestUtils.testScriptPath;
1145 String pathSep = Platform.pathSeparator; 1185 String pathSep = Platform.pathSeparator;
1146 int index = filePath.lastIndexOf(pathSep); 1186 int index = filePath.lastIndexOf(pathSep);
1147 filePath = '${filePath.substring(0, index)}${pathSep}testing${pathSep}'; 1187 filePath = '${filePath.substring(0, index)}${pathSep}testing${pathSep}';
1148 var lister = new Directory(filePath).list(); 1188 var lister = new Directory(filePath).list();
1149 lister.onFile = (String file) { 1189 lister.onFile = (String file) {
1150 if (const RegExp(r"selenium-server-standalone-.*\.jar").hasMatch(file) 1190 if (const RegExp(r"selenium-server-standalone-.*\.jar").hasMatch(file)
1151 && _seleniumServer == null) { 1191 && _seleniumServer == null) {
1152 _seleniumServer = Process.start('java', ['-jar', file]); 1192 void processErrorHandler(e) {
1153 _seleniumServer.onError = (e) { 1193 print("Process error:");
1154 print("Error starting process:");
1155 print(" Command: java -jar $file"); 1194 print(" Command: java -jar $file");
1156 print(" Error: $e"); 1195 print(" Error: $e");
1157 // TODO(ahe): How to report this as a test failure? 1196 // TODO(ahe): How to report this as a test failure?
1158 exit(1); 1197 exit(1);
1159 }; 1198 }
1160 // Heads up: there seems to an obscure data race of some form in 1199 Future processFuture = Process.start('java', ['-jar', file]);
1161 // the VM between launching the server process and launching the test 1200 processFuture.then((server) {
1162 // tasks that disappears when you read IO (which is convenient, since 1201 _seleniumServer = server;
1163 // that is our condition for knowing that the server is ready). 1202 _seleniumServer.onError = processErrorHandler;
1164 StringInputStream stdoutStringStream = 1203 // Heads up: there seems to an obscure data race of some form in
1165 new StringInputStream(_seleniumServer.stdout); 1204 // the VM between launching the server process and launching the test
1166 StringInputStream stderrStringStream = 1205 // tasks that disappears when you read IO (which is convenient, since
1167 new StringInputStream(_seleniumServer.stderr); 1206 // that is our condition for knowing that the server is ready).
1168 stdoutStringStream.onLine = 1207 StringInputStream stdoutStringStream =
1169 makeSeleniumServerHandler(stdoutStringStream); 1208 new StringInputStream(_seleniumServer.stdout);
1170 stderrStringStream.onLine = 1209 StringInputStream stderrStringStream =
1171 makeSeleniumServerHandler(stderrStringStream); 1210 new StringInputStream(_seleniumServer.stderr);
1211 stdoutStringStream.onLine =
1212 makeSeleniumServerHandler(stdoutStringStream);
1213 stderrStringStream.onLine =
1214 makeSeleniumServerHandler(stderrStringStream);
1215 });
1216 processFuture.handleException((e) {
1217 processErrorHandler(e);
1218 return true;
1219 });
1172 } 1220 }
1173 }; 1221 };
1174 } 1222 }
1175 1223
1176 Future _terminateBatchRunners() { 1224 Future _terminateBatchRunners() {
1177 var futures = new List(); 1225 var futures = new List();
1178 for (var runners in _batchProcesses.getValues()) { 1226 for (var runners in _batchProcesses.getValues()) {
1179 for (var runner in runners) { 1227 for (var runner in runners) {
1180 futures.add(runner.terminate()); 1228 futures.add(runner.terminate());
1181 } 1229 }
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
1247 // the developer doesn't waste his or her time trying to fix a bunch of 1295 // 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 1296 // tests that appear to be broken but were actually just flakes that
1249 // didn't get retried because there had already been one failure. 1297 // didn't get retried because there had already been one failure.
1250 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests; 1298 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests;
1251 new RunningProcess(test, allowRetry, this).start(); 1299 new RunningProcess(test, allowRetry, this).start();
1252 } 1300 }
1253 _numProcesses++; 1301 _numProcesses++;
1254 } 1302 }
1255 } 1303 }
1256 } 1304 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698