OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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 library browser; | 4 library browser; |
5 | 5 |
6 import "dart:async"; | 6 import "dart:async"; |
7 import "dart:convert" show LineSplitter, UTF8, JSON; | 7 import "dart:convert" show LineSplitter, UTF8, JSON; |
8 import "dart:core"; | 8 import "dart:core"; |
9 import "dart:io"; | 9 import "dart:io"; |
| 10 import "dart:math" show max, min; |
10 | 11 |
11 import 'android.dart'; | 12 import 'android.dart'; |
12 import 'http_server.dart'; | 13 import 'http_server.dart'; |
13 import 'path.dart'; | 14 import 'path.dart'; |
14 import 'utils.dart'; | 15 import 'utils.dart'; |
15 | 16 |
16 class BrowserOutput { | 17 class BrowserOutput { |
17 final StringBuffer stdout = new StringBuffer(); | 18 final StringBuffer stdout = new StringBuffer(); |
18 final StringBuffer stderr = new StringBuffer(); | 19 final StringBuffer stderr = new StringBuffer(); |
19 final StringBuffer eventLog = new StringBuffer(); | 20 final StringBuffer eventLog = new StringBuffer(); |
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
136 } else { | 137 } else { |
137 _logEvent("The process is already dead."); | 138 _logEvent("The process is already dead."); |
138 return new Future.value(true); | 139 return new Future.value(true); |
139 } | 140 } |
140 } | 141 } |
141 | 142 |
142 /** | 143 /** |
143 * Start the browser using the supplied argument. | 144 * Start the browser using the supplied argument. |
144 * This sets up the error handling and usage logging. | 145 * This sets up the error handling and usage logging. |
145 */ | 146 */ |
146 Future<bool> startBrowser(String command, | 147 Future<bool> startBrowserProcess(String command, |
147 List<String> arguments, | 148 List<String> arguments, |
148 {Map<String,String> environment}) { | 149 {Map<String,String> environment}) { |
149 return Process.start(command, arguments, environment: environment) | 150 return Process.start(command, arguments, environment: environment) |
150 .then((startedProcess) { | 151 .then((startedProcess) { |
151 _logEvent("Started browser using $command ${arguments.join(' ')}"); | 152 _logEvent("Started browser using $command ${arguments.join(' ')}"); |
152 process = startedProcess; | 153 process = startedProcess; |
153 // Used to notify when exiting, and as a return value on calls to | 154 // Used to notify when exiting, and as a return value on calls to |
154 // close(). | 155 // close(). |
155 var doneCompleter = new Completer(); | 156 var doneCompleter = new Completer(); |
156 done = doneCompleter.future; | 157 done = doneCompleter.future; |
(...skipping 218 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
375 _logEvent("Could not clear cache"); | 376 _logEvent("Could not clear cache"); |
376 return false; | 377 return false; |
377 } | 378 } |
378 // Get the version and log that. | 379 // Get the version and log that. |
379 return getVersion().then((version) { | 380 return getVersion().then((version) { |
380 _logEvent("Got version: $version"); | 381 _logEvent("Got version: $version"); |
381 return Directory.systemTemp.createTemp().then((userDir) { | 382 return Directory.systemTemp.createTemp().then((userDir) { |
382 _cleanup = () { userDir.deleteSync(recursive: true); }; | 383 _cleanup = () { userDir.deleteSync(recursive: true); }; |
383 _createLaunchHTML(userDir.path, url); | 384 _createLaunchHTML(userDir.path, url); |
384 var args = ["${userDir.path}/launch.html"]; | 385 var args = ["${userDir.path}/launch.html"]; |
385 return startBrowser(_binary, args); | 386 return startBrowserProcess(_binary, args); |
386 }); | 387 }); |
387 }).catchError((error) { | 388 }).catchError((error) { |
388 _logEvent("Running $_binary --version failed with $error"); | 389 _logEvent("Running $_binary --version failed with $error"); |
389 return false; | 390 return false; |
390 }); | 391 }); |
391 }); | 392 }); |
392 }); | 393 }); |
393 } | 394 } |
394 | 395 |
395 String toString() => "Safari"; | 396 String toString() => "Safari"; |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
435 // Get the version and log that. | 436 // Get the version and log that. |
436 return _getVersion().then((success) { | 437 return _getVersion().then((success) { |
437 if (!success) return false; | 438 if (!success) return false; |
438 _logEvent("Got version: $_version"); | 439 _logEvent("Got version: $_version"); |
439 | 440 |
440 return Directory.systemTemp.createTemp().then((userDir) { | 441 return Directory.systemTemp.createTemp().then((userDir) { |
441 _cleanup = () { userDir.deleteSync(recursive: true); }; | 442 _cleanup = () { userDir.deleteSync(recursive: true); }; |
442 var args = ["--user-data-dir=${userDir.path}", url, | 443 var args = ["--user-data-dir=${userDir.path}", url, |
443 "--disable-extensions", "--disable-popup-blocking", | 444 "--disable-extensions", "--disable-popup-blocking", |
444 "--bwsi", "--no-first-run"]; | 445 "--bwsi", "--no-first-run"]; |
445 return startBrowser(_binary, args, environment: _getEnvironment()); | 446 return startBrowserProcess(_binary, args, environment: _getEnvironment()
); |
446 }); | 447 }); |
447 }).catchError((e) { | 448 }).catchError((e) { |
448 _logEvent("Running $_binary --version failed with $e"); | 449 _logEvent("Running $_binary --version failed with $e"); |
449 return false; | 450 return false; |
450 }); | 451 }); |
451 } | 452 } |
452 | 453 |
453 String toString() => "Chrome"; | 454 String toString() => "Chrome"; |
454 } | 455 } |
455 | 456 |
(...skipping 21 matching lines...) Expand all Loading... |
477 if (!success) { | 478 if (!success) { |
478 _logEvent("Could not clear cache, exiting"); | 479 _logEvent("Could not clear cache, exiting"); |
479 return false; | 480 return false; |
480 } | 481 } |
481 var args = ["-SimulateApplication", | 482 var args = ["-SimulateApplication", |
482 "/Applications/Xcode.app/Contents/Developer/Platforms/" | 483 "/Applications/Xcode.app/Contents/Developer/Platforms/" |
483 "iPhoneSimulator.platform/Developer/SDKs/" | 484 "iPhoneSimulator.platform/Developer/SDKs/" |
484 "iPhoneSimulator7.1.sdk/Applications/MobileSafari.app/" | 485 "iPhoneSimulator7.1.sdk/Applications/MobileSafari.app/" |
485 "MobileSafari", | 486 "MobileSafari", |
486 "-u", url]; | 487 "-u", url]; |
487 return startBrowser(_binary, args) | 488 return startBrowserProcess(_binary, args) |
488 .catchError((e) { | 489 .catchError((e) { |
489 _logEvent("Running $_binary --version failed with $e"); | 490 _logEvent("Running $_binary --version failed with $e"); |
490 return false; | 491 return false; |
491 }); | 492 }); |
492 }); | 493 }); |
493 } | 494 } |
494 | 495 |
495 String toString() => "SafariMobileSimulator"; | 496 String toString() => "SafariMobileSimulator"; |
496 } | 497 } |
497 | 498 |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
549 .catchError((error) { | 550 .catchError((error) { |
550 _logEvent("Deleting recovery dir failed with $error"); | 551 _logEvent("Deleting recovery dir failed with $error"); |
551 return false; | 552 return false; |
552 }); | 553 }); |
553 } | 554 } |
554 | 555 |
555 Future<bool> start(String url) { | 556 Future<bool> start(String url) { |
556 _logEvent("Starting ie browser on: $url"); | 557 _logEvent("Starting ie browser on: $url"); |
557 return clearCache().then((_) => getVersion()).then((version) { | 558 return clearCache().then((_) => getVersion()).then((version) { |
558 _logEvent("Got version: $version"); | 559 _logEvent("Got version: $version"); |
559 return startBrowser(_binary, [url]); | 560 return startBrowserProcess(_binary, [url]); |
560 }); | 561 }); |
561 } | 562 } |
562 String toString() => "IE"; | 563 String toString() => "IE"; |
563 | 564 |
564 } | 565 } |
565 | 566 |
566 | 567 |
567 class AndroidBrowserConfig { | 568 class AndroidBrowserConfig { |
568 final String name; | 569 final String name; |
569 final String package; | 570 final String package; |
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
639 | 640 |
640 | 641 |
641 class AndroidChrome extends Browser { | 642 class AndroidChrome extends Browser { |
642 static const String viewAction = 'android.intent.action.VIEW'; | 643 static const String viewAction = 'android.intent.action.VIEW'; |
643 static const String mainAction = 'android.intent.action.MAIN'; | 644 static const String mainAction = 'android.intent.action.MAIN'; |
644 static const String chromePackage = 'com.android.chrome'; | 645 static const String chromePackage = 'com.android.chrome'; |
645 static const String browserPackage = 'com.android.browser'; | 646 static const String browserPackage = 'com.android.browser'; |
646 static const String firefoxPackage = 'org.mozilla.firefox'; | 647 static const String firefoxPackage = 'org.mozilla.firefox'; |
647 static const String turnScreenOnPackage = 'com.google.dart.turnscreenon'; | 648 static const String turnScreenOnPackage = 'com.google.dart.turnscreenon'; |
648 | 649 |
649 AndroidEmulator _emulator; | |
650 AdbDevice _adbDevice; | 650 AdbDevice _adbDevice; |
651 | 651 |
652 AndroidChrome(this._adbDevice); | 652 AndroidChrome(this._adbDevice); |
653 | 653 |
654 Future<bool> start(String url) { | 654 Future<bool> start(String url) { |
655 var browserIntent = new Intent( | 655 var browserIntent = new Intent( |
656 viewAction, browserPackage, '.BrowserActivity', url); | 656 viewAction, browserPackage, '.BrowserActivity', url); |
657 var chromeIntent = new Intent(viewAction, chromePackage, '.Main', url); | 657 var chromeIntent = new Intent(viewAction, chromePackage, '.Main', url); |
658 var firefoxIntent = new Intent(viewAction, firefoxPackage, '.App', url); | 658 var firefoxIntent = new Intent(viewAction, firefoxPackage, '.App', url); |
659 var turnScreenOnIntent = | 659 var turnScreenOnIntent = |
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
739 version = versionResult.stdout; | 739 version = versionResult.stdout; |
740 _logEvent("Got version: $version"); | 740 _logEvent("Got version: $version"); |
741 | 741 |
742 return Directory.systemTemp.createTemp().then((userDir) { | 742 return Directory.systemTemp.createTemp().then((userDir) { |
743 _createPreferenceFile(userDir.path); | 743 _createPreferenceFile(userDir.path); |
744 _cleanup = () { userDir.deleteSync(recursive: true); }; | 744 _cleanup = () { userDir.deleteSync(recursive: true); }; |
745 var args = ["-profile", "${userDir.path}", | 745 var args = ["-profile", "${userDir.path}", |
746 "-no-remote", "-new-instance", url]; | 746 "-no-remote", "-new-instance", url]; |
747 var environment = new Map<String,String>.from(Platform.environment); | 747 var environment = new Map<String,String>.from(Platform.environment); |
748 environment["MOZ_CRASHREPORTER_DISABLE"] = "1"; | 748 environment["MOZ_CRASHREPORTER_DISABLE"] = "1"; |
749 return startBrowser(_binary, args, environment: environment); | 749 return startBrowserProcess(_binary, args, environment: environment); |
750 | 750 |
751 }); | 751 }); |
752 }).catchError((e) { | 752 }).catchError((e) { |
753 _logEvent("Running $_binary --version failed with $e"); | 753 _logEvent("Running $_binary --version failed with $e"); |
754 return false; | 754 return false; |
755 }); | 755 }); |
756 } | 756 } |
757 | 757 |
758 String toString() => "Firefox"; | 758 String toString() => "Firefox"; |
759 } | 759 } |
760 | 760 |
761 | 761 |
762 /** | 762 /** |
763 * Describes the current state of a browser used for testing. | 763 * Describes the current state of a browser used for testing. |
764 */ | 764 */ |
765 class BrowserTestingStatus { | 765 class BrowserStatus { |
766 Browser browser; | 766 Browser browser; |
767 BrowserTest currentTest; | 767 BrowserTest currentTest; |
768 | 768 |
769 // This is currently not used for anything except for error reporting. | 769 // This is currently not used for anything except for error reporting. |
770 // Given the usefulness of this in debugging issues this should not be | 770 // Given the usefulness of this in debugging issues this should not be |
771 // removed even when we have a really stable system. | 771 // removed even when we have a really stable system. |
772 BrowserTest lastTest; | 772 BrowserTest lastTest; |
773 bool timeout = false; | 773 bool timeout = false; |
774 Timer nextTestTimeout; | 774 Timer nextTestTimeout; |
775 Stopwatch timeSinceRestart = new Stopwatch(); | 775 Stopwatch timeSinceRestart = new Stopwatch()..start(); |
776 | 776 |
777 BrowserTestingStatus(Browser this.browser); | 777 BrowserStatus(Browser this.browser); |
778 } | 778 } |
779 | 779 |
780 | 780 |
781 /** | 781 /** |
782 * Describes a single test to be run in the browser. | 782 * Describes a single test to be run in the browser. |
783 */ | 783 */ |
784 class BrowserTest { | 784 class BrowserTest { |
785 // TODO(ricow): Add timeout callback instead of the string passing hack. | 785 // TODO(ricow): Add timeout callback instead of the string passing hack. |
786 Function doneCallback; | 786 Function doneCallback; |
787 String url; | 787 String url; |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
835 final String lastKnownMessage; | 835 final String lastKnownMessage; |
836 | 836 |
837 final BrowserOutput browserOutput; | 837 final BrowserOutput browserOutput; |
838 final bool didTimeout; | 838 final bool didTimeout; |
839 | 839 |
840 BrowserTestOutput( | 840 BrowserTestOutput( |
841 this.delayUntilTestStarted, this.duration, this.lastKnownMessage, | 841 this.delayUntilTestStarted, this.duration, this.lastKnownMessage, |
842 this.browserOutput, {this.didTimeout: false}); | 842 this.browserOutput, {this.didTimeout: false}); |
843 } | 843 } |
844 | 844 |
845 /** | 845 |
846 * Encapsulates all the functionality for running tests in browsers. | 846 /// Encapsulates all the functionality for running tests in browsers. |
847 * The interface is rather simple. After starting, the runner tests | 847 /// Tests are added to the queue and the supplied callbacks are called |
848 * are simply added to the queue and a the supplied callbacks are called | 848 /// when a test completes. |
849 * whenever a test completes. | 849 /// BrowserTestRunner starts up to maxNumBrowser instances of the browser, |
850 */ | 850 /// to run the tests, starting them sequentially, as needed, so only |
| 851 /// one is starting up at a time. |
| 852 /// BrowserTestRunner starts a BrowserTestingServer, which serves a |
| 853 /// driver page to the browsers, serves tests, and receives results and |
| 854 /// requests back from the browsers. |
851 class BrowserTestRunner { | 855 class BrowserTestRunner { |
852 static const int MAX_NEXT_TEST_TIMEOUTS = 10; | 856 static const int MAX_NEXT_TEST_TIMEOUTS = 10; |
853 static const Duration NEXT_TEST_TIMEOUT = const Duration(seconds: 60); | 857 static const Duration NEXT_TEST_TIMEOUT = const Duration(seconds: 60); |
854 static const Duration RESTART_BROWSER_INTERVAL = const Duration(seconds: 60); | 858 static const Duration RESTART_BROWSER_INTERVAL = const Duration(seconds: 60); |
855 | 859 |
| 860 /// If the queue was recently empty, don't start another browser. |
| 861 static const Duration MIN_NONEMPTY_QUEUE_TIME = const Duration(seconds: 1); |
| 862 |
856 final Map configuration; | 863 final Map configuration; |
| 864 BrowserTestingServer testingServer; |
857 | 865 |
858 final String localIp; | 866 final String localIp; |
859 String browserName; | 867 String browserName; |
860 final int maxNumBrowsers; | 868 int maxNumBrowsers; |
861 bool checkedMode; | 869 bool checkedMode; |
| 870 int numBrowsers = 0; |
862 // Used to send back logs from the browser (start, stop etc) | 871 // Used to send back logs from the browser (start, stop etc) |
863 Function logger; | 872 Function logger; |
864 int browserIdCount = 0; | |
865 | 873 |
| 874 int browserIdCounter = 1; |
| 875 |
| 876 bool testingServerStarted = false; |
866 bool underTermination = false; | 877 bool underTermination = false; |
867 int numBrowserGetTestTimeouts = 0; | 878 int numBrowserGetTestTimeouts = 0; |
868 | 879 DateTime lastEmptyTestQueueTime = new DateTime.now(); |
| 880 String _currentStartingBrowserId; |
869 List<BrowserTest> testQueue = new List<BrowserTest>(); | 881 List<BrowserTest> testQueue = new List<BrowserTest>(); |
870 Map<String, BrowserTestingStatus> browserStatus = | 882 Map<String, BrowserStatus> browserStatus = |
871 new Map<String, BrowserTestingStatus>(); | 883 new Map<String, BrowserStatus>(); |
872 | 884 |
873 var adbDeviceMapping = new Map<String, AdbDevice>(); | 885 var adbDeviceMapping = new Map<String, AdbDevice>(); |
| 886 List<AdbDevice> idleAdbDevices; |
| 887 |
874 // This cache is used to guarantee that we never see double reporting. | 888 // This cache is used to guarantee that we never see double reporting. |
875 // If we do we need to provide developers with this information. | 889 // If we do we need to provide developers with this information. |
876 // We don't add urls to the cache until we have run it. | 890 // We don't add urls to the cache until we have run it. |
877 Map<int, String> testCache = new Map<int, String>(); | 891 Map<int, String> testCache = new Map<int, String>(); |
| 892 |
878 Map<int, String> doubleReportingOutputs = new Map<int, String>(); | 893 Map<int, String> doubleReportingOutputs = new Map<int, String>(); |
| 894 List<String> timedOut = []; |
879 | 895 |
880 BrowserTestingServer testingServer; | 896 // We will start a new browser when the test queue hasn't been empty |
| 897 // recently, we have fewer than maxNumBrowsers browsers, and there is |
| 898 // no other browser instance currently starting up. |
| 899 bool get queueWasEmptyRecently { |
| 900 return testQueue.isEmpty || |
| 901 new DateTime.now().difference(lastEmptyTestQueueTime) < |
| 902 MIN_NONEMPTY_QUEUE_TIME; |
| 903 } |
881 | 904 |
882 /** | 905 // While a browser is starting, but has not requested its first test, its |
883 * The TestRunner takes the testingServer in as a constructor parameter in | 906 // browserId is stored in _currentStartingBrowserId. |
884 * case we wish to have a testing server with different behavior (such as the | 907 // When no browser is currently starting, _currentStartingBrowserId is null. |
885 * case for performance testing. | 908 bool get aBrowserIsCurrentlyStarting => _currentStartingBrowserId != null; |
886 */ | 909 void markCurrentlyStarting(String id) { |
| 910 _currentStartingBrowserId = id; |
| 911 } |
| 912 void markNotCurrentlyStarting(String id) { |
| 913 if (_currentStartingBrowserId == id) _currentStartingBrowserId = null; |
| 914 } |
| 915 |
| 916 // If [browserName] doesn't support opening new windows, we use new iframes |
| 917 // instead. |
| 918 bool get useIframe => |
| 919 !Browser.BROWSERS_WITH_WINDOW_SUPPORT.contains(browserName); |
| 920 |
| 921 /// The optional testingServer parameter allows callers to pass in |
| 922 /// a testing server with different behavior than the default |
| 923 /// BrowserTestServer. The url handlers of the testingServer are |
| 924 /// overwritten, so an existing handler can't be shared between instances. |
887 BrowserTestRunner(this.configuration, | 925 BrowserTestRunner(this.configuration, |
888 this.localIp, | 926 this.localIp, |
889 this.browserName, | 927 this.browserName, |
890 this.maxNumBrowsers, | 928 this.maxNumBrowsers, |
891 {BrowserTestingServer this.testingServer}) { | 929 {BrowserTestingServer this.testingServer}) { |
892 checkedMode = configuration['checked']; | 930 checkedMode = configuration['checked']; |
| 931 if (browserName == 'ff') browserName = 'firefox'; |
893 } | 932 } |
894 | 933 |
895 Future<bool> start() { | 934 Future start() async { |
896 // If [browserName] doesn't support opening new windows, we use new iframes | |
897 // instead. | |
898 bool useIframe = | |
899 !Browser.BROWSERS_WITH_WINDOW_SUPPORT.contains(browserName); | |
900 if (testingServer == null) { | 935 if (testingServer == null) { |
901 testingServer = new BrowserTestingServer( | 936 testingServer = new BrowserTestingServer( |
902 configuration, localIp, useIframe); | 937 configuration, localIp, useIframe); |
903 } | 938 } |
904 return testingServer.start().then((_) { | 939 await testingServer.start(); |
905 testingServer.testDoneCallBack = handleResults; | 940 testingServer |
906 testingServer.testStatusUpdateCallBack = handleStatusUpdate; | 941 ..testDoneCallBack = handleResults |
907 testingServer.testStartedCallBack = handleStarted; | 942 ..testStatusUpdateCallBack = handleStatusUpdate |
908 testingServer.nextTestCallBack = getNextTest; | 943 ..testStartedCallBack = handleStarted |
909 return getBrowsers().then((browsers) { | 944 ..nextTestCallBack = getNextTest; |
910 var futures = []; | 945 if (browserName == 'chromeOnAndroid') { |
911 for (var browser in browsers) { | 946 var idbNames = await AdbHelper.listDevices(); |
912 var url = testingServer.getDriverUrl(browser.id); | 947 idleAdbDevices = new List.from(idbNames.map((id) => new AdbDevice(id))); |
913 var future = browser.start(url).then((success) { | 948 maxNumBrowsers = min(maxNumBrowsers, idleAdbDevices.length); |
914 if (success) { | 949 } |
915 var status = new BrowserTestingStatus(browser); | 950 testingServerStarted = true; |
916 browserStatus[browser.id] = status; | 951 requestBrowser(); |
917 status.nextTestTimeout = createNextTestTimer(status); | |
918 status.timeSinceRestart.start(); | |
919 } | |
920 return success; | |
921 }); | |
922 futures.add(future); | |
923 } | |
924 return Future.wait(futures).then((values) { | |
925 return !values.contains(false); | |
926 }); | |
927 }); | |
928 }); | |
929 } | 952 } |
930 | 953 |
931 Future<List<Browser>> getBrowsers() { | 954 /// requestBrowser() is called whenever we might want to start an additional |
932 // TODO(kustermann): This is a hackisch way to accomplish it and should | 955 /// browser instance. |
933 // be encapsulated | 956 /// It is called when starting the BrowserTestRunner, and whenever a browser |
934 var browsersCompleter = new Completer(); | 957 /// is killed, whenever a new test is enqueued, or whenever a browser |
935 var androidBrowserCreationMapping = { | 958 /// finishes a test. |
936 'chromeOnAndroid' : (AdbDevice device) => new AndroidChrome(device), | 959 /// So we are guaranteed that this will always eventually be called, as long |
937 'ContentShellOnAndroid' : (AdbDevice device) => new AndroidBrowser( | 960 /// as the test queue isn't empty. |
938 device, | 961 void requestBrowser() { |
939 contentShellOnAndroidConfig, | 962 if (!testingServerStarted) return; |
940 checkedMode, | 963 if (underTermination) return; |
941 configuration['drt']), | 964 if (numBrowsers == maxNumBrowsers) return; |
942 'DartiumOnAndroid' : (AdbDevice device) => new AndroidBrowser( | 965 if (aBrowserIsCurrentlyStarting) return; |
943 device, | 966 if (numBrowsers > 0 && queueWasEmptyRecently) return; |
944 dartiumOnAndroidConfig, | 967 createBrowser(); |
945 checkedMode, | |
946 configuration['dartium']), | |
947 }; | |
948 if (androidBrowserCreationMapping.containsKey(browserName)) { | |
949 AdbHelper.listDevices().then((deviceIds) { | |
950 if (deviceIds.length > 0) { | |
951 var browsers = []; | |
952 for (int i = 0; i < deviceIds.length; i++) { | |
953 var id = "BROWSER$i"; | |
954 var device = new AdbDevice(deviceIds[i]); | |
955 adbDeviceMapping[id] = device; | |
956 var browser = androidBrowserCreationMapping[browserName](device); | |
957 browsers.add(browser); | |
958 // We store this in case we need to kill the browser. | |
959 browser.id = id; | |
960 } | |
961 browsersCompleter.complete(browsers); | |
962 } else { | |
963 throw new StateError("No android devices found."); | |
964 } | |
965 }); | |
966 } else { | |
967 var browsers = []; | |
968 for (int i = 0; i < maxNumBrowsers; i++) { | |
969 var id = "BROWSER$browserIdCount"; | |
970 browserIdCount++; | |
971 var browser = getInstance(); | |
972 browsers.add(browser); | |
973 // We store this in case we need to kill the browser. | |
974 browser.id = id; | |
975 } | |
976 browsersCompleter.complete(browsers); | |
977 } | |
978 return browsersCompleter.future; | |
979 } | 968 } |
980 | 969 |
981 var timedOut = []; | 970 String getNextBrowserId() => "BROWSER${browserIdCounter++}"; |
| 971 |
| 972 void createBrowser() { |
| 973 final String id = getNextBrowserId(); |
| 974 final String url = testingServer.getDriverUrl(id); |
| 975 Browser browser; |
| 976 if (browserName == 'chromeOnAndroid') { |
| 977 AdbDevice device = idleAdbDevices.removeLast(); |
| 978 adbDeviceMapping[id] = device; |
| 979 browser = new AndroidChrome(device); |
| 980 } else { |
| 981 String path = Locations.getBrowserLocation(browserName, configuration); |
| 982 browser = new Browser.byName(browserName, path, checkedMode); |
| 983 browser.logger = logger; |
| 984 } |
| 985 browser.id = id; |
| 986 markCurrentlyStarting(id); |
| 987 final status = new BrowserStatus(browser); |
| 988 browserStatus[id] = status; |
| 989 numBrowsers++; |
| 990 status.nextTestTimeout = createNextTestTimer(status); |
| 991 browser.start(url); |
| 992 } |
982 | 993 |
983 void handleResults(String browserId, String output, int testId) { | 994 void handleResults(String browserId, String output, int testId) { |
984 var status = browserStatus[browserId]; | 995 var status = browserStatus[browserId]; |
985 if (testCache.containsKey(testId)) { | 996 if (testCache.containsKey(testId)) { |
986 doubleReportingOutputs[testId] = output; | 997 doubleReportingOutputs[testId] = output; |
987 return; | 998 return; |
988 } | 999 } |
989 | 1000 |
990 if (status == null || status.timeout) { | 1001 if (status == null || status.timeout) { |
991 // We don't do anything, this browser is currently being killed and | 1002 // We don't do anything, this browser is currently being killed and |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1043 | 1054 |
1044 if (status != null && !status.timeout && status.currentTest != null) { | 1055 if (status != null && !status.timeout && status.currentTest != null) { |
1045 status.currentTest.timeoutTimer.cancel(); | 1056 status.currentTest.timeoutTimer.cancel(); |
1046 status.currentTest.timeoutTimer = | 1057 status.currentTest.timeoutTimer = |
1047 createTimeoutTimer(status.currentTest, status); | 1058 createTimeoutTimer(status.currentTest, status); |
1048 status.currentTest.delayUntilTestStarted = | 1059 status.currentTest.delayUntilTestStarted = |
1049 status.currentTest.stopwatch.elapsed; | 1060 status.currentTest.stopwatch.elapsed; |
1050 } | 1061 } |
1051 } | 1062 } |
1052 | 1063 |
1053 void handleTimeout(BrowserTestingStatus status) { | 1064 void handleTimeout(BrowserStatus status) { |
1054 // We simply kill the browser and starts up a new one! | 1065 // We simply kill the browser and starts up a new one! |
1055 // We could be smarter here, but it does not seems like it is worth it. | 1066 // We could be smarter here, but it does not seems like it is worth it. |
1056 if (status.timeout) { | 1067 if (status.timeout) { |
1057 DebugLogger.error( | 1068 DebugLogger.error( |
1058 "Got test timeout for an already restarting browser"); | 1069 "Got test timeout for an already restarting browser"); |
1059 return; | 1070 return; |
1060 } | 1071 } |
1061 status.timeout = true; | 1072 status.timeout = true; |
1062 timedOut.add(status.currentTest.url); | 1073 timedOut.add(status.currentTest.url); |
1063 var id = status.browser.id; | 1074 var id = status.browser.id; |
(...skipping 13 matching lines...) Expand all Loading... |
1077 status.currentTest.stopwatch.elapsed, | 1088 status.currentTest.stopwatch.elapsed, |
1078 lastKnownMessage, | 1089 lastKnownMessage, |
1079 status.browser.testBrowserOutput, | 1090 status.browser.testBrowserOutput, |
1080 didTimeout: true); | 1091 didTimeout: true); |
1081 status.currentTest.doneCallback(browserTestOutput); | 1092 status.currentTest.doneCallback(browserTestOutput); |
1082 status.lastTest = status.currentTest; | 1093 status.lastTest = status.currentTest; |
1083 status.currentTest = null; | 1094 status.currentTest = null; |
1084 | 1095 |
1085 // We don't want to start a new browser if we are terminating. | 1096 // We don't want to start a new browser if we are terminating. |
1086 if (underTermination) return; | 1097 if (underTermination) return; |
1087 restartBrowser(id); | 1098 removeBrowser(id); |
| 1099 requestBrowser(); |
1088 }); | 1100 }); |
1089 } | 1101 } |
1090 | 1102 |
1091 void restartBrowser(String id) { | 1103 /// Remove a browser that has closed from our data structures that track |
1092 if (browserName.contains('OnAndroid')) { | 1104 /// open browsers. Check if we want to replace it with a new browser. |
1093 DebugLogger.info("Restarting browser $id"); | 1105 void removeBrowser(String id) { |
| 1106 if (browserName == 'chromeOnAndroid') { |
| 1107 idleAdbDevices.add(adbDeviceMapping.remove(id)); |
1094 } | 1108 } |
1095 var browser; | 1109 markNotCurrentlyStarting(id); |
1096 var new_id = id; | 1110 browserStatus.remove(id); |
1097 if (browserName == 'chromeOnAndroid') { | 1111 --numBrowsers; |
1098 browser = new AndroidChrome(adbDeviceMapping[id]); | |
1099 } else if (browserName == 'ContentShellOnAndroid') { | |
1100 browser = new AndroidBrowser(adbDeviceMapping[id], | |
1101 contentShellOnAndroidConfig, | |
1102 checkedMode, | |
1103 configuration['drt']); | |
1104 } else if (browserName == 'DartiumOnAndroid') { | |
1105 browser = new AndroidBrowser(adbDeviceMapping[id], | |
1106 dartiumOnAndroidConfig, | |
1107 checkedMode, | |
1108 configuration['dartium']); | |
1109 } else { | |
1110 browserStatus.remove(id); | |
1111 browser = getInstance(); | |
1112 new_id = "BROWSER$browserIdCount"; | |
1113 browserIdCount++; | |
1114 } | |
1115 browser.id = new_id; | |
1116 var status = new BrowserTestingStatus(browser); | |
1117 browserStatus[new_id] = status; | |
1118 status.nextTestTimeout = createNextTestTimer(status); | |
1119 status.timeSinceRestart.start(); | |
1120 browser.start(testingServer.getDriverUrl(new_id)).then((success) { | |
1121 // We may have started terminating in the mean time. | |
1122 if (underTermination) { | |
1123 if (status.nextTestTimeout != null) { | |
1124 status.nextTestTimeout.cancel(); | |
1125 status.nextTestTimeout = null; | |
1126 } | |
1127 browser.close().then((success) { | |
1128 // We should never hit this, print it out. | |
1129 if (!success) { | |
1130 print("Could not kill browser ($id) started due to timeout"); | |
1131 } | |
1132 }); | |
1133 return; | |
1134 } | |
1135 if (!success) { | |
1136 // TODO(ricow): Handle this better. | |
1137 print("This is bad, should never happen, could not start browser"); | |
1138 exit(1); | |
1139 } | |
1140 }); | |
1141 } | 1112 } |
1142 | 1113 |
1143 BrowserTest getNextTest(String browserId) { | 1114 BrowserTest getNextTest(String browserId) { |
| 1115 markNotCurrentlyStarting(browserId); |
1144 var status = browserStatus[browserId]; | 1116 var status = browserStatus[browserId]; |
1145 if (status == null) return null; | 1117 if (status == null) return null; |
1146 if (status.nextTestTimeout != null) { | 1118 if (status.nextTestTimeout != null) { |
1147 status.nextTestTimeout.cancel(); | 1119 status.nextTestTimeout.cancel(); |
1148 status.nextTestTimeout = null; | 1120 status.nextTestTimeout = null; |
1149 } | 1121 } |
1150 if (testQueue.isEmpty) return null; | 1122 if (testQueue.isEmpty) return null; |
1151 | 1123 |
1152 // We are currently terminating this browser, don't start a new test. | 1124 // We are currently terminating this browser, don't start a new test. |
1153 if (status.timeout) return null; | 1125 if (status.timeout) return null; |
1154 | 1126 |
1155 // Restart content_shell and dartium on Android if they have been | 1127 // Restart Internet Explorer if it has been |
1156 // running for longer than RESTART_BROWSER_INTERVAL. The tests have | 1128 // running for longer than RESTART_BROWSER_INTERVAL. The tests have |
1157 // had flaky timeouts, and this may help. | 1129 // had flaky timeouts, and this may help. |
1158 if ((browserName == 'ContentShellOnAndroid' || | 1130 if ((browserName == 'ie10' || |
1159 browserName == 'DartiumOnAndroid' || | |
1160 browserName == 'ie10' || | |
1161 browserName == 'ie11') && | 1131 browserName == 'ie11') && |
1162 status.timeSinceRestart.elapsed > RESTART_BROWSER_INTERVAL) { | 1132 status.timeSinceRestart.elapsed > RESTART_BROWSER_INTERVAL) { |
1163 var id = status.browser.id; | 1133 var id = status.browser.id; |
1164 // Reset stopwatch so we don't trigger again before restarting. | 1134 // Reset stopwatch so we don't trigger again before restarting. |
1165 status.timeout = true; | 1135 status.timeout = true; |
1166 status.browser.close().then((_) { | 1136 status.browser.close().then((_) { |
1167 // We don't want to start a new browser if we are terminating. | 1137 // We don't want to start a new browser if we are terminating. |
1168 if (underTermination) return; | 1138 if (underTermination) return; |
1169 restartBrowser(id); | 1139 removeBrowser(id); |
| 1140 requestBrowser(); |
1170 }); | 1141 }); |
1171 // Don't send a test to the browser we are restarting. | 1142 // Don't send a test to the browser we are restarting. |
1172 return null; | 1143 return null; |
1173 } | 1144 } |
1174 | 1145 |
1175 BrowserTest test = testQueue.removeLast(); | 1146 BrowserTest test = testQueue.removeLast(); |
| 1147 // If our queue isn't empty, try starting more browsers |
| 1148 if (testQueue.isEmpty) { |
| 1149 lastEmptyTestQueueTime = new DateTime.now(); |
| 1150 } else { |
| 1151 requestBrowser(); |
| 1152 } |
1176 if (status.currentTest == null) { | 1153 if (status.currentTest == null) { |
1177 status.currentTest = test; | 1154 status.currentTest = test; |
1178 status.currentTest.lastKnownMessage = ''; | 1155 status.currentTest.lastKnownMessage = ''; |
1179 } else { | 1156 } else { |
1180 // TODO(ricow): Handle this better. | 1157 // TODO(ricow): Handle this better. |
1181 print("Browser requested next test before reporting previous result"); | 1158 print("Browser requested next test before reporting previous result"); |
1182 print("This happened for browser $browserId"); | 1159 print("This happened for browser $browserId"); |
1183 print("Old test was: ${status.currentTest.url}"); | 1160 print("Old test was: ${status.currentTest.url}"); |
1184 print("The test before that was: ${status.lastTest.url}"); | 1161 print("The test before that was: ${status.lastTest.url}"); |
1185 print("Timed out tests:"); | 1162 print("Timed out tests:"); |
1186 for (var v in timedOut) { | 1163 for (var v in timedOut) { |
1187 print(" $v"); | 1164 print(" $v"); |
1188 } | 1165 } |
1189 exit(1); | 1166 exit(1); |
1190 } | 1167 } |
1191 | 1168 |
1192 status.currentTest.timeoutTimer = createTimeoutTimer(test, status); | 1169 status.currentTest.timeoutTimer = createTimeoutTimer(test, status); |
1193 status.currentTest.stopwatch = new Stopwatch()..start(); | 1170 status.currentTest.stopwatch = new Stopwatch()..start(); |
1194 | 1171 |
1195 // Reset the test specific output information (stdout, stderr) on the | 1172 // Reset the test specific output information (stdout, stderr) on the |
1196 // browser, since a new test is being started. | 1173 // browser, since a new test is being started. |
1197 status.browser.resetTestBrowserOutput(); | 1174 status.browser.resetTestBrowserOutput(); |
1198 status.browser.logBrowserInfoToTestBrowserOutput(); | 1175 status.browser.logBrowserInfoToTestBrowserOutput(); |
1199 if (browserName.contains('OnAndroid')) { | |
1200 DebugLogger.info("Browser $browserId getting test ${test.url}"); | |
1201 } | |
1202 | |
1203 return test; | 1176 return test; |
1204 } | 1177 } |
1205 | 1178 |
1206 Timer createTimeoutTimer(BrowserTest test, BrowserTestingStatus status) { | 1179 /// Creates a timer that is active while a test is running on a browser. |
| 1180 Timer createTimeoutTimer(BrowserTest test, BrowserStatus status) { |
1207 return new Timer(new Duration(seconds: test.timeout), | 1181 return new Timer(new Duration(seconds: test.timeout), |
1208 () { handleTimeout(status); }); | 1182 () { handleTimeout(status); }); |
1209 } | 1183 } |
1210 | 1184 |
1211 Timer createNextTestTimer(BrowserTestingStatus status) { | 1185 /// Creates a timer that is active while no test is running on the |
| 1186 /// browser. It has finished one test, and it has not requested a new test. |
| 1187 Timer createNextTestTimer(BrowserStatus status) { |
1212 return new Timer(BrowserTestRunner.NEXT_TEST_TIMEOUT, | 1188 return new Timer(BrowserTestRunner.NEXT_TEST_TIMEOUT, |
1213 () { handleNextTestTimeout(status); }); | 1189 () { handleNextTestTimeout(status); }); |
1214 } | 1190 } |
1215 | 1191 |
1216 void handleNextTestTimeout(status) { | 1192 void handleNextTestTimeout(status) { |
1217 DebugLogger.warning( | 1193 DebugLogger.warning( |
1218 "Browser timed out before getting next test. Restarting"); | 1194 "Browser timed out before getting next test. Restarting"); |
1219 if (status.timeout) return; | 1195 if (status.timeout) return; |
1220 numBrowserGetTestTimeouts++; | 1196 numBrowserGetTestTimeouts++; |
1221 if (numBrowserGetTestTimeouts >= MAX_NEXT_TEST_TIMEOUTS) { | 1197 if (numBrowserGetTestTimeouts >= MAX_NEXT_TEST_TIMEOUTS) { |
1222 DebugLogger.error( | 1198 DebugLogger.error( |
1223 "Too many browser timeouts before getting next test. Terminating"); | 1199 "Too many browser timeouts before getting next test. Terminating"); |
1224 terminate().then((_) => exit(1)); | 1200 terminate().then((_) => exit(1)); |
1225 } else { | 1201 } else { |
1226 status.timeout = true; | 1202 status.timeout = true; |
1227 status.browser.close().then((_) => restartBrowser(status.browser.id)); | 1203 status.browser.close().then((_) { |
| 1204 removeBrowser(status.browser.id); |
| 1205 requestBrowser(); |
| 1206 }); |
1228 } | 1207 } |
1229 } | 1208 } |
1230 | 1209 |
1231 void queueTest(BrowserTest test) { | 1210 void enqueueTest(BrowserTest test) { |
1232 testQueue.add(test); | 1211 testQueue.add(test); |
| 1212 requestBrowser(); |
1233 } | 1213 } |
1234 | 1214 |
1235 void printDoubleReportingTests() { | 1215 void printDoubleReportingTests() { |
1236 if (doubleReportingOutputs.length == 0) return; | 1216 if (doubleReportingOutputs.length == 0) return; |
1237 // TODO(ricow): die on double reporting. | 1217 // TODO(ricow): die on double reporting. |
1238 // Currently we just report this here, we could have a callback to the | 1218 // Currently we just report this here, we could have a callback to the |
1239 // encapsulating environment. | 1219 // encapsulating environment. |
1240 print(""); | 1220 print(""); |
1241 print("Double reporting tests"); | 1221 print("Double reporting tests"); |
1242 for (var id in doubleReportingOutputs.keys) { | 1222 for (var id in doubleReportingOutputs.keys) { |
1243 print(" ${testCache[id]}"); | 1223 print(" ${testCache[id]}"); |
1244 } | 1224 } |
1245 | 1225 |
1246 DebugLogger.warning("Double reporting tests:"); | 1226 DebugLogger.warning("Double reporting tests:"); |
1247 for (var id in doubleReportingOutputs.keys) { | 1227 for (var id in doubleReportingOutputs.keys) { |
1248 DebugLogger.warning("${testCache[id]}, output: "); | 1228 DebugLogger.warning("${testCache[id]}, output: "); |
1249 DebugLogger.warning("${doubleReportingOutputs[id]}"); | 1229 DebugLogger.warning("${doubleReportingOutputs[id]}"); |
1250 DebugLogger.warning(""); | 1230 DebugLogger.warning(""); |
1251 DebugLogger.warning(""); | 1231 DebugLogger.warning(""); |
1252 } | 1232 } |
1253 } | 1233 } |
1254 | 1234 |
1255 Future<bool> terminate() { | 1235 // TODO(26191): Call a unified fatalError(), that shuts down all subprocesses. |
| 1236 // This just kills the browsers in this BrowserTestRunner instance. |
| 1237 Future terminate() async { |
1256 var browsers = []; | 1238 var browsers = []; |
1257 underTermination = true; | 1239 underTermination = true; |
1258 testingServer.underTermination = true; | 1240 testingServer.underTermination = true; |
1259 for (BrowserTestingStatus status in browserStatus.values) { | 1241 for (BrowserStatus status in browserStatus.values) { |
1260 browsers.add(status.browser); | 1242 browsers.add(status.browser); |
1261 if (status.nextTestTimeout != null) { | 1243 if (status.nextTestTimeout != null) { |
1262 status.nextTestTimeout.cancel(); | 1244 status.nextTestTimeout.cancel(); |
1263 status.nextTestTimeout = null; | 1245 status.nextTestTimeout = null; |
1264 } | 1246 } |
1265 } | 1247 } |
1266 // Success if all the browsers closed successfully. | 1248 for (Browser b in browsers) { |
1267 bool success = true; | 1249 await b.close(); |
1268 Future closeBrowser(Browser b) { | |
1269 return b.close().then((bool closeSucceeded) { | |
1270 if (!closeSucceeded) { | |
1271 success = false; | |
1272 } | |
1273 }); | |
1274 } | 1250 } |
1275 return Future.forEach(browsers, closeBrowser).then((_) { | 1251 testingServer.errorReportingServer.close(); |
1276 testingServer.errorReportingServer.close(); | 1252 printDoubleReportingTests(); |
1277 printDoubleReportingTests(); | |
1278 return success; | |
1279 }); | |
1280 } | |
1281 | |
1282 Browser getInstance() { | |
1283 if (browserName == 'ff') browserName = 'firefox'; | |
1284 var path = Locations.getBrowserLocation(browserName, configuration); | |
1285 var browser = new Browser.byName(browserName, path, checkedMode); | |
1286 browser.logger = logger; | |
1287 return browser; | |
1288 } | 1253 } |
1289 } | 1254 } |
1290 | 1255 |
1291 class BrowserTestingServer { | 1256 class BrowserTestingServer { |
1292 final Map configuration; | 1257 final Map configuration; |
1293 /// Interface of the testing server: | 1258 /// Interface of the testing server: |
1294 /// | 1259 /// |
1295 /// GET /driver/BROWSER_ID -- This will get the driver page to fetch | 1260 /// GET /driver/BROWSER_ID -- This will get the driver page to fetch |
1296 /// and run tests ... | 1261 /// and run tests ... |
1297 /// GET /next_test/BROWSER_ID -- returns "WAIT" "TERMINATE" or "url#id" | 1262 /// GET /next_test/BROWSER_ID -- returns "WAIT" "TERMINATE" or "url#id" |
(...skipping 498 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1796 </div> | 1761 </div> |
1797 <div id="embedded_iframe_div" class="test box"> | 1762 <div id="embedded_iframe_div" class="test box"> |
1798 <iframe style="width:100%;height:100%;" id="embedded_iframe"></iframe> | 1763 <iframe style="width:100%;height:100%;" id="embedded_iframe"></iframe> |
1799 </div> | 1764 </div> |
1800 </body> | 1765 </body> |
1801 </html> | 1766 </html> |
1802 """; | 1767 """; |
1803 return driverContent; | 1768 return driverContent; |
1804 } | 1769 } |
1805 } | 1770 } |
OLD | NEW |