Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 library browser; | |
| 5 | |
| 6 import "dart:async"; | |
| 7 import "dart:core"; | |
| 8 import "dart:io"; | |
| 9 | |
| 10 | |
| 11 /** Class describing the interface for communicating with browsers. */ | |
| 12 class Browser { | |
| 13 // Browsers actually takes a while to cleanup after itself when closing | |
| 14 // Give it sufficient time to do that. | |
| 15 static final Duration killRepeatInternal = const Duration(seconds: 10); | |
| 16 static final int killRetries = 5; | |
| 17 StringBuffer _stdout = new StringBuffer(); | |
| 18 StringBuffer _stderr = new StringBuffer(); | |
| 19 StringBuffer _usageLog = new StringBuffer(); | |
| 20 // This function is called when the process is closed. | |
| 21 // This is extracted to an external function so that we can do additional | |
| 22 // functionality when the process closes (cleanup and call onExit) | |
| 23 Function _processClosed; | |
| 24 // This is called after the process is closed, after _processClosed has | |
| 25 // been called, but before onExit. Subclasses can use this to cleanup | |
| 26 // any browser specific resources (temp directories, profiles, etc) | |
| 27 Function _cleanup; | |
|
kustermann
2013/05/14 09:05:24
We have a ton of callback in this code. In this ca
| |
| 28 | |
| 29 /** The version of the browser - normally set when starting a browser */ | |
| 30 String version = ""; | |
| 31 /** | |
| 32 * The underlying process - don't mess directly with this if you don't | |
| 33 * know what you are doing (this is an interactive process that needs | |
| 34 * special threatment to not leak). | |
| 35 */ | |
| 36 Process process; | |
| 37 | |
| 38 /** | |
| 39 * Id of the browser | |
| 40 */ | |
| 41 String id; | |
| 42 | |
| 43 /** Callback that will be executed when the browser has closed */ | |
| 44 Function onClose; | |
| 45 | |
| 46 /** Print everything (stdout, stderr, usageLog) whenever we add to it */ | |
| 47 bool debugPrint = false; | |
| 48 | |
| 49 void _logEvent(String event) { | |
| 50 String toLog = "$this ($id) - ${new DateTime.now()}: $event \n"; | |
| 51 if (debugPrint) print("usageLog: $toLog"); | |
| 52 _usageLog.write(toLog); | |
|
kustermann
2013/05/14 09:05:24
a) Is the 'debugPrint' still used?
b) You don't wr
| |
| 53 } | |
| 54 | |
| 55 void _addStdout(String output) { | |
| 56 if (debugPrint) print("stdout: $output"); | |
| 57 _stdout.write(output); | |
| 58 } | |
| 59 | |
| 60 void _addStderr(String output) { | |
| 61 if (debugPrint) print("stderr: $output"); | |
| 62 _stderr.write(output); | |
| 63 } | |
| 64 | |
| 65 // Kill the underlying process using the supplied kill function | |
| 66 // If there is a alternativeKillFunction we will use that after trying | |
| 67 // the default killFunction. | |
| 68 Future _killIt(killFunction, retries, [alternativeKillFunction = null]) { | |
| 69 Completer<bool> completer = new Completer<bool>(); | |
| 70 | |
| 71 // To capture non successfull attempts we set up a timer that will | |
| 72 // trigger a retry (using the alternativeKillFunction if supplied). | |
| 73 Timer timer = new Timer(killRepeatInternal, () { | |
| 74 // Remove the handler, we will set this again in the call to killIt | |
| 75 // below | |
| 76 if (retries <= 0) { | |
| 77 _logEvent("Could not kill the process, not trying anymore"); | |
| 78 // TODO(ricow): Should we crash the test script here and | |
| 79 // write out all our log. This is basically not a situation | |
| 80 // that we want to ignore. We could potentially have a handler we | |
| 81 // can call if this happens, which will shutdown the main process | |
| 82 // with info that people should contact [ricow,kustermann,?] | |
| 83 completer.complete(false); | |
|
kustermann
2013/05/14 09:05:24
What about calling utils.py:die("....")?
| |
| 84 } | |
| 85 _logEvent("Could not kill the process, retrying"); | |
| 86 var nextKillFunction = killFunction; | |
| 87 if (alternativeKillFunction != null) { | |
| 88 nextKillFunction = alternativeKillFunction; | |
| 89 } | |
| 90 _killIt(nextKillFunction, retries - 1).then((success) { | |
| 91 completer.complete(success); | |
| 92 }); | |
| 93 }); | |
| 94 | |
| 95 // Make sure we intercept onExit calls and eliminate the timer. | |
| 96 _processClosed = () { | |
| 97 timer.cancel(); | |
| 98 _logEvent("Proccess exited, cancel timer in kill loop"); | |
| 99 _processClosed = null; | |
| 100 completer.complete(true); | |
|
kustermann
2013/05/14 09:05:24
You could set 'process = null' here.
| |
| 101 }; | |
| 102 | |
| 103 | |
| 104 _logEvent("calling kill function"); | |
| 105 if (killFunction()) { | |
| 106 // We successfully sent the signal. | |
| 107 _logEvent("killing signal sent"); | |
| 108 } else { | |
| 109 _logEvent("The process is already dead, kill signal could not be send"); | |
| 110 completer.complete(true); | |
| 111 } | |
| 112 return completer.future; | |
| 113 } | |
| 114 | |
| 115 | |
| 116 /** Close the browser */ | |
| 117 Future<bool> close() { | |
| 118 _logEvent("Close called on browser"); | |
| 119 if (process == null) { | |
| 120 _logEvent("No process open, nothing to kill."); | |
| 121 return new Future.immediate(true); | |
| 122 } | |
| 123 var killFunction = process.kill; | |
| 124 // We use a SIGKILL signal if we don't kill the process in the first go. | |
| 125 var alternativeKillFunction = | |
| 126 () { return process.kill(ProcessSignal.SIGKILL);}; | |
| 127 return _killIt(killFunction, killRetries, alternativeKillFunction); | |
| 128 } | |
| 129 | |
| 130 /** | |
| 131 * Start the browser using the supplied argument. | |
| 132 * This sets up the error handling and usage logging. | |
| 133 */ | |
| 134 Future<bool> startBrowser(String command, List<String> arguments) { | |
| 135 return Process.start(command, arguments).then((p) { | |
|
kustermann
2013/05/14 09:05:24
I have no strong opinion about this, but you told
| |
| 136 process = p; | |
| 137 p.stdout.transform(new StringDecoder()).listen((data) { | |
| 138 _addStdout(data); | |
| 139 }, onError: (e) { | |
| 140 // This should _never_ happen, but we really want this in the log | |
| 141 // if it actually does due to dart:io or vm bug. | |
| 142 _usageLog.add("An error occured in the process stdout handling: $e"); | |
| 143 }); | |
| 144 | |
| 145 p.stderr.transform(new StringDecoder()).listen((data) { | |
| 146 _addStderr(data); | |
| 147 }, onError: (e) { | |
| 148 // This should _never_ happen, but we really want this in the log | |
| 149 // if it actually does due to dart:io or vm bug. | |
| 150 _usageLog.add("An error occured in the process stderr handling: $e"); | |
| 151 }); | |
| 152 | |
| 153 process.exitCode.then((exitCode) { | |
| 154 _logEvent("Browser closed with exitcode $exitCode"); | |
| 155 if (_processClosed != null) _processClosed(); | |
| 156 if (_cleanup != null) _cleanup(); | |
| 157 if (onClose != null) onClose(exitCode); | |
| 158 }); | |
|
kustermann
2013/05/14 09:05:24
Indentation.
| |
| 159 return true; | |
| 160 }).catchError((e) { | |
| 161 _logEvent("Running $binary $arguments failed with $e"); | |
| 162 return false; | |
| 163 }); | |
| 164 } | |
| 165 | |
| 166 /** | |
| 167 * Get any stdout that the browser wrote during execution. | |
| 168 */ | |
| 169 String get stdout => _stdout.toString(); | |
| 170 String get stderr => _stderr.toString(); | |
| 171 String get usageLog => _usageLog.toString(); | |
|
kustermann
2013/05/14 09:05:24
Where are these three variables used?
| |
| 172 | |
| 173 String toString(); | |
| 174 /** Starts the browser loading the given url */ | |
| 175 Future<bool> start(String url); | |
| 176 } | |
| 177 | |
| 178 | |
| 179 class Chrome extends Browser { | |
| 180 /** | |
| 181 * The binary used to run chrome - changing this can be nececcary for | |
| 182 * testing or using non standard chrome installation. | |
| 183 */ | |
| 184 const String binary = "google-chrome"; | |
| 185 | |
| 186 Future<bool> start(String url) { | |
| 187 _logEvent("Starting chrome browser on: $url"); | |
| 188 // Get the version and log that. | |
| 189 return Process.run(binary, ["--version"]).then((var versionResult) { | |
| 190 if (versionResult.exitCode != 0) { | |
| 191 _logEvent("Failed to chrome get version"); | |
| 192 _logEvent("Make sure $binary is a valid program for running chrome"); | |
| 193 return new Future.immediate(false); | |
| 194 } | |
| 195 version = versionResult.stdout; | |
| 196 _logEvent("Got version: $version"); | |
| 197 | |
| 198 return new Directory('').createTemp().then((userDir) { | |
| 199 _cleanup = () { userDir.delete(recursive: true); }; | |
| 200 var args = ["--user-data-dir=${userDir.path}", url, | |
| 201 "--disable-extensions", "--disable-popup-blocking", | |
| 202 "--bwsi"]; | |
| 203 return startBrowser(binary, args); | |
| 204 | |
| 205 }); | |
| 206 }).catchError((e) { | |
| 207 _logEvent("Running $binary --version failed with $e"); | |
| 208 return false; | |
| 209 }); | |
| 210 } | |
| 211 | |
| 212 String toString() => "Chrome"; | |
| 213 } | |
| 214 | |
| 215 class Firefox extends Browser { | |
| 216 /** | |
| 217 * The binary used to run firefox - changing this can be nececcary for | |
| 218 * testing or using non standard firefox installation. | |
| 219 */ | |
| 220 const String binary = "firefox"; | |
| 221 | |
| 222 const String enablePopUp = | |
| 223 "user_pref(\"dom.disable_open_during_load\", false);"; | |
| 224 | |
| 225 Future _createPreferenceFile(var path) { | |
| 226 var file = new File("${path.toString()}/user.js"); | |
| 227 var randomFile = file.openSync(FileMode.WRITE); | |
| 228 randomFile.writeStringSync(enablePopUp); | |
| 229 randomFile.close(); | |
| 230 } | |
| 231 | |
| 232 | |
| 233 Future<bool> start(String url) { | |
| 234 _logEvent("Starting firefox browser on: $url"); | |
| 235 // Get the version and log that. | |
| 236 return Process.run(binary, ["--version"]).then((var versionResult) { | |
| 237 if (versionResult.exitCode != 0) { | |
| 238 _logEvent("Failed to firefox get version"); | |
| 239 _logEvent("Make sure $binary is a valid program for running firefox"); | |
| 240 return new Future.immediate(false); | |
| 241 } | |
| 242 version = versionResult.stdout; | |
| 243 _logEvent("Got version: $version"); | |
| 244 | |
| 245 return new Directory('').createTemp().then((userDir) { | |
| 246 _createPreferenceFile(userDir.path); | |
| 247 _cleanup = () { userDir.delete(recursive: true); }; | |
| 248 var args = ["-profile", "${userDir.path}", | |
| 249 "-no-remote", "-new-instance", url]; | |
| 250 return startBrowser(binary, args); | |
| 251 | |
| 252 }); | |
| 253 }).catchError((e) { | |
| 254 _logEvent("Running $binary --version failed with $e"); | |
| 255 return false; | |
| 256 }); | |
| 257 } | |
| 258 | |
| 259 String toString() => "Firefox"; | |
| 260 } | |
| 261 | |
| 262 | |
| 263 /** | |
| 264 * Describes the current state of a browser used for testing. | |
| 265 */ | |
| 266 class BrowserTestingStatus { | |
| 267 // TODO(ricow): Add prefetching to the browsers. We spend a lot of time waiting | |
| 268 // for the next test. Handling timeouts is the hard part of this! | |
| 269 | |
| 270 | |
| 271 Browser browser; | |
| 272 BrowserTest currentlyRunning; | |
|
kustermann
2013/05/14 09:05:24
'currentlyRunning' feels like it's a boolean or so
| |
| 273 // This is currently not used for anything except for error reporting. | |
| 274 // Given the usefulness of this in debugging issues this should not be | |
| 275 // removed even when we have really stable system. | |
| 276 BrowserTest last; | |
|
kustermann
2013/05/14 09:05:24
last -> lastTest
| |
| 277 bool timeout = false; | |
| 278 BrowserTestingStatus(Browser this.browser); | |
| 279 } | |
| 280 | |
| 281 | |
| 282 /** | |
| 283 * Describes a single test to be run int the browser. | |
| 284 */ | |
| 285 class BrowserTest { | |
| 286 // TODO(ricow): Add timeout callback instead of the string passing hack. | |
| 287 Function doneCallback; | |
|
kustermann
2013/05/14 09:05:24
Again, we could make it a completer and let the te
| |
| 288 String url; | |
| 289 int timeout; | |
| 290 // We store this here for easy access when tests time out (instead of | |
| 291 // capturing this in a closure) | |
| 292 Timer timeoutTimer; | |
| 293 | |
| 294 // Used for debugging, this is simply a unique identifier assigned to each | |
| 295 // test. | |
| 296 int id; | |
| 297 static int _idCounter = 0; | |
| 298 | |
| 299 BrowserTest(this.url, this.doneCallback, this.timeout) { | |
| 300 id = _idCounter++; | |
| 301 } | |
| 302 } | |
| 303 | |
| 304 | |
| 305 /** | |
| 306 * Encapsulates all the functionality for running tests in browsers. | |
| 307 * The interface is rather simple. After starting the runner tests | |
| 308 * are simply added to the queue and a the supplied callbacks are called | |
| 309 * whenever a test completes. | |
| 310 */ | |
| 311 class BrowserTestRunner { | |
| 312 int maxNumBrowsers; | |
| 313 String browserName; | |
| 314 | |
| 315 bool underTermination = false; | |
| 316 | |
| 317 List<BrowserTest> testQueue = new List<BrowserTest>(); | |
| 318 Map<String, BrowserTestingStatus> browserStatus = | |
| 319 new Map<String, BrowserTestingStatus>(); | |
|
kustermann
2013/05/14 09:05:24
The type of the variable should be inferred -- no
| |
| 320 // This cache is used to guarantee that we never see double reporting. | |
| 321 // If we do we need to provide developers with this information. | |
| 322 // We don't add urls to the cache until we have run it. | |
| 323 Map<int, String> testCache = new Map<int, String>(); | |
| 324 List<int> doubleReportingTests = new List<int>(); | |
| 325 | |
| 326 BrowserTestingServer testingServer; | |
| 327 | |
| 328 BrowserTestRunner(String this.browserName, int this.maxNumBrowsers); | |
| 329 | |
| 330 Future<bool> start() { | |
| 331 testingServer = new BrowserTestingServer(); | |
| 332 return testingServer.start().then((_) { | |
| 333 testingServer.testDoneCallBack = handleResults; | |
| 334 testingServer.nextTestCallBack = getNextTest; | |
| 335 var futures = []; | |
| 336 for (int i = 0; i < maxNumBrowsers; i++) { | |
| 337 var browser = getInstance(); | |
| 338 var id = "BROWSER$i"; | |
| 339 // We store this in case we need to kill the browser. | |
| 340 browser.id = id; | |
| 341 var future = | |
| 342 browser.start(testingServer.getDriverUrl(id)).then((success) { | |
| 343 if (success) { | |
| 344 browserStatus[id] = new BrowserTestingStatus(browser); | |
| 345 } | |
| 346 return success; | |
| 347 }); | |
| 348 futures.add(future); | |
| 349 } | |
| 350 return Future.wait(futures).then((values) { | |
| 351 return !values.contains(false); | |
| 352 }); | |
| 353 }); | |
| 354 } | |
| 355 | |
| 356 var timedOut = []; | |
| 357 | |
| 358 void handleResults(String browserId, String output, int testId) { | |
| 359 var status = browserStatus[browserId]; | |
| 360 if (testCache.containsKey(testId)) { | |
| 361 doubleReportingTests.add(testId); | |
| 362 return; | |
| 363 } | |
| 364 | |
| 365 if (status.timeout) { | |
| 366 // We don't do anything, this browser is currently being killed and | |
| 367 // replaced. | |
| 368 } else if (status.currentlyRunning != null) { | |
| 369 status.currentlyRunning.timeoutTimer.cancel(); | |
| 370 if (status.currentlyRunning.id != testId) { | |
| 371 print("Expected test id ${status.currentlyRunning.id} for" | |
| 372 "${status.currentlyRunning.url}"); | |
| 373 print("Got test id ${testId}"); | |
| 374 print("Last test id was ${status.last.id} for " | |
| 375 "${status.currentlyRunning.url}"); | |
| 376 throw("This should never happen, wrong test id"); | |
| 377 } | |
| 378 testCache[testId] = status.currentlyRunning.url; | |
| 379 status.currentlyRunning.doneCallback(output); | |
| 380 status.last = status.currentlyRunning; | |
| 381 status.currentlyRunning = null; | |
| 382 } else { | |
| 383 print("\nThis is bad, should never happen, handleResult no test"); | |
| 384 print("URL: ${status.last.url}"); | |
| 385 print(output); | |
| 386 terminate().then((_) { | |
| 387 exit(1); | |
| 388 }); | |
| 389 } | |
| 390 } | |
| 391 | |
| 392 void handleTimeout(BrowserTestingStatus status) { | |
| 393 // We simply kill the browser and starts up a new one! | |
| 394 // We could be smarter here, but it does not seems like it is worth it. | |
| 395 status.timeout = true; | |
| 396 timedOut.add(status.currentlyRunning.url); | |
| 397 // Start the new browser first | |
| 398 var browser = getInstance(); | |
|
kustermann
2013/05/14 09:05:24
The comment is outdated now + nove the 'getInstanc
| |
| 399 var id = status.browser.id; | |
| 400 status.browser.close().then((closed) { | |
| 401 if (!closed) { | |
| 402 // Very bad, we could not kill the browser. | |
| 403 print("could not kill browser $id"); | |
| 404 return; | |
| 405 } | |
| 406 browser.start(testingServer.getDriverUrl(id)).then((success) { | |
| 407 // We may have started terminating in the mean time. | |
| 408 if (underTermination) { | |
| 409 browser.close().then((success) { | |
|
kustermann
2013/05/14 09:05:24
If we're 'underTermination' then the 'browser.clos
| |
| 410 // We should never hit this, print it out. | |
| 411 if (!success) { | |
| 412 print("Could not kill browser ($id) started due to timeout"); | |
| 413 } | |
| 414 }); | |
| 415 return; | |
|
kustermann
2013/05/14 09:05:24
indentation
| |
| 416 } | |
| 417 if (success) { | |
| 418 browser.id = id; | |
| 419 status.browser = browser; | |
| 420 status.timeout = false; | |
| 421 } else { | |
| 422 // TODO(ricow): Handle this better. | |
| 423 print("This is bad, should never happen, could not start browser"); | |
| 424 exit(1); | |
| 425 } | |
| 426 }); | |
| 427 }); | |
| 428 | |
| 429 status.currentlyRunning.doneCallback("TIMEOUT"); | |
| 430 status.currentlyRunning = null; | |
| 431 } | |
| 432 | |
| 433 BrowserTest getNextTest(String browserId) { | |
| 434 if (testQueue.isEmpty) return null; | |
| 435 var status = browserStatus[browserId]; | |
| 436 if (status == null) return null; | |
| 437 // We are currently terminating this browser, don't start a new test. | |
| 438 if (status.timeout) return null; | |
| 439 BrowserTest test = testQueue.removeLast(); | |
| 440 if (status.currentlyRunning == null) { | |
| 441 status.currentlyRunning = test; | |
| 442 } else { | |
| 443 // TODO(ricow): Handle this better. | |
| 444 print("This is bad, should never happen, getNextTest all full"); | |
| 445 print("Old test was: ${status.currentlyRunning.url}"); | |
| 446 print("Timed out tests:"); | |
| 447 for (var v in timedOut) { | |
| 448 print(" $v"); | |
| 449 } | |
| 450 exit(1); | |
|
kustermann
2013/05/14 09:05:24
utils.py:die() ?
| |
| 451 } | |
| 452 Timer timer = new Timer(new Duration(seconds: test.timeout), | |
| 453 () { handleTimeout(status); }); | |
| 454 status.currentlyRunning.timeoutTimer = timer; | |
| 455 return test; | |
| 456 } | |
| 457 | |
| 458 void queueTest(BrowserTest test) { | |
| 459 testQueue.add(test); | |
| 460 } | |
| 461 | |
| 462 void printDoubleReportingTests() { | |
| 463 if (doubleReportingTests.length == 0) return; | |
| 464 // Currently we just report this here, we could have a callback to the | |
| 465 // encapsulating environment. | |
| 466 print(""); | |
| 467 print("Double reporting tests"); | |
| 468 for (var id in doubleReportingTests) { | |
| 469 print(" ${testCache[id]}"); | |
| 470 } | |
|
kustermann
2013/05/14 09:05:24
Add a TODO to 'die()' in the future if we get doub
| |
| 471 } | |
| 472 | |
| 473 Future<bool> terminate() { | |
| 474 var futures = []; | |
| 475 underTermination = true; | |
| 476 testingServer.underTermination = true; | |
| 477 for (BrowserTestingStatus status in browserStatus.values) { | |
| 478 futures.add(status.browser.close()); | |
| 479 } | |
| 480 return Future.wait(futures).then((values) { | |
| 481 testingServer.httpServer.close(); | |
| 482 printDoubleReportingTests(); | |
| 483 return !values.contains(false); | |
| 484 }); | |
| 485 } | |
| 486 | |
| 487 Browser getInstance() { | |
| 488 if (browserName == "chrome") { | |
| 489 return new Chrome(); | |
| 490 } else if (browserName == "firefox") { | |
| 491 return new Firefox(); | |
| 492 } | |
| 493 throw "Non supported browser for browser controller"; | |
| 494 } | |
| 495 } | |
| 496 | |
| 497 class BrowserTestingServer { | |
| 498 const String server = "127.0.0.1"; | |
| 499 | |
| 500 /// Interface of the testing server: | |
| 501 /// | |
| 502 /// /driver: This will get the driver page to fetch and run tests | |
| 503 /// /nextTestPath: This will get the next available test to run, returned | |
| 504 /// as a url and an id. If there are currently no available | |
| 505 /// tests the waitSignal is send back. If we are in the | |
| 506 /// process of terminating the terminateSignal is send back | |
| 507 /// and the browser will stop requesting new tasks. | |
| 508 /// /reportPath: Used for shipping back the result of running a test. | |
|
kustermann
2013/05/14 09:05:24
This is not the interface. Please document exactly
| |
| 509 | |
| 510 | |
| 511 const String driverPath = "/driver"; | |
| 512 const String nextTestPath = "/next_test"; | |
| 513 const String reportPath = "/report"; | |
| 514 const String waitSignal = "WAIT"; | |
| 515 const String terminateSignal = "TERMINATE"; | |
| 516 | |
| 517 var testCount = 0; | |
| 518 var httpServer; | |
| 519 bool underTermination = false; | |
| 520 | |
| 521 Function testDoneCallBack; | |
| 522 Function nextTestCallBack; | |
| 523 | |
| 524 Future start() { | |
| 525 return HttpServer.bind(server, 0).then((createdServer) { | |
| 526 httpServer = createdServer; | |
| 527 void handler(HttpRequest request) { | |
| 528 if (request.uri.path.startsWith(reportPath)) { | |
| 529 var browserId = request.uri.path.substring(reportPath.length + 1); | |
| 530 var testId = int.parse(request.queryParameters["id"].split("=")[1]); | |
| 531 | |
| 532 handleReport(request, browserId, testId); | |
| 533 // handleReport will asynchroniously fetch the data and will handle | |
| 534 // the closing of the streams. | |
| 535 return; | |
| 536 } | |
| 537 var textResponse = ""; | |
| 538 if (request.uri.path.startsWith(driverPath)) { | |
| 539 var browserId = request.uri.path.substring(driverPath.length + 1); | |
| 540 textResponse = getDriverPage(browserId); | |
| 541 } else if (request.uri.path.startsWith(nextTestPath)) { | |
| 542 var browserId = request.uri.path.substring(nextTestPath.length + 1); | |
| 543 textResponse = getNextTest(browserId); | |
| 544 } else { | |
| 545 // We silently ignore other requests. | |
| 546 } | |
| 547 request.response.write(textResponse); | |
| 548 request.listen((_) {}, onDone: request.response.close); | |
| 549 request.response.done.catchError((error) { | |
| 550 if (!underTermination) { | |
| 551 print("URI ${request.uri}"); | |
| 552 print("Textresponse $textResponse"); | |
| 553 throw("Error returning content to browser: $error"); | |
| 554 } | |
| 555 }); | |
| 556 } | |
| 557 void errorHandler(e) { | |
| 558 if (!underTermination) print("Error occured in httpserver: $e"); | |
| 559 }; | |
| 560 httpServer.listen(handler, onError: errorHandler); | |
| 561 return true; | |
| 562 }); | |
| 563 } | |
| 564 | |
| 565 void handleReport(HttpRequest request, String browserId, var testId) { | |
| 566 StringBuffer buffer = new StringBuffer(); | |
| 567 request.transform(new StringDecoder()).listen((data) { | |
| 568 buffer.write(data); | |
| 569 }, onDone: () { | |
| 570 String back = buffer.toString(); | |
| 571 request.response.close(); | |
| 572 testDoneCallBack(browserId, back, testId); | |
| 573 }, onError: (error) { print(error); }); | |
| 574 } | |
| 575 | |
| 576 String getNextTest(String browserId) { | |
| 577 var nextTest = nextTestCallBack(browserId); | |
| 578 if (underTermination) { | |
| 579 // Browsers will be killed shortly, send them a terminate signal so | |
| 580 // that they stop pulling. | |
| 581 return terminateSignal; | |
| 582 } else if (nextTest == null) { | |
| 583 // We don't currently have any tests ready for consumption, wait. | |
| 584 return waitSignal; | |
| 585 } else { | |
| 586 return "${nextTest.url}#id=${nextTest.id}"; | |
| 587 } | |
| 588 } | |
| 589 | |
| 590 String getDriverUrl(String browserId) { | |
| 591 if (httpServer == null) { | |
| 592 print("Bad browser testing server, you are not started yet. Can't " | |
| 593 "produce driver url"); | |
| 594 exit(1); | |
| 595 // This should never happen - exit immediately; | |
| 596 } | |
| 597 return "http://$server:${httpServer.port}/driver/$browserId"; | |
| 598 } | |
| 599 | |
| 600 | |
| 601 String getDriverPage(String browserId) { | |
| 602 String driverContent = """ | |
| 603 <!DOCTYPE html><html> | |
| 604 <head> | |
| 605 <title>Driving page</title> | |
| 606 <script type='text/javascript'> | |
| 607 var numberOfTests = 0; | |
| 608 var currentId; | |
| 609 var testing_window; | |
| 610 | |
| 611 function newTaskHandler() { | |
| 612 if (this.readyState == this.DONE) { | |
| 613 if (this.status == 200) { | |
| 614 if (this.responseText == '$waitSignal') { | |
| 615 setTimeout(getNextTask, 500); | |
| 616 } else if (this.responseText == 'TERMINATE') { | |
|
kustermann
2013/05/14 09:05:24
Use the constant above (i.e. " == '$terminateSigna
| |
| 617 // Don't do anything, we will be killed shortly. | |
| 618 } else { | |
| 619 // TODO(ricow): Do something more clever here. | |
| 620 if (nextTask != undefined) alert('This is really bad'); | |
| 621 // The task is send to us as: | |
| 622 // URL#ID | |
| 623 var split = this.responseText.split('#'); | |
| 624 var nextTask = split[0]; | |
| 625 id = split[1]; | |
| 626 run(nextTask); | |
| 627 } | |
| 628 } else { | |
| 629 // We are basically in trouble - do something clever. | |
| 630 } | |
| 631 } | |
| 632 } | |
| 633 | |
| 634 function getNextTask() { | |
| 635 var client = new XMLHttpRequest(); | |
| 636 client.onreadystatechange = newTaskHandler; | |
| 637 client.open('GET', '$nextTestPath/$browserId'); | |
| 638 client.send(); | |
| 639 } | |
| 640 | |
| 641 function run(url) { | |
| 642 numberOfTests++; | |
| 643 document.getElementById('number').innerHTML = numberOfTests; | |
| 644 if (testing_window == undefined) { | |
| 645 testing_window = window.open(url); | |
| 646 } else { | |
| 647 testing_window.location = url; | |
| 648 } | |
| 649 } | |
| 650 | |
| 651 function reportMessage(msg) { | |
| 652 var client = new XMLHttpRequest(); | |
| 653 function handleReady() { | |
| 654 if (this.readyState == this.DONE) { | |
| 655 getNextTask(); | |
| 656 } | |
| 657 } | |
| 658 client.onreadystatechange = handleReady; | |
| 659 client.open('POST', '$reportPath/${browserId}?id=' + id); | |
| 660 client.setRequestHeader('Content-type', | |
| 661 'application/x-www-form-urlencoded'); | |
| 662 client.send(msg); | |
| 663 // TODO(ricow) add error handling to somehow report the fact that | |
| 664 // we could not send back a result. | |
| 665 } | |
| 666 | |
| 667 function messageHandler(e) { | |
| 668 var msg = e.data; | |
| 669 if (typeof msg != 'string') return; | |
| 670 reportMessage(msg); | |
| 671 } | |
| 672 | |
| 673 window.addEventListener('message', messageHandler, false); | |
| 674 waitForDone = false; | |
| 675 | |
| 676 getNextTask(); | |
| 677 | |
| 678 </script> | |
| 679 </head> | |
| 680 <body> | |
| 681 Dart test driver, number of tests: <div id="number"></div> | |
| 682 </body> | |
| 683 </html> | |
| 684 """; | |
| 685 return driverContent; | |
| 686 } | |
| 687 } | |
| OLD | NEW |