Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
|
kustermann
2013/05/13 16:00:00
2013
ricow1
2013/05/14 07:20:58
Done.
| |
| 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:io"; | |
| 7 import "dart:async"; | |
| 8 import "dart:core"; | |
|
kustermann
2013/05/13 16:00:00
You could sort them.
ricow1
2013/05/14 07:20:58
Done.
| |
| 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; | |
| 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); | |
| 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); | |
| 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).then((success) { | |
|
kustermann
2013/05/13 16:00:00
Why not 'retries -1'?
ricow1
2013/05/14 07:20:58
Done.
| |
| 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); | |
| 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) { | |
| 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 }); | |
|
kustermann
2013/05/13 16:00:00
Indentation.
ricow1
2013/05/14 07:20:58
Done.
| |
| 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 }); | |
| 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(); | |
| 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"; | |
|
kustermann
2013/05/13 16:00:00
We probably need to adjust the path to the binary
ricow1
2013/05/14 07:20:58
Yes, we may
| |
| 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; | |
| 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; | |
| 277 // | |
| 278 bool timeout = false; | |
| 279 BrowserTestingStatus(Browser this.browser); | |
| 280 } | |
| 281 | |
| 282 | |
| 283 /** | |
| 284 * Describes a single test to be run int the browser. | |
| 285 */ | |
| 286 class BrowserTest { | |
| 287 // TODO(ricow): Add timeout callback instead of the string passing hack. | |
| 288 Function doneCallback; | |
| 289 String url; | |
| 290 int timeout; | |
| 291 // We store this here for easy access when tests time out (instead of | |
| 292 // capturing this in a closure) | |
| 293 Timer timeoutTimer; | |
| 294 | |
| 295 // Used for debugging, this is simply a unique identifier assigned to each | |
| 296 // test. | |
| 297 int id; | |
| 298 static int _idCounter = 0; | |
| 299 | |
| 300 BrowserTest(this.url, this.doneCallback, this.timeout) { | |
| 301 id = _idCounter++; | |
| 302 } | |
| 303 } | |
| 304 | |
| 305 | |
| 306 /** | |
| 307 * Encapsulates all the functionality for running tests in browsers. | |
| 308 * The interface is rather simple. After starting the runner tests | |
| 309 * are simply added to the queue and a the supplied callbacks are called | |
| 310 * whenever a test completes. | |
| 311 */ | |
| 312 class BrowserTestRunner { | |
| 313 int maxNumBrowsers; | |
|
kustermann
2013/05/13 16:00:00
I think we should always use '_' for private membe
ricow1
2013/05/14 07:20:58
That depends on how easy you want testing to be
| |
| 314 String browserName; | |
| 315 | |
| 316 bool underTermination = false; | |
| 317 | |
| 318 List<BrowserTest> testQueue = new List<BrowserTest>(); | |
| 319 Map<String, BrowserTestingStatus> browserStatus = | |
| 320 new Map<String, BrowserTestingStatus>(); | |
| 321 // This cache is used to guarantee that we never see double reporting. | |
| 322 // If we do we need to provide developers with this information. | |
| 323 // We don't add urls to the cache until we have run it. | |
| 324 Map<int, String> testCache = new Map<int, String>(); | |
| 325 List<int> doubleReportingTests = new List<int>(); | |
| 326 | |
| 327 BrowserTestingServer testingServer; | |
| 328 | |
| 329 BrowserTestRunner(String this.browserName, int this.maxNumBrowsers); | |
| 330 | |
| 331 Future<bool> start() { | |
| 332 testingServer = new BrowserTestingServer(); | |
| 333 return testingServer.start().then((_) { | |
| 334 testingServer.testDoneCallBack = handleResults; | |
| 335 testingServer.nextTestCallBack = getNextTest; | |
| 336 var futures = []; | |
| 337 for (int i = 0; i < maxNumBrowsers; i++) { | |
| 338 var browser = getInstance(); | |
| 339 var id = "BROWSER$i"; | |
| 340 // We store this in case we need to kill the browser. | |
| 341 browser.id = id; | |
| 342 var future = | |
| 343 browser.start(testingServer.getDriverUrl(id)).then((success) { | |
| 344 if (success) { | |
| 345 browserStatus[id] = new BrowserTestingStatus(browser); | |
| 346 } | |
| 347 return success; | |
| 348 }); | |
| 349 futures.add(future); | |
| 350 } | |
| 351 return Future.wait(futures).then((values) { | |
| 352 return !values.contains(false); | |
| 353 }); | |
| 354 }); | |
| 355 } | |
| 356 | |
| 357 var timedOut = []; | |
| 358 | |
| 359 void handleResults(String browserId, String output, int testId) { | |
| 360 var status = browserStatus[browserId]; | |
| 361 if (testCache.containsKey(testId)) { | |
| 362 doubleReportingTests.add(testId); | |
|
kustermann
2013/05/13 16:00:00
Maybe we should make the buildbot red in case of "
ricow1
2013/05/14 07:20:58
I think that would make sense yes, but until the u
| |
| 363 return; | |
| 364 } | |
| 365 | |
| 366 if (status.timeout) { | |
| 367 // We don't do anything, this browser is currently being killed and | |
| 368 // replaced. | |
| 369 } else if (status.currentlyRunning != null) { | |
| 370 status.currentlyRunning.timeoutTimer.cancel(); | |
| 371 if (status.currentlyRunning.id != testId) { | |
| 372 print("Expected test id ${status.currentlyRunning.id} for" | |
| 373 "${status.currentlyRunning.url}"); | |
| 374 print("Got test id ${testId}"); | |
| 375 print("Last test id was ${status.last.id} for " | |
| 376 "${status.currentlyRunning.url}"); | |
| 377 throw("This should never happen, wrong test id"); | |
| 378 } | |
| 379 testCache[testId] = status.currentlyRunning.url; | |
| 380 status.currentlyRunning.doneCallback(output); | |
| 381 status.last = status.currentlyRunning; | |
| 382 status.currentlyRunning = null; | |
| 383 } else { | |
| 384 print("\nThis is bad, should never happen, handleResult no test"); | |
| 385 print("URL: ${status.last.url}"); | |
| 386 print(output); | |
| 387 terminate().then((_) { | |
| 388 exit(1); | |
| 389 }); | |
| 390 } | |
| 391 } | |
| 392 | |
| 393 void handleTimeout(BrowserTestingStatus status) { | |
| 394 // We simply kill the browser and starts up a new one! | |
| 395 // We could be smarter here, but it does not seems like it is worth it. | |
| 396 status.timeout = true; | |
| 397 timedOut.add(status.currentlyRunning.url); | |
| 398 // Start the new browser first | |
| 399 var browser = getInstance(); | |
| 400 var id = status.browser.id; | |
| 401 browser.start(testingServer.getDriverUrl(id)).then((success) { | |
|
kustermann
2013/05/13 16:00:00
IMHO this is not the way we should do it. We shoul
ricow1
2013/05/14 07:20:58
I did not really think about the mobile case here,
| |
| 402 // We may have started terminating in the mean time. | |
| 403 if (underTermination) { | |
| 404 browser.close().then((success) { | |
| 405 // We should never hit this, print it out. | |
| 406 if (!success) { | |
| 407 print("Could not kill browser ($id) started due to timeout"); | |
| 408 } | |
| 409 }); | |
| 410 return; | |
| 411 } | |
| 412 if (success) { | |
| 413 status.browser.close().then((closed) { | |
| 414 if (!closed) { | |
| 415 // BAD, we could not kill the browser. | |
| 416 print("could not kill browser $id"); | |
| 417 } | |
| 418 }); | |
| 419 browser.id = id; | |
| 420 status.browser = browser; | |
| 421 status.timeout = false; | |
| 422 } else { | |
| 423 // TODO(ricow): Handle this better. | |
| 424 print("This is bad, should never happen, could not start browser"); | |
| 425 exit(1); | |
| 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); | |
| 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 } | |
| 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"; | |
|
kustermann
2013/05/13 16:00:00
Please make a comment descripting the API (see for
ricow1
2013/05/14 07:20:58
Done.
| |
| 499 const String driverPath = "/driver"; | |
| 500 const String nextTestPath = "/next_test"; | |
| 501 const String reportPath = "/report"; | |
| 502 const String waitSignal = "WAIT"; | |
| 503 const String terminateSignal = "TERMINATE"; | |
| 504 | |
| 505 var testCount = 0; | |
| 506 var httpServer; | |
| 507 bool underTermination = false; | |
| 508 | |
| 509 Function testDoneCallBack; | |
| 510 Function nextTestCallBack; | |
| 511 | |
| 512 Future start() { | |
| 513 return HttpServer.bind(server, 0).then((createdServer) { | |
| 514 httpServer = createdServer; | |
| 515 void handler(HttpRequest request) { | |
| 516 if (request.uri.path.startsWith(reportPath)) { | |
| 517 var browserId = request.uri.path.substring(reportPath.length + 1); | |
| 518 var testId = int.parse(request.queryParameters["id"].split("=")[1]); | |
| 519 | |
| 520 handleReport(request, browserId, testId); | |
| 521 // handleReport will asynchroniously fetch the data and will handle | |
| 522 // the closing of the streams. | |
| 523 return; | |
| 524 } | |
| 525 var textResponse = ""; | |
| 526 if (request.uri.path.startsWith(driverPath)) { | |
| 527 var browserId = request.uri.path.substring(driverPath.length + 1); | |
| 528 textResponse = getDriverPage(browserId); | |
| 529 } else if (request.uri.path.startsWith(nextTestPath)) { | |
| 530 var browserId = request.uri.path.substring(nextTestPath.length + 1); | |
| 531 textResponse = getNextTest(browserId); | |
| 532 } else { | |
| 533 // We silently ignore other requests. | |
| 534 } | |
| 535 request.response.write(textResponse); | |
|
kustermann
2013/05/13 16:00:00
It is important to drain the request stream (even
ricow1
2013/05/14 07:20:58
Done.
| |
| 536 request.response.close(); | |
|
kustermann
2013/05/13 16:00:00
I'm not 100% sure if we need to catch the future h
ricow1
2013/05/14 07:20:58
The checked in binary does not return a future on
| |
| 537 request.response.done.catchError((error) { | |
|
kustermann
2013/05/13 16:00:00
Indentation
ricow1
2013/05/14 07:20:58
Done.
ricow1
2013/05/14 07:20:58
Done.
| |
| 538 if (!underTermination) { | |
| 539 print("URI ${request.uri}"); | |
| 540 print("Textresponse $textResponse"); | |
| 541 throw("Error returning content to browser: $error"); | |
| 542 } | |
| 543 }); | |
| 544 } | |
| 545 void errorHandler(e) { | |
| 546 if (!underTermination) print("Error occured in httpserver: $e"); | |
| 547 }; | |
| 548 httpServer.listen(handler, onError: errorHandler); | |
| 549 return true; | |
| 550 }); | |
| 551 } | |
| 552 | |
| 553 void handleReport(HttpRequest request, String browserId, var testId) { | |
| 554 StringBuffer buffer = new StringBuffer(); | |
| 555 request.transform(new StringDecoder()).listen((data) { | |
|
kustermann
2013/05/13 16:00:00
You could use 'fold()'.
ricow1
2013/05/14 07:20:58
No can do. Checked in bin: r20101, fold introduced
| |
| 556 buffer.write(data); | |
| 557 }, onDone: () { | |
| 558 String back = buffer.toString(); | |
| 559 request.response.close(); | |
| 560 testDoneCallBack(browserId, back, testId); | |
| 561 }, onError: (error) {print(error);}); | |
| 562 } | |
| 563 | |
| 564 String getNextTest(String browserId) { | |
| 565 var nextTest = nextTestCallBack(browserId); | |
| 566 if (underTermination) { | |
| 567 // Browsers will be killed shortly, send them a terminate signal so | |
| 568 // that they stop pulling. | |
| 569 return terminateSignal; | |
| 570 } else if (nextTest == null) { | |
| 571 // We don't currently have any tests ready for consumption, wait. | |
| 572 return waitSignal; | |
| 573 } else { | |
| 574 return "${nextTest.url}#id=${nextTest.id}"; | |
| 575 } | |
| 576 } | |
| 577 | |
| 578 String getDriverUrl(String browserId) { | |
| 579 if (httpServer == null) { | |
| 580 print("Bad browser testing server, you are not started yet. Can't " | |
| 581 "produce driver url"); | |
| 582 exit(1); | |
| 583 // This should never happen - exit immediately; | |
| 584 } | |
| 585 return "http://$server:${httpServer.port}/driver/$browserId"; | |
| 586 } | |
| 587 | |
| 588 | |
| 589 String getDriverPage(String browserId) { | |
| 590 String driverContent = """ | |
| 591 <!DOCTYPE html><html> | |
| 592 <head> | |
| 593 <title>Driving page</title> | |
| 594 <script type='text/javascript'> | |
| 595 var numberOfTests = 0; | |
| 596 var currentId; | |
| 597 var testing_window; | |
| 598 | |
| 599 function newTaskHandler() { | |
| 600 if(this.readyState == this.DONE) { | |
| 601 if(this.status == 200) { | |
|
kustermann
2013/05/13 16:00:00
spaces after 'if'
ricow1
2013/05/14 07:20:58
Done, plus above (no stealing my signature comment
| |
| 602 if (this.responseText == '$waitSignal') { | |
| 603 setTimeout(getNextTask, 500); | |
| 604 } else if (this.responseText == 'TERMINATE') { | |
| 605 // Don't do anything, we will be killed shortly. | |
| 606 } else { | |
| 607 // TODO(ricow): Do something more clever here. | |
| 608 if (nextTask != undefined) alert('This is really bad'); | |
| 609 var split = this.responseText.split('#'); | |
|
kustermann
2013/05/13 16:00:00
Please document how the response text looks like.
ricow1
2013/05/14 07:20:58
Done.
| |
| 610 var nextTask = split[0]; | |
| 611 id = split[1]; | |
| 612 run(nextTask); | |
| 613 } | |
| 614 } else { | |
| 615 // We are basically fucked - do something clever. | |
| 616 } | |
| 617 } | |
| 618 } | |
| 619 | |
| 620 function getNextTask() { | |
| 621 var client = new XMLHttpRequest(); | |
| 622 client.onreadystatechange = newTaskHandler; | |
| 623 client.open('GET', '$nextTestPath/$browserId'); | |
| 624 client.send(); | |
| 625 } | |
| 626 | |
| 627 function run(url) { | |
| 628 numberOfTests++; | |
| 629 document.getElementById('number').innerHTML = numberOfTests; | |
| 630 if (testing_window == undefined) { | |
| 631 testing_window = window.open(url); | |
| 632 } else { | |
| 633 testing_window.location = url; | |
| 634 } | |
| 635 } | |
| 636 | |
| 637 function reportMessage(msg) { | |
| 638 var client = new XMLHttpRequest(); | |
| 639 function handleReady() { | |
| 640 if (this.readyState == this.DONE) { | |
| 641 getNextTask(); | |
| 642 } | |
| 643 } | |
| 644 client.onreadystatechange = handleReady; | |
| 645 client.open('POST', '$reportPath/${browserId}?id=' + id); | |
| 646 client.setRequestHeader('Content-type', | |
| 647 'application/x-www-form-urlencoded'); | |
| 648 client.send(msg); | |
| 649 // TODO(ricow) add error handling to somehow report the fact that | |
| 650 // we could not send back a result. | |
| 651 } | |
| 652 | |
| 653 function messageHandler(e) { | |
| 654 var msg = e.data; | |
| 655 if (typeof msg != 'string') return; | |
| 656 reportMessage(msg); | |
| 657 } | |
| 658 | |
| 659 window.addEventListener('message', messageHandler, false); | |
| 660 waitForDone = false; | |
| 661 | |
| 662 getNextTask(); | |
| 663 | |
| 664 </script> | |
| 665 </head> | |
| 666 <body> | |
| 667 Dart test driver, number of tests: <div id="number"></div> | |
| 668 </body> | |
| 669 </html> | |
| 670 """; | |
| 671 return driverContent; | |
| 672 } | |
| 673 } | |
| OLD | NEW |