| 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; | 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 | 10 |
| 11 import 'android.dart'; | 11 import 'android.dart'; |
| 12 import 'http_server.dart'; | 12 import 'http_server.dart'; |
| 13 import 'utils.dart'; | 13 import 'utils.dart'; |
| 14 | 14 |
| 15 class BrowserOutput { | 15 class BrowserOutput { |
| 16 final StringBuffer stdout = new StringBuffer(); | 16 final StringBuffer stdout = new StringBuffer(); |
| 17 final StringBuffer stderr = new StringBuffer(); | 17 final StringBuffer stderr = new StringBuffer(); |
| (...skipping 758 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 776 Timer timeoutTimer; | 776 Timer timeoutTimer; |
| 777 | 777 |
| 778 // Used for debugging, this is simply a unique identifier assigned to each | 778 // Used for debugging, this is simply a unique identifier assigned to each |
| 779 // test. | 779 // test. |
| 780 int id; | 780 int id; |
| 781 static int _idCounter = 0; | 781 static int _idCounter = 0; |
| 782 | 782 |
| 783 BrowserTest(this.url, this.doneCallback, this.timeout) { | 783 BrowserTest(this.url, this.doneCallback, this.timeout) { |
| 784 id = _idCounter++; | 784 id = _idCounter++; |
| 785 } | 785 } |
| 786 |
| 787 String toJSON() => JSON.encode({'url': url, |
| 788 'id': id, |
| 789 'isSimpleHtmlTest': false}); |
| 786 } | 790 } |
| 787 | 791 |
| 792 |
| 793 /** |
| 794 * Describes a test with a custom HTML page to be run in the browser. |
| 795 */ |
| 796 class HtmlTest extends BrowserTest { |
| 797 List<String> expectedMessages; |
| 798 |
| 799 HtmlTest(url, doneCallback, timeout, this.expectedMessages) |
| 800 : super(url, doneCallback, timeout) { } |
| 801 |
| 802 String toJSON() => JSON.encode({'url': url, |
| 803 'id': id, |
| 804 'isSimpleHtmlTest': true, |
| 805 'expectedMessages': expectedMessages}); |
| 806 } |
| 807 |
| 808 |
| 788 /* Describes the output of running the test in a browser */ | 809 /* Describes the output of running the test in a browser */ |
| 789 class BrowserTestOutput { | 810 class BrowserTestOutput { |
| 790 final Duration delayUntilTestStarted; | 811 final Duration delayUntilTestStarted; |
| 791 final Duration duration; | 812 final Duration duration; |
| 792 | 813 |
| 793 final String lastKnownMessage; | 814 final String lastKnownMessage; |
| 794 | 815 |
| 795 final BrowserOutput browserOutput; | 816 final BrowserOutput browserOutput; |
| 796 final bool didTimeout; | 817 final bool didTimeout; |
| 797 | 818 |
| (...skipping 497 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1295 errorReportingServer.listen(errorReportingHandler, onError: errorHandler); | 1316 errorReportingServer.listen(errorReportingHandler, onError: errorHandler); |
| 1296 } | 1317 } |
| 1297 | 1318 |
| 1298 void setupDispatchingServer(_) { | 1319 void setupDispatchingServer(_) { |
| 1299 DispatchingServer server = configuration['_servers_'].server; | 1320 DispatchingServer server = configuration['_servers_'].server; |
| 1300 void noCache(request) { | 1321 void noCache(request) { |
| 1301 request.response.headers.set("Cache-Control", | 1322 request.response.headers.set("Cache-Control", |
| 1302 "no-cache, no-store, must-revalidate"); | 1323 "no-cache, no-store, must-revalidate"); |
| 1303 } | 1324 } |
| 1304 int testId(request) => | 1325 int testId(request) => |
| 1305 int.parse(request.uri.queryParameters["id"].split("=")[1]); | 1326 int.parse(request.uri.queryParameters["id"]); |
| 1306 String browserId(request, prefix) => | 1327 String browserId(request, prefix) => |
| 1307 request.uri.path.substring(prefix.length + 1); | 1328 request.uri.path.substring(prefix.length + 1); |
| 1308 | 1329 |
| 1309 | 1330 |
| 1310 server.addHandler(reportPath, (HttpRequest request) { | 1331 server.addHandler(reportPath, (HttpRequest request) { |
| 1311 noCache(request); | 1332 noCache(request); |
| 1312 handleReport(request, browserId(request, reportPath), | 1333 handleReport(request, browserId(request, reportPath), |
| 1313 testId(request), isStatusUpdate: false); | 1334 testId(request), isStatusUpdate: false); |
| 1314 }); | 1335 }); |
| 1315 server.addHandler(statusUpdatePath, (HttpRequest request) { | 1336 server.addHandler(statusUpdatePath, (HttpRequest request) { |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1378 testStartedCallBack(browserId, back, testId); | 1399 testStartedCallBack(browserId, back, testId); |
| 1379 }, onError: (error) { DebugLogger.error("$error"); }); | 1400 }, onError: (error) { DebugLogger.error("$error"); }); |
| 1380 } | 1401 } |
| 1381 | 1402 |
| 1382 String getNextTest(String browserId) { | 1403 String getNextTest(String browserId) { |
| 1383 var nextTest = nextTestCallBack(browserId); | 1404 var nextTest = nextTestCallBack(browserId); |
| 1384 if (underTermination) { | 1405 if (underTermination) { |
| 1385 // Browsers will be killed shortly, send them a terminate signal so | 1406 // Browsers will be killed shortly, send them a terminate signal so |
| 1386 // that they stop pulling. | 1407 // that they stop pulling. |
| 1387 return terminateSignal; | 1408 return terminateSignal; |
| 1388 } else if (nextTest == null) { | |
| 1389 // We don't currently have any tests ready for consumption, wait. | |
| 1390 return waitSignal; | |
| 1391 } else { | 1409 } else { |
| 1392 return "${nextTest.url}#id=${nextTest.id}"; | 1410 return nextTest == null ? waitSignal : nextTest.toJSON(); |
| 1393 } | 1411 } |
| 1394 } | 1412 } |
| 1395 | 1413 |
| 1396 String getDriverUrl(String browserId) { | 1414 String getDriverUrl(String browserId) { |
| 1397 if (errorReportingServer == null) { | 1415 if (errorReportingServer == null) { |
| 1398 print("Bad browser testing server, you are not started yet. Can't " | 1416 print("Bad browser testing server, you are not started yet. Can't " |
| 1399 "produce driver url"); | 1417 "produce driver url"); |
| 1400 exit(1); | 1418 exit(1); |
| 1401 // This should never happen - exit immediately; | 1419 // This should never happen - exit immediately; |
| 1402 } | 1420 } |
| 1403 var port = configuration['_servers_'].port; | 1421 var port = configuration['_servers_'].port; |
| 1404 return "http://$localIp:$port/driver/$browserId"; | 1422 return "http://$localIp:$port/driver/$browserId"; |
| 1405 } | 1423 } |
| 1406 | 1424 |
| 1407 | 1425 |
| 1408 String getDriverPage(String browserId) { | 1426 String getDriverPage(String browserId) { |
| 1409 var errorReportingUrl = | 1427 var errorReportingUrl = |
| 1410 "http://$localIp:${errorReportingServer.port}/$browserId"; | 1428 "http://$localIp:${errorReportingServer.port}/$browserId"; |
| 1411 String driverContent = """ | 1429 String driverContent = """ |
| 1412 <!DOCTYPE html><html> | 1430 <!DOCTYPE html><html> |
| 1413 <head> | 1431 <head> |
| 1414 <title>Driving page</title> | 1432 <title>Driving page</title> |
| 1415 <script type='text/javascript'> | 1433 <script type='text/javascript'> |
| 1434 var STATUS_UPDATE_INTERVAL = 10000; |
| 1435 |
| 1416 function startTesting() { | 1436 function startTesting() { |
| 1417 var number_of_tests = 0; | 1437 var number_of_tests = 0; |
| 1418 var current_id; | 1438 var current_id; |
| 1419 var next_id; | 1439 var next_id; |
| 1420 // Describes a state where we are currently fetching the next test | 1440 // Describes a state where we are currently fetching the next test |
| 1421 // from the server. We use this to never double request tasks. | 1441 // from the server. We use this to never double request tasks. |
| 1422 var test_completed = true; | 1442 var test_completed = true; |
| 1423 var testing_window; | 1443 var testing_window; |
| 1424 | 1444 |
| 1425 var embedded_iframe = document.getElementById('embedded_iframe'); | 1445 var embedded_iframe = document.getElementById('embedded_iframe'); |
| 1426 var number_div = document.getElementById('number'); | 1446 var number_div = document.getElementById('number'); |
| 1427 var executing_div = document.getElementById('currently_executing'); | 1447 var executing_div = document.getElementById('currently_executing'); |
| 1428 var error_div = document.getElementById('unhandled_error'); | 1448 var error_div = document.getElementById('unhandled_error'); |
| 1429 var use_iframe = ${useIframe}; | 1449 var use_iframe = ${useIframe}; |
| 1430 var start = new Date(); | 1450 var start = new Date(); |
| 1431 | 1451 |
| 1452 // Object that holds the state of an HTML test |
| 1453 var html_test; |
| 1454 |
| 1432 function newTaskHandler() { | 1455 function newTaskHandler() { |
| 1433 if (this.readyState == this.DONE) { | 1456 if (this.readyState == this.DONE) { |
| 1434 if (this.status == 200) { | 1457 if (this.status == 200) { |
| 1435 if (this.responseText == '$waitSignal') { | 1458 if (this.responseText == '$waitSignal') { |
| 1436 setTimeout(getNextTask, 500); | 1459 setTimeout(getNextTask, 500); |
| 1437 } else if (this.responseText == '$terminateSignal') { | 1460 } else if (this.responseText == '$terminateSignal') { |
| 1438 // Don't do anything, we will be killed shortly. | 1461 // Don't do anything, we will be killed shortly. |
| 1439 } else { | 1462 } else { |
| 1440 var elapsed = new Date() - start; | 1463 var elapsed = new Date() - start; |
| 1441 // The task is send to us as: | 1464 var nextTask = JSON.parse(this.responseText); |
| 1442 // URL#ID | 1465 var url = nextTask.url; |
| 1443 var split = this.responseText.split('#'); | 1466 next_id = nextTask.id; |
| 1444 var nextTask = split[0]; | 1467 if (nextTask.isHtmlTest) { |
| 1445 next_id = split[1]; | 1468 html_test = { |
| 1446 run(nextTask); | 1469 expected_messages: nextTask.expectedMessages, |
| 1470 found_message_count: 0, |
| 1471 double_received_messages: [], |
| 1472 unexpected_messages: [], |
| 1473 found_messages: {} |
| 1474 }; |
| 1475 for (var i = 0; i < html_test.expected_messages.length; ++i) { |
| 1476 html_test.found_messages[html_test.expected_messages[i]] = 0; |
| 1477 } |
| 1478 } else { |
| 1479 html_test = null; |
| 1480 } |
| 1481 run(url); |
| 1447 } | 1482 } |
| 1448 } else { | 1483 } else { |
| 1449 reportError('Could not contact the server and get a new task'); | 1484 reportError('Could not contact the server and get a new task'); |
| 1450 } | 1485 } |
| 1451 } | 1486 } |
| 1452 } | 1487 } |
| 1453 | 1488 |
| 1454 function contactBrowserController(method, | 1489 function contactBrowserController(method, |
| 1455 path, | 1490 path, |
| 1456 callback, | 1491 callback, |
| 1457 msg, | 1492 msg, |
| 1458 isUrlEncoded) { | 1493 isUrlEncoded) { |
| 1459 var client = new XMLHttpRequest(); | 1494 var client = new XMLHttpRequest(); |
| 1460 client.onreadystatechange = callback; | 1495 client.onreadystatechange = callback; |
| 1461 client.open(method, path); | 1496 client.open(method, path); |
| 1462 if (isUrlEncoded) { | 1497 if (isUrlEncoded) { |
| 1463 client.setRequestHeader('Content-type', | 1498 client.setRequestHeader('Content-type', |
| 1464 'application/x-www-form-urlencoded'); | 1499 'application/x-www-form-urlencoded'); |
| 1465 } | 1500 } |
| 1466 client.send(msg); | 1501 client.send(msg); |
| 1467 } | 1502 } |
| 1468 | 1503 |
| 1469 function getNextTask() { | 1504 function getNextTask() { |
| 1470 // Until we have the next task we set the current_id to a specific | 1505 // Until we have the next task we set the current_id to a specific |
| 1471 // negative value. | 1506 // negative value. |
| 1472 contactBrowserController( | 1507 contactBrowserController( |
| 1473 'GET', '$nextTestPath/$browserId', newTaskHandler, "", false); | 1508 'GET', '$nextTestPath/$browserId', newTaskHandler, "", false); |
| 1474 } | 1509 } |
| 1475 | 1510 |
| 1511 function childError(message, filename, lineno, colno, error) { |
| 1512 sendStatusUpdate(); |
| 1513 if (error) { |
| 1514 reportMessage('FAIL:' + filename + ':' + lineno + |
| 1515 ':' + colno + ':' + message + '\\n' + error.stack, false, false); |
| 1516 } else if (filename) { |
| 1517 reportMessage('FAIL:' + filename + ':' + lineno + |
| 1518 ':' + colno + ':' + message, false, false); |
| 1519 } else { |
| 1520 reportMessage('FAIL: ' + message, false, false); |
| 1521 } |
| 1522 return true; |
| 1523 } |
| 1524 |
| 1525 function setChildHandlers(e) { |
| 1526 embedded_iframe.contentWindow.addEventListener('message', |
| 1527 childMessageHandler, |
| 1528 false); |
| 1529 embedded_iframe.contentWindow.onerror = childError; |
| 1530 reportMessage("First message from html test", true, false); |
| 1531 html_test.handlers_installed = true; |
| 1532 sendRepeatingStatusUpdate(); |
| 1533 } |
| 1534 |
| 1535 function checkChildHandlersInstalled() { |
| 1536 if (!html_test.handlers_installed) { |
| 1537 reportMessage("First message from html test", true, false); |
| 1538 reportMessage( |
| 1539 'FAIL: Html test did not call ' + |
| 1540 'window.parent.dispatchEvent(new Event("detect_errors")) ' + |
| 1541 'as its first action', false, false); |
| 1542 } |
| 1543 } |
| 1544 |
| 1476 function run(url) { | 1545 function run(url) { |
| 1477 number_of_tests++; | 1546 number_of_tests++; |
| 1478 number_div.innerHTML = number_of_tests; | 1547 number_div.innerHTML = number_of_tests; |
| 1479 executing_div.innerHTML = url; | 1548 executing_div.innerHTML = url; |
| 1480 if (use_iframe) { | 1549 if (use_iframe) { |
| 1550 if (html_test) { |
| 1551 window.addEventListener('detect_errors', setChildHandlers, false); |
| 1552 embedded_iframe.onload = checkChildHandlersInstalled; |
| 1553 } else { |
| 1554 embedded_iframe.onload = null; |
| 1555 } |
| 1481 embedded_iframe.src = url; | 1556 embedded_iframe.src = url; |
| 1482 } else { | 1557 } else { |
| 1483 if (typeof testing_window != 'undefined') { | 1558 if (typeof testing_window != 'undefined') { |
| 1484 testing_window.close(); | 1559 testing_window.close(); |
| 1485 } | 1560 } |
| 1486 testing_window = window.open(url); | 1561 testing_window = window.open(url); |
| 1487 } | 1562 } |
| 1488 } | 1563 } |
| 1489 | 1564 |
| 1490 window.onerror = function (message, url, lineNumber) { | 1565 window.onerror = function (message, url, lineNumber) { |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1551 } | 1626 } |
| 1552 | 1627 |
| 1553 function parseResult(result) { | 1628 function parseResult(result) { |
| 1554 var parsedData = null; | 1629 var parsedData = null; |
| 1555 try { | 1630 try { |
| 1556 parsedData = JSON.parse(result); | 1631 parsedData = JSON.parse(result); |
| 1557 } catch(error) { } | 1632 } catch(error) { } |
| 1558 return parsedData; | 1633 return parsedData; |
| 1559 } | 1634 } |
| 1560 | 1635 |
| 1636 // Browser tests send JSON messages to the driver window, handled here. |
| 1561 function messageHandler(e) { | 1637 function messageHandler(e) { |
| 1562 var msg = e.data; | 1638 var msg = e.data; |
| 1563 if (typeof msg != 'string') return; | 1639 if (typeof msg != 'string') return; |
| 1564 | 1640 |
| 1565 var parsedData = parseResult(msg); | 1641 var parsedData = parseResult(msg); |
| 1566 if (parsedData) { | 1642 if (parsedData) { |
| 1567 // Only if the JSON message contains all required parameters, | 1643 // Only if the JSON message contains all required parameters, |
| 1568 // will we handle it and post it back to the test controller. | 1644 // will we handle it and post it back to the test controller. |
| 1569 if ('message' in parsedData && | 1645 if ('message' in parsedData && |
| 1570 'is_first_message' in parsedData && | 1646 'is_first_message' in parsedData && |
| 1571 'is_status_update' in parsedData && | 1647 'is_status_update' in parsedData && |
| 1572 'is_done' in parsedData) { | 1648 'is_done' in parsedData) { |
| 1573 var message = parsedData['message']; | 1649 var message = parsedData['message']; |
| 1574 var isFirstMessage = parsedData['is_first_message']; | 1650 var isFirstMessage = parsedData['is_first_message']; |
| 1575 var isStatusUpdate = parsedData['is_status_update']; | 1651 var isStatusUpdate = parsedData['is_status_update']; |
| 1576 var isDone = parsedData['is_done']; | 1652 var isDone = parsedData['is_done']; |
| 1577 if (!isFirstMessage && !isStatusUpdate) { | 1653 if (!isFirstMessage && !isStatusUpdate) { |
| 1578 if (!isDone) { | 1654 if (!isDone) { |
| 1579 alert("Bug in test_controller.js: " + | 1655 alert("Bug in test_controller.js: " + |
| 1580 "isFirstMessage/isStatusUpdate/isDone were all false"); | 1656 "isFirstMessage/isStatusUpdate/isDone were all false"); |
| 1581 } | 1657 } |
| 1582 } | 1658 } |
| 1583 reportMessage(message, isFirstMessage, isStatusUpdate); | 1659 reportMessage(message, isFirstMessage, isStatusUpdate); |
| 1584 } | 1660 } |
| 1585 } | 1661 } |
| 1586 } | 1662 } |
| 1587 | 1663 |
| 1588 window.addEventListener('message', messageHandler, false); | 1664 function sendStatusUpdate () { |
| 1589 waitForDone = false; | 1665 var dom = |
| 1666 embedded_iframe.contentWindow.document.documentElement.innerHTML; |
| 1667 reportMessage('Status:\\n Messages received multiple times:\\n ' + |
| 1668 html_test.double_received_messages + |
| 1669 '\\n Unexpected messages:\\n ' + |
| 1670 html_test.unexpected_messages + |
| 1671 '\\n DOM:\\n ' + dom, false, true); |
| 1672 } |
| 1590 | 1673 |
| 1674 function sendRepeatingStatusUpdate() { |
| 1675 sendStatusUpdate(); |
| 1676 setTimeout(sendRepeatingStatusUpdate, STATUS_UPDATE_INTERVAL); |
| 1677 } |
| 1678 |
| 1679 // HTML tests post messages to their own window, handled by this handler. |
| 1680 // This handler is installed on the child window when it sends the |
| 1681 // 'detect_errors' event. Every HTML test must send 'detect_errors' to |
| 1682 // its parent window as its first action, so all errors will be caught. |
| 1683 function childMessageHandler(e) { |
| 1684 var msg = e.data; |
| 1685 if (typeof msg != 'string') return; |
| 1686 if (msg in html_test.found_messages) { |
| 1687 html_test.found_messages[msg]++; |
| 1688 if (html_test.found_messages[msg] == 1) { |
| 1689 html_test.found_message_count++; |
| 1690 } else { |
| 1691 html_test.double_received_messages.push(msg); |
| 1692 sendStatusUpdate(); |
| 1693 } |
| 1694 } else { |
| 1695 html_test.unexpected_messages.push(msg); |
| 1696 sendStatusUpdate(); |
| 1697 } |
| 1698 if (html_test.found_message_count == |
| 1699 html_test.expected_messages.length) { |
| 1700 reportMessage('Test done: PASS', false, false); |
| 1701 } |
| 1702 } |
| 1703 |
| 1704 if (!html_test) { |
| 1705 window.addEventListener('message', messageHandler, false); |
| 1706 waitForDone = false; |
| 1707 } |
| 1591 getNextTask(); | 1708 getNextTask(); |
| 1592 } | 1709 } |
| 1593 | |
| 1594 </script> | 1710 </script> |
| 1595 </head> | 1711 </head> |
| 1596 <body onload="startTesting()"> | 1712 <body onload="startTesting()"> |
| 1597 Dart test driver, number of tests: <div id="number"></div><br> | 1713 Dart test driver, number of tests: <div id="number"></div><br> |
| 1598 Currently executing: <div id="currently_executing"></div><br> | 1714 Currently executing: <div id="currently_executing"></div><br> |
| 1599 Unhandled error: <div id="unhandled_error"></div> | 1715 Unhandled error: <div id="unhandled_error"></div> |
| 1600 <iframe id="embedded_iframe"></iframe> | 1716 <iframe id="embedded_iframe"></iframe> |
| 1601 </body> | 1717 </body> |
| 1602 </html> | 1718 </html> |
| 1603 """; | 1719 """; |
| 1604 return driverContent; | 1720 return driverContent; |
| 1605 } | 1721 } |
| 1606 } | 1722 } |
| OLD | NEW |