Index: tools/testing/dart/browser_controller.dart |
diff --git a/tools/testing/dart/browser_controller.dart b/tools/testing/dart/browser_controller.dart |
index a6d061cf9bf248ff2b0506c5877135ec14badd37..c00a7eae0ab9989f1696fb66b650240060761563 100644 |
--- a/tools/testing/dart/browser_controller.dart |
+++ b/tools/testing/dart/browser_controller.dart |
@@ -7,6 +7,7 @@ import "dart:async"; |
import "dart:convert" show LineSplitter, UTF8, JSON; |
import "dart:core"; |
import "dart:io"; |
+import "dart:math" show max, min; |
import 'android.dart'; |
import 'http_server.dart'; |
@@ -772,7 +773,7 @@ class BrowserTestingStatus { |
BrowserTest lastTest; |
bool timeout = false; |
Timer nextTestTimeout; |
- Stopwatch timeSinceRestart = new Stopwatch(); |
+ Stopwatch timeSinceRestart = new Stopwatch()..start(); |
BrowserTestingStatus(Browser this.browser); |
} |
@@ -852,25 +853,36 @@ class BrowserTestRunner { |
static const int MAX_NEXT_TEST_TIMEOUTS = 10; |
static const Duration NEXT_TEST_TIMEOUT = const Duration(seconds: 60); |
static const Duration RESTART_BROWSER_INTERVAL = const Duration(seconds: 60); |
+ // If the test has been empty within this period, don't start another browser. |
ahe
2016/04/05 13:19:41
Add newline before comment to make it obvious wher
Bill Hesse
2016/04/05 14:51:22
Done.
|
+ static const Duration MIN_NONEMPTY_QUEUE_TIME = const Duration(seconds: 1); |
final Map configuration; |
final String localIp; |
String browserName; |
- final int maxNumBrowsers; |
+ int maxNumBrowsers; |
bool checkedMode; |
// Used to send back logs from the browser (start, stop etc) |
Function logger; |
- int browserIdCount = 0; |
+ int browserIdCounter = 0; |
ahe
2016/04/05 13:19:42
Isn't this a count, not a counter. A counter is so
Bill Hesse
2016/04/05 14:51:22
This is a counter, that ticks up, just to give a n
|
+ bool testServerStarted = false; |
bool underTermination = false; |
int numBrowserGetTestTimeouts = 0; |
+ // We will start a new browser when the test queue hasn't been empty |
+ // recently, we have fewer than maxNumBrowsers browsers, and there is |
+ // no browser currently starting up. |
+ DateTime lastEmptyTestQueueTime = new DateTime.now(); |
+ int numBrowsers = 0; |
+ String startingBrowserId; |
+ |
List<BrowserTest> testQueue = new List<BrowserTest>(); |
Map<String, BrowserTestingStatus> browserStatus = |
new Map<String, BrowserTestingStatus>(); |
var adbDeviceMapping = new Map<String, AdbDevice>(); |
+ List<AdbDevice> idleAdbDevices; |
// This cache is used to guarantee that we never see double reporting. |
// If we do we need to provide developers with this information. |
// We don't add urls to the cache until we have run it. |
@@ -878,11 +890,16 @@ class BrowserTestRunner { |
Map<int, String> doubleReportingOutputs = new Map<int, String>(); |
BrowserTestingServer testingServer; |
+ // If [browserName] doesn't support opening new windows, we use new iframes |
+ // instead. |
+ bool get useIframe => |
+ !Browser.BROWSERS_WITH_WINDOW_SUPPORT.contains(browserName); |
/** |
* The TestRunner takes the testingServer in as a constructor parameter in |
* case we wish to have a testing server with different behavior (such as the |
- * case for performance testing. |
+ * case for performance testing. Note that the testingServer handlers are |
+ * overwritten with handlers for this specific configuration, in that case. |
*/ |
BrowserTestRunner(this.configuration, |
this.localIp, |
@@ -890,92 +907,64 @@ class BrowserTestRunner { |
this.maxNumBrowsers, |
{BrowserTestingServer this.testingServer}) { |
checkedMode = configuration['checked']; |
+ if (browserName == 'ff') browserName = 'firefox'; |
} |
- Future<bool> start() { |
- // If [browserName] doesn't support opening new windows, we use new iframes |
- // instead. |
- bool useIframe = |
- !Browser.BROWSERS_WITH_WINDOW_SUPPORT.contains(browserName); |
+ Future start() async { |
if (testingServer == null) { |
testingServer = new BrowserTestingServer( |
configuration, localIp, useIframe); |
} |
- return testingServer.start().then((_) { |
- testingServer.testDoneCallBack = handleResults; |
- testingServer.testStatusUpdateCallBack = handleStatusUpdate; |
- testingServer.testStartedCallBack = handleStarted; |
- testingServer.nextTestCallBack = getNextTest; |
- return getBrowsers().then((browsers) { |
- var futures = []; |
- for (var browser in browsers) { |
- var url = testingServer.getDriverUrl(browser.id); |
- var future = browser.start(url).then((success) { |
- if (success) { |
- var status = new BrowserTestingStatus(browser); |
- browserStatus[browser.id] = status; |
- status.nextTestTimeout = createNextTestTimer(status); |
- status.timeSinceRestart.start(); |
- } |
- return success; |
- }); |
- futures.add(future); |
- } |
- return Future.wait(futures).then((values) { |
- return !values.contains(false); |
- }); |
- }); |
- }); |
+ await testingServer.start(); |
+ testingServer |
+ ..testDoneCallBack = handleResults |
+ ..testStatusUpdateCallBack = handleStatusUpdate |
+ ..testStartedCallBack = handleStarted |
+ ..nextTestCallBack = getNextTest; |
+ if (browserName == 'chromeOnAndroid') { |
+ var idbNames = await AdbHelper.listDevices(); |
+ idleAdbDevices = new List.from(idbNames.map((id) => new AdbDevice(id))); |
+ maxNumBrowsers = min(maxNumBrowsers, idleAdbDevices.length); |
+ } |
+ testServerStarted = true; |
+ requestBrowser(); |
} |
- Future<List<Browser>> getBrowsers() { |
- // TODO(kustermann): This is a hackisch way to accomplish it and should |
- // be encapsulated |
- var browsersCompleter = new Completer(); |
- var androidBrowserCreationMapping = { |
- 'chromeOnAndroid' : (AdbDevice device) => new AndroidChrome(device), |
- 'ContentShellOnAndroid' : (AdbDevice device) => new AndroidBrowser( |
- device, |
- contentShellOnAndroidConfig, |
- checkedMode, |
- configuration['drt']), |
- 'DartiumOnAndroid' : (AdbDevice device) => new AndroidBrowser( |
- device, |
- dartiumOnAndroidConfig, |
- checkedMode, |
- configuration['dartium']), |
- }; |
- if (androidBrowserCreationMapping.containsKey(browserName)) { |
- AdbHelper.listDevices().then((deviceIds) { |
- if (deviceIds.length > 0) { |
- var browsers = []; |
- for (int i = 0; i < deviceIds.length; i++) { |
- var id = "BROWSER$i"; |
- var device = new AdbDevice(deviceIds[i]); |
- adbDeviceMapping[id] = device; |
- var browser = androidBrowserCreationMapping[browserName](device); |
- browsers.add(browser); |
- // We store this in case we need to kill the browser. |
- browser.id = id; |
- } |
- browsersCompleter.complete(browsers); |
- } else { |
- throw new StateError("No android devices found."); |
- } |
- }); |
+ void requestBrowser() { |
ahe
2016/04/05 13:19:41
Who calls this method? Why and when?
Bill Hesse
2016/04/05 14:51:22
Added doc comment.
|
+ if (!testServerStarted) return; |
ahe
2016/04/05 13:19:41
Why is it OK to not start a browser if the test se
Bill Hesse
2016/04/05 14:51:22
Because 4 lines above, when the test server is fin
|
+ if (underTermination) return; |
ahe
2016/04/05 13:19:41
Who would be requesting a browser during terminati
Bill Hesse
2016/04/05 14:51:22
Another browser that is requesting a new test. If
|
+ if (numBrowsers == maxNumBrowsers) return; |
+ if (startingBrowserId != null) return; |
ahe
2016/04/05 13:19:42
What does it mean if startingBrowserId is null?
Bill Hesse
2016/04/05 14:51:22
It means no browser is currently starting. I cons
|
+ if (numBrowsers > 0 && |
+ (testQueue.isEmpty || |
+ lastEmptyTestQueueTime.add(MIN_NONEMPTY_QUEUE_TIME) |
+ .isAfter(new DateTime.now()))) return; |
+ startBrowser(); |
+ } |
+ |
+ void startBrowser() { |
ahe
2016/04/05 13:19:42
How is this different from Browser.startBrowser?
C
Bill Hesse
2016/04/05 14:51:22
Browser.startBrowser is a bad name for that functi
|
+ final id = "BROWSER$browserIdCounter"; |
+ print("startBrowser called: $id"); |
ahe
2016/04/05 13:19:42
Debug message?
Bill Hesse
2016/04/05 14:51:22
Done.
|
+ ++browserIdCounter; |
+ final url = testingServer.getDriverUrl(id); |
+ Browser browser; |
+ if (browserName == 'chromeOnAndroid') { |
+ AdbDevice device = idleAdbDevices.removeLast(); |
+ adbDeviceMapping[id] = device; |
+ browser = new AndroidChrome(device); |
} else { |
- var browsers = []; |
- for (int i = 0; i < maxNumBrowsers; i++) { |
- var id = "BROWSER$browserIdCount"; |
- browserIdCount++; |
- var browser = getInstance(); |
- browsers.add(browser); |
- // We store this in case we need to kill the browser. |
- browser.id = id; |
- } |
- browsersCompleter.complete(browsers); |
+ var path = Locations.getBrowserLocation(browserName, configuration); |
+ browser = new Browser.byName(browserName, path, checkedMode); |
+ browser.logger = logger; |
} |
- return browsersCompleter.future; |
+ browser.id = id; |
+ startingBrowserId = id; |
+ final status = new BrowserTestingStatus(browser); |
+ browserStatus[id] = status; |
+ numBrowsers++; |
+ print("Started $id $numBrowsers"); |
ahe
2016/04/05 13:19:42
Debug?
Bill Hesse
2016/04/05 14:51:22
Done.
|
+ status.nextTestTimeout = createNextTestTimer(status); |
ahe
2016/04/05 13:19:42
What does this do?
Bill Hesse
2016/04/05 14:51:22
Added a doc comment to createNextTestTimer and cre
|
+ browser.start(url); |
} |
var timedOut = []; |
@@ -1084,63 +1073,22 @@ class BrowserTestRunner { |
// We don't want to start a new browser if we are terminating. |
if (underTermination) return; |
- restartBrowser(id); |
+ removeBrowser(id); |
}); |
} |
- void restartBrowser(String id) { |
- if (browserName.contains('OnAndroid')) { |
- DebugLogger.info("Restarting browser $id"); |
- } |
- var browser; |
- var new_id = id; |
+ void removeBrowser(String id) { |
ahe
2016/04/05 13:19:42
This is confusing: remove browser seems to add a b
Bill Hesse
2016/04/05 14:51:22
Remove the entries for that browserID from our dat
|
if (browserName == 'chromeOnAndroid') { |
- browser = new AndroidChrome(adbDeviceMapping[id]); |
- } else if (browserName == 'ContentShellOnAndroid') { |
- browser = new AndroidBrowser(adbDeviceMapping[id], |
- contentShellOnAndroidConfig, |
- checkedMode, |
- configuration['drt']); |
- } else if (browserName == 'DartiumOnAndroid') { |
- browser = new AndroidBrowser(adbDeviceMapping[id], |
- dartiumOnAndroidConfig, |
- checkedMode, |
- configuration['dartium']); |
- } else { |
- browserStatus.remove(id); |
- browser = getInstance(); |
- new_id = "BROWSER$browserIdCount"; |
- browserIdCount++; |
+ idleAdbDevices.add(adbDeviceMapping.remove(id)); |
} |
- browser.id = new_id; |
- var status = new BrowserTestingStatus(browser); |
- browserStatus[new_id] = status; |
- status.nextTestTimeout = createNextTestTimer(status); |
- status.timeSinceRestart.start(); |
- browser.start(testingServer.getDriverUrl(new_id)).then((success) { |
- // We may have started terminating in the mean time. |
- if (underTermination) { |
- if (status.nextTestTimeout != null) { |
- status.nextTestTimeout.cancel(); |
- status.nextTestTimeout = null; |
- } |
- browser.close().then((success) { |
- // We should never hit this, print it out. |
- if (!success) { |
- print("Could not kill browser ($id) started due to timeout"); |
- } |
- }); |
- return; |
- } |
- if (!success) { |
- // TODO(ricow): Handle this better. |
- print("This is bad, should never happen, could not start browser"); |
- exit(1); |
- } |
- }); |
+ if (startingBrowserId == id) startingBrowserId = null; |
ahe
2016/04/05 13:19:41
What does this mean?
Bill Hesse
2016/04/05 14:51:22
startingBrowserId == null when there is no browser
|
+ browserStatus.remove(id); |
+ --numBrowsers; |
+ requestBrowser(); |
} |
BrowserTest getNextTest(String browserId) { |
+ if (startingBrowserId == browserId) startingBrowserId = null; |
var status = browserStatus[browserId]; |
if (status == null) return null; |
if (status.nextTestTimeout != null) { |
@@ -1152,12 +1100,10 @@ class BrowserTestRunner { |
// We are currently terminating this browser, don't start a new test. |
if (status.timeout) return null; |
- // Restart content_shell and dartium on Android if they have been |
+ // Restart Internet Explorer if it has been |
// running for longer than RESTART_BROWSER_INTERVAL. The tests have |
// had flaky timeouts, and this may help. |
- if ((browserName == 'ContentShellOnAndroid' || |
- browserName == 'DartiumOnAndroid' || |
- browserName == 'ie10' || |
+ if ((browserName == 'ie10' || |
browserName == 'ie11') && |
status.timeSinceRestart.elapsed > RESTART_BROWSER_INTERVAL) { |
var id = status.browser.id; |
@@ -1166,13 +1112,19 @@ class BrowserTestRunner { |
status.browser.close().then((_) { |
// We don't want to start a new browser if we are terminating. |
if (underTermination) return; |
- restartBrowser(id); |
+ removeBrowser(id); |
}); |
// Don't send a test to the browser we are restarting. |
return null; |
} |
BrowserTest test = testQueue.removeLast(); |
+ // If our queue isn't empty, try starting more browsers |
+ if (testQueue.isEmpty) { |
+ lastEmptyTestQueueTime = new DateTime.now(); |
+ } else { |
+ requestBrowser(); |
+ } |
if (status.currentTest == null) { |
status.currentTest = test; |
status.currentTest.lastKnownMessage = ''; |
@@ -1196,10 +1148,6 @@ class BrowserTestRunner { |
// browser, since a new test is being started. |
status.browser.resetTestBrowserOutput(); |
status.browser.logBrowserInfoToTestBrowserOutput(); |
- if (browserName.contains('OnAndroid')) { |
- DebugLogger.info("Browser $browserId getting test ${test.url}"); |
- } |
- |
return test; |
} |
@@ -1216,6 +1164,7 @@ class BrowserTestRunner { |
void handleNextTestTimeout(status) { |
DebugLogger.warning( |
"Browser timed out before getting next test. Restarting"); |
+ if (startingBrowserId == status.browser.id) startingBrowserId = null; |
ahe
2016/04/05 13:19:41
?
Bill Hesse
2016/04/05 14:51:22
This is covered by the call to removeBrowser below
|
if (status.timeout) return; |
numBrowserGetTestTimeouts++; |
if (numBrowserGetTestTimeouts >= MAX_NEXT_TEST_TIMEOUTS) { |
@@ -1224,12 +1173,13 @@ class BrowserTestRunner { |
terminate().then((_) => exit(1)); |
} else { |
status.timeout = true; |
- status.browser.close().then((_) => restartBrowser(status.browser.id)); |
+ status.browser.close().then((_) => removeBrowser(status.browser.id)); |
} |
} |
void queueTest(BrowserTest test) { |
testQueue.add(test); |
+ requestBrowser(); |
} |
void printDoubleReportingTests() { |
@@ -1252,7 +1202,9 @@ class BrowserTestRunner { |
} |
} |
- Future<bool> terminate() { |
+ // TODO(26191): Call a unified fatalError(), that shuts down all subprocesses. |
+ // This just kills the browsers in this BrowserTestRunner instance. |
+ Future terminate() async { |
var browsers = []; |
underTermination = true; |
testingServer.underTermination = true; |
@@ -1263,28 +1215,11 @@ class BrowserTestRunner { |
status.nextTestTimeout = null; |
} |
} |
- // Success if all the browsers closed successfully. |
- bool success = true; |
- Future closeBrowser(Browser b) { |
- return b.close().then((bool closeSucceeded) { |
- if (!closeSucceeded) { |
- success = false; |
- } |
- }); |
+ for (Browser b in browsers) { |
+ await b.close(); |
} |
- return Future.forEach(browsers, closeBrowser).then((_) { |
- testingServer.errorReportingServer.close(); |
- printDoubleReportingTests(); |
- return success; |
- }); |
- } |
- |
- Browser getInstance() { |
- if (browserName == 'ff') browserName = 'firefox'; |
- var path = Locations.getBrowserLocation(browserName, configuration); |
- var browser = new Browser.byName(browserName, path, checkedMode); |
- browser.logger = logger; |
- return browser; |
+ testingServer.errorReportingServer.close(); |
+ printDoubleReportingTests(); |
} |
} |