From 2fc2651226baac27029e38c9d6ef883fa32084db Mon Sep 17 00:00:00 2001 From: Steve Block Date: Wed, 18 May 2011 13:36:51 +0100 Subject: Merge WebKit at r78450: Initial merge by git. Change-Id: I6d3e5f1f868ec266a0aafdef66182ddc3f265dc1 --- .../build.webkit.org-config/config.json | 16 +- .../build.webkit.org-config/master.cfg | 9 +- Tools/BuildSlaveSupport/built-product-archive | 2 +- Tools/BuildSlaveSupport/test-result-archive | 2 +- Tools/CMakeListsWinCE.txt | 1 + Tools/ChangeLog | 2689 ++++++++++++++++++++ Tools/DerivedSources.pro | 16 + Tools/DumpRenderTree/AccessibilityController.cpp | 9 + Tools/DumpRenderTree/AccessibilityController.h | 1 + Tools/DumpRenderTree/DumpRenderTree.gypi | 4 + Tools/DumpRenderTree/LayoutTestController.cpp | 12 + Tools/DumpRenderTree/LayoutTestController.h | 1 + .../TestNetscapePlugIn/PluginObject.cpp | 20 +- .../TestNetscapePlugIn/PluginTest.cpp | 5 +- Tools/DumpRenderTree/TestNetscapePlugIn/main.cpp | 32 + Tools/DumpRenderTree/chromium/DumpRenderTree.cpp | 8 + Tools/DumpRenderTree/chromium/ImageDiff.cpp | 2 +- .../chromium/LayoutTestController.cpp | 26 +- .../DumpRenderTree/chromium/LayoutTestController.h | 4 +- .../chromium/NotificationPresenter.cpp | 2 +- Tools/DumpRenderTree/chromium/TestShell.cpp | 1 + Tools/DumpRenderTree/chromium/WebViewHost.cpp | 41 +- Tools/DumpRenderTree/chromium/WebViewHost.h | 9 +- .../DumpRenderTree/gtk/AccessibilityCallbacks.cpp | 196 ++ Tools/DumpRenderTree/gtk/AccessibilityCallbacks.h | 35 + .../gtk/AccessibilityControllerGtk.cpp | 18 + Tools/DumpRenderTree/gtk/DumpRenderTree.cpp | 19 +- Tools/DumpRenderTree/gtk/EventSender.cpp | 47 +- .../DumpRenderTree/gtk/LayoutTestControllerGtk.cpp | 64 +- Tools/DumpRenderTree/gtk/PixelDumpSupportGtk.cpp | 9 +- Tools/DumpRenderTree/gtk/TextInputController.cpp | 177 ++ Tools/DumpRenderTree/gtk/TextInputController.h | 37 + .../mac/AccessibilityControllerMac.mm | 4 + Tools/DumpRenderTree/mac/DumpRenderTree.mm | 6 +- .../pthreads/JavaScriptThreadingPthreads.cpp | 62 +- Tools/DumpRenderTree/qt/DumpRenderTree.pro | 4 +- Tools/DumpRenderTree/qt/DumpRenderTreeQt.cpp | 40 +- Tools/DumpRenderTree/qt/DumpRenderTreeQt.h | 2 +- Tools/DumpRenderTree/qt/EventSenderQt.cpp | 3 +- Tools/DumpRenderTree/qt/EventSenderQt.h | 2 +- Tools/DumpRenderTree/qt/ImageDiff.pro | 2 +- Tools/DumpRenderTree/qt/LayoutTestControllerQt.cpp | 6 + Tools/DumpRenderTree/qt/LayoutTestControllerQt.h | 2 + .../qt/TestNetscapePlugin/TestNetscapePlugin.pro | 2 +- .../unix/TestNetscapePlugin/TestNetscapePlugin.cpp | 3 + .../win/AccessibilityControllerWin.cpp | 4 + Tools/DumpRenderTree/win/ImageDiffCommon.vsprops | 1 + Tools/DumpRenderTree/wx/LayoutTestControllerWx.cpp | 5 + Tools/GNUmakefile.am | 4 + Tools/MiniBrowser/DerivedSources.pro | 7 +- Tools/MiniBrowser/mac/BrowserWindowController.m | 9 +- Tools/MiniBrowser/mac/WebBundle/WebBundleMain.m | 2 +- Tools/MiniBrowser/qt/BrowserView.cpp | 5 +- Tools/MiniBrowser/qt/BrowserView.h | 1 - Tools/MiniBrowser/qt/BrowserWindow.cpp | 259 +- Tools/MiniBrowser/qt/BrowserWindow.h | 34 +- Tools/MiniBrowser/qt/MiniBrowser.pro | 14 +- Tools/MiniBrowser/qt/MiniBrowserApplication.cpp | 95 + Tools/MiniBrowser/qt/MiniBrowserApplication.h | 68 + Tools/MiniBrowser/qt/UrlLoader.cpp | 129 + Tools/MiniBrowser/qt/UrlLoader.h | 71 + Tools/MiniBrowser/qt/main.cpp | 44 +- Tools/MiniBrowser/qt/utils.cpp | 86 + Tools/MiniBrowser/qt/utils.h | 53 + Tools/QtTestBrowser/QtTestBrowser.pro | 2 +- Tools/QtTestBrowser/launcherwindow.cpp | 82 +- Tools/QtTestBrowser/launcherwindow.h | 16 + Tools/QtTestBrowser/locationedit.h | 2 + Tools/QtTestBrowser/main.cpp | 10 +- Tools/QtTestBrowser/mainwindow.cpp | 8 + Tools/QtTestBrowser/mainwindow.h | 3 + Tools/QueueStatusServer/templates/submittoews.html | 2 +- Tools/Scripts/build-webkit | 14 +- Tools/Scripts/check-inspector-strings | 12 +- Tools/Scripts/do-webcore-rename | 2 +- Tools/Scripts/old-run-webkit-tests | 170 +- Tools/Scripts/rebaseline-chromium-webkit-tests | 2 +- Tools/Scripts/run-chromium-webkit-unit-tests | 4 +- Tools/Scripts/run-webkit-httpd | 12 +- Tools/Scripts/update-webkit | 11 +- Tools/Scripts/webkit-patch | 7 +- Tools/Scripts/webkitdirs.pm | 79 +- Tools/Scripts/webkitperl/httpd.pm | 25 +- Tools/Scripts/webkitpy/common/checkout/api.py | 4 + .../Scripts/webkitpy/common/checkout/changelog.py | 21 +- .../webkitpy/common/checkout/changelog_unittest.py | 86 - Tools/Scripts/webkitpy/common/checkout/deps.py | 61 + Tools/Scripts/webkitpy/common/checkout/scm.py | 24 +- .../webkitpy/common/checkout/scm_unittest.py | 11 + Tools/Scripts/webkitpy/common/config/committers.py | 8 +- Tools/Scripts/webkitpy/common/config/urls.py | 1 + .../webkitpy/common/net/buildbot/buildbot.py | 5 +- .../common/net/buildbot/buildbot_unittest.py | 6 +- Tools/Scripts/webkitpy/common/net/irc/ircbot.py | 2 +- .../webkitpy/common/net/layouttestresults.py | 4 + Tools/Scripts/webkitpy/common/net/testoutput.py | 179 ++ .../webkitpy/common/net/testoutput_unittest.py | 133 + Tools/Scripts/webkitpy/common/net/testoutputset.py | 130 + .../webkitpy/common/net/testoutputset_unittest.py | 125 + .../webkitpy/common/system/directoryfileset.py | 14 +- Tools/Scripts/webkitpy/common/system/fileset.py | 6 +- Tools/Scripts/webkitpy/common/system/filesystem.py | 16 +- .../webkitpy/common/system/filesystem_mock.py | 71 +- .../webkitpy/common/system/filesystem_unittest.py | 13 + Tools/Scripts/webkitpy/common/system/logutils.py | 2 +- .../webkitpy/common/system/logutils_unittest.py | 2 +- Tools/Scripts/webkitpy/common/system/ospath.py | 8 +- .../Scripts/webkitpy/common/system/stack_utils.py | 67 + .../webkitpy/common/system/stack_utils_unittest.py | 76 + Tools/Scripts/webkitpy/common/system/urlfetcher.py | 55 + .../webkitpy/common/system/urlfetcher_mock.py | 46 + Tools/Scripts/webkitpy/common/system/zip_mock.py | 55 + Tools/Scripts/webkitpy/common/system/zipfileset.py | 10 +- .../webkitpy/common/system/zipfileset_mock.py | 51 + .../webkitpy/common/system/zipfileset_unittest.py | 6 +- .../webkitpy/layout_tests/deduplicate_tests.py | 11 +- .../layout_package/dump_render_tree_thread.py | 422 +-- .../json_layout_results_generator.py | 6 +- .../layout_package/json_results_generator.py | 113 +- .../json_results_generator_unittest.py | 29 +- .../layout_package/manager_worker_broker.py | 282 ++ .../manager_worker_broker_unittest.py | 227 ++ .../layout_tests/layout_package/message_broker.py | 41 +- .../layout_tests/layout_package/message_broker2.py | 196 ++ .../layout_package/message_broker2_unittest.py | 83 + .../layout_package/message_broker_unittest.py | 54 +- .../layout_package/printing_unittest.py | 8 +- .../layout_package/single_test_runner.py | 322 +++ .../layout_package/test_expectations.py | 554 ++-- .../layout_package/test_expectations_unittest.py | 293 ++- .../layout_tests/layout_package/test_input.py | 4 - .../layout_tests/layout_package/test_output.py | 56 - .../layout_tests/layout_package/test_runner.py | 107 +- .../layout_tests/layout_package/test_runner2.py | 129 + .../webkitpy/layout_tests/layout_package/worker.py | 104 + Tools/Scripts/webkitpy/layout_tests/port/base.py | 175 +- .../webkitpy/layout_tests/port/base_unittest.py | 14 - .../Scripts/webkitpy/layout_tests/port/chromium.py | 74 +- .../webkitpy/layout_tests/port/chromium_gpu.py | 119 +- .../layout_tests/port/chromium_gpu_unittest.py | 43 +- .../webkitpy/layout_tests/port/chromium_linux.py | 4 +- .../webkitpy/layout_tests/port/chromium_mac.py | 24 +- .../layout_tests/port/chromium_unittest.py | 7 - .../webkitpy/layout_tests/port/chromium_win.py | 10 +- Tools/Scripts/webkitpy/layout_tests/port/dryrun.py | 22 +- .../Scripts/webkitpy/layout_tests/port/factory.py | 5 +- .../webkitpy/layout_tests/port/http_server_base.py | 2 +- Tools/Scripts/webkitpy/layout_tests/port/mac.py | 6 +- .../webkitpy/layout_tests/port/mac_unittest.py | 23 +- .../Scripts/webkitpy/layout_tests/port/mock_drt.py | 280 ++ .../layout_tests/port/mock_drt_unittest.py | 261 ++ .../webkitpy/layout_tests/port/port_testcase.py | 12 + .../webkitpy/layout_tests/port/server_process.py | 6 +- .../layout_tests/port/server_process_unittest.py | 77 + Tools/Scripts/webkitpy/layout_tests/port/test.py | 221 +- .../webkitpy/layout_tests/port/test_files.py | 58 +- .../layout_tests/port/test_files_unittest.py | 57 +- Tools/Scripts/webkitpy/layout_tests/port/webkit.py | 53 +- .../webkitpy/layout_tests/port/webkit_unittest.py | 36 +- .../webkitpy/layout_tests/port/websocket_server.py | 2 +- .../rebaseline_chromium_webkit_tests.py | 125 +- .../rebaseline_chromium_webkit_tests_unittest.py | 184 +- .../webkitpy/layout_tests/run_webkit_tests.py | 33 +- .../layout_tests/run_webkit_tests_unittest.py | 57 +- .../webkitpy/layout_tests/test_types/image_diff.py | 60 +- .../layout_tests/test_types/test_type_base.py | 63 +- .../test_types/test_type_base_unittest.py | 2 +- .../webkitpy/layout_tests/test_types/text_diff.py | 18 +- .../layout_tests/update_webgl_conformance_tests.py | 2 +- Tools/Scripts/webkitpy/style/checker.py | 27 +- Tools/Scripts/webkitpy/style/checker_unittest.py | 25 +- Tools/Scripts/webkitpy/style/checkers/cpp.py | 36 +- .../webkitpy/style/checkers/cpp_unittest.py | 55 +- .../webkitpy/style/checkers/test_expectations.py | 3 +- .../style/checkers/test_expectations_unittest.py | 35 +- Tools/Scripts/webkitpy/tool/bot/irc_command.py | 2 +- Tools/Scripts/webkitpy/tool/bot/sheriffircbot.py | 2 +- Tools/Scripts/webkitpy/tool/commands/__init__.py | 1 + .../Scripts/webkitpy/tool/commands/commandtest.py | 4 +- Tools/Scripts/webkitpy/tool/commands/download.py | 2 +- .../webkitpy/tool/commands/download_unittest.py | 12 +- Tools/Scripts/webkitpy/tool/commands/prettydiff.py | 2 +- Tools/Scripts/webkitpy/tool/commands/queries.py | 2 +- Tools/Scripts/webkitpy/tool/commands/roll.py | 48 + .../webkitpy/tool/commands/roll_unittest.py | 50 + .../Scripts/webkitpy/tool/commands/stepsequence.py | 6 +- Tools/Scripts/webkitpy/tool/commands/upload.py | 6 +- .../webkitpy/tool/commands/upload_unittest.py | 12 +- Tools/Scripts/webkitpy/tool/main.py | 2 +- Tools/Scripts/webkitpy/tool/mocktool.py | 31 +- Tools/Scripts/webkitpy/tool/steps/__init__.py | 2 + .../tool/steps/preparechangelogfordepsroll.py | 40 + .../tool/steps/preparechangelogforrevert.py | 18 +- .../steps/preparechangelogforrevert_unittest.py | 130 + .../tool/steps/suggestreviewers_unittest.py | 5 +- .../webkitpy/tool/steps/updatechromiumdeps.py | 64 + .../tool/steps/validatechangelogs_unittest.py | 5 +- Tools/TestWebKitAPI/JavaScriptTest.cpp | 65 + Tools/TestWebKitAPI/JavaScriptTest.h | 32 + Tools/TestWebKitAPI/PlatformWebView.h | 2 +- Tools/TestWebKitAPI/Test.h | 4 +- .../TestWebKitAPI.xcodeproj/project.pbxproj | 14 + .../TestWebKitAPI/Tests/WebKit2/PageLoadBasic.cpp | 6 +- .../RestoreSessionStateContainingFormData.cpp | 86 + .../Tests/WebKit2/SpacebarScrolling.cpp | 32 +- Tools/TestWebKitAPI/Tests/WebKit2/simple-form.html | 11 + .../Tests/WebKit2/win/HideFindIndicator.cpp | 85 + .../Tests/WebKit2/win/ResizeViewWhileHidden.cpp | 125 + Tools/TestWebKitAPI/Tests/WebKit2/win/WMPrint.cpp | 46 + Tools/TestWebKitAPI/mac/PlatformWebViewMac.mm | 2 +- Tools/TestWebKitAPI/win/PlatformWebViewWin.cpp | 7 +- Tools/TestWebKitAPI/win/TestWebKitAPI.vcproj | 24 + Tools/TestWebKitAPI/win/copy-resources.cmd | 1 + Tools/Tools.pro | 15 + .../WebKitTestRunner/Configurations/Base.xcconfig | 2 +- Tools/WebKitTestRunner/DerivedSources.pro | 2 +- .../Bindings/LayoutTestController.idl | 14 +- .../InjectedBundle/InjectedBundle.cpp | 8 + .../InjectedBundle/InjectedBundle.h | 2 + .../InjectedBundle/InjectedBundlePage.cpp | 87 +- .../InjectedBundle/InjectedBundlePage.h | 17 +- .../InjectedBundle/LayoutTestController.cpp | 30 + .../InjectedBundle/LayoutTestController.h | 12 + .../InjectedBundle/qt/ActivateFontsQt.cpp | 17 + .../InjectedBundle/qt/InjectedBundle.pro | 8 +- Tools/WebKitTestRunner/TestController.cpp | 39 +- Tools/WebKitTestRunner/TestController.h | 3 + Tools/WebKitTestRunner/TestInvocation.cpp | 19 +- .../WebKitTestRunner.xcodeproj/project.pbxproj | 6 + Tools/WebKitTestRunner/WebKitTestRunnerPrefix.h | 1 + Tools/WebKitTestRunner/mac/TestControllerMac.mm | 5 + Tools/WebKitTestRunner/qt/TestControllerQt.cpp | 5 + Tools/WebKitTestRunner/qt/WebKitTestRunner.pro | 4 +- Tools/WebKitTestRunner/win/TestControllerWin.cpp | 5 + Tools/wx/browser/wscript | 2 +- Tools/wx/build/build_utils.py | 2 +- Tools/wx/build/settings.py | 3 +- 237 files changed, 10859 insertions(+), 2098 deletions(-) create mode 100644 Tools/DerivedSources.pro create mode 100644 Tools/DumpRenderTree/gtk/AccessibilityCallbacks.cpp create mode 100644 Tools/DumpRenderTree/gtk/AccessibilityCallbacks.h create mode 100644 Tools/DumpRenderTree/gtk/TextInputController.cpp create mode 100644 Tools/DumpRenderTree/gtk/TextInputController.h create mode 100644 Tools/MiniBrowser/qt/MiniBrowserApplication.cpp create mode 100644 Tools/MiniBrowser/qt/MiniBrowserApplication.h create mode 100644 Tools/MiniBrowser/qt/UrlLoader.cpp create mode 100644 Tools/MiniBrowser/qt/UrlLoader.h create mode 100644 Tools/MiniBrowser/qt/utils.cpp create mode 100644 Tools/MiniBrowser/qt/utils.h create mode 100644 Tools/Scripts/webkitpy/common/checkout/deps.py create mode 100644 Tools/Scripts/webkitpy/common/net/testoutput.py create mode 100644 Tools/Scripts/webkitpy/common/net/testoutput_unittest.py create mode 100644 Tools/Scripts/webkitpy/common/net/testoutputset.py create mode 100644 Tools/Scripts/webkitpy/common/net/testoutputset_unittest.py create mode 100644 Tools/Scripts/webkitpy/common/system/stack_utils.py create mode 100644 Tools/Scripts/webkitpy/common/system/stack_utils_unittest.py create mode 100644 Tools/Scripts/webkitpy/common/system/urlfetcher.py create mode 100644 Tools/Scripts/webkitpy/common/system/urlfetcher_mock.py create mode 100644 Tools/Scripts/webkitpy/common/system/zip_mock.py create mode 100644 Tools/Scripts/webkitpy/common/system/zipfileset_mock.py create mode 100644 Tools/Scripts/webkitpy/layout_tests/layout_package/manager_worker_broker.py create mode 100644 Tools/Scripts/webkitpy/layout_tests/layout_package/manager_worker_broker_unittest.py create mode 100644 Tools/Scripts/webkitpy/layout_tests/layout_package/message_broker2.py create mode 100644 Tools/Scripts/webkitpy/layout_tests/layout_package/message_broker2_unittest.py create mode 100644 Tools/Scripts/webkitpy/layout_tests/layout_package/single_test_runner.py delete mode 100644 Tools/Scripts/webkitpy/layout_tests/layout_package/test_output.py create mode 100644 Tools/Scripts/webkitpy/layout_tests/layout_package/test_runner2.py create mode 100644 Tools/Scripts/webkitpy/layout_tests/layout_package/worker.py create mode 100644 Tools/Scripts/webkitpy/layout_tests/port/mock_drt.py create mode 100644 Tools/Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py create mode 100644 Tools/Scripts/webkitpy/layout_tests/port/server_process_unittest.py create mode 100644 Tools/Scripts/webkitpy/tool/commands/roll.py create mode 100644 Tools/Scripts/webkitpy/tool/commands/roll_unittest.py create mode 100644 Tools/Scripts/webkitpy/tool/steps/preparechangelogfordepsroll.py create mode 100644 Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert_unittest.py create mode 100644 Tools/Scripts/webkitpy/tool/steps/updatechromiumdeps.py create mode 100644 Tools/TestWebKitAPI/JavaScriptTest.cpp create mode 100644 Tools/TestWebKitAPI/JavaScriptTest.h create mode 100644 Tools/TestWebKitAPI/Tests/WebKit2/RestoreSessionStateContainingFormData.cpp create mode 100644 Tools/TestWebKitAPI/Tests/WebKit2/simple-form.html create mode 100644 Tools/TestWebKitAPI/Tests/WebKit2/win/HideFindIndicator.cpp create mode 100644 Tools/TestWebKitAPI/Tests/WebKit2/win/ResizeViewWhileHidden.cpp create mode 100644 Tools/TestWebKitAPI/Tests/WebKit2/win/WMPrint.cpp create mode 100644 Tools/Tools.pro (limited to 'Tools') diff --git a/Tools/BuildSlaveSupport/build.webkit.org-config/config.json b/Tools/BuildSlaveSupport/build.webkit.org-config/config.json index 6e03f7a..07edacc 100644 --- a/Tools/BuildSlaveSupport/build.webkit.org-config/config.json +++ b/Tools/BuildSlaveSupport/build.webkit.org-config/config.json @@ -17,7 +17,9 @@ { "name": "apple-macpro-3", "platform": "mac-snowleopard" }, { "name": "apple-macpro-4", "platform": "mac-leopard" }, { "name": "apple-macpro-5", "platform": "mac-leopard" }, - { "name": "apple-macpro-6", "platform": "mac-leopard" }, + { "name": "apple-macpro-6", "platform": "mac-leopard" }, + { "name": "apple-macpro-7", "platform": "mac-leopard" }, + { "name": "apple-windows-1", "platform": "win"}, { "name": "apple-windows-2", "platform": "win"}, @@ -38,7 +40,6 @@ { "name": "gtk-linux-slave-1", "platform": "gtk"}, { "name": "gtk-linux-slave-2", "platform": "gtk"}, { "name": "gtk-linux-slave-3", "platform": "gtk"}, - { "name": "gtk-linux-slave-4", "platform": "gtk"}, { "name": "szeged-linux-1", "platform": "qt"}, { "name": "szeged-linux-2", "platform": "qt"}, @@ -70,7 +71,7 @@ }, { "name": "Leopard Intel Release (Tests)", "type": "Test", "builddir": "leopard-intel-release-tests", "platform": "mac-leopard", "configuration": "release", "architectures": ["i386"], - "slavenames": ["apple-macpro-4", "apple-pixel-1", "test-slave"] + "slavenames": ["apple-macpro-4", "apple-pixel-1", "test-slave", "apple-macpro-7"] }, { "name": "Leopard Intel Debug (Build)", "type": "Build", "builddir": "leopard-intel-debug", @@ -80,7 +81,7 @@ }, { "name": "Leopard Intel Debug (Tests)", "type": "Test", "builddir": "leopard-intel-debug-tests", "platform": "mac-leopard", "configuration": "debug", "architectures": ["i386"], - "slavenames": ["apple-xserve-3", "test-slave", "apple-macpro-5", "apple-macpro-6"] + "slavenames": ["apple-xserve-3", "test-slave", "apple-macpro-5", "apple-macpro-6", "apple-macpro-7"] }, { "name": "SnowLeopard Intel Release (Build)", "type": "Build", "builddir": "snowleopard-intel-release", "platform": "mac-snowleopard", "configuration": "release", "architectures": ["x86_64"], @@ -141,11 +142,6 @@ "slavenames": ["gtk-linux-slave-3"] }, { - "name": "GTK Linux 64-bit Release", "type": "BuildAndTest", "builddir": "gtk-linux-64-release", - "platform": "gtk", "configuration": "release", "architectures": ["x86_64"], - "slavenames": ["gtk-linux-slave-4"] - }, - { "name": "Qt Linux Release", "type": "BuildAndTest", "builddir": "qt-linux-release", "platform": "qt", "configuration": "release", "architectures": ["i386"], "slavenames": ["szeged-linux-1"] @@ -230,7 +226,7 @@ "schedulers": [ { "type": "AnyBranchScheduler", "name": "trunk", "change_filter": "trunk_filter", "treeStableTimer": 45.0, "builderNames": ["Leopard Intel Release (Build)", "Leopard Intel Debug (Build)", "SnowLeopard Intel Release (Build)", "SnowLeopard Intel Leaks", - "GTK Linux 32-bit Release", "GTK Linux 32-bit Debug", "GTK Linux 64-bit Debug", "GTK Linux 64-bit Release", + "GTK Linux 32-bit Release", "GTK Linux 32-bit Debug", "GTK Linux 64-bit Debug", "Qt Linux Release", "Qt Linux Release minimal", "Qt Linux ARMv5 Release", "Qt Linux ARMv7 Release", "Qt Windows 32-bit Release", "Qt Windows 32-bit Debug", "Chromium Win Release", "Chromium Mac Release", "Chromium Linux Release", diff --git a/Tools/BuildSlaveSupport/build.webkit.org-config/master.cfg b/Tools/BuildSlaveSupport/build.webkit.org-config/master.cfg index 1ce5b88..4ee277c 100644 --- a/Tools/BuildSlaveSupport/build.webkit.org-config/master.cfg +++ b/Tools/BuildSlaveSupport/build.webkit.org-config/master.cfg @@ -318,8 +318,8 @@ class RunQtAPITests(shell.Test): description = ["API tests running"] descriptionDone = ["API tests"] command = ["python", "./Tools/Scripts/run-qtwebkit-tests", - "--output-file=qt-unit-tests.html", "--do-not-open-results", "--timeout=30", - WithProperties("WebKitBuild/%(configuration_pretty)s/Source/WebKit/qt/tests/")] + "--output-file=qt-unit-tests.html", "--do-not-open-results", "--timeout=120", + WithProperties("WebKitBuild/%(configuration_pretty)s/WebKit/qt/tests/")] def start(self): self.setProperty("configuration_pretty", self.getProperty("configuration").title()) @@ -445,9 +445,10 @@ class TestFactory(Factory): self.addStep(self.TestClass, skipBuild=(platform == 'win')) # Tiger's Python 2.3 is too old. WebKit Python requires 2.5+. # Sadly we have no way to detect the version on the slave from here. - if platform != "mac-tiger": + if platform != "mac-tiger" and platform != "chromium-win": self.addStep(RunPythonTests) - self.addStep(RunPerlTests) + if platform != "chromium-win": + self.addStep(RunPerlTests) self.addStep(ArchiveTestResults) self.addStep(UploadTestResults) self.addStep(ExtractTestResults) diff --git a/Tools/BuildSlaveSupport/built-product-archive b/Tools/BuildSlaveSupport/built-product-archive index 611d006..9dee922 100644 --- a/Tools/BuildSlaveSupport/built-product-archive +++ b/Tools/BuildSlaveSupport/built-product-archive @@ -89,7 +89,7 @@ def archiveBuiltProduct(configuration, platform): shutil.rmtree(thinDirectory) os.mkdir(thinDirectory) - for dirname in ["bin", "lib", "Source/JavaScriptCore"]: + for dirname in ["bin", "lib", "JavaScriptCore"]: fromDir = os.path.join(configurationBuildDirectory, dirname, "*") toDir = os.path.join(thinDirectory, dirname) os.makedirs(toDir) diff --git a/Tools/BuildSlaveSupport/test-result-archive b/Tools/BuildSlaveSupport/test-result-archive index af66bf0..a4d3ca3 100644 --- a/Tools/BuildSlaveSupport/test-result-archive +++ b/Tools/BuildSlaveSupport/test-result-archive @@ -46,7 +46,7 @@ def main(): if options.platform == 'chromium': # See results_directory() in webkitpy/layout_tests/port/chromium.py. layoutTestResultsDir = os.path.abspath(os.path.join(sourceRootDirectory, - "WebKit", "chromium", "webkit", options.configuration.capitalize(), + "Source", "WebKit", "chromium", "webkit", options.configuration.capitalize(), "layout-test-results")) return archiveTestResults(options.configuration, options.platform, layoutTestResultsDir) diff --git a/Tools/CMakeListsWinCE.txt b/Tools/CMakeListsWinCE.txt index 89f6b97..7945daf 100644 --- a/Tools/CMakeListsWinCE.txt +++ b/Tools/CMakeListsWinCE.txt @@ -21,4 +21,5 @@ SET(WinCELauncher_LIBRARIES INCLUDE_DIRECTORIES(${WinCELauncher_INCLUDE_DIRECTORIES}) ADD_EXECUTABLE(WinCELauncher ${WinCELauncher_SOURCES}) +ADD_DEPENDENCIES(WinCELauncher ${WebKit_LIBRARY_NAME}) TARGET_LINK_LIBRARIES(WinCELauncher ${WinCELauncher_LIBRARIES}) diff --git a/Tools/ChangeLog b/Tools/ChangeLog index 3ef3b4a..4e59dae 100644 --- a/Tools/ChangeLog +++ b/Tools/ChangeLog @@ -1,3 +1,2692 @@ +2011-02-12 Chang Shu + + Unreviewed. + + Update my own email addresses and IRC nickname. + + * Scripts/webkitpy/common/config/committers.py: + +2011-02-11 Dirk Pranke + + Reviewed by Tony Chang. + + This patch adds to NRWT most of the support needed to run the new + message-based workers in separate threads or processes. The code + isn't fully complete yet because we don't support cancel() or + is_alive(). + + https://bugs.webkit.org/show_bug.cgi?id=54070 + + * Scripts/webkitpy/layout_tests/layout_package/manager_worker_broker.py: + * Scripts/webkitpy/layout_tests/layout_package/manager_worker_broker_unittest.py: + * Scripts/webkitpy/layout_tests/layout_package/test_runner2.py: + * Scripts/webkitpy/layout_tests/port/base.py: + * Scripts/webkitpy/layout_tests/port/mock_drt.py: + * Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py: + +2011-02-11 Sailesh Agrawal + + Reviewed by Kenneth Russell. + + plugins/invalidate_rect.html fails on chromium-mac + https://bugs.webkit.org/show_bug.cgi?id=54051 + + This change fixes the invalidate_rect.html test failure on Windows. + + There were two problems. First, the test specified that the plugin was window less by doing . The windowedPlugin parameter was never being read by the plugin. Fix was to simply set the NPPVpluginWindowBool variable based on the parameter. + + The second problem was that the plugin never handled paint events on Windows. Fix was to simply copy the Mac code to handle paint events. + + This change also updates the build path in chromium_win.py to use the new Source directory. + + * DumpRenderTree/TestNetscapePlugIn/PluginObject.cpp: + (invalidateRect): + * DumpRenderTree/TestNetscapePlugIn/main.cpp: + (NPP_New): + (handleEventWin): + (NPP_HandleEvent): + * Scripts/webkitpy/layout_tests/port/chromium_win.py: + +2011-02-11 Csaba Osztrogonác + + Reviewed by Andreas Kling. + + [Qt] Increase the timeout of Qt API tests to 120 seconds. + + * BuildSlaveSupport/build.webkit.org-config/master.cfg: + +2011-02-10 Zhenyao Mo + + Unreviewed, build fix. + + * DumpRenderTree/DumpRenderTree.gypi: + +2011-02-10 Dirk Pranke + + Reviewed by Tony Chang. + + This patch adds the bulk of the remaining functionality for + the NRWT message-passing implementation. The patch adds a new + Worker class that will eventually replace the TestShellThread + class in dump_render_tree_thread.py, and implements enough of + TestRunner2 and the inline version of the manager_worker_broker + to actually be able to send a full set of messages back and + forth. The Worker stubs do not actually run tests, and there's + lots of error handling and stats needed, but this is the core + logic. + + https://bugs.webkit.org/show_bug.cgi?id=54068 + + * Scripts/webkitpy/layout_tests/layout_package/manager_worker_broker.py: + * Scripts/webkitpy/layout_tests/layout_package/manager_worker_broker_unittest.py: + * Scripts/webkitpy/layout_tests/layout_package/test_runner2.py: + * Scripts/webkitpy/layout_tests/layout_package/worker.py: Added. + * Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py: + +2011-02-10 Zhenyao Mo + + Unreviewed, build fix. + + * DumpRenderTree/DumpRenderTree.gypi: + +2011-02-10 Zhenyao Mo + + Unreviewed, build fix. + + * DumpRenderTree/DumpRenderTree.gypi: + +2011-02-10 Zhenyao Mo + + Unreviewed, attempt to fix crashing plugin tests. + + * DumpRenderTree/DumpRenderTree.gypi: + +2011-02-10 Zhenyao Mo + + Unreviewed, build fix. + + * DumpRenderTree/DumpRenderTree.gypi: + +2011-02-10 Sam Weinig + + Try and fix some crashing tests on the chromium build bot. + + * DumpRenderTree/DumpRenderTree.gypi: + +2011-02-10 Anders Carlsson + + Reviewed by Sam Weinig. + + Log an error if a plug-in test can't be found + https://bugs.webkit.org/show_bug.cgi?id=54252 + + * DumpRenderTree/TestNetscapePlugIn/PluginTest.cpp: + (PluginTest::create): + Return null if the test wasn't found. + + * DumpRenderTree/TestNetscapePlugIn/main.cpp: + (NPP_New): + Report an error if PluginTest::create returns null. + +2011-02-10 Robert Hogan + + Reviewed by Laszlo Gombos. + + [Qt] Return menu items from eventSender.contextMenu() + https://bugs.webkit.org/show_bug.cgi?id=53039 + + * DumpRenderTree/qt/EventSenderQt.cpp: + (EventSender::contextClick): + * DumpRenderTree/qt/EventSenderQt.h: + +2011-02-10 Adam Roben + + Test showing and hiding the find indicator on Windows + + Test for REGRESSION (r78198): Crash in + FindIndicator::contentImage when scrolling page + + Reviewed by Steve Falkenburg. + + * TestWebKitAPI/Tests/WebKit2/win/HideFindIndicator.cpp: Added. + (TestWebKitAPI::didFinishLoadForFrame): Record that the load finished. + (TestWebKitAPI::findIndicatorCallback): Record that the callback was called, and save the + bitmap. + (TestWebKitAPI::initialize): Hook up our callbacks. + (TestWebKitAPI::TEST): Test showing then hiding the find indicator to see if we crash. + + * TestWebKitAPI/PlatformWebView.h: + * TestWebKitAPI/mac/PlatformWebViewMac.mm: + (TestWebKitAPI::PlatformWebView::page): + * TestWebKitAPI/win/PlatformWebViewWin.cpp: + (TestWebKitAPI::PlatformWebView::page): + Made page a const member function. + + * TestWebKitAPI/win/TestWebKitAPI.vcproj: Added the new test. + +2011-02-10 Mario Sanchez Prada + + Reviewed by Martin Robinson. + + [GTK] Log signals from AtkDocument interface also in AccessibilityController + https://bugs.webkit.org/show_bug.cgi?id=54198 + + Trace AtkDocument's signals emission through AccessibilityController. + + Also, taken the code related to adding and removing global + listeners for ATK signals out to a separate file, for the sake of + clarity and to ease future additions. + + * DumpRenderTree/gtk/AccessibilityCallbacks.h: Added. + * DumpRenderTree/gtk/AccessibilityCallbacks.cpp: Added + (printAccessibilityEvent): Print information about an event. + (axObjectEventListener): Global listener for AtkObject's signals. + (axDocumentEventListener): Global listener for AtkDocument's signals. + (connectAccessibilityCallbacks): Connect all global listeners. + (disconnectAccessibilityCallbacks): Disconnect all global listeners. + + * DumpRenderTree/gtk/AccessibilityControllerGtk.cpp: + (AccessibilityController::setLogAccessibilityEvents): Call to + connectAccessibilityCallbacks and disconnectAccessibilityCallbacks. + + * GNUmakefile.am: Added new files. + +2011-02-09 Martin Robinson + + Reviewed by Gustavo Noronha Silva. + + [GTK] Default error page is interfering with tests which require failed loads + https://bugs.webkit.org/show_bug.cgi?id=54157 + + Disable the default error page during DRT runs. This prevents unexpected loads + from interfering with tests that have planned failed loads. + + * DumpRenderTree/gtk/DumpRenderTree.cpp: + (webViewLoadError): Added, disable default handler. + (createWebView): Connect the new load-error handler. + +2011-02-10 Zoltan Horvath + + Reviewed by Andreas Kling. + + [Qt] Add UrlLoader and command line argument handling to MiniBrowser + https://bugs.webkit.org/show_bug.cgi?id=54192 + + Copy and modify files from QtTestBrowser's implementation. + + * MiniBrowser/qt/BrowserWindow.cpp: + (BrowserWindow::BrowserWindow): + (BrowserWindow::newWindow): + * MiniBrowser/qt/BrowserWindow.h: + * MiniBrowser/qt/MiniBrowser.pro: + * MiniBrowser/qt/MiniBrowserApplication.cpp: Copied from QtTestBrowser/launcherwindow.cpp + (MiniBrowserApplication::MiniBrowserApplication): + (MiniBrowserApplication::handleUserOptions): + * MiniBrowser/qt/MiniBrowserApplication.h: Copied from QtTestBrowser/launcherwindow.h + (WindowOptions::WindowOptions): + (MiniBrowserApplication::urls): + (MiniBrowserApplication::isRobotized): + (MiniBrowserApplication::robotTimeout): + (MiniBrowserApplication::robotExtraTime): + * MiniBrowser/qt/UrlLoader.cpp: Copied from QtTestBrowser/urlloader.cpp + (UrlLoader::UrlLoader): + (UrlLoader::loadNext): + (UrlLoader::checkIfFinished): + (UrlLoader::frameLoadStarted): + (UrlLoader::frameLoadFinished): + (UrlLoader::loadUrlList): + (UrlLoader::getUrl): + * MiniBrowser/qt/UrlLoader.h: Copied from QtTestBrowser/urlloader.h + * MiniBrowser/qt/main.cpp: + (main): + * MiniBrowser/qt/utils.cpp: Copied from QtTestBrowser/utils.cpp + (takeOptionValue): + (formatKeys): + (enumToKeys): + (appQuit): + (urlFromUserInput): + * MiniBrowser/qt/utils.h: Copied from QtTestBrowser/utils.h + +2011-02-10 Zoltan Horvath + + Reviewed by Andreas Kling. + + check-webkit-style: Add -build/include exemption for Tools/MiniBrowser/qt + https://bugs.webkit.org/show_bug.cgi?id=54200 + + * Scripts/webkitpy/style/checker.py: + * Scripts/webkitpy/style/checker_unittest.py: + +2011-02-10 Peter Varga + + Reviewed by Csaba Osztrogonác. + + Remove PCRE source from trunk + https://bugs.webkit.org/show_bug.cgi?id=54188 + + * wx/build/settings.py: + +2011-02-10 David Levin + + Reviewed by Shinichiro Hamaji. + + check-webkit-style should be able to figure out function modifiers and return type. + https://bugs.webkit.org/show_bug.cgi?id=54124 + + * Scripts/webkitpy/style/checkers/cpp.py: + (_rfind_in_lines): A way to search backwards in lines. + (_FunctionState.modifiers_and_return_type): + * Scripts/webkitpy/style/checkers/cpp_unittest.py: Added tests for the new functionality. + +2011-02-09 Adam Roben + + Test that WM_PRINT doesn't trigger an assertion + + Test for WM_PRINT doesn't work with new + drawing area (assertion failure in DrawingAreaProxyImpl in Debug builds) + + Reviewed by Darin Adler. + + * TestWebKitAPI/Tests/WebKit2/win/WMPrint.cpp: Added. + (TestWebKitAPI::TEST): Send a WM_PRINT message to a WKView. + +2011-02-09 Adam Roben + + Test that having a WKView paint after being resized while hidden doesn't cause a crash + + Test for Crash in + DrawingAreaProxyImpl::paint when WKView paints after being resized while hidden + + Reviewed by Darin Adler. + + * TestWebKitAPI/Tests/WebKit2/win/ResizeViewWhileHidden.cpp: + (TestWebKitAPI::TEST): Force the WKView to paint after resizing it while hidden. + +2011-02-09 Adam Roben + + Add a test that resizes a WKView while it's hidden + + Test for WKView draws at wrong size, + performs badly if is resized while hidden (assertion fails in + BackingStore::incorporateUpdate in Debug builds) + + Reviewed by Darin Adler. + + * TestWebKitAPI/Tests/WebKit2/win/ResizeViewWhileHidden.cpp: Added. + (TestWebKitAPI::didFinishLoadForFrame): Record that the load finished. + (TestWebKitAPI::setPageLoaderClient): Hook up our didFinishLoadForFrame callback. + (TestWebKitAPI::flushMessages): Load a URL and wait for the load to complete. This ensures + that all pending messages have been handled by the UI and web process. + (TestWebKitAPI::timerCallback): Kill the timer and record that it fired. + (TestWebKitAPI::runForDuration): Set a timer and run the run loop until it fires. + (TestWebKitAPI::waitForBackingStoreUpdate): Wait for half a second to give the web process a + chance to display, then flush all pending messages. + (TestWebKitAPI::TEST): Resize the WKView while it's hidden, then show it again and wait for + the backing store to update. This triggers the assertion from bug 54141. + + * TestWebKitAPI/win/PlatformWebViewWin.cpp: + (TestWebKitAPI::PlatformWebView::resizeTo): Implemented. + + * TestWebKitAPI/win/TestWebKitAPI.vcproj: Added new test. + +2011-02-10 Philippe Normand + + Unreviewed, GTK build fix after roll out of r78157... + + * GNUmakefile.am: + +2011-02-10 Eric Seidel + + Unreviewed, rolling out r78157. + http://trac.webkit.org/changeset/78157 + https://bugs.webkit.org/show_bug.cgi?id=54150 + + Fails on a bunch of bots + + * DumpRenderTree/DumpRenderTree.xcodeproj/project.pbxproj: + * DumpRenderTree/TestNetscapePlugIn/Tests/EvaluateJSDestroyingPluginFromDestroyStream.cpp: Removed. + * DumpRenderTree/TestNetscapePlugIn/win/TestNetscapePlugin.vcproj: + * DumpRenderTree/qt/TestNetscapePlugin/TestNetscapePlugin.pro: + +2011-02-10 Mario Sanchez Prada + + Reviewed by Xan Lopez. + + [GTK] Reset GTK' DRT's AccessibilityController to consistent value before every test + https://bugs.webkit.org/show_bug.cgi?id=54185 + + * DumpRenderTree/gtk/DumpRenderTree.cpp: + (resetDefaultsToConsistentValues): + +2011-02-10 Philippe Normand + + Unreviewed GTK build fix. + + Adding + Tools/DumpRenderTree/TestNetscapePlugIn/Tests/EvaluateJSDestroyingPluginFromDestroyStream.cpp + in the build, fixing the + plugins/npruntime/evaluate-js-destroying-plugin-from-destroy-stream.html + test. + + * GNUmakefile.am: + +2011-02-10 Mario Sanchez Prada + + Reviewed by Chris Fleizach. + + [GTK] Add support in DRT to log "accessibility events" + https://bugs.webkit.org/show_bug.cgi?id=54116 + + Added a way to log accessibility related events, in a + platform-dependant way. + + Add new function to AccessibilityController. + + * DumpRenderTree/AccessibilityController.h: + * DumpRenderTree/AccessibilityController.cpp: + (logAccessibilityEventsCallback): New callback. + (AccessibilityController::getJSClass): Added new function. + (AccessibilityController::resetToConsistentState): Initialized + calling setLogAccessibilityEvents(false). + + Provided implementation for the GTK port. + + * DumpRenderTree/gtk/AccessibilityControllerGtk.cpp: + (accessibility_event_listener): Common listener for logging + information about all the signals emitted by any AtkObject. + (AccessibilityController::setLogAccessibilityEvents): Add or + remove listeners for signals, as specified by the parameter. + + Provide dummy implementations for mac and win ports. + + * DumpRenderTree/mac/AccessibilityControllerMac.mm: + (AccessibilityController::setLogAccessibilityEvents): + * DumpRenderTree/win/AccessibilityControllerWin.cpp: + (AccessibilityController::setLogAccessibilityEvents): + +2011-02-09 Hayato Ito + + Reviewed by Tony Chang. + + [NRWT] Remove encoding parameters where we can assume data can be + written in binary mode. + + https://bugs.webkit.org/show_bug.cgi?id=54066 + + * Scripts/webkitpy/layout_tests/layout_package/single_test_runner.py: + * Scripts/webkitpy/layout_tests/port/base.py: + +2011-02-09 Dirk Pranke + + Reviewed by Tony Chang. + + nrwt multiprocessing: minor cleanup prior to implementing the new worker + + This patch renames AbstractManager to ManagerConnection and + changes some argument names to be more consistent between + manager_worker_broker and message_broker. It also fixes a couple + of typos in message_broker. These changes will be tested by code + introduced in the next patch. + + https://bugs.webkit.org/show_bug.cgi?id=54067 + + * Scripts/webkitpy/layout_tests/layout_package/manager_worker_broker.py: + * Scripts/webkitpy/layout_tests/layout_package/message_broker2.py: + +2011-02-09 Anders Carlsson + + Reviewed by Sam Weinig. + + Repro crash with Sony Google TV ad at Gizmodo + https://bugs.webkit.org/show_bug.cgi?id=54150 + + + Add a new plug-in test that runs JavaScript that destroys the plug-in from within its NPN_DestroyStream callback. + + * DumpRenderTree/DumpRenderTree.xcodeproj/project.pbxproj: + * DumpRenderTree/TestNetscapePlugIn/Tests/EvaluateJSDestroyingPluginFromDestroyStream.cpp: Added. + (EvaluateJSDestroyingPluginFromDestroyStream::EvaluateJSDestroyingPluginFromDestroyStream): + (EvaluateJSDestroyingPluginFromDestroyStream::NPP_Destroy): + (EvaluateJSDestroyingPluginFromDestroyStream::NPP_DestroyStream): + * DumpRenderTree/TestNetscapePlugIn/win/TestNetscapePlugin.vcproj: + * DumpRenderTree/qt/TestNetscapePlugin/TestNetscapePlugin.pro: + +2011-02-09 David Levin + + Reviewed by Eric Seidel. + + check-webkit-style: The error message about NULL should be more clear for comments. + https://bugs.webkit.org/show_bug.cgi?id=53786 + + * Scripts/webkitpy/style/checkers/cpp.py: Clarified the error message. + * Scripts/webkitpy/style/checkers/cpp_unittest.py: Fixed the corresponding test. + +2011-02-09 Nebojsa Ciric + + Reviewed by Darin Fisher. + + Implements Locale object of JavaScript internationalization API proposal, as an + v8 extension. Extension code is hosted in v8 project (src/extensions/experimental/i18n-extension.{cc,h}) + and in this patch we just provide flags, tests and build rules for chromium port. + https://bugs.webkit.org/show_bug.cgi?id=49414 + + * DumpRenderTree/chromium/TestShell.cpp: + (TestShell::TestShell): + +2011-02-09 Laszlo Gombos + + Reviewed by Kenneth Rohde Christiansen. + + [Qt] Remove UiTools dependency from MiniBrowser + https://bugs.webkit.org/show_bug.cgi?id=54096 + + * MiniBrowser/qt/MiniBrowser.pro: + +2011-02-08 Hayato Ito + + Reviewed by Tony Chang. + + [NRWT] Pull up rebaseline code from compare_output() function defined + in text_diff.py and image_diff.py into a SingleTestRunner. + + This patch is a first step for eliminating test_type/* classes. + + https://bugs.webkit.org/show_bug.cgi?id=53071 + + * Scripts/webkitpy/layout_tests/layout_package/single_test_runner.py: + * Scripts/webkitpy/layout_tests/test_types/image_diff.py: + * Scripts/webkitpy/layout_tests/test_types/test_type_base.py: + * Scripts/webkitpy/layout_tests/test_types/text_diff.py: + +2011-02-07 Ojan Vafai + + Reviewed by Mihai Parparita. + + stop generating results.json files + https://bugs.webkit.org/show_bug.cgi?id=53977 + + We've only used incremental_results.json for a while now + and there are plans to start generating a results.json file that matches + the format of unexpected_results.json. + + * Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py: + * Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py: + * Scripts/webkitpy/layout_tests/layout_package/json_results_generator_unittest.py: + * Scripts/webkitpy/layout_tests/layout_package/test_runner.py: + * Scripts/webkitpy/layout_tests/run_webkit_tests.py: + +2011-02-08 Sailesh Agrawal + + Reviewed by Kenneth Russell. + + Invalidate rect doesn't work for windowless plugins on Chromium + https://bugs.webkit.org/show_bug.cgi?id=53117 + + Added two new utility methods. + - layoutTestController.displayInvalidatedRegion() does a paint of any area that has been invalidated. This is different from layoutTestController.display() which explicitly invalidates the entire page then paints. + - plugin.invalidateRect(left, top, right, bottom) - invalidates the given rect + This is used to test that invalidating a rect correctly causes a repaint of the plugin. + + * DumpRenderTree/LayoutTestController.cpp: + (displayInvalidatedRegionCallback): + (LayoutTestController::staticFunctions): + * DumpRenderTree/LayoutTestController.h: + * DumpRenderTree/TestNetscapePlugIn/PluginObject.cpp: + (invalidateRect): + (pluginInvoke): + * DumpRenderTree/chromium/LayoutTestController.cpp: + (LayoutTestController::LayoutTestController): + (LayoutTestController::displayInvalidatedRegion): + * DumpRenderTree/chromium/LayoutTestController.h: + * Scripts/webkitpy/layout_tests/port/chromium_gpu.py: + +2011-02-08 Dirk Pranke + + Reviewed by Tony Chang. + + new-run-webkit-tests: move the logic that starts and stops the + servers from dump_render_tree_thread into single_test_runner + so that we can reuse it in the new multiprocessing worker class + as well. + + https://bugs.webkit.org/show_bug.cgi?id=53840 + + * Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py: + * Scripts/webkitpy/layout_tests/layout_package/single_test_runner.py: + +2011-02-08 Dirk Pranke + + Reviewed by Mihai Parparita. + + new-run-webkit-tests: split out thread stack logging code into a sharable module + + This patch splits out the code used to find and log thread + stacks from NRWT-specific packages to something generic and + shareable by other python modules. It will be shared in the near + future by the manager_worker_broker module, for example. + + https://bugs.webkit.org/show_bug.cgi?id=53656 + + * Scripts/webkitpy/common/system/stack_utils.py: Added. + * Scripts/webkitpy/common/system/stack_utils_unittest.py: Added. + * Scripts/webkitpy/layout_tests/layout_package/message_broker.py: + * Scripts/webkitpy/layout_tests/layout_package/message_broker_unittest.py: + +2011-02-08 Dirk Pranke + + Reviewed by Tony Chang. + + new-run-webkit-tests: move a bunch of testing logic out of + dump_render_tree_thread and into single_test_runner so that we + will be able to reuse it in the new multiprocessing worker class as well. + + https://bugs.webkit.org/show_bug.cgi?id=53838 + + * Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py: + * Scripts/webkitpy/layout_tests/layout_package/single_test_runner.py: + + +2011-02-08 Dirk Pranke + + Reviewed by Ojan Vafai. + + new-run-webkit-tests: remove no longer needed WatchableThread + class. + + https://bugs.webkit.org/show_bug.cgi?id=53839 + + * Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py: + +2011-02-08 Dirk Pranke + + Reviewed by Tony Chang. + + new-run-webkit-tests: add stubs for the Manager objects that + will abstract the concurrency implementation (inline / threads / + processes). These classes do nothing yet and are not wired up to + anything. + + https://bugs.webkit.org/show_bug.cgi?id=53477 + + * Scripts/webkitpy/layout_tests/layout_package/manager_worker_broker.py: Added. + * Scripts/webkitpy/layout_tests/layout_package/manager_worker_broker_unittest.py: Added. + +2011-02-08 Dirk Pranke + + Reviewed by Tony Chang. + + new-run-webkit-tests: add simplified message broker for new-style + messaging. This change adds a very simple message broker that + will be used to shuttle messages between the TestRunner2 manager + thread and the Worker threads. For now the class is not used by + anything, but the eventual usage can be seen in the patches + attached to bug 49566. + + https://bugs.webkit.org/show_bug.cgi?id=53158 + + * Scripts/webkitpy/layout_tests/layout_package/message_broker2.py: Added. + * Scripts/webkitpy/layout_tests/layout_package/message_broker2_unittest.py: Added. + +2011-02-08 Adam Barth + + Reviewed by Dimitri Glazkov. + + chromium-win builder shouldn't run python or perl tests + https://bugs.webkit.org/show_bug.cgi?id=54032 + + These tests don't pass on this builder because the builder isn't + running in cygwin. There isn't really any point in running them and + making the bot red forever. + + * BuildSlaveSupport/build.webkit.org-config/master.cfg: + +2011-02-08 Brady Eidson + + Reviewed by Darin Adler. + + and https://bugs.webkit.org/show_bug.cgi?id=54036 + didChangeBackForwardList should include some context about what changed + + * MiniBrowser/mac/BrowserWindowController.m: + (didChangeBackForwardList): + +2011-02-08 Sam Weinig + + Reviewed by Anders Carlsson. + + WK2: Add ability to pass context to policy delegate methods + https://bugs.webkit.org/show_bug.cgi?id=54031 + + * MiniBrowser/mac/BrowserWindowController.m: + (decidePolicyForNavigationAction): + (decidePolicyForNewWindowAction): + (decidePolicyForMIMEType): + * TestWebKitAPI/Tests/WebKit2/PageLoadBasic.cpp: + (TestWebKitAPI::decidePolicyForNavigationAction): + (TestWebKitAPI::decidePolicyForNewWindowAction): + (TestWebKitAPI::decidePolicyForMIMEType): + Update policy client for new API. + +2011-02-08 Martin Robinson + + Reviewed by Xan Lopez. + + [GTK] DRT needs an implementation of LayoutTestController.setIconDatabaseEnabled + https://bugs.webkit.org/show_bug.cgi?id=54033 + + Add an implementation of LayoutTestController.setIconDatabaseEnabled that just + call DumpRenderTreeSupportGtk. Turn off the icon database between tests. + + * DumpRenderTree/gtk/DumpRenderTree.cpp: + (resetDefaultsToConsistentValues): Turn off the icon database. + * DumpRenderTree/gtk/LayoutTestControllerGtk.cpp: + (LayoutTestController::setIconDatabaseEnabled): Call the appropriate DumpRenderTreeSupportGtk + method. + +2011-02-08 Kundu Suchismita + + Reviewed by Laszlo Gombos. + + Local Storage settings can be enable/disable from "Develop" menu + https://bugs.webkit.org/show_bug.cgi?id=52296 + + * QtTestBrowser/launcherwindow.cpp: + (LauncherWindow::createChrome): + (LauncherWindow::toggleLocalStorage): + (LauncherWindow::toggleOfflineStorageDatabase): + (LauncherWindow::toggleOfflineWebApplicationCache): + (LauncherWindow::setOfflineStorageDefaultQuota): + * QtTestBrowser/launcherwindow.h: + (WindowOptions::WindowOptions): + * QtTestBrowser/main.cpp: + (LauncherApplication::handleUserOptions): + +2011-02-08 Martin Robinson + + Reviewed by Eric Seidel. + + [GTK] EventSender.keyDown does not support non-array modifier arguments + https://bugs.webkit.org/show_bug.cgi?id=53962 + + Support handling either an array or a string for the modifier argument + to EventSender.keyDown. + + * DumpRenderTree/gtk/EventSender.cpp: + (gdkModifierFromJSValue): Added this helper which factors out the + string comparison bits. + (gdkModifersFromJSValue): Test up front whether the value is a string, + to handle it specially. + +2011-02-08 Benjamin Poulain + + Reviewed by Csaba Osztrogonác. + + openDatabaseSync() stop responding after too many call + https://bugs.webkit.org/show_bug.cgi?id=53945 + + Reset the database quota to a known state between each test. + + Define a quota for the known origins at each test run and delete + all the databases. + This way, the database related test do not depend on previous + allocations. + + * DumpRenderTree/qt/DumpRenderTreeQt.cpp: + (WebCore::DumpRenderTree::resetToConsistentStateBeforeTesting): + (WebCore::DumpRenderTree::dumpDatabaseQuota): + * DumpRenderTree/qt/LayoutTestControllerQt.cpp: + (LayoutTestController::reset): + +2011-02-08 Carlos Garcia Campos + + Reviewed by Martin Robinson. + + [GTK] DRT's TextInputController is unimplemented on GTK + https://bugs.webkit.org/show_bug.cgi?id=52997 + + Initial implementation of TextInputController for GTK port. + + * DumpRenderTree/gtk/DumpRenderTree.cpp: + (webViewWindowObjectCleared): + * DumpRenderTree/gtk/TextInputController.cpp: Added. + (setMarkedTextCallback): + (insertTextCallback): + (unmarkTextCallback): + (firstRectForCharacterRangeCallback): + (selectedRangeCallback): + (getClass): + (makeTextInputController): + * DumpRenderTree/gtk/TextInputController.h: Added. + * GNUmakefile.am: + +2011-02-07 Tony Chang + + Reviewed by Adam Barth. + + clean up python imports + https://bugs.webkit.org/show_bug.cgi?id=53966 + + Convert "import A.B.C.D as D" to "from A.B.C import D" and + make some imports absolute as required by PEP-8 + + * Scripts/webkitpy/common/checkout/scm.py: + * Scripts/webkitpy/common/net/irc/ircbot.py: + * Scripts/webkitpy/common/system/logutils.py: + * Scripts/webkitpy/common/system/logutils_unittest.py: + * Scripts/webkitpy/layout_tests/deduplicate_tests.py: + * Scripts/webkitpy/layout_tests/port/chromium.py: + * Scripts/webkitpy/layout_tests/port/mac.py: Remove some unused imports + * Scripts/webkitpy/layout_tests/port/webkit.py: + * Scripts/webkitpy/layout_tests/update_webgl_conformance_tests.py: + * Scripts/webkitpy/tool/bot/irc_command.py: + * Scripts/webkitpy/tool/bot/sheriffircbot.py: + * Scripts/webkitpy/tool/commands/download.py: + * Scripts/webkitpy/tool/commands/prettydiff.py: + * Scripts/webkitpy/tool/commands/queries.py: + * Scripts/webkitpy/tool/commands/roll.py: + * Scripts/webkitpy/tool/commands/stepsequence.py: + * Scripts/webkitpy/tool/commands/upload.py: + * Scripts/webkitpy/tool/main.py: + +2011-02-07 James Robinson + + Reviewed by Adam Barth. + + Teach do-webcore-rename about the WebKit move to Source/ + https://bugs.webkit.org/show_bug.cgi?id=53967 + + * Scripts/do-webcore-rename: + +2011-02-07 Maciej Stachowiak + + Reviewed by Dan Bernstein. + + Add resource load client for injected bundle and move willSendRequest there + https://bugs.webkit.org/show_bug.cgi?id=53972 + + * MiniBrowser/mac/WebBundle/WebBundleMain.m: + (didCreatePage): + * WebKitTestRunner/InjectedBundle/InjectedBundlePage.cpp: + (WTR::InjectedBundlePage::InjectedBundlePage): + (WTR::InjectedBundlePage::didInitiateLoadForResource): + (WTR::InjectedBundlePage::didReceiveResponseForResource): + (WTR::InjectedBundlePage::didReceiveContentLengthForResource): + (WTR::InjectedBundlePage::didFinishLoadForResource): + (WTR::InjectedBundlePage::didFailLoadForResource): + (WTR::InjectedBundlePage::willSendRequestForFrame): + * WebKitTestRunner/InjectedBundle/InjectedBundlePage.h: + +2011-02-07 Adam Barth + + Add Leopard Debug back to the core builders. + + * Scripts/webkitpy/common/net/buildbot/buildbot.py: + * Scripts/webkitpy/common/net/buildbot/buildbot_unittest.py: + +2011-02-07 Adam Klein + + Reviewed by Jian Li. + + [chromium] Remove deprecated method WebNotification::dir + https://bugs.webkit.org/show_bug.cgi?id=53735 + + * DumpRenderTree/chromium/NotificationPresenter.cpp: + (NotificationPresenter::show): + +2011-02-07 Adam Barth + + Reviewed by Eric Seidel. + + Remove Leopard Debug as a core builder + https://bugs.webkit.org/show_bug.cgi?id=53971 + + The Leopard Debug builder has been failing to compile for several days. + My understanding is that folks are working on fixing the build, but for + the time being we should probably remove it from the list of core + builders so that the core waterfall can be green. We can certainly add + it back once the build is fixed. + + Also, I've removed the Tiger builders because those no longer appear to + be attached to the buildbot master. + + * Scripts/webkitpy/common/net/buildbot/buildbot.py: + * Scripts/webkitpy/common/net/buildbot/buildbot_unittest.py: + +2011-02-01 Ojan Vafai + + Reviewed by Adam Roben. + + include svn revisions in git diffs for the code review tool to use + https://bugs.webkit.org/show_bug.cgi?id=53569 + + * Scripts/webkitpy/common/checkout/scm.py: + * Scripts/webkitpy/common/checkout/scm_unittest.py: + +2011-02-03 MORITA Hajime + + Reviewed by Darin Fisher. + + [Chromium] Should implement EditorClientImpl::requestCheckingOfString() + https://bugs.webkit.org/show_bug.cgi?id=51013 + + Gave DRT implementation for requestTextCheck(). + + * DumpRenderTree/chromium/LayoutTestController.cpp: + (LayoutTestController::setAsynchronousSpellCheckingEnabled): Implemented. + * DumpRenderTree/chromium/WebViewHost.cpp: + (invokeFinishLastTextCheck): Added. + (WebViewHost::requestTextCheck): Added. + (WebViewHost::finishLastTextCheck): Added. + * DumpRenderTree/chromium/WebViewHost.h: + +2011-02-07 Joone Hur + + Reviewed by Martin Robinson. + + [Gtk] Implement layoutTestController.findString + https://bugs.webkit.org/show_bug.cgi?id=50237 + + * DumpRenderTree/gtk/LayoutTestControllerGtk.cpp: + (LayoutTestController::findString): + +2011-02-07 Chang Shu + + Reviewed by Darin Adler. + + We should disable spatial navigation explicitly during the initialization + of DumpRenderTree. + https://bugs.webkit.org/show_bug.cgi?id=53928 + + * DumpRenderTree/mac/DumpRenderTree.mm: + (resetDefaultsToConsistentValues): + +2011-02-07 Maciej Stachowiak + + Not reviewed. + + Fix WebKitTestRunner build on the SL bot. + + * WebKitTestRunner/Configurations/Base.xcconfig: Look for the JSC copy + of ICU headers. + +2011-02-07 Csaba Osztrogonác + + Unreviewed. + + [Qt][WK2] Buildfix after r77794. + + WebKitTestRunner does not block remote resources or complain about them + https://bugs.webkit.org/show_bug.cgi?id=42139 + + + * WebKitTestRunner/InjectedBundle/qt/InjectedBundle.pro: Add missing includepaths. + +2011-02-07 Maciej Stachowiak + + Not reviewed. + + More bot appeasement. + + * WebKitTestRunner/WebKitTestRunner.xcodeproj/project.pbxproj: + +2011-02-07 Maciej Stachowiak + + Not reviewed. + + Remove accidental references to directories on my laptop. + + * WebKitTestRunner/WebKitTestRunner.xcodeproj/project.pbxproj: + +2011-02-07 Maciej Stachowiak + + Reviewed by Antti Koivisto. + + WebKitTestRunner does not block remote resources or complain about them + https://bugs.webkit.org/show_bug.cgi?id=42139 + + + * WebKitTestRunner/InjectedBundle/InjectedBundlePage.cpp: + (WTR::InjectedBundlePage::willSendRequestForFrame): Implement the required + checks (using KURL, to avoid need to invent a whole URL API). + * WebKitTestRunner/WebKitTestRunner.xcodeproj/project.pbxproj: Link WebCore + directly to get at KURL symbols. + +2011-02-06 Maciej Stachowiak + + Reviewed by Daniel Bates. + + Add WebKit2 bot to list of expected builders in Python regression test results. + https://bugs.webkit.org/show_bug.cgi?id=53905 + + * Scripts/webkitpy/common/net/buildbot/buildbot_unittest.py: + +2011-02-06 Maciej Stachowiak + + Reviewed by Ryosuke Niwa. + + Update test expectations for new core builder + https://bugs.webkit.org/show_bug.cgi?id=53904 + + * Scripts/webkitpy/common/net/buildbot/buildbot_unittest.py: + +2011-02-06 Maciej Stachowiak + + Reviewed by David Levin. + + Add WebKit2 test bot to core builders + https://bugs.webkit.org/show_bug.cgi?id=53901 + + * Scripts/webkitpy/common/net/buildbot/buildbot.py: + +2011-02-06 Ryosuke Niwa + + Another unreviewed Chromium build fix. + + * DumpRenderTree/chromium/LayoutTestController.cpp: + (LayoutTestController::pathToLocalResource): + +2011-02-06 Ryosuke Niwa + + Unreviewed Chromium build fix. + + * DumpRenderTree/chromium/LayoutTestController.cpp: + (LayoutTestController::pathToLocalResource): + +2011-02-06 Ryosuke Niwa + + Reviewed by Darin Adler. + + OwnArraryPtr.h uses deleteOwnedPtr but doesn’t include OwnPtrCommon.h + https://bugs.webkit.org/show_bug.cgi?id=52867 + + * DumpRenderTree/chromium/ImageDiff.cpp: + (Image::craeteFromStdin): Call adoptArrayPtr. + +2011-02-06 James Kozianski + + Reviewed by Ojan Vafai. + + Add classes for representing test outputs. + https://bugs.webkit.org/show_bug.cgi?id=52136 + + These classes will make it easier to write scripts that deal with test + outputs such as rebaselining and deduping scripts. The intent is that + eventually we will be have buildbot return TestOutputs for a + particular build which can be compared with TestOutputs derived from a + local LayoutTests directory. + + * Scripts/webkitpy/common/net/testoutput.py: Added. + * Scripts/webkitpy/common/net/testoutput_unittest.py: Added. + * Scripts/webkitpy/common/net/testoutputset.py: Added. + * Scripts/webkitpy/common/net/testoutputset_unittest.py: Added. + * Scripts/webkitpy/common/system/zip_mock.py: Added. + +2011-02-06 Robert Hogan + + Reviewed by Andreas Kling. + + [Qt] Clear page's groupName even when not in DRT + https://bugs.webkit.org/show_bug.cgi?id=53874 + + * DumpRenderTree/qt/DumpRenderTreeQt.cpp: + (WebCore::DumpRenderTree::windowCloseRequested): + +2011-02-06 Patrick Gansterer + + Reviewed by Andreas Kling. + + [CMake] Add dependencies for Visual Studio projects + https://bugs.webkit.org/show_bug.cgi?id=53773 + + Add a WebKit dependecy to WinCELauncher, so CMake can + generate the correct build order for the solution. + + * CMakeListsWinCE.txt: + +2011-02-06 Andreas Kling + + Reviewed by Antonio Gomes. + + [Qt] MiniBrowser: Clean up handling of titleChanged() signal + https://bugs.webkit.org/show_bug.cgi?id=53869 + + * MiniBrowser/qt/BrowserView.cpp: + (BrowserView::BrowserView): Remove unnecessary titleChanged() handling. + * MiniBrowser/qt/BrowserWindow.cpp: + (BrowserWindow::BrowserWindow): Connect titleChanged() directly to setWindowTitle(). + * MiniBrowser/qt/BrowserWindow.h: + +2011-02-06 Maciej Stachowiak + + Reviewed by Dan Bernstein. + + WebKitTestRunner needs layoutTestController.setWillSendRequestReturnsNull + https://bugs.webkit.org/show_bug.cgi?id=42690 + + + * WebKitTestRunner/InjectedBundle/Bindings/LayoutTestController.idl: + * WebKitTestRunner/InjectedBundle/InjectedBundlePage.cpp: + (WTR::InjectedBundlePage::willSendRequestForFrame): + * WebKitTestRunner/InjectedBundle/LayoutTestController.cpp: + (WTR::LayoutTestController::LayoutTestController): + * WebKitTestRunner/InjectedBundle/LayoutTestController.h: + (WTR::LayoutTestController::willSendRequestReturnsNull): + (WTR::LayoutTestController::setWillSendRequestReturnsNull): + +2011-02-05 Sheriff Bot + + Unreviewed, rolling out r77720. + http://trac.webkit.org/changeset/77720 + https://bugs.webkit.org/show_bug.cgi?id=53854 + + "Broke nrwt on Chromium win." (Requested by dglazkov|away on + #webkit). + + * Scripts/webkitpy/common/system/executive.py: + +2011-02-05 Sheriff Bot + + Unreviewed, rolling out r77725. + http://trac.webkit.org/changeset/77725 + https://bugs.webkit.org/show_bug.cgi?id=53844 + + It broke Qt minimal build (Requested by Ossy_ on #webkit). + + * QtTestBrowser/launcherwindow.cpp: + (LauncherWindow::createChrome): + * QtTestBrowser/launcherwindow.h: + (WindowOptions::WindowOptions): + * QtTestBrowser/main.cpp: + (LauncherApplication::handleUserOptions): + +2011-02-04 Martin Robinson + + Reviewed by Adam Barth. + + [GTK] plugins/plugin-document-back-forward.html fails + https://bugs.webkit.org/show_bug.cgi?id=53833 + + Add the new "alert on load" functionality for the Unix version of the + test plugin. This functionality was originally introduced in r77706. + + * DumpRenderTree/unix/TestNetscapePlugin/TestNetscapePlugin.cpp: + (webkit_test_plugin_new_instance): Add new plugin fuctionality. + +2011-02-04 Kundu Suchismita + + Reviewed by Andreas Kling. + + Local Storage settings can be enable/disable from "Develop" menu + https://bugs.webkit.org/show_bug.cgi?id=52296 + + * QtTestBrowser/launcherwindow.cpp: + (LauncherWindow::createChrome): + (LauncherWindow::toggleLocalStorage): + (LauncherWindow::toggleOfflineStorageDatabase): + (LauncherWindow::toggleOfflineWebApplicationCache): + (LauncherWindow::setOfflineStorageDefaultQuota): + * QtTestBrowser/launcherwindow.h: + (WindowOptions::WindowOptions): + * QtTestBrowser/main.cpp: + (LauncherApplication::handleUserOptions): + +2011-02-04 Tony Chang + + Reviewed by Eric Seidel. + + get test-webkitpy running on win32 python + https://bugs.webkit.org/show_bug.cgi?id=53822 + + The test harness crashes with a WindowsError because it can't find + 'svn' when using subprocess.Popen. This gets us past the error + so we can see the failing tests on the Chromium Win Release Tests + bot. + + * Scripts/webkitpy/common/system/executive.py: + +2011-02-04 Martin Robinson + + Reviewed by Gustavo Noronha Silva. + + [GTK] fast/events/pagehide-timeout.html fails + https://bugs.webkit.org/show_bug.cgi?id=53771 + + Add knowledge of the document browser cache model the DRT. When resetting + WebKit settings to consistent values between tests, default to the document + browser cache model. + + * DumpRenderTree/gtk/DumpRenderTree.cpp: + (resetDefaultsToConsistentValues): Reset the cache model to the document browser + cache model between tests. + * DumpRenderTree/gtk/LayoutTestControllerGtk.cpp: + (LayoutTestController::setCacheModel): An int value of 2 here corresponds to the + document browser cache model. + +2011-02-04 Adam Barth + + Reviewed by Eric Seidel. + + PluginDocuments don't create widgets for plugins on back/forward + https://bugs.webkit.org/show_bug.cgi?id=53474 + + Teach the test plugin how to call alert on load. + + * DumpRenderTree/TestNetscapePlugIn/main.cpp: + (NPP_New): + +2011-02-04 Adam Roben + + Add a test case for encode/decode of FormData/FormDataElement + + If the fix made in r77401 is broken, this new test will crash. + + Fixes . + + Reviewed by Sam Weinig. + + * TestWebKitAPI/Test.h: Moved the bulk of TEST_ASSERT into a new _TEST_ASSERT_HELPER macro. + Added a new TEST_ASSERT_RETURN macro that can be used in functions with a return value. + + * TestWebKitAPI/Tests/WebKit2/RestoreSessionStateContainingFormData.cpp: Added. + (TestWebKitAPI::didFinishLoadForFrame): Record that the load is finished. + (TestWebKitAPI::setPageLoaderClient): Hook up the loader client. + (TestWebKitAPI::createSessionStateContainingFormData): Load simple-form.html, submit the + form, an return the session state data. + (TestWebKitAPI::TEST): Create some session state that contains form data from one WKPage, + and restore it into another WKPage. + + * TestWebKitAPI/Tests/WebKit2/simple-form.html: Added. + + * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj: + * TestWebKitAPI/win/TestWebKitAPI.vcproj: + * TestWebKitAPI/win/copy-resources.cmd: + Added new files. + +2011-02-04 Adam Roben + + Move code to run JavaScript tests into its own files + + This will allow other tests to use this mechanism. + + Fixes SpacebarScrolling should share its JavaScript-fu with the + world + + Reviewed by Sam Weinig. + + * TestWebKitAPI/JavaScriptTest.cpp: Added. + * TestWebKitAPI/JavaScriptTest.h: Added. + Moved code here... + + * TestWebKitAPI/Tests/WebKit2/SpacebarScrolling.cpp: ...from here. + + * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj: + * TestWebKitAPI/win/TestWebKitAPI.vcproj: + Added the new files. + +2011-02-04 Dirk Pranke + + Reviewed by Mihai Parparita. + + merge test expectations for chromium, chromium-gpu. The + chromium-gpu port will no longer maintain its own + test expectations file. + + Also add in a graphics_type() call on the port object to + determine what graphics type to use in the test configuration, + and log the configuration as a config setting. + + https://bugs.webkit.org/show_bug.cgi?id=53562 + + * Scripts/webkitpy/layout_tests/port/base.py: + * Scripts/webkitpy/layout_tests/layout_package/test_runner.py: + * Scripts/webkitpy/layout_tests/port/chromium_gpu.py: + * Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py: + +2011-02-04 Mikhail Naganov + + Reviewed by Pavel Feldman. + + Web Inspector: fix processing Unicode literals from .js sources in check-inspector-strings. + https://bugs.webkit.org/show_bug.cgi?id=53781 + + * Scripts/check-inspector-strings: + +2011-02-04 Adam Roben + + Include the crashing function in the link to a crash log on Mac + + Fixes Crash log links in results.html should include the + function that crashed on Mac + + Reviewed by David Kilzer. + + * Scripts/old-run-webkit-tests: + (crashLocation): Moved all the Windows code inside an if instead of using an early return, + but didn't otherwise change it. Added an if for Mac that parses the crashing function out of + the crash log. + +2011-02-04 Adam Roben + + Link to Mac crash logs from results.html + + Fixes run-webkit-tests should link to Mac crash logs in + results.html + + Reviewed by David Kilzer. + + * Scripts/old-run-webkit-tests: + (captureSavedCrashLog): Added a case for Apple's Mac port. We get the crash log out of + ~/Library/Logs/CrashReporter, and wait for ReportCrash to exit before trying to get the log. + +2011-02-03 Maciej Stachowiak + + Reviewed by Dan Bernstein. + + WebKit2: Need WebKit2 equivalent of WebResourceLoadDelegate::willSendRequest in the Bundle + https://bugs.webkit.org/show_bug.cgi?id=52897 + + + * WebKitTestRunner/InjectedBundle/InjectedBundlePage.cpp: + (WTR::InjectedBundlePage::InjectedBundlePage): + (WTR::InjectedBundlePage::willSendRequestForFrame): + * WebKitTestRunner/InjectedBundle/InjectedBundlePage.h: + +2011-02-03 Ivan Krstić + + Unreviewed. + + Adding myself to committers.py. + + * Scripts/webkitpy/common/config/committers.py: + +2011-02-03 James Kozianski + + Reviewed by Dimitri Glazkov. + + Add navigator.registerProtocolHandler behind a flag. + https://bugs.webkit.org/show_bug.cgi?id=52609 + + * Scripts/build-webkit: + +2011-02-03 Hayato Ito + + Reviewed by Eric Seidel. + + [NRWT] Remove TestArgs class, which is wrongly used. + + https://bugs.webkit.org/show_bug.cgi?id=53063 + + * Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py: + * Scripts/webkitpy/layout_tests/layout_package/single_test_runner.py: + * Scripts/webkitpy/layout_tests/test_types/image_diff.py: + * Scripts/webkitpy/layout_tests/test_types/test_type_base.py: + * Scripts/webkitpy/layout_tests/test_types/test_type_base_unittest.py: + * Scripts/webkitpy/layout_tests/test_types/text_diff.py: + +2011-02-03 Andrew Wason + + Reviewed by Kenneth Russell. + + Change ENABLE_3D_CANVAS to ENABLE_WEBGL + https://bugs.webkit.org/show_bug.cgi?id=53714 + + * Scripts/build-webkit: change --3d-canvas build option + to set ENABLE_WEBGL + +2011-02-03 Hayato Ito + + Reviewed by Tony Chang. + + [NRWT] Introduces Input/Output class used by base.Driver into port/base.py and + move _run_single_test() and _process_output() functions from + dump_render_tree_thread.py to a single_test_runner.py as an individual module. + + This is clean up and acts as a preparation for elimination of test_types/* + classes. These classes will move to the single_test_runner module introduced + in this patch. + + https://bugs.webkit.org/show_bug.cgi?id=53004 + + * Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py: + * Scripts/webkitpy/layout_tests/layout_package/single_test_runner.py: Added. + * Scripts/webkitpy/layout_tests/layout_package/test_input.py: + * Scripts/webkitpy/layout_tests/layout_package/test_output.py: Removed. + * Scripts/webkitpy/layout_tests/port/base.py: + * Scripts/webkitpy/layout_tests/port/chromium.py: + * Scripts/webkitpy/layout_tests/port/dryrun.py: + * Scripts/webkitpy/layout_tests/port/test.py: + * Scripts/webkitpy/layout_tests/port/webkit.py: + * Scripts/webkitpy/layout_tests/test_types/image_diff.py: + * Scripts/webkitpy/layout_tests/test_types/test_type_base.py: + * Scripts/webkitpy/layout_tests/test_types/text_diff.py: + +2011-02-03 Dirk Pranke + + Unreviewed, build fix. + + Delete a line that was accidentally missed in r77586; without it + there is still a race between the two threads. + + https://bugs.webkit.org/show_bug.cgi?id=51572 + + * Scripts/webkitpy/layout_tests/layout_package/message_broker_unittest.py: + +2011-02-03 Dirk Pranke + + Reviewed by Tony Chang. + + nrwt: handle "broken pipe" notifications from DRT more cleanly. + + https://bugs.webkit.org/show_bug.cgi?id=52927 + + * Scripts/webkitpy/layout_tests/port/server_process.py: + * Scripts/webkitpy/layout_tests/port/server_process_unittest.py: + +2011-02-03 Dirk Pranke + + Reviewed by Kenneth Russell. + + Update, resubmit change to bug 51572. I think the race that was + fixed in r76703 caused the hang that caused us to roll this + patch out before, so I'd like to try again. + + https://bugs.webkit.org/show_bug.cgi?id=51572 + + * Scripts/webkitpy/layout_tests/layout_package/message_broker.py: + * Scripts/webkitpy/layout_tests/layout_package/message_broker_unittest.py: + +2011-02-03 Daniel Cheng + + Unreviewed. + + Adding myself to committers.py. + + * Scripts/webkitpy/common/config/committers.py: + +2011-02-03 Adam Roben + + Include the crashing function in the link to a crash log + + Fixes Crash log links in results.html should include the + function that crashed + + Reviewed by David Kilzer. + + * Scripts/old-run-webkit-tests: + (crashLocation): Added. Returns the location of the crash. + (linksForErrorTest): Include the crash location in the link text for the crash log, if one + could be determined. + +2011-02-03 Dirk Pranke + + Unreviewed, build fix. + + Actually make the changes suggested by Mihai in his review + of bug 53720 :( + + https://bugs.webkit.org/show_bug.cgi?id=53720 + + * Scripts/webkitpy/layout_tests/port/test_files.py: + * Scripts/webkitpy/layout_tests/port/test_files_unittest.py: + +2011-02-03 Dirk Pranke + + Reviewed by Mihai Parparita. + + new-run-webkit-tests: Fix bug introduced in r77434 that was + causing us to run the canvas GPU tests on the Mac GPU port. This + bug revealed that we were not setting the port.name() field + properly in many cases, so I've cleaned up all of that code, and + removed a few comments about "version-specific" GPU ports that + don't exist and just confused things. + + Testing also revealed that port.abspath_for_test() wasn't + normalizing paths on Windows properly, so I fixed that as well. + + https://bugs.webkit.org/show_bug.cgi?id=53719 + + * Scripts/webkitpy/layout_tests/port/base.py: + * Scripts/webkitpy/layout_tests/port/chromium_gpu.py: + * Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py: + +2011-02-03 Dirk Pranke + + Reviewed by Mihai Parparita. + + new-run-webkit-tests: fix normalization of paths on windows when gathering files + + r77434 introduced a bug that was causing the test expectations + for the GPU ports to not be treated properly. It turns out that + when we gathered the list of test files on Windows, we would + return paths of the form "c:\LayoutTests/fast/canvas", and the + mixture of backslashes and forward slashes was confusing things. + + This patch normalizes all of the filenames returned from + test_files.find(), and adds better tests for this (fixing a + couple of other bugs found in the meantime). + + https://bugs.webkit.org/show_bug.cgi?id=53720 + + * Scripts/webkitpy/layout_tests/port/test.py: + * Scripts/webkitpy/layout_tests/port/test_files.py: + * Scripts/webkitpy/layout_tests/port/test_files_unittest.py: + +2011-02-03 Adam Roben + + Add links to Windows crash logs in results.html + + Fixes run-webkit-tests should link to Windows crash logs in + results.html + + Reviewed by David Kilzer. + + * Scripts/old-run-webkit-tests: + (top level): Added $crashLogTag and $windowsCrashLogFilePrefix (which came from + createDebuggerCommandFile). + (testCrashedOrTimedOut): If the test crashed, capture any saved crash log after the dump + tool has exited. + (captureSavedCrashLog): Added. Finds the crash log for the test that just crashed and moves + it into the test results directory tree. Only implemented for Cygwin currently. + (findNewestFileMatchingGlob): Added. Does what it says. + (htmlForResultsSection): Only link to files that exist. + (linksForErrorTest): Add a link to the crash log. + (deleteExpectedAndActualResults): Delete any old crash log for this test. + (createDebuggerCommandFile): Use the new $windowsCrashLogFilePrefix constant instead of + hardcoding it here. + +2011-02-03 Adam Roben + + Tell the debugger the path to the WebKit source tree when saving a crash log + + This allows the debugger to include the crashing line of code in the log. + + Fixes Crash logs from buildslaves don't show the crashing line + of code + + Reviewed by Sam Weinig. + + * Scripts/old-run-webkit-tests: + (createDebuggerCommandFile): Added. Saves commands that we'd like the debugger to run to a + file and returns the path to that file. The commands we pass came from + setUpWindowsCrashLogSaving, but I've added a .srcpath command to tell the debugger where the + WebKit source code lives. + (setUpWindowsCrashLogSaving): Instead of specifying the commands directly on the command + line using -c, save them to a file and specify the path to that file using -cf. This works + around what is presumably a bug in Windows's command line parsing, where having multiple + quoted paths in the debugger commands causes the post-mortem debugger not to be invoked at + all. Also pulled the options we pass to the debugger out into a list that is then join()ed + together to make them easier to modify in the future. + +2011-02-02 Sam Weinig + + Reviewed by Anders Carlsson. + + Add notification of the end of a rubber band. + + + * MiniBrowser/mac/BrowserWindowController.m: + (-[BrowserWindowController awakeFromNib]): + * WebKitTestRunner/TestController.cpp: + (WTR::TestController::createOtherPage): + (WTR::TestController::initialize): + Stub out the new WKUIClient function. + +2011-02-03 Gabor Rapcsanyi + + Unreviewed. + + Adding myself to committers.py. + + * Scripts/webkitpy/common/config/committers.py: + +2011-02-03 Balazs Kelemen + + Reviewed by Csaba Osztrogonác. + + [Qt][WK2] MiniBrowser's window size should be 800x600 + https://bugs.webkit.org/show_bug.cgi?id=53670 + + Make layout test failure debugging easier. + * MiniBrowser/qt/BrowserWindow.cpp: + (BrowserWindow::BrowserWindow): + +2011-02-02 Andy Estes + + Reviewed by Mark Rowe. + + 'update-webkit' should handle OpenSource and Internal using different + VCSs. + + * Scripts/update-webkit: Re-check the VCS type when updating Apple's + Internal directory. + +2011-02-02 Dirk Pranke + + Reviewed by Mihai Parparita. + + new-run-webkit-tests: hardcode the directories to scan for tests + for GPU bots instead of SKIPping them. See bug 53562 for some + context, but we will now hard-code the list of directories to + use by default instead of skipping over directories in the + expectations file. We do this so that we will be able to merge + the expectations files without getting conflicting SKIP + directives. Note that this change will reduce the # of tests + being run on the Mac GPU bots, because we're accidentally + including some today. + + https://bugs.webkit.org/show_bug.cgi?id=53631 + + * Scripts/webkitpy/layout_tests/port/base.py: + * Scripts/webkitpy/layout_tests/port/chromium_gpu.py: + * Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py: + +2011-02-02 Sam Weinig + + Reviewed by Beth Dakin. + + Add ChromeClient function to paint custom overhang areas. + https://bugs.webkit.org/show_bug.cgi?id=53639 + + * WebKitTestRunner/InjectedBundle/InjectedBundlePage.cpp: + (WTR::InjectedBundlePage::InjectedBundlePage): + Stub out new callback. + +2011-02-02 Dirk Pranke + + Reviewed by Tony Chang. + + new-run-webkit-tests: support chromium code paths in mock_drt + + https://bugs.webkit.org/show_bug.cgi?id=53471 + + * Scripts/webkitpy/layout_tests/port/mock_drt.py: + * Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py: + +2011-02-02 Adam Klein + + Reviewed by Tony Chang. + + Update new-run-webkit-tests --chromium to put output under Source/... + https://bugs.webkit.org/show_bug.cgi?id=53612 + + * BuildSlaveSupport/test-result-archive: + * Scripts/webkitpy/layout_tests/port/chromium.py: + +2011-01-28 Tony Chang + + Reviewed by Eric Seidel. + + [chromium] remove --use-test-shell from NRWT + https://bugs.webkit.org/show_bug.cgi?id=53346 + + I've already started removing some of the bindings like eventSender so + this already doesn't work isn't useful. Also updated some comments to + refer to DRT instead of test_shell. + + * Scripts/webkitpy/layout_tests/layout_package/test_expectations.py: + * Scripts/webkitpy/layout_tests/port/chromium.py: + * Scripts/webkitpy/layout_tests/port/chromium_linux.py: + * Scripts/webkitpy/layout_tests/port/chromium_mac.py: + * Scripts/webkitpy/layout_tests/port/chromium_unittest.py: + * Scripts/webkitpy/layout_tests/port/chromium_win.py: + * Scripts/webkitpy/layout_tests/run_webkit_tests.py: + * Scripts/webkitpy/layout_tests/test_types/text_diff.py: + +2011-02-02 Steve Lacey + + Reviewed by Eric Carlson. + + Implement basic media statistics on media elements. + https://bugs.webkit.org/show_bug.cgi?id=53322 + + * Scripts/build-webkit: + +2011-02-02 Kevin Ollivier + + [wx] Build fixes for wxWebKit. + + * DumpRenderTree/wx/LayoutTestControllerWx.cpp: + (LayoutTestController::setSerializeHTTPLoads): + * wx/browser/wscript: + * wx/build/build_utils.py: + * wx/build/settings.py: + +2011-02-01 Dirk Pranke + + Reviewed by Mihai Parparita. + + new-run-webkit-tests: stop skipping so many tests + + r77163 introduced a regression where we weren't resetting test + expectations properly and were skipping too many tests as a + result. This patch fixes that and adds a test for it. + + https://bugs.webkit.org/show_bug.cgi?id=53551 + + * Scripts/webkitpy/layout_tests/layout_package/test_expectations.py: + * Scripts/webkitpy/layout_tests/layout_package/test_expectations_unittest.py: + +2011-02-01 Dirk Pranke + + Reviewed by Tony Chang. + + new-run-webkit-tests: add first stub of test_runner2. This + will add support for the 'inline', 'threads', and 'processes' + flags to --worker-model, but for now the implementatios just + fall back on the old ones. + + https://bugs.webkit.org/show_bug.cgi?id=53157 + + * Scripts/webkitpy/layout_tests/layout_package/test_runner2.py: + * Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py: + +2011-02-01 Mihai Parparita + + Reviewed by James Robinson. + + [Chromium] run-chromium-webkit-unit-tests looks for binaries in the wrong place + https://bugs.webkit.org/show_bug.cgi?id=53522 + + * Scripts/run-chromium-webkit-unit-tests: + +2011-02-01 Adam Barth + + Reviewed by Mihai Parparita. + + Using Control-C to cancel webkit-patch or other python tools causes + unpleasant traceback console spew + https://bugs.webkit.org/show_bug.cgi?id=53462 + + Catch the exception and don't print the stack trace. + + * Scripts/webkit-patch: + +2011-02-01 Scott Cameron + + Reviewed by Daniel Bates. + + Use Windows format for MinGW HTTPD path. + https://bugs.webkit.org/show_bug.cgi?id=53503 + + * Scripts/webkitperl/httpd.pm: + - Use single-quotes around MySys value for $httpdPath in getHTTPDPath() + so that we don't have to escape the space characters in the path. Also, + changed path to Windows-style path and removed FIXME comment. + +2011-02-01 Balazs Kelemen + + Reviewed by Andreas Kling. + + [Qt][WK2] Add a way to use shared process model in MiniBrowser + https://bugs.webkit.org/show_bug.cgi?id=53090 + + * MiniBrowser/qt/BrowserView.cpp: + (BrowserView::BrowserView): Removed the m_context member. + From now the context is guaranteed to be non-null and we + don't need to store that in the object. + * MiniBrowser/qt/BrowserView.h: + * MiniBrowser/qt/BrowserWindow.cpp: + Added static bool to determine that new windows need to be + created with their own context or not. Use the same context + and web process by default to be inilne with the other ports. + (BrowserWindow::BrowserWindow): + (BrowserWindow::newWindow): + * MiniBrowser/qt/BrowserWindow.h: + * MiniBrowser/qt/main.cpp: + (main): Added command line switch to be able to use the + non-shared process model. Simplify the handling of the command line + switches a little bit. + +2011-02-01 Zoltan Horvath + + Reviewed by Andreas Kling. + + [Qt] Add Window menu and Toggle FullScreen action to MiniBrowser. + https://bugs.webkit.org/show_bug.cgi?id=53491 + + * MiniBrowser/qt/BrowserWindow.cpp: + (BrowserWindow::BrowserWindow): + (BrowserWindow::toggleFullScreenMode): + * MiniBrowser/qt/BrowserWindow.h: + +2011-02-01 Zoltan Horvath + + Reviewed by Andreas Kling. + + [Qt] Add Toggle Frame Flattening, Auto Load Images and Disable JavaScript actions to MiniBrowser. + https://bugs.webkit.org/show_bug.cgi?id=53489 + + Add Toggle Frame Flattening action to Develop menu. Add Auto Load Images and Disable JavaScript actions + to Settings menu. + + * MiniBrowser/qt/BrowserWindow.cpp: + (BrowserWindow::BrowserWindow): + (BrowserWindow::toggleFrameFlattening): + (BrowserWindow::toggleDisableJavaScript): + (BrowserWindow::toggleAutoLoadImages): + * MiniBrowser/qt/BrowserWindow.h: + +2011-01-31 Mihai Parparita + + Reviewed by Tony Chang. + + [Chromium] Switch chromium-mac to use ChromiumDriver + https://bugs.webkit.org/show_bug.cgi?id=53461 + + For the sake of consistency with the other Chromium platforms (and so + that chromium-mac picks up special flags like --enable-hardware-gpu), + switch chromium-mac from the WebKitDriver to ChromiumDriver. + + * Scripts/webkitpy/layout_tests/port/chromium.py: + +2011-01-31 Dirk Pranke + + Reviewed by Mihai Parparita. + + The current modifier parsing code in test_expectations is + fragile and hard-coded, so it's not easy to understand the logic + or easily add new types of modifiers (like GPU vs. CPU testing + for graphics tests, or 32-bit vs. 64-bit differences). + + This is the first of two patches that will add in more generic + support and then eliminate the GPU-specific test expectations + files for Chromium. + + This patch adds two standalone objects for handling modifiers. The + rules for interpreting modifiers, precedence, and conflicts are + given in the docstring to the ModifierMatcher class, which + returns ModifierMatchResult objects. + + This patch also adds routines to the Port interface and a + default set of values in the base object, in order to obtain the + values needed on a given test run. These values are then passed + to the expectation parser. This also allows us to clean up the + logic used to lint all of the different configurations in a + single test_expectations.txt file. + + The next patch will merge in the separate GPU expectations file. + + https://bugs.webkit.org/show_bug.cgi?id=51222 + + * Scripts/webkitpy/layout_tests/layout_package/printing_unittest.py: + * Scripts/webkitpy/layout_tests/layout_package/test_expectations.py: + * Scripts/webkitpy/layout_tests/layout_package/test_expectations_unittest.py: + * Scripts/webkitpy/layout_tests/layout_package/test_runner.py: + * Scripts/webkitpy/layout_tests/port/base.py: + * Scripts/webkitpy/layout_tests/port/base_unittest.py: + * Scripts/webkitpy/layout_tests/port/chromium.py: + * Scripts/webkitpy/layout_tests/port/port_testcase.py: + * Scripts/webkitpy/layout_tests/port/test.py: + * Scripts/webkitpy/layout_tests/port/webkit.py: + * Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py: + * Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py: + * Scripts/webkitpy/layout_tests/run_webkit_tests.py: + * Tools/Scripts/webkitpy/style/checkers/test_expectations.py: + * Tools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py: + +2011-01-31 Scott Cameron + + Reviewed by Daniel Bates. + + update run-webkit-httpd to be able to launch Apache2.2 in a MSYS environment + https://bugs.webkit.org/show_bug.cgi?id=50036 + + This will add an Apache2.2 configuration file and modifies scripts to allow + running an httpd server in an MSYS environment. + + The default Apache2.2 installation path has been preserved and PHP5 modules disabled + in order to allow for simple installation/execution. Simply install the latest + Apache2.2 version with OpenSSL from http://httpd.apache.org/download.cgi#apache22 to + be able to execute run-webkit-httpd. + + + * Scripts/run-webkit-httpd: + * Scripts/webkitperl/httpd.pm: + +2011-01-31 Tony Chang + + Reviewed by Eric Seidel. + + mac DRT should report RTL scroll offset relative to top right corner + https://bugs.webkit.org/show_bug.cgi?id=53324 + + * DumpRenderTree/mac/DumpRenderTree.mm: + (dumpFrameScrollPosition): + +2011-01-31 Zoltan Horvath + + Reviewed by Andreas Kling. + + [Qt] Add Take Screen Shot action to MiniBrowser + https://bugs.webkit.org/show_bug.cgi?id=53422 + + Add Take Screen Shot action to MiniBrowser's view menu. + + * MiniBrowser/qt/BrowserWindow.cpp: + (BrowserWindow::BrowserWindow): + (BrowserWindow::screenshot): + * MiniBrowser/qt/BrowserWindow.h: + +2011-01-31 Gustavo Noronha Silva + + Reviewed by Xan Lopez. + + [GTK] REGRESSION: http/tests/media/video-{cookie,referer}.html failing + https://bugs.webkit.org/show_bug.cgi?id=53379 + + Remove left over #ifdef's. I thought all of the conditional code + had been freed from the condition, but I forgot to check DRT. + + * DumpRenderTree/gtk/DumpRenderTree.cpp: + (resetDefaultsToConsistentValues): + * DumpRenderTree/gtk/LayoutTestControllerGtk.cpp: + (LayoutTestController::setAlwaysAcceptCookies): + +2011-01-30 Dirk Pranke + + Unreviewed, build fix. + + Fix regression introduced in r77093 - path.rsplit() doesn't + take keyword arguments. + + https://bugs.webkit.org/show_bug.cgi?id=53326 + + * Scripts/webkitpy/common/system/filesystem_mock.py: + +2011-01-30 Dirk Pranke + + Reviewed by Mihai Parparita. + + Add more unit tests for rebaseline-chromium-webkit-tests. This + change involves restructuring a bunch of r-c-w-t code to make it + more testable as well. We also add wrapper classes for handling + testing zip files and fetching URLs. + + https://bugs.webkit.org/show_bug.cgi?id=53040 + + * Scripts/webkitpy/common/system/urlfetcher.py: + * Scripts/webkitpy/common/system/urlfetcher_mock.py: + * Scripts/webkitpy/common/system/zipfileset_mock.py: + * Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py: + * Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py: + * Scripts/webkitpy/tool/mocktool.py: + +2011-01-30 Dirk Pranke + + Reviewed by Eric Seidel. + + Clean up of the filesystem-related modules used in webkitpy. + I've added relpath() to the filesystem interface, modified + ospath.relpath() so that it could work with the filesystem + interface, and modified the fileset* routines to use the + filesystem interface consistently. + + This patch also adds a close() routine to the fileset routines + to indicate that the caller is done with the fileset. This + allows zipfileset to clean up after itself when it creates + tempfiles to store downloads. + + https://bugs.webkit.org/show_bug.cgi?id=53326 + + * Scripts/webkitpy/common/system/directoryfileset.py: + * Scripts/webkitpy/common/system/fileset.py: + * Scripts/webkitpy/common/system/filesystem.py: + * Scripts/webkitpy/common/system/filesystem_mock.py: + * Scripts/webkitpy/common/system/filesystem_unittest.py: + * Scripts/webkitpy/common/system/ospath.py: + * Scripts/webkitpy/common/system/zipfileset.py: + * Scripts/webkitpy/common/system/zipfileset_unittest.py: + +2011-01-30 Balazs Kelemen + + Reviewed by Csaba Osztrogonác. + + [Qt][WK2]REGRESSION (r76991): Fix build errors + https://bugs.webkit.org/show_bug.cgi?id=53400 + + Revert the temporary build fix (http://trac.webkit.org/changeset/77088) + and remove WebKit2Prefix.h from the build. + * MiniBrowser/qt/MiniBrowser.pro: + +2011-01-30 Csaba Osztrogonác + + Unreviewed. + + [Qt][WK2] Buildfix. + + * MiniBrowser/qt/MiniBrowser.pro: + +2011-01-28 Simon Fraser + + Reviewed by Adam Roben. + + define NOMINMAX when building ImageDiff, as we do elsewhere. + + Add various clampToInt() methods to MathExtras.h + https://bugs.webkit.org/show_bug.cgi?id=52910 + + * DumpRenderTree/win/ImageDiffCommon.vsprops: + +2011-01-28 Maciej Stachowiak + + Reviewed by Darin Adler. + + WebKitTestRunner needs layoutTestController.setPOSIXLocale + https://bugs.webkit.org/show_bug.cgi?id=42682 + + * WebKitTestRunner/InjectedBundle/Bindings/LayoutTestController.idl: + * WebKitTestRunner/InjectedBundle/InjectedBundle.cpp: + (WTR::InjectedBundle::resetLocalSettings): + (WTR::InjectedBundle::didReceiveMessage): + * WebKitTestRunner/InjectedBundle/InjectedBundle.h: + * WebKitTestRunner/InjectedBundle/LayoutTestController.cpp: + (WTR::LayoutTestController::setPOSIXLocale): + * WebKitTestRunner/InjectedBundle/LayoutTestController.h: + +2011-01-28 Dirk Pranke + + Reviewed by Mihai Parparita. + + test-webkitpy: fix webkitpy.layout_tests.port.mac_unittest.MacTest.test_skipped_file_paths + + This patch re-enables this test and changes it to + handle all of the mac platform versions, not just the one + it is running on. + + https://bugs.webkit.org/show_bug.cgi?id=53356 + + * Scripts/webkitpy/layout_tests/port/mac_unittest.py: + +2011-01-28 Dirk Pranke + + Unreviewed, build fix. + + Take two. The fix in 77023 didn't work, because we were + still calling path.abspath_to_uri, which calls _cygpath under + the covers, and it appears the cygpath on the bots does + something different than it does on my machine. This patch + removes the calls to path.abspath_to_uri, so it should be safe. + If it doesn't work, I'll roll it out along with r76982 and 77023. + + https://bugs.webkit.org/show_bug.cgi?id=53126 + + * Scripts/webkitpy/layout_tests/port/test.py: + +2011-01-28 David Kilzer + + build-webkit gives a bogus warning with newer versions of Xcode + + + Reviewed by Mark Rowe. + + * Scripts/webkitdirs.pm: + (checkRequiredSystemConfig): Check the Xcode marketing version + in addition to the DevCoreTools build version before complaining + about an old version of Xcode. Also make the Mac OS X version + check use Perl's built-in version string comparitor. + +2011-01-28 Dirk Pranke + + Unreviewed, build fix. + + Work around breakage on Win 7 Release bot caused by r76982 + and the fact that windows ports use "file:////" instead of + "file:///". Ideally the test code should be isolated from + this, but it isn't yet. Will fix properly in a bit. + + * Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py: + +2011-01-28 Dirk Pranke + + Reviewed by Tony Chang. + + new-run-webkit-tests: change worker model values to + "old-inline", "old-threads" in preparation for test_runner2 / + multiprocessing changes. + + https://bugs.webkit.org/show_bug.cgi?id=53156 + + * Scripts/webkitpy/layout_tests/layout_package/test_runner.py: + * Scripts/webkitpy/layout_tests/port/base.py: + * Scripts/webkitpy/layout_tests/port/chromium_mac.py: + * Scripts/webkitpy/layout_tests/port/mac.py: + * Scripts/webkitpy/layout_tests/run_webkit_tests.py: + +2011-01-28 Dirk Pranke + + Reviewed by Tony Chang. + + committers.py - add an IRC nickname for dpranke + https://bugs.webkit.org/show_bug.cgi?id=53335 + + * Scripts/webkitpy/common/config/committers.py: + +2011-01-28 Dirk Pranke + + Reviewed by Tony Chang. + + new-run-webkit-tests: add a "mock DRT" port implementation + and a separate class that emulates what we expect the + DumpRenderTree behavior to be. + + This will eventually replace port/dryrun.py and allow us to get + better test coverage of the new-run-webkit-tests code as well as + a reference for what new-run-webkit-tests expects from DRT. + + This is the first attempt at this, and it is pretty bare-boned. It + really only has been tested on the 'mac' port (and a little on + the 'chromium-mac' port. + + https://bugs.webkit.org/show_bug.cgi?id=53126 + + * Scripts/webkitpy/common/system/filesystem_mock.py: + * Scripts/webkitpy/layout_tests/port/dryrun.py: + * Scripts/webkitpy/layout_tests/port/factory.py: + * Scripts/webkitpy/layout_tests/port/mock_drt.py: Added. + * Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py: Added. + * Scripts/webkitpy/layout_tests/port/test.py: + +2011-01-28 Pratik Solanki + + Unreviewed. Removing .swp file checked in by mistake. + + * Scripts/.webkitdirs.pm.swp: Removed. + +2011-01-28 Pratik Solanki + + Reviewed by David Kilzer. + + Space not necessary for undefined feature + https://bugs.webkit.org/show_bug.cgi?id=53317 + + * Scripts/build-webkit: + +2011-01-28 Chang Shu + + Reviewed by Andreas Kling. + + Reset NetworkAccessManager to clean up credentials from previous tests. + This change causes no performance overhead either. + https://bugs.webkit.org/show_bug.cgi?id=36688 + + * DumpRenderTree/qt/DumpRenderTreeQt.cpp: + (WebCore::DumpRenderTree::resetToConsistentStateBeforeTesting): + (WebCore::DumpRenderTree::open): + * DumpRenderTree/qt/DumpRenderTreeQt.h: + +2011-01-28 Zoltan Horvath + + Reviewed by Andreas Kling. + + [Qt] Add Zoom Text Only action to MiniBrowser + https://bugs.webkit.org/show_bug.cgi?id=53297 + + Add Zoom Text Only action to MiniBrowser's view menu, change zoom levels to avoid unnecessary divides. + + * MiniBrowser/qt/BrowserWindow.cpp: + (BrowserWindow::BrowserWindow): + (BrowserWindow::zoomIn): + (BrowserWindow::zoomOut): + (BrowserWindow::resetZoom): + (BrowserWindow::toggleZoomTextOnly): + (BrowserWindow::applyZoom): + * MiniBrowser/qt/BrowserWindow.h: + +2011-01-28 Adam Barth + + Reviewed by Eric Seidel. + + Add webkit-patch roll-chromium-deps + https://bugs.webkit.org/show_bug.cgi?id=53288 + + This command updates the Source/WebKit/chromium/DEPS file with the + last-known good revision of Chromium (or a revision specified on the + command line). I'd eventually like to turn this into a SheriffBot + command, but this is the first step. + + This patch somewhat sprawled because I needed to move a bunch of code + out of ChangeLog that should never have been there in the first place. + Also, I had to fix a bug in MockUser in order to test the new command. + + * Scripts/webkitpy/common/checkout/api.py: + * Scripts/webkitpy/common/checkout/changelog.py: + * Scripts/webkitpy/common/checkout/changelog_unittest.py: + * Scripts/webkitpy/common/checkout/deps.py: Added. + * Scripts/webkitpy/common/config/urls.py: + * Scripts/webkitpy/tool/commands/__init__.py: + * Scripts/webkitpy/tool/commands/download_unittest.py: + * Scripts/webkitpy/tool/commands/roll.py: Added. + * Scripts/webkitpy/tool/commands/roll_unittest.py: Added. + * Scripts/webkitpy/tool/commands/upload_unittest.py: + * Scripts/webkitpy/tool/mocktool.py: + * Scripts/webkitpy/tool/steps/__init__.py: + * Scripts/webkitpy/tool/steps/preparechangelogfordepsroll.py: Added. + * Scripts/webkitpy/tool/steps/preparechangelogforrevert.py: + * Scripts/webkitpy/tool/steps/preparechangelogforrevert_unittest.py: Added. + * Scripts/webkitpy/tool/steps/suggestreviewers_unittest.py: + * Scripts/webkitpy/tool/steps/updatechromiumdeps.py: Added. + * Scripts/webkitpy/tool/steps/validatechangelogs_unittest.py: + +2011-01-27 Greg Coletta + + Reviewed by Laszlo Gombos. + + Get rid of prefix header dependency for WebKit2 build system + https://bugs.webkit.org/show_bug.cgi?id=50174 + + Change the style checker so that it enforces config.h include for + WebKit2. + + * Scripts/webkitpy/style/checker.py: + +2011-01-27 Maciej Stachowiak + + Reviewed by Darin Adler. + + execCommand("Paste") doesn't work in WebKitTestRunner + https://bugs.webkit.org/show_bug.cgi?id=52785 + + Enable both of the settings needed to allow paste. + + * WebKitTestRunner/TestController.cpp: + (WTR::TestController::resetStateToConsistentValues): + +2011-01-27 Jacob Dinu + + Reviewed by Andreas Kling. + + [Qt] QtTestBrowser: User input lost when toggling use of QGraphicsView + https://bugs.webkit.org/show_bug.cgi?id=48440 + + Preserve user input when toggling use of QGraphicsView + + * QtTestBrowser/launcherwindow.cpp: + (LauncherWindow::initializeView): + (LauncherWindow::loadFinished): + * QtTestBrowser/launcherwindow.h: + * QtTestBrowser/mainwindow.cpp: + (MainWindow::addressUrl): + * QtTestBrowser/mainwindow.h: + +2011-01-27 Dirk Pranke + + Reviewed by Mihai Parparita. + + new-run-webkit-tests: turn off pixel tests correctly by default + for webkit-based ports. r70013 (bug 47510) used + port.set_option_default() to attempt to set default values, but + that didn't work correctly. I have removed set_option_default + for now since it was only being used in two places and in three + useless unit tests. There is a separate bug open to fix the + option parsing (48095), so this workaround is fine for now. + + https://bugs.webkit.org/show_bug.cgi?id=53217 + + * Scripts/webkitpy/layout_tests/port/webkit.py: + * Scripts/webkitpy/layout_tests/port/base.py: + * Scripts/webkitpy/layout_tests/port/base_unittest.py: + +2011-01-27 Balazs Kelemen + + Rubber-stamped by Ariya Hidayat. + + Trivial crash fix in WTR. + + * WebKitTestRunner/TestController.cpp: + (WTR::TestController::initialize): Increase the size + of the vector by one to be able to store the null character. + +2011-01-27 Balazs Kelemen + + Reviewed by Csaba Osztrogonác. + + [Qt][WK2] WTR should be initialized in the same way as DRT + https://bugs.webkit.org/show_bug.cgi?id=53240 + + * WebKitTestRunner/InjectedBundle/qt/ActivateFontsQt.cpp: + (WTR::activateFonts): Added missing initialization steps. + +2011-01-27 Joone Hur + + Unreviewed. + + Adding myself to committers.py. + + * Scripts/webkitpy/common/config/committers.py: + +2011-01-27 Laszlo Gombos + + Reviewed by Csaba Osztrogonác. + + [Qt] [Symbian] Move project files into Source + https://bugs.webkit.org/show_bug.cgi?id=52891 + + Fix the Symbian build after the project files + are moved to Source directory. On Symbian qmake + needs to run in the same directory where the main + pro files (WebKit.pro, Tools.pro) are located. + + * Scripts/webkitdirs.pm: Change to the directory where the pro file + is located before running qmake for Symbian. Qmake on Symbian + does not properly honor the "-o" option, work it around by setting + the name of the Makefile to bld.inf. + +2011-01-27 Zoltan Horvath + + Reviewed by Andreas Kling. + + [Qt] Add simple zooming features to MiniBrowser + https://bugs.webkit.org/show_bug.cgi?id=53231 + + Add View menu and Zoom In, Zoom Out, Zoom Reset actions to MiniBrowser. + + * MiniBrowser/qt/BrowserWindow.cpp: + (BrowserWindow::BrowserWindow): + (BrowserWindow::zoomIn): + (BrowserWindow::zoomOut): + (BrowserWindow::resetZoom): + (BrowserWindow::updateUserAgentList): + (BrowserWindow::applyZoom): + * MiniBrowser/qt/BrowserWindow.h: + +2011-01-26 Maciej Stachowiak + + Reviewed by Dan Bernstein. + + WebKitTestRunner needs to support layoutTestController.evaluateInWebInspector + https://bugs.webkit.org/show_bug.cgi?id=42319 + + Add evaluateInWebInspector and other APIs needed for inspector tests to run. + + * WebKitTestRunner/InjectedBundle/Bindings/LayoutTestController.idl: + * WebKitTestRunner/InjectedBundle/LayoutTestController.cpp: + (WTR::LayoutTestController::showWebInspector): + (WTR::LayoutTestController::closeWebInspector): + (WTR::LayoutTestController::evaluateInWebInspector): + (WTR::LayoutTestController::setTimelineProfilingEnabled): + * WebKitTestRunner/InjectedBundle/LayoutTestController.h: + * WebKitTestRunner/TestInvocation.cpp: + (WTR::shouldOpenWebInspector): + (WTR::TestInvocation::invoke): + * WebKitTestRunner/WebKitTestRunnerPrefix.h: + +2011-01-26 Martin Robinson + + Reviewed by Xan Lopez. + + [GTK] Pixel dumps do not include scrollbars in output images + https://bugs.webkit.org/show_bug.cgi?id=53216 + + Show scrollbars in pixel dumps. This will fix pixel dumps for tests + that have scrollbars. + + * DumpRenderTree/gtk/PixelDumpSupportGtk.cpp: + (createBitmapContextFromWebView): Take the snapshot of the containing GtkScrolledWindow + instead of the web view itself. + +2011-01-26 Dirk Pranke + + Reviewed by Mihai Parparita. + + Fix regression introduced in r76322 ... new-run-webkit-tests + was attempting to save the image diff output as a UTF-8-encoded + file. + + https://bugs.webkit.org/show_bug.cgi?id=53210 + + * Scripts/webkitpy/layout_tests/port/webkit.py: + +2011-01-26 Sheriff Bot + + Unreviewed, rolling out r76709. + http://trac.webkit.org/changeset/76709 + https://bugs.webkit.org/show_bug.cgi?id=53194 + + "broke python tests on non-snowleopard platforms" (Requested + by dpranke on #webkit). + + * Scripts/webkitpy/layout_tests/port/factory.py: + * Scripts/webkitpy/layout_tests/port/mock_drt.py: Removed. + * Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py: Removed. + +2011-01-26 Dirk Pranke + + Reviewed by Tony Chang. + + new-run-webkit-tests: add a "mock DRT" port implementation + and a separate class that emulates what we expect the + DumpRenderTree behavior to be. + + This will eventually replace port/dryrun.py and allow us to get + better test coverage of the new-run-webkit-tests code as well as + a reference for what new-run-webkit-tests expects from DRT. + + This is the first attempt at this, and it is pretty bare-boned. It + really only has been tested on the 'mac' port (and a little on + the 'chromium-mac' port. + + https://bugs.webkit.org/show_bug.cgi?id=53126 + + * Scripts/webkitpy/layout_tests/port/mock_drt.py: Added. + * Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py: Added. + * Scripts/webkitpy/layout_tests/port/factory.py: + +2011-01-26 Xianzhu Wang + + Reviewed by Tony Chang. + + Add '--no-timeout' option to Chromium DRT to ease debugging. + https://bugs.webkit.org/show_bug.cgi?id=52873 + + * DumpRenderTree/chromium/DumpRenderTree.cpp: + (main): + +2011-01-26 Csaba Osztrogonác + + Reviewed by Andreas Kling. + + [Qt] Fix qt_minimal build + https://bugs.webkit.org/show_bug.cgi?id=53172 + + * QtTestBrowser/locationedit.h: + +2011-01-26 Mansi Mithal + + Reviewed by Antonio Gomes. + + QtTestBrowser should have a UI Setting to disable plugins + https://bugs.webkit.org/show_bug.cgi?id=52408 + + Added a new action item named "Disable Plugins" + under the "Settings" menu. + + * QtTestBrowser/launcherwindow.cpp: + (LauncherWindow::createChrome): + (LauncherWindow::togglePlugins): + * QtTestBrowser/launcherwindow.h: + +2011-01-25 Patrick Gansterer + + Reviewed by Adam Barth. + + Move main CMakeLists.txt into Source directory + https://bugs.webkit.org/show_bug.cgi?id=52888 + + * Scripts/webkitdirs.pm: + +2011-01-25 Patrick Gansterer + + Reviewed by Eric Seidel. + + Improve label text in submit-to-ews + https://bugs.webkit.org/show_bug.cgi?id=53130 + + * QueueStatusServer/templates/submittoews.html: + +2011-01-25 Mansi Mithal + + Reviewed by Antonio Gomes. + + QtTestBrowser should have a UI Settings to prevent loading images + https://bugs.webkit.org/show_bug.cgi?id=52409 + + Added a new action item named "DisableAutoLoadImages" + under the "Settings" menu + + * QtTestBrowser/launcherwindow.cpp: + (LauncherWindow::createChrome): + (LauncherWindow::toggleAutoLoadImages): + * QtTestBrowser/launcherwindow.h: + +2011-01-25 Dirk Pranke + + Reviewed by Tony Chang. + + Minor bug fixes and cleanup for filesystem wrappers, port/* test + classes, test_expectations.py. This change adds "test-win" and + "test-mac" variants to the test port so that we can better test + rebaselining, and adds a MockUser() object for reuse in testing. + + https://bugs.webkit.org/show_bug.cgi?id=53036 + + * Scripts/webkitpy/common/system/filesystem.py: + * Scripts/webkitpy/common/system/filesystem_mock.py: + * Scripts/webkitpy/layout_tests/layout_package/test_expectations.py: + * Scripts/webkitpy/layout_tests/port/factory.py: + * Scripts/webkitpy/layout_tests/port/base.py: + * Scripts/webkitpy/layout_tests/port/test.py: + * Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py: + * Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py: + * Scripts/webkitpy/tool/mocktool.py: + +2011-01-25 Tony Chang + + Reviewed by Dimitri Glazkov. + + [chromium] remove unused layoutTestController callback + https://bugs.webkit.org/show_bug.cgi?id=53103 + + This method is used by a test_shell_test, but not needed by DRT. + + * DumpRenderTree/chromium/LayoutTestController.cpp: + (LayoutTestController::LayoutTestController): + * DumpRenderTree/chromium/LayoutTestController.h: + +2011-01-25 Gustavo Noronha Silva + + Reviewed by Andreas Kling. + + [GTK] Remove 64 bits release bot from the buildbot master + https://bugs.webkit.org/show_bug.cgi?id=52899 + + Removes the 64 bits release slave from the buildbot configuration, + so that we can use it as an EWS. + + * BuildSlaveSupport/build.webkit.org-config/config.json: + +2011-01-24 Maciej Stachowiak + + Reviewed by Dan Bernstein. + + Implement database quota callback to fix storage tests on WebKit2 bot + https://bugs.webkit.org/show_bug.cgi?id=53064 + + * WebKitTestRunner/TestController.cpp: + (WTR::exceededDatabaseQuota): + (WTR::TestController::createOtherPage): + (WTR::TestController::initialize): + +2011-01-24 Maciej Stachowiak + + Reviewed by Anders Carlsson. + + Use designated temp directory for the database for WebKit2 + https://bugs.webkit.org/show_bug.cgi?id=53052 + + Adopt the new WK2 API for this. + + * WebKitTestRunner/TestController.cpp: + (WTR::TestController::libraryPathForTesting): + (WTR::TestController::initialize): + * WebKitTestRunner/TestController.h: + * WebKitTestRunner/mac/TestControllerMac.mm: + (WTR::TestController::platformLibraryPathForTesting): + * WebKitTestRunner/qt/TestControllerQt.cpp: + (WTR::TestController::platformLibraryPathForTesting): + * WebKitTestRunner/win/TestControllerWin.cpp: + (WTR::TestController::platformLibraryPathForTesting): + +2011-01-24 Lucas Forschler + + Reviewed by Stephanie Lewis. + + Add a new Leopard test bot. + + * BuildSlaveSupport/build.webkit.org-config/config.json: + +2011-01-24 Gyuyoung Kim + + Reviewed by Adam Barth. + + Add EFL Builder to core waterfall + https://bugs.webkit.org/show_bug.cgi?id=52704 + + Add EFL Builder to the core waterfall again. + + * Scripts/webkitpy/common/net/buildbot/buildbot.py: + * Scripts/webkitpy/common/net/buildbot/buildbot_unittest.py: + +2011-01-24 Xianzhu Wang + + Reviewed by Tony Chang. + + new-run-webkit-tests fails to start HTTP server if there are proxy settings + https://bugs.webkit.org/show_bug.cgi?id=52872 + + Force disabling proxy to resolve the problem. + + * Scripts/webkitpy/layout_tests/port/http_server_base.py: + * Scripts/webkitpy/layout_tests/port/websocket_server.py: + +2011-01-24 Andreas Kling + + Reviewed by Darin Adler. + + check-webkit-style: Update exemptions following Source/ move. + https://bugs.webkit.org/show_bug.cgi?id=53017 + + * Scripts/webkitpy/style/checker.py: + * Scripts/webkitpy/style/checker_unittest.py: + +2011-01-24 Andras Becsi + + Rubber-stamped by Csaba Osztrogonác. + + [Qt] Move project files into Source + https://bugs.webkit.org/show_bug.cgi?id=52891 + + * DumpRenderTree/qt/ImageDiff.pro: Add missing "Source" to WebKit.pri path + +2011-01-24 Mikhail Naganov + + Reviewed by Yury Semikhatsky. + + Web Inspector: Find duplicate strings in localizedStrings.js + + https://bugs.webkit.org/show_bug.cgi?id=53005 + + * Scripts/check-inspector-strings: + +2011-01-24 Zoltan Horvath + + Reviewed by Andreas Kling. + + [Qt] Regroup MiniBrowser's menubar and add open file action + https://bugs.webkit.org/show_bug.cgi?id=53000 + + Add File and Develop menu to MiniBrowser's menubar and add open file action to File menu. + + * MiniBrowser/qt/BrowserWindow.cpp: + (BrowserWindow::BrowserWindow): + (BrowserWindow::openFile): + (BrowserWindow::~BrowserWindow): + * MiniBrowser/qt/BrowserWindow.h: + +2011-01-24 Csaba Osztrogonác + + Unreviewed fix after r76496 + + [Qt] Move project files into Source + https://bugs.webkit.org/show_bug.cgi?id=52891 + + * BuildSlaveSupport/build.webkit.org-config/master.cfg: Remove unnecessary "Source" from the path. + * BuildSlaveSupport/built-product-archive: Remove unnecessary "Source" from the path. + * Scripts/webkitdirs.pm: Add the accidentally removed slash to the path. + +2011-01-24 Andras Becsi + + Reviewed by Csaba Osztrogonác. + + [Qt] Move project files into Source + https://bugs.webkit.org/show_bug.cgi?id=52891 + + * DerivedSources.pro: Renamed from DerivedSources.pro. + * DumpRenderTree/qt/DumpRenderTree.pro: + * DumpRenderTree/qt/TestNetscapePlugin/TestNetscapePlugin.pro: + * MiniBrowser/DerivedSources.pro: + * MiniBrowser/qt/MiniBrowser.pro: + * QtTestBrowser/QtTestBrowser.pro: + * Scripts/webkitdirs.pm: + * Tools.pro: Added. + * WebKitTestRunner/DerivedSources.pro: + * WebKitTestRunner/InjectedBundle/qt/InjectedBundle.pro: + * WebKitTestRunner/qt/WebKitTestRunner.pro: + +2011-01-22 Geoffrey Garen + + Reviewed by Dan Bernstein. + + Beefed up --threaded mode to catch even more kinds of errors. + https://bugs.webkit.org/show_bug.cgi?id=52971 + + * DumpRenderTree/pthreads/JavaScriptThreadingPthreads.cpp: Use a shared + context group to force JSC to mark multiple threads. (This used to be + the default, but it changed in SnowLeopard.) + (runJavaScriptThread): Do more locking and unlocking, and more allocation, + to give threading mistakes more chances to show themselves. + (startJavaScriptThreads): + (stopJavaScriptThreads): + +2011-01-22 Robert Hogan + + Reviewed by Andreas Kling. + + [Qt] Support layoutTestController.addURLToRedirect() + https://bugs.webkit.org/show_bug.cgi?id=52956 + + * DumpRenderTree/qt/LayoutTestControllerQt.cpp: + (LayoutTestController::addURLToRedirect): + * DumpRenderTree/qt/LayoutTestControllerQt.h: + +2011-01-22 Robert Hogan + + Reviewed by Kenneth Rohde Christiansen. + + [Qt] REGRESSION (r72360): http/tests/security/aboutBlank/xss-DENIED-* tests fail + https://bugs.webkit.org/show_bug.cgi?id=49802 + + * DumpRenderTree/qt/DumpRenderTreeQt.cpp: + (WebCore::DumpRenderTree::resetToConsistentStateBeforeTesting): + (WebCore::DumpRenderTree::open): + +2011-01-21 Adam Barth + + Reviewed by Maciej Stachowiak. + + Disable mac_unittest.py that fails on some of the Mac bots + https://bugs.webkit.org/show_bug.cgi?id=52947 + + As noted in the comment, this test does not appear to be correct + because the function this test is testing returns different results + depending on which flavor of Mac the test is run on. This patch + disable the test. Hopefully we can re-enable the test once we've + probably insulated the test from its environment. + + * Scripts/webkitpy/layout_tests/port/mac_unittest.py: + +2011-01-21 Dirk Pranke + + Reviewed by Mihai Parparita. + + Fix bug introduced in r76322 that caused NRWT to not actually + read the Skipped files properly. + + https://bugs.webkit.org/show_bug.cgi?id=52771 + + * Scripts/webkitpy/layout_tests/port/mac_unittest.py: + * Scripts/webkitpy/layout_tests/port/webkit.py: + * Scripts/webkitpy/layout_tests/port/webkit_unittest.py: + 2011-01-21 Sam Weinig Reviewed by Geoffrey Sean Garen and Mark Rowe. diff --git a/Tools/DerivedSources.pro b/Tools/DerivedSources.pro new file mode 100644 index 0000000..55a26a2 --- /dev/null +++ b/Tools/DerivedSources.pro @@ -0,0 +1,16 @@ +TEMPLATE = subdirs +CONFIG += ordered + +SUBDIRS += \ + MiniBrowser/DerivedSources.pro \ + WebKitTestRunner/DerivedSources.pro + +for(subpro, SUBDIRS) { + subdir = $${dirname(subpro)} + subtarget = $$replace(subpro, [^a-zA-Z0-9_], -) + eval($${subtarget}.makefile = "Makefile.DerivedSources") + eval(generated_files-$${subtarget}.commands = (cd $$subdir && $(MAKE) -f Makefile.DerivedSources generated_files)) + QMAKE_EXTRA_TARGETS += generated_files-$${subtarget} + generated_files.depends += generated_files-$${subtarget} +} +QMAKE_EXTRA_TARGETS += generated_files diff --git a/Tools/DumpRenderTree/AccessibilityController.cpp b/Tools/DumpRenderTree/AccessibilityController.cpp index 798389f..4fcd4f5 100644 --- a/Tools/DumpRenderTree/AccessibilityController.cpp +++ b/Tools/DumpRenderTree/AccessibilityController.cpp @@ -77,6 +77,13 @@ static JSValueRef logScrollingStartEventsCallback(JSContextRef ctx, JSObjectRef, return JSValueMakeUndefined(ctx); } +static JSValueRef logAccessibilityEventsCallback(JSContextRef ctx, JSObjectRef, JSObjectRef thisObject, size_t, const JSValueRef[], JSValueRef*) +{ + AccessibilityController* controller = static_cast(JSObjectGetPrivate(thisObject)); + controller->setLogAccessibilityEvents(true); + return JSValueMakeUndefined(ctx); +} + static JSValueRef getElementAtPointCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { int x = 0; @@ -96,6 +103,7 @@ JSClassRef AccessibilityController::getJSClass() { "logFocusEvents", logFocusEventsCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "logValueChangeEvents", logValueChangeEventsCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "logScrollingStartEvents", logScrollingStartEventsCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, + { "logAccessibilityEvents", logAccessibilityEventsCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "elementAtPoint", getElementAtPointCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { 0, 0, 0 } }; @@ -119,4 +127,5 @@ void AccessibilityController::resetToConsistentState() setLogFocusEvents(false); setLogValueChangeEvents(false); setLogScrollingStartEvents(false); + setLogAccessibilityEvents(false); } diff --git a/Tools/DumpRenderTree/AccessibilityController.h b/Tools/DumpRenderTree/AccessibilityController.h index 5a6ca13..462c484 100644 --- a/Tools/DumpRenderTree/AccessibilityController.h +++ b/Tools/DumpRenderTree/AccessibilityController.h @@ -50,6 +50,7 @@ public: void setLogFocusEvents(bool); void setLogValueChangeEvents(bool); void setLogScrollingStartEvents(bool); + void setLogAccessibilityEvents(bool); void resetToConsistentState(); diff --git a/Tools/DumpRenderTree/DumpRenderTree.gypi b/Tools/DumpRenderTree/DumpRenderTree.gypi index b7de580..3c3384d 100644 --- a/Tools/DumpRenderTree/DumpRenderTree.gypi +++ b/Tools/DumpRenderTree/DumpRenderTree.gypi @@ -53,8 +53,12 @@ 'TestNetscapePlugIn/TestObject.cpp', 'TestNetscapePlugIn/TestObject.h', 'TestNetscapePlugIn/Tests/DocumentOpenInDestroyStream.cpp', + 'TestNetscapePlugIn/Tests/EvaluateJSAfterRemovingPluginElement.cpp', + 'TestNetscapePlugIn/Tests/GetUserAgentWithNullNPPFromNPPNew.cpp', 'TestNetscapePlugIn/Tests/NPRuntimeObjectFromDestroyedPlugin.cpp', 'TestNetscapePlugIn/Tests/NPRuntimeRemoveProperty.cpp', + 'TestNetscapePlugIn/Tests/NullNPPGetValuePointer.cpp', + 'TestNetscapePlugIn/Tests/PassDifferentNPPStruct.cpp', 'TestNetscapePlugIn/Tests/PluginScriptableNPObjectInvokeDefault.cpp', 'TestNetscapePlugIn/main.cpp', ], diff --git a/Tools/DumpRenderTree/LayoutTestController.cpp b/Tools/DumpRenderTree/LayoutTestController.cpp index 1133a88..29eeeb0 100644 --- a/Tools/DumpRenderTree/LayoutTestController.cpp +++ b/Tools/DumpRenderTree/LayoutTestController.cpp @@ -445,6 +445,17 @@ static JSValueRef displayCallback(JSContextRef context, JSObjectRef function, JS return JSValueMakeUndefined(context); } +static JSValueRef displayInvalidatedRegionCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + // Has mac & windows implementation + LayoutTestController* controller = static_cast(JSObjectGetPrivate(thisObject)); + // LayoutTestController::display() only renders the invalidated region so + // we can just use that. + controller->display(); + + return JSValueMakeUndefined(context); +} + static JSValueRef encodeHostNameCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { // Has mac implementation @@ -1980,6 +1991,7 @@ JSStaticFunction* LayoutTestController::staticFunctions() { "disableImageLoading", disableImageLoadingCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "dispatchPendingLoadRequests", dispatchPendingLoadRequestsCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "display", displayCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, + { "displayInvalidatedRegion", displayInvalidatedRegionCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "dumpApplicationCacheDelegateCallbacks", dumpApplicationCacheDelegateCallbacksCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "dumpAsText", dumpAsTextCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "dumpBackForwardList", dumpBackForwardListCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, diff --git a/Tools/DumpRenderTree/LayoutTestController.h b/Tools/DumpRenderTree/LayoutTestController.h index b80d805..11558dd 100644 --- a/Tools/DumpRenderTree/LayoutTestController.h +++ b/Tools/DumpRenderTree/LayoutTestController.h @@ -59,6 +59,7 @@ public: void disableImageLoading(); void dispatchPendingLoadRequests(); void display(); + void displayInvalidatedRegion(); void execCommand(JSStringRef name, JSStringRef value); bool findString(JSContextRef, JSStringRef, JSObjectRef optionsArray); bool isCommandEnabled(JSStringRef name); diff --git a/Tools/DumpRenderTree/TestNetscapePlugIn/PluginObject.cpp b/Tools/DumpRenderTree/TestNetscapePlugIn/PluginObject.cpp index 45e5ddb..24ee12c 100644 --- a/Tools/DumpRenderTree/TestNetscapePlugIn/PluginObject.cpp +++ b/Tools/DumpRenderTree/TestNetscapePlugIn/PluginObject.cpp @@ -199,6 +199,7 @@ enum { ID_SET_STATUS, ID_RESIZE_TO, ID_NORMALIZE, + ID_INVALIDATE_RECT, NUM_METHOD_IDENTIFIERS }; @@ -239,7 +240,8 @@ static const NPUTF8 *pluginMethodIdentifierNames[NUM_METHOD_IDENTIFIERS] = { "refCount", "setStatus", "resizeTo", - "normalize" + "normalize", + "invalidateRect" }; static NPUTF8* createCStringFromNPVariant(const NPVariant* variant) @@ -986,6 +988,20 @@ static bool normalizeOverride(PluginObject* obj, const NPVariant* args, uint32_t return true; } +static bool invalidateRect(PluginObject* obj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 4) + return false; + + NPRect rect; + rect.left = static_cast(NPVARIANT_TO_DOUBLE(args[0])); + rect.top = static_cast(NPVARIANT_TO_DOUBLE(args[1])); + rect.right = static_cast(NPVARIANT_TO_DOUBLE(args[2])); + rect.bottom = static_cast(NPVARIANT_TO_DOUBLE(args[3])); + + browser->invalidaterect(obj->npp, &rect); + return true; +} static bool pluginInvoke(NPObject* header, NPIdentifier name, const NPVariant* args, uint32_t argCount, NPVariant* result) { @@ -1101,6 +1117,8 @@ static bool pluginInvoke(NPObject* header, NPIdentifier name, const NPVariant* a return testResizeTo(plugin, args, argCount, result); if (name == pluginMethodIdentifiers[ID_NORMALIZE]) return normalizeOverride(plugin, args, argCount, result); + if (name == pluginMethodIdentifiers[ID_INVALIDATE_RECT]) + return invalidateRect(plugin, args, argCount, result); return false; } diff --git a/Tools/DumpRenderTree/TestNetscapePlugIn/PluginTest.cpp b/Tools/DumpRenderTree/TestNetscapePlugIn/PluginTest.cpp index 703d9d5..0dd9da6 100644 --- a/Tools/DumpRenderTree/TestNetscapePlugIn/PluginTest.cpp +++ b/Tools/DumpRenderTree/TestNetscapePlugIn/PluginTest.cpp @@ -33,11 +33,14 @@ extern NPNetscapeFuncs *browser; PluginTest* PluginTest::create(NPP npp, const string& identifier) { + if (identifier.empty()) + return new PluginTest(npp, identifier); + CreateTestFunction createTestFunction = createTestFunctions()[identifier]; if (createTestFunction) return createTestFunction(npp, identifier); - return new PluginTest(npp, identifier); + return 0; } PluginTest::PluginTest(NPP npp, const string& identifier) diff --git a/Tools/DumpRenderTree/TestNetscapePlugIn/main.cpp b/Tools/DumpRenderTree/TestNetscapePlugIn/main.cpp index b4b7fa3..7635a09 100644 --- a/Tools/DumpRenderTree/TestNetscapePlugIn/main.cpp +++ b/Tools/DumpRenderTree/TestNetscapePlugIn/main.cpp @@ -200,6 +200,9 @@ NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc else if (strcasecmp(argn[i], "src") == 0 && strcasecmp(argv[i], "data:application/x-webkit-test-netscape,returnerrorfromnewstream") == 0) obj->returnErrorFromNewStream = TRUE; + else if (strcasecmp(argn[i], "src") == 0 && + strcasecmp(argv[i], "data:application/x-webkit-test-netscape,alertwhenloaded") == 0) + executeScript(obj, "alert('Plugin Loaded!')"); else if (strcasecmp(argn[i], "onSetWindow") == 0 && !obj->onSetWindow) obj->onSetWindow = strdup(argv[i]); else if (strcasecmp(argn[i], "onNew") == 0 && !onNewScript) @@ -256,6 +259,15 @@ NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc // When testing evaluate script on mouse-down or key-down, allow event logging to handle events. if (obj->evaluateScriptOnMouseDownOrKeyDown) obj->eventLogging = true; + } else if (!strcasecmp(argn[i], "windowedPlugin")) { + void* windowed = 0; + if (!strcasecmp(argv[i], "false") || !strcasecmp(argv[i], "0")) + windowed = 0; + else if (!strcasecmp(argv[i], "true") || !strcasecmp(argv[i], "1")) + windowed = reinterpret_cast(1); + else + assert(false); + browser->setvalue(instance, NPPVpluginWindowBool, windowed); } } @@ -271,6 +283,11 @@ NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc obj->pluginTest = PluginTest::create(instance, testIdentifier); + if (!obj->pluginTest) { + pluginLog(instance, "NPP_New: Could not find a test named \"%s\", maybe its .cpp file wasn't added to the build system?", testIdentifier.c_str()); + return NPERR_GENERIC_ERROR; + } + #ifdef XP_UNIX // On Unix, plugins only get events if they are windowless. browser->setvalue(instance, NPPVpluginWindowBool, 0); @@ -663,6 +680,19 @@ static int16_t handleEventX11(NPP instance, PluginObject* obj, XEvent* event) } #endif // XP_UNIX +#ifdef XP_WIN +static int16_t handleEventWin(NPP instance, PluginObject* obj, NPEvent* event) +{ + switch (event->event) { + case WM_PAINT: + if (obj->onPaintEvent) + executeScript(obj, obj->onPaintEvent); + return 1; + } + return 0; +} +#endif // XP_WIN + int16_t NPP_HandleEvent(NPP instance, void *event) { PluginObject* obj = static_cast(instance->pdata); @@ -677,6 +707,8 @@ int16_t NPP_HandleEvent(NPP instance, void *event) return handleEventCocoa(instance, obj, static_cast(event)); #elif defined(XP_UNIX) return handleEventX11(instance, obj, static_cast(event)); +#elif defined(XP_WIN) + return handleEventWin(instance, obj, static_cast(event)); #else // FIXME: Implement for other platforms. return 0; diff --git a/Tools/DumpRenderTree/chromium/DumpRenderTree.cpp b/Tools/DumpRenderTree/chromium/DumpRenderTree.cpp index bd5075c..7e9010f 100644 --- a/Tools/DumpRenderTree/chromium/DumpRenderTree.cpp +++ b/Tools/DumpRenderTree/chromium/DumpRenderTree.cpp @@ -58,6 +58,7 @@ static const char optionEnableAccelerated2DCanvas[] = "--enable-accelerated-2d-c static const char optionStressOpt[] = "--stress-opt"; static const char optionStressDeopt[] = "--stress-deopt"; static const char optionJavaScriptFlags[] = "--js-flags="; +static const char optionNoTimeout[] = "--no-timeout="; static void runTest(TestShell& shell, TestParams& params, const string& testName, bool testShellMode) { @@ -123,6 +124,7 @@ int main(int argc, char* argv[]) bool stressDeopt = false; bool hardwareAcceleratedGL = false; string javaScriptFlags; + bool noTimeout = false; for (int i = 1; i < argc; ++i) { string argument(argv[i]); if (argument == "-") @@ -155,6 +157,8 @@ int main(int argc, char* argv[]) stressDeopt = true; else if (!argument.find(optionJavaScriptFlags)) javaScriptFlags = argument.substr(strlen(optionJavaScriptFlags)); + else if (!argument.find(optionNoTimeout)) + noTimeout = true; else if (argument.size() && argument[0] == '-') fprintf(stderr, "Unknown option: %s\n", argv[i]); else @@ -182,6 +186,10 @@ int main(int argc, char* argv[]) shell.setJavaScriptFlags(javaScriptFlags); shell.setStressOpt(stressOpt); shell.setStressDeopt(stressDeopt); + if (noTimeout) { + // 0x20000000ms is big enough for the purpose to avoid timeout in debugging. + shell.setLayoutTestTimeout(0x20000000); + } if (serverMode && !tests.size()) { params.printSeparators = true; char testString[2048]; // 2048 is the same as the sizes of other platforms. diff --git a/Tools/DumpRenderTree/chromium/ImageDiff.cpp b/Tools/DumpRenderTree/chromium/ImageDiff.cpp index f2875dd..d676430 100644 --- a/Tools/DumpRenderTree/chromium/ImageDiff.cpp +++ b/Tools/DumpRenderTree/chromium/ImageDiff.cpp @@ -88,7 +88,7 @@ public: if (!byteLength) return false; - OwnArrayPtr source(new unsigned char[byteLength]); + OwnArrayPtr source = adoptArrayPtr(new unsigned char[byteLength]); if (fread(source.get(), 1, byteLength, stdin) != byteLength) return false; diff --git a/Tools/DumpRenderTree/chromium/LayoutTestController.cpp b/Tools/DumpRenderTree/chromium/LayoutTestController.cpp index 529019b..59537c6 100644 --- a/Tools/DumpRenderTree/chromium/LayoutTestController.cpp +++ b/Tools/DumpRenderTree/chromium/LayoutTestController.cpp @@ -89,6 +89,7 @@ LayoutTestController::LayoutTestController(TestShell* shell) bindMethod("counterValueForElementById", &LayoutTestController::counterValueForElementById); bindMethod("disableImageLoading", &LayoutTestController::disableImageLoading); bindMethod("display", &LayoutTestController::display); + bindMethod("displayInvalidatedRegion", &LayoutTestController::displayInvalidatedRegion); bindMethod("dumpAsText", &LayoutTestController::dumpAsText); bindMethod("dumpBackForwardList", &LayoutTestController::dumpBackForwardList); bindMethod("dumpChildFramesAsText", &LayoutTestController::dumpChildFramesAsText); @@ -106,7 +107,6 @@ LayoutTestController::LayoutTestController(TestShell* shell) bindMethod("evaluateInWebInspector", &LayoutTestController::evaluateInWebInspector); bindMethod("evaluateScriptInIsolatedWorld", &LayoutTestController::evaluateScriptInIsolatedWorld); bindMethod("execCommand", &LayoutTestController::execCommand); - bindMethod("forceRedSelectionColors", &LayoutTestController::forceRedSelectionColors); bindMethod("grantDesktopNotificationPermission", &LayoutTestController::grantDesktopNotificationPermission); bindMethod("isCommandEnabled", &LayoutTestController::isCommandEnabled); bindMethod("layerTreeAsText", &LayoutTestController::layerTreeAsText); @@ -628,9 +628,11 @@ void LayoutTestController::setAlwaysAcceptCookies(const CppArgumentList& argumen result->setNull(); } -void LayoutTestController::setAsynchronousSpellCheckingEnabled(const CppArgumentList&, CppVariant*) +void LayoutTestController::setAsynchronousSpellCheckingEnabled(const CppArgumentList& arguments, CppVariant* result) { - // FIXME: Implement this. + if (arguments.size() > 0 && arguments[0].isBool()) + m_shell->webView()->settings()->setAsynchronousSpellCheckingEnabled(cppVariantToBool(arguments[0])); + result->setNull(); } void LayoutTestController::showWebInspector(const CppArgumentList&, CppVariant* result) @@ -800,11 +802,11 @@ void LayoutTestController::pathToLocalResource(const CppArgumentList& arguments, // We want a temp file. const unsigned tempPrefixLength = 5; size_t bufferSize = MAX_PATH; - OwnArrayPtr tempPath(new WCHAR[bufferSize]); + OwnArrayPtr tempPath = adoptArrayPtr(new WCHAR[bufferSize]); DWORD tempLength = ::GetTempPathW(bufferSize, tempPath.get()); if (tempLength + url.length() - tempPrefixLength + 1 > bufferSize) { bufferSize = tempLength + url.length() - tempPrefixLength + 1; - tempPath.set(new WCHAR[bufferSize]); + tempPath = adoptArrayPtr(new WCHAR[bufferSize]); tempLength = GetTempPathW(bufferSize, tempPath.get()); ASSERT(tempLength < bufferSize); } @@ -1083,6 +1085,14 @@ void LayoutTestController::display(const CppArgumentList& arguments, CppVariant* result->setNull(); } +void LayoutTestController::displayInvalidatedRegion(const CppArgumentList& arguments, CppVariant* result) +{ + WebViewHost* host = m_shell->webViewHost(); + host->paintInvalidatedRegion(); + host->displayRepaintMask(); + result->setNull(); +} + void LayoutTestController::testRepaint(const CppArgumentList&, CppVariant* result) { m_testRepaint = true; @@ -1464,12 +1474,6 @@ void LayoutTestController::evaluateInWebInspector(const CppArgumentList& argumen m_shell->drtDevToolsAgent()->evaluateInWebInspector(arguments[0].toInt32(), arguments[1].toString()); } -void LayoutTestController::forceRedSelectionColors(const CppArgumentList& arguments, CppVariant* result) -{ - result->setNull(); - m_shell->webView()->setSelectionColors(0xffee0000, 0xff00ee00, 0xff000000, 0xffc0c0c0); -} - void LayoutTestController::addUserScript(const CppArgumentList& arguments, CppVariant* result) { result->setNull(); diff --git a/Tools/DumpRenderTree/chromium/LayoutTestController.h b/Tools/DumpRenderTree/chromium/LayoutTestController.h index 13d1447..0ef8607 100644 --- a/Tools/DumpRenderTree/chromium/LayoutTestController.h +++ b/Tools/DumpRenderTree/chromium/LayoutTestController.h @@ -250,6 +250,7 @@ public: void dumpTitleChanges(const CppArgumentList&, CppVariant*); void setMainFrameIsFirstResponder(const CppArgumentList&, CppVariant*); void display(const CppArgumentList&, CppVariant*); + void displayInvalidatedRegion(const CppArgumentList&, CppVariant*); void testRepaint(const CppArgumentList&, CppVariant*); void repaintSweepHorizontally(const CppArgumentList&, CppVariant*); void clearBackForwardList(const CppArgumentList&, CppVariant*); @@ -311,9 +312,6 @@ public: // Allows layout tests to exec scripts at WebInspector side. void evaluateInWebInspector(const CppArgumentList&, CppVariant*); - // Forces the selection colors for testing under Linux. - void forceRedSelectionColors(const CppArgumentList&, CppVariant*); - // Adds a user script or user style sheet to be injected into new documents. void addUserScript(const CppArgumentList&, CppVariant*); void addUserStyleSheet(const CppArgumentList&, CppVariant*); diff --git a/Tools/DumpRenderTree/chromium/NotificationPresenter.cpp b/Tools/DumpRenderTree/chromium/NotificationPresenter.cpp index 7e7053b..63a70e4 100644 --- a/Tools/DumpRenderTree/chromium/NotificationPresenter.cpp +++ b/Tools/DumpRenderTree/chromium/NotificationPresenter.cpp @@ -95,7 +95,7 @@ bool NotificationPresenter::show(const WebNotification& notification) notification.url().spec().data()); } else { printf("DESKTOP NOTIFICATION:%s icon %s, title %s, text %s\n", - notification.dir() == "rtl" ? "(RTL)" : "", + notification.direction() == WebTextDirectionRightToLeft ? "(RTL)" : "", notification.iconURL().isEmpty() ? "" : notification.iconURL().spec().data(), notification.title().isEmpty() ? "" : diff --git a/Tools/DumpRenderTree/chromium/TestShell.cpp b/Tools/DumpRenderTree/chromium/TestShell.cpp index ec6a502..11a598f 100644 --- a/Tools/DumpRenderTree/chromium/TestShell.cpp +++ b/Tools/DumpRenderTree/chromium/TestShell.cpp @@ -93,6 +93,7 @@ TestShell::TestShell(bool testShellMode) WebRuntimeFeatures::enableGeolocation(true); WebRuntimeFeatures::enableIndexedDatabase(true); WebRuntimeFeatures::enableFileSystem(true); + WebRuntimeFeatures::enableJavaScriptI18NAPI(true); m_accessibilityController.set(new AccessibilityController(this)); m_layoutTestController.set(new LayoutTestController(this)); m_eventSender.set(new EventSender(this)); diff --git a/Tools/DumpRenderTree/chromium/WebViewHost.cpp b/Tools/DumpRenderTree/chromium/WebViewHost.cpp index 18b107f..e1a2fcb 100644 --- a/Tools/DumpRenderTree/chromium/WebViewHost.cpp +++ b/Tools/DumpRenderTree/chromium/WebViewHost.cpp @@ -52,6 +52,8 @@ #include "WebSize.h" #include "WebSpeechInputControllerMock.h" #include "WebStorageNamespace.h" +#include "WebTextCheckingCompletion.h" +#include "WebTextCheckingResult.h" #include "WebURLRequest.h" #include "WebURLResponse.h" #include "WebView.h" @@ -60,6 +62,7 @@ #include "webkit/support/webkit_support.h" #include #include +#include using namespace WebCore; using namespace WebKit; @@ -225,6 +228,12 @@ static string textAffinityDescription(WebTextAffinity affinity) return "(UNKNOWN AFFINITY)"; } +static void invokeFinishLastTextCheck(void* context) +{ + WebViewHost* wvh = static_cast(context); + wvh->finishLastTextCheck(); +} + // WebViewClient ------------------------------------------------------------- WebView* WebViewHost::createView(WebFrame*, const WebURLRequest&, const WebWindowFeatures&, const WebString&) @@ -409,6 +418,35 @@ void WebViewHost::spellCheck(const WebString& text, int& misspelledOffset, int& m_spellcheck.spellCheckWord(text, &misspelledOffset, &misspelledLength); } +void WebViewHost::requestCheckingOfText(const WebString& text, WebTextCheckingCompletion* completion) +{ + m_lastRequestedTextCheckingCompletion = completion; + m_lastRequestedTextCheckString = text; + webkit_support::PostDelayedTask(invokeFinishLastTextCheck, static_cast(this), 0); +} + +void WebViewHost::finishLastTextCheck() +{ + Vector results; + // FIXME: Do the grammar check. + int offset = 0; + String text(m_lastRequestedTextCheckString.data(), m_lastRequestedTextCheckString.length()); + while (text.length()) { + int misspelledPosition = 0; + int misspelledLength = 0; + m_spellcheck.spellCheckWord(WebString(text.characters(), text.length()), &misspelledPosition, &misspelledLength); + if (!misspelledLength) + break; + results.append(WebTextCheckingResult(WebTextCheckingResult::ErrorSpelling, offset + misspelledPosition, misspelledLength)); + text = text.substring(misspelledPosition + misspelledLength); + offset += misspelledPosition; + } + + m_lastRequestedTextCheckingCompletion->didFinishCheckingText(results); + m_lastRequestedTextCheckingCompletion = 0; +} + + WebString WebViewHost::autoCorrectWord(const WebString&) { // Returns an empty string as Mac WebKit ('WebKitSupport/WebEditorClient.mm') @@ -1094,7 +1132,7 @@ void WebViewHost::didDisplayInsecureContent(WebFrame*) fputs("didDisplayInsecureContent\n", stdout); } -void WebViewHost::didRunInsecureContent(WebFrame*, const WebSecurityOrigin& origin) +void WebViewHost::didRunInsecureContent(WebFrame*, const WebSecurityOrigin& origin, const WebURL& insecureURL) { if (m_shell->shouldDumpFrameLoadCallbacks()) fputs("didRunInsecureContent\n", stdout); @@ -1115,6 +1153,7 @@ void WebViewHost::openFileSystem(WebFrame* frame, WebFileSystem::Type type, long WebViewHost::WebViewHost(TestShell* shell) : m_shell(shell) , m_webWidget(0) + , m_lastRequestedTextCheckingCompletion(0) { reset(); } diff --git a/Tools/DumpRenderTree/chromium/WebViewHost.h b/Tools/DumpRenderTree/chromium/WebViewHost.h index 83d21dc..e6d82ae 100644 --- a/Tools/DumpRenderTree/chromium/WebViewHost.h +++ b/Tools/DumpRenderTree/chromium/WebViewHost.h @@ -126,6 +126,7 @@ class WebViewHost : public WebKit::WebViewClient, public WebKit::WebFrameClient, virtual void didEndEditing(); virtual bool handleCurrentKeyboardEvent(); virtual void spellCheck(const WebKit::WebString&, int& offset, int& length); + virtual void requestCheckingOfText(const WebKit::WebString&, WebKit::WebTextCheckingCompletion*); virtual WebKit::WebString autoCorrectWord(const WebKit::WebString&); virtual void runModalAlertDialog(WebKit::WebFrame*, const WebKit::WebString&); virtual bool runModalConfirmDialog(WebKit::WebFrame*, const WebKit::WebString&); @@ -202,12 +203,15 @@ class WebViewHost : public WebKit::WebViewClient, public WebKit::WebFrameClient, virtual void didFinishResourceLoad(WebKit::WebFrame*, unsigned identifier); virtual void didFailResourceLoad(WebKit::WebFrame*, unsigned identifier, const WebKit::WebURLError&); virtual void didDisplayInsecureContent(WebKit::WebFrame*); - virtual void didRunInsecureContent(WebKit::WebFrame*, const WebKit::WebSecurityOrigin&); + virtual void didRunInsecureContent(WebKit::WebFrame*, const WebKit::WebSecurityOrigin&, const WebKit::WebURL&); virtual bool allowScript(WebKit::WebFrame*, bool enabledPerSettings); virtual void openFileSystem(WebKit::WebFrame*, WebKit::WebFileSystem::Type, long long size, bool create, WebKit::WebFileSystemCallbacks*); WebKit::WebDeviceOrientationClientMock* deviceOrientationClientMock(); + + // Spellcheck related helper APIs MockSpellCheck* mockSpellCheck(); + void finishLastTextCheck(); // Geolocation client mocks for LayoutTestController WebKit::WebGeolocationClientMock* geolocationClientMock(); @@ -320,6 +324,9 @@ private: OwnPtr m_speechInputControllerMock; OwnPtr m_navigationController; + + WebKit::WebString m_lastRequestedTextCheckString; + WebKit::WebTextCheckingCompletion* m_lastRequestedTextCheckingCompletion; }; #endif // WebViewHost_h diff --git a/Tools/DumpRenderTree/gtk/AccessibilityCallbacks.cpp b/Tools/DumpRenderTree/gtk/AccessibilityCallbacks.cpp new file mode 100644 index 0000000..be66513 --- /dev/null +++ b/Tools/DumpRenderTree/gtk/AccessibilityCallbacks.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2011 Igalia S.L. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityCallbacks.h" + +#include "AccessibilityController.h" +#include "DumpRenderTree.h" +#include "GOwnPtr.h" +#include "WebCoreSupport/DumpRenderTreeSupportGtk.h" +#include +#include + +static guint stateChangeListenerId = 0; +static guint focusEventListenerId = 0; +static guint activeDescendantChangedListenerId = 0; +static guint childrenChangedListenerId = 0; +static guint propertyChangedListenerId = 0; +static guint visibleDataChangedListenerId = 0; + +static guint loadCompleteListenerId = 0; +static guint loadStoppedListenerId = 0; +static guint reloadListenerId = 0; + +static void printAccessibilityEvent(AtkObject* accessible, const gchar* signalName) +{ + // Sanity check. + if (!accessible || !ATK_IS_OBJECT(accessible) || !signalName) + return; + + const gchar* objectName = atk_object_get_name(accessible); + guint objectRole = atk_object_get_role(accessible); + + // Try to always provide a name to be logged for the object. + if (!objectName || *objectName == '\0') + objectName = "(No name)"; + + printf("Accessibility object emitted \"%s\" / Name: \"%s\" / Role: %d\n", + signalName, objectName, objectRole); +} + +static gboolean axObjectEventListener(GSignalInvocationHint *signalHint, + guint numParamValues, + const GValue *paramValues, + gpointer data) +{ + // At least we should receive the instance emitting the signal. + if (numParamValues < 1) + return TRUE; + + AtkObject* accessible = ATK_OBJECT(g_value_get_object(¶mValues[0])); + if (!accessible || !ATK_IS_OBJECT(accessible)) + return TRUE; + + GSignalQuery signal_query; + GOwnPtr signalName; + + g_signal_query(signalHint->signal_id, &signal_query); + + if (!g_strcmp0(signal_query.signal_name, "state-change")) { + signalName.set(g_strdup_printf("state-change:%s = %d", + g_value_get_string(¶mValues[1]), + g_value_get_boolean(¶mValues[2]))); + } else if (!g_strcmp0(signal_query.signal_name, "focus-event")) { + signalName.set(g_strdup_printf("focus-event = %d", + g_value_get_boolean(¶mValues[1]))); + } else if (!g_strcmp0(signal_query.signal_name, "children-changed")) { + signalName.set(g_strdup_printf("children-changed = %d", + g_value_get_uint(¶mValues[1]))); + } else + signalName.set(g_strdup(signal_query.signal_name)); + + printAccessibilityEvent(accessible, signalName.get()); + + return TRUE; +} + +static gboolean axDocumentEventListener(GSignalInvocationHint *signalHint, + guint numParamValues, + const GValue *paramValues, + gpointer data) +{ + // At least we should receive the instance emitting the signal. + if (numParamValues < 1) + return TRUE; + + AtkObject* accessible = ATK_OBJECT(g_value_get_object(¶mValues[0])); + if (!accessible || !ATK_IS_DOCUMENT(accessible)) + return TRUE; + + GSignalQuery signal_query; + g_signal_query(signalHint->signal_id, &signal_query); + + printAccessibilityEvent(accessible, signal_query.signal_name); + + return TRUE; +} + +void connectAccessibilityCallbacks() +{ + // Ensure no callbacks are connected before. + disconnectAccessibilityCallbacks(); + + // Ensure that accessibility is initialized for the WebView by querying for + // the root accessible object, which will create the full hierarchy. + DumpRenderTreeSupportGtk::getRootAccessibleElement(mainFrame); + + // Add global listeners for AtkObject's signals. + stateChangeListenerId = atk_add_global_event_listener(axObjectEventListener, "Gtk:AtkObject:state-change"); + focusEventListenerId = atk_add_global_event_listener(axObjectEventListener, "Gtk:AtkObject:focus-event"); + activeDescendantChangedListenerId = atk_add_global_event_listener(axObjectEventListener, "Gtk:AtkObject:active-descendant-changed"); + childrenChangedListenerId = atk_add_global_event_listener(axObjectEventListener, "Gtk:AtkObject:children-changed"); + propertyChangedListenerId = atk_add_global_event_listener(axObjectEventListener, "Gtk:AtkObject:property-change"); + visibleDataChangedListenerId = atk_add_global_event_listener(axObjectEventListener, "Gtk:AtkObject:visible-data-changed"); + + // Ensure the Atk interface types are registered, otherwise + // the AtkDocument signal handlers below won't get registered. + GObject* dummyAxObject = G_OBJECT(g_object_new(ATK_TYPE_OBJECT, 0)); + AtkObject* dummyNoOpAxObject = atk_no_op_object_new(dummyAxObject); + g_object_unref(G_OBJECT(dummyNoOpAxObject)); + g_object_unref(dummyAxObject); + + // Add global listeners for AtkDocument's signals. + loadCompleteListenerId = atk_add_global_event_listener(axDocumentEventListener, "Gtk:AtkDocument:load-complete"); + loadStoppedListenerId = atk_add_global_event_listener(axDocumentEventListener, "Gtk:AtkDocument:load-stopped"); + reloadListenerId = atk_add_global_event_listener(axDocumentEventListener, "Gtk:AtkDocument:reload"); +} + +void disconnectAccessibilityCallbacks() +{ + // AtkObject signals. + if (stateChangeListenerId) { + atk_remove_global_event_listener(stateChangeListenerId); + stateChangeListenerId = 0; + } + if (focusEventListenerId) { + atk_remove_global_event_listener(focusEventListenerId); + focusEventListenerId = 0; + } + if (activeDescendantChangedListenerId) { + atk_remove_global_event_listener(activeDescendantChangedListenerId); + activeDescendantChangedListenerId = 0; + } + if (childrenChangedListenerId) { + atk_remove_global_event_listener(childrenChangedListenerId); + childrenChangedListenerId = 0; + } + if (propertyChangedListenerId) { + atk_remove_global_event_listener(propertyChangedListenerId); + propertyChangedListenerId = 0; + } + if (visibleDataChangedListenerId) { + atk_remove_global_event_listener(visibleDataChangedListenerId); + visibleDataChangedListenerId = 0; + } + + // AtkDocument signals. + if (loadCompleteListenerId) { + atk_remove_global_event_listener(loadCompleteListenerId); + loadCompleteListenerId = 0; + } + if (loadStoppedListenerId) { + atk_remove_global_event_listener(loadStoppedListenerId); + loadStoppedListenerId = 0; + } + if (reloadListenerId) { + atk_remove_global_event_listener(reloadListenerId); + reloadListenerId = 0; + } +} + diff --git a/Tools/DumpRenderTree/gtk/AccessibilityCallbacks.h b/Tools/DumpRenderTree/gtk/AccessibilityCallbacks.h new file mode 100644 index 0000000..7225757 --- /dev/null +++ b/Tools/DumpRenderTree/gtk/AccessibilityCallbacks.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2011 Igalia S.L. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityCallbacks_h +#define AccessibilityCallbacks_h + +void connectAccessibilityCallbacks(); +void disconnectAccessibilityCallbacks(); + +#endif diff --git a/Tools/DumpRenderTree/gtk/AccessibilityControllerGtk.cpp b/Tools/DumpRenderTree/gtk/AccessibilityControllerGtk.cpp index c572633..da70efc 100644 --- a/Tools/DumpRenderTree/gtk/AccessibilityControllerGtk.cpp +++ b/Tools/DumpRenderTree/gtk/AccessibilityControllerGtk.cpp @@ -27,6 +27,7 @@ #include "config.h" #include "AccessibilityController.h" +#include "AccessibilityCallbacks.h" #include "AccessibilityUIElement.h" #include "DumpRenderTree.h" #include "WebCoreSupport/DumpRenderTreeSupportGtk.h" @@ -35,6 +36,8 @@ #include #include +static bool loggingAccessibilityEvents = false; + AccessibilityController::AccessibilityController() { } @@ -79,6 +82,21 @@ void AccessibilityController::setLogValueChangeEvents(bool) { } +void AccessibilityController::setLogAccessibilityEvents(bool logAccessibilityEvents) +{ + if (logAccessibilityEvents == loggingAccessibilityEvents) + return; + + if (!logAccessibilityEvents) { + disconnectAccessibilityCallbacks(); + loggingAccessibilityEvents = false; + return; + } + + connectAccessibilityCallbacks(); + loggingAccessibilityEvents = true; +} + void AccessibilityController::addNotificationListener(PlatformUIElement, JSObjectRef) { } diff --git a/Tools/DumpRenderTree/gtk/DumpRenderTree.cpp b/Tools/DumpRenderTree/gtk/DumpRenderTree.cpp index ff90d81..ff3327f 100644 --- a/Tools/DumpRenderTree/gtk/DumpRenderTree.cpp +++ b/Tools/DumpRenderTree/gtk/DumpRenderTree.cpp @@ -39,6 +39,7 @@ #include "GOwnPtr.h" #include "LayoutTestController.h" #include "PixelDumpSupport.h" +#include "TextInputController.h" #include "WebCoreSupport/DumpRenderTreeSupportGtk.h" #include "WorkQueue.h" #include "WorkQueueItem.h" @@ -419,6 +420,7 @@ static void resetDefaultsToConsistentValues() "enable-fullscreen", TRUE, NULL); webkit_web_view_set_settings(webView, settings); + webkit_set_cache_model(WEBKIT_CACHE_MODEL_DOCUMENT_BROWSER); DumpRenderTreeSupportGtk::clearMainFrameName(mainFrame); @@ -432,7 +434,6 @@ static void resetDefaultsToConsistentValues() WebKitWebBackForwardList* list = webkit_web_view_get_back_forward_list(webView); webkit_web_back_forward_list_clear(list); -#ifdef HAVE_LIBSOUP_2_29_90 SoupSession* session = webkit_get_default_session(); SoupCookieJar* jar = reinterpret_cast(soup_session_get_feature(session, SOUP_TYPE_COOKIE_JAR)); @@ -440,11 +441,14 @@ static void resetDefaultsToConsistentValues() // HTTP. Should we initialize it earlier, perhaps? if (jar) g_object_set(G_OBJECT(jar), SOUP_COOKIE_JAR_ACCEPT_POLICY, SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY, NULL); -#endif setlocale(LC_ALL, ""); DumpRenderTreeSupportGtk::setLinksIncludedInFocusChain(true); + DumpRenderTreeSupportGtk::setIconDatabaseEnabled(false); + + if (axController) + axController->resetToConsistentState(); } static bool useLongRunningServerMode(int argc, char *argv[]) @@ -738,6 +742,11 @@ static void webViewLoadFinished(WebKitWebView* view, WebKitWebFrame* frame, void dump(); } +static gboolean webViewLoadError(WebKitWebView*, WebKitWebFrame*, gchar*, gpointer, gpointer) +{ + return TRUE; // Return true here to disable the default error page. +} + static void webViewDocumentLoadFinished(WebKitWebView* view, WebKitWebFrame* frame, void*) { if (!done && gLayoutTestController->dumpFrameLoadCallbacks()) { @@ -781,6 +790,11 @@ static void webViewWindowObjectCleared(WebKitWebView* view, WebKitWebFrame* fram JSValueRef eventSender = makeEventSender(context, !webkit_web_frame_get_parent(frame)); JSObjectSetProperty(context, windowObject, eventSenderStr, eventSender, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete, 0); JSStringRelease(eventSenderStr); + + JSStringRef textInputControllerStr = JSStringCreateWithUTF8CString("textInputController"); + JSValueRef textInputController = makeTextInputController(context); + JSObjectSetProperty(context, windowObject, textInputControllerStr, textInputController, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete, 0); + JSStringRelease(textInputControllerStr); } static gboolean webViewConsoleMessage(WebKitWebView* view, const gchar* message, unsigned int line, const gchar* sourceId, gpointer data) @@ -999,6 +1013,7 @@ static WebKitWebView* createWebView() g_object_connect(G_OBJECT(view), "signal::load-started", webViewLoadStarted, 0, "signal::load-finished", webViewLoadFinished, 0, + "signal::load-error", webViewLoadError, 0, "signal::window-object-cleared", webViewWindowObjectCleared, 0, "signal::console-message", webViewConsoleMessage, 0, "signal::script-alert", webViewScriptAlert, 0, diff --git a/Tools/DumpRenderTree/gtk/EventSender.cpp b/Tools/DumpRenderTree/gtk/EventSender.cpp index 923a4ba..10e129c 100644 --- a/Tools/DumpRenderTree/gtk/EventSender.cpp +++ b/Tools/DumpRenderTree/gtk/EventSender.cpp @@ -240,33 +240,42 @@ static void updateClickCount(int button) clickCount++; } +static guint gdkModifierFromJSValue(JSContextRef context, const JSValueRef value) +{ + JSStringRef string = JSValueToStringCopy(context, value, 0); + guint gdkModifier = 0; + if (JSStringIsEqualToUTF8CString(string, "ctrlKey") + || JSStringIsEqualToUTF8CString(string, "addSelectionKey")) + gdkModifier = GDK_CONTROL_MASK; + else if (JSStringIsEqualToUTF8CString(string, "shiftKey") + || JSStringIsEqualToUTF8CString(string, "rangeSelectionKey")) + gdkModifier = GDK_SHIFT_MASK; + else if (JSStringIsEqualToUTF8CString(string, "altKey")) + gdkModifier = GDK_MOD1_MASK; + + // Currently the metaKey as defined in WebCore/platform/gtk/MouseEventGtk.cpp + // is GDK_MOD2_MASK. This code must be kept in sync with that file. + else if (JSStringIsEqualToUTF8CString(string, "metaKey")) + gdkModifier = GDK_MOD2_MASK; + + JSStringRelease(string); + return gdkModifier; +} + static guint gdkModifersFromJSValue(JSContextRef context, const JSValueRef modifiers) { + // The value may either be a string with a single modifier or an array of modifiers. + if (JSValueIsString(context, modifiers)) + return gdkModifierFromJSValue(context, modifiers); + JSObjectRef modifiersArray = JSValueToObject(context, modifiers, 0); if (!modifiersArray) return 0; guint gdkModifiers = 0; int modifiersCount = JSValueToNumber(context, JSObjectGetProperty(context, modifiersArray, JSStringCreateWithUTF8CString("length"), 0), 0); - for (int i = 0; i < modifiersCount; ++i) { - JSValueRef value = JSObjectGetPropertyAtIndex(context, modifiersArray, i, 0); - JSStringRef string = JSValueToStringCopy(context, value, 0); - if (JSStringIsEqualToUTF8CString(string, "ctrlKey") - || JSStringIsEqualToUTF8CString(string, "addSelectionKey")) - gdkModifiers |= GDK_CONTROL_MASK; - else if (JSStringIsEqualToUTF8CString(string, "shiftKey") - || JSStringIsEqualToUTF8CString(string, "rangeSelectionKey")) - gdkModifiers |= GDK_SHIFT_MASK; - else if (JSStringIsEqualToUTF8CString(string, "altKey")) - gdkModifiers |= GDK_MOD1_MASK; - - // Currently the metaKey as defined in WebCore/platform/gtk/MouseEventGtk.cpp - // is GDK_MOD2_MASK. This code must be kept in sync with that file. - else if (JSStringIsEqualToUTF8CString(string, "metaKey")) - gdkModifiers |= GDK_MOD2_MASK; - - JSStringRelease(string); - } + for (int i = 0; i < modifiersCount; ++i) + gdkModifiers |= gdkModifierFromJSValue(context, JSObjectGetPropertyAtIndex(context, modifiersArray, i, 0)); return gdkModifiers; } diff --git a/Tools/DumpRenderTree/gtk/LayoutTestControllerGtk.cpp b/Tools/DumpRenderTree/gtk/LayoutTestControllerGtk.cpp index 1527811..56d75f7 100644 --- a/Tools/DumpRenderTree/gtk/LayoutTestControllerGtk.cpp +++ b/Tools/DumpRenderTree/gtk/LayoutTestControllerGtk.cpp @@ -3,7 +3,7 @@ * Copyright (C) 2007 Eric Seidel * Copyright (C) 2008 Nuanti Ltd. * Copyright (C) 2009 Jan Michael Alonzo - * Copyright (C) 2009 Collabora Ltd. + * Copyright (C) 2009,2011 Collabora Ltd. * Copyright (C) 2010 Joone Hur * * Redistribution and use in source and binary forms, with or without @@ -230,7 +230,6 @@ void LayoutTestController::setAcceptsEditing(bool acceptsEditing) void LayoutTestController::setAlwaysAcceptCookies(bool alwaysAcceptCookies) { -#ifdef HAVE_LIBSOUP_2_29_90 SoupSession* session = webkit_get_default_session(); SoupCookieJar* jar = reinterpret_cast(soup_session_get_feature(session, SOUP_TYPE_COOKIE_JAR)); @@ -251,7 +250,6 @@ void LayoutTestController::setAlwaysAcceptCookies(bool alwaysAcceptCookies) policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY; g_object_set(G_OBJECT(jar), SOUP_COOKIE_JAR_ACCEPT_POLICY, policy, NULL); -#endif } void LayoutTestController::setCustomPolicyDelegate(bool setDelegate, bool permissive) @@ -492,9 +490,9 @@ void LayoutTestController::addMockSpeechInputResult(JSStringRef result, double c // See https://bugs.webkit.org/show_bug.cgi?id=39485. } -void LayoutTestController::setIconDatabaseEnabled(bool flag) +void LayoutTestController::setIconDatabaseEnabled(bool enabled) { - // FIXME: implement + DumpRenderTreeSupportGtk::setIconDatabaseEnabled(enabled); } void LayoutTestController::setJavaScriptProfilingEnabled(bool flag) @@ -546,10 +544,42 @@ void LayoutTestController::execCommand(JSStringRef name, JSStringRef value) g_free(cValue); } -bool LayoutTestController::findString(JSContextRef /* context */, JSStringRef /* target */, JSObjectRef /* optionsArray */) +bool LayoutTestController::findString(JSContextRef context, JSStringRef target, JSObjectRef optionsArray) { - // FIXME: Implement - return false; + WebKitFindOptions findOptions = 0; + WebKitWebView* webView = webkit_web_frame_get_web_view(mainFrame); + ASSERT(webView); + + JSRetainPtr lengthPropertyName(Adopt, JSStringCreateWithUTF8CString("length")); + JSValueRef lengthValue = JSObjectGetProperty(context, optionsArray, lengthPropertyName.get(), 0); + if (!JSValueIsNumber(context, lengthValue)) + return false; + + GOwnPtr targetString(JSStringCopyUTF8CString(target)); + + size_t length = static_cast(JSValueToNumber(context, lengthValue, 0)); + for (size_t i = 0; i < length; ++i) { + JSValueRef value = JSObjectGetPropertyAtIndex(context, optionsArray, i, 0); + if (!JSValueIsString(context, value)) + continue; + + JSRetainPtr optionName(Adopt, JSValueToStringCopy(context, value, 0)); + + if (JSStringIsEqualToUTF8CString(optionName.get(), "CaseInsensitive")) + findOptions |= WebKit::WebFindOptionsCaseInsensitive; + else if (JSStringIsEqualToUTF8CString(optionName.get(), "AtWordStarts")) + findOptions |= WebKit::WebFindOptionsAtWordStarts; + else if (JSStringIsEqualToUTF8CString(optionName.get(), "TreatMedialCapitalAsWordStart")) + findOptions |= WebKit::WebFindOptionsTreatMedialCapitalAsWordStart; + else if (JSStringIsEqualToUTF8CString(optionName.get(), "Backwards")) + findOptions |= WebKit::WebFindOptionsBackwards; + else if (JSStringIsEqualToUTF8CString(optionName.get(), "WrapAround")) + findOptions |= WebKit::WebFindOptionsWrapAround; + else if (JSStringIsEqualToUTF8CString(optionName.get(), "StartInSelection")) + findOptions |= WebKit::WebFindOptionsStartInSelection; + } + + return DumpRenderTreeSupportGtk::findString(webView, targetString.get(), findOptions); } bool LayoutTestController::isCommandEnabled(JSStringRef name) @@ -565,10 +595,20 @@ bool LayoutTestController::isCommandEnabled(JSStringRef name) void LayoutTestController::setCacheModel(int cacheModel) { - if (!cacheModel) // WebCacheModelDocumentViewer - webkit_set_cache_model(WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER); - else - webkit_set_cache_model(WEBKIT_CACHE_MODEL_WEB_BROWSER); + // These constants are derived from the Mac cache model enum in Source/WebKit/mac/WebView/WebPreferences.h. + switch (cacheModel) { + case 0: + webkit_set_cache_model(WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER); + break; + case 1: + webkit_set_cache_model(WEBKIT_CACHE_MODEL_DOCUMENT_BROWSER); + break; + case 3: + webkit_set_cache_model(WEBKIT_CACHE_MODEL_DOCUMENT_BROWSER); + break; + default: + ASSERT_NOT_REACHED(); + } } void LayoutTestController::setPersistentUserStyleSheetLocation(JSStringRef jsURL) diff --git a/Tools/DumpRenderTree/gtk/PixelDumpSupportGtk.cpp b/Tools/DumpRenderTree/gtk/PixelDumpSupportGtk.cpp index 32bc600..1e591bb 100644 --- a/Tools/DumpRenderTree/gtk/PixelDumpSupportGtk.cpp +++ b/Tools/DumpRenderTree/gtk/PixelDumpSupportGtk.cpp @@ -37,13 +37,14 @@ PassRefPtr createBitmapContextFromWebView(bool, bool, bool, bool) { WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame); + GtkWidget* viewContainer = gtk_widget_get_parent(GTK_WIDGET(view)); gint width, height; #ifdef GTK_API_VERSION_2 - GdkPixmap* pixmap = gtk_widget_get_snapshot(GTK_WIDGET(view), 0); + GdkPixmap* pixmap = gtk_widget_get_snapshot(viewContainer, 0); gdk_pixmap_get_size(pixmap, &width, &height); #else - width = gtk_widget_get_allocated_width(GTK_WIDGET(view)); - height = gtk_widget_get_allocated_height(GTK_WIDGET(view)); + width = gtk_widget_get_allocated_width(viewContainer); + height = gtk_widget_get_allocated_height(viewContainer); #endif cairo_surface_t* imageSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); @@ -53,7 +54,7 @@ PassRefPtr createBitmapContextFromWebView(bool, bool, bool, bool) cairo_paint(context); g_object_unref(pixmap); #else - gtk_widget_draw(GTK_WIDGET(view), context); + gtk_widget_draw(viewContainer, context); #endif return BitmapContext::createByAdoptingBitmapAndContext(0, context); diff --git a/Tools/DumpRenderTree/gtk/TextInputController.cpp b/Tools/DumpRenderTree/gtk/TextInputController.cpp new file mode 100644 index 0000000..7243fdc --- /dev/null +++ b/Tools/DumpRenderTree/gtk/TextInputController.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2011 Igalia S.L. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "TextInputController.h" + +#include "DumpRenderTree.h" +#include "WebCoreSupport/DumpRenderTreeSupportGtk.h" +#include +#include +#include +#include +#include + +static JSValueRef setMarkedTextCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame); + if (!view) + return JSValueMakeUndefined(context); + + if (argumentCount < 3) + return JSValueMakeUndefined(context); + + JSStringRef string = JSValueToStringCopy(context, arguments[0], exception); + g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context)); + + size_t bufferSize = JSStringGetMaximumUTF8CStringSize(string); + GOwnPtr stringBuffer(static_cast(g_malloc(bufferSize))); + JSStringGetUTF8CString(string, stringBuffer.get(), bufferSize); + JSStringRelease(string); + + int start = static_cast(JSValueToNumber(context, arguments[1], exception)); + g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context)); + + int end = static_cast(JSValueToNumber(context, arguments[2], exception)); + g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context)); + + DumpRenderTreeSupportGtk::setComposition(view, stringBuffer.get(), start, end); + + return JSValueMakeUndefined(context); +} + +static JSValueRef insertTextCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame); + if (!view) + return JSValueMakeUndefined(context); + + if (argumentCount < 1) + return JSValueMakeUndefined(context); + + JSStringRef string = JSValueToStringCopy(context, arguments[0], exception); + g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context)); + + size_t bufferSize = JSStringGetMaximumUTF8CStringSize(string); + GOwnPtr stringBuffer(static_cast(g_malloc(bufferSize))); + JSStringGetUTF8CString(string, stringBuffer.get(), bufferSize); + JSStringRelease(string); + + DumpRenderTreeSupportGtk::confirmComposition(view, stringBuffer.get()); + + return JSValueMakeUndefined(context); +} + +static JSValueRef unmarkTextCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame); + if (!view) + return JSValueMakeUndefined(context); + + DumpRenderTreeSupportGtk::confirmComposition(view, 0); + return JSValueMakeUndefined(context); +} + +static JSValueRef firstRectForCharacterRangeCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame); + if (!view) + return JSValueMakeUndefined(context); + + if (argumentCount < 2) + return JSValueMakeUndefined(context); + + int location = static_cast(JSValueToNumber(context, arguments[0], exception)); + g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context)); + + int length = static_cast(JSValueToNumber(context, arguments[1], exception)); + g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context)); + + GdkRectangle rect; + if (!DumpRenderTreeSupportGtk::firstRectForCharacterRange(view, location, length, &rect)) + return JSValueMakeUndefined(context); + + JSValueRef arrayValues[4]; + arrayValues[0] = JSValueMakeNumber(context, rect.x); + arrayValues[1] = JSValueMakeNumber(context, rect.y); + arrayValues[2] = JSValueMakeNumber(context, rect.width); + arrayValues[3] = JSValueMakeNumber(context, rect.height); + JSObjectRef arrayObject = JSObjectMakeArray(context, 4, arrayValues, exception); + g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context)); + + return arrayObject; +} + +static JSValueRef selectedRangeCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame); + if (!view) + return JSValueMakeUndefined(context); + + int start, end; + if (!DumpRenderTreeSupportGtk::selectedRange(view, &start, &end)) + return JSValueMakeUndefined(context); + + JSValueRef arrayValues[2]; + arrayValues[0] = JSValueMakeNumber(context, start); + arrayValues[1] = JSValueMakeNumber(context, end); + JSObjectRef arrayObject = JSObjectMakeArray(context, 2, arrayValues, exception); + g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context)); + + return arrayObject; +} + +static JSStaticFunction staticFunctions[] = { + { "setMarkedText", setMarkedTextCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, + { "insertText", insertTextCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, + { "unmarkText", unmarkTextCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, + { "firstRectForCharacterRange", firstRectForCharacterRangeCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, + { "selectedRange", selectedRangeCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, + { 0, 0, 0 } +}; + +static JSClassRef getClass(JSContextRef context) +{ + static JSClassRef textInputControllerClass = 0; + + if (!textInputControllerClass) { + JSClassDefinition classDefinition = { + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + classDefinition.staticFunctions = staticFunctions; + + textInputControllerClass = JSClassCreate(&classDefinition); + } + + return textInputControllerClass; +} + +JSObjectRef makeTextInputController(JSContextRef context) +{ + return JSObjectMake(context, getClass(context), 0); +} diff --git a/Tools/DumpRenderTree/gtk/TextInputController.h b/Tools/DumpRenderTree/gtk/TextInputController.h new file mode 100644 index 0000000..53793f6 --- /dev/null +++ b/Tools/DumpRenderTree/gtk/TextInputController.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2011 Igalia S.L. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TextInputController_h +#define TextInputController_h + +typedef const struct OpaqueJSContext* JSContextRef; +typedef struct OpaqueJSValue* JSObjectRef; + +JSObjectRef makeTextInputController(JSContextRef); + +#endif diff --git a/Tools/DumpRenderTree/mac/AccessibilityControllerMac.mm b/Tools/DumpRenderTree/mac/AccessibilityControllerMac.mm index 1a9f9c9..d41f01d 100644 --- a/Tools/DumpRenderTree/mac/AccessibilityControllerMac.mm +++ b/Tools/DumpRenderTree/mac/AccessibilityControllerMac.mm @@ -74,6 +74,10 @@ void AccessibilityController::setLogValueChangeEvents(bool) { } +void AccessibilityController::setLogAccessibilityEvents(bool) +{ +} + void AccessibilityController::addNotificationListener(PlatformUIElement, JSObjectRef) { } diff --git a/Tools/DumpRenderTree/mac/DumpRenderTree.mm b/Tools/DumpRenderTree/mac/DumpRenderTree.mm index ed09cf6..eab3742 100644 --- a/Tools/DumpRenderTree/mac/DumpRenderTree.mm +++ b/Tools/DumpRenderTree/mac/DumpRenderTree.mm @@ -448,6 +448,7 @@ static void resetDefaultsToConsistentValues() [preferences setDeveloperExtrasEnabled:NO]; [preferences setLoadsImagesAutomatically:YES]; [preferences setFrameFlatteningEnabled:NO]; + [preferences setSpatialNavigationEnabled:NO]; [preferences setEditingBehavior:WebKitEditingMacBehavior]; if (persistentUserStyleSheetLocation) { [preferences setUserStyleSheetLocation:[NSURL URLWithString:(NSString *)(persistentUserStyleSheetLocation.get())]]; @@ -753,7 +754,10 @@ static void dumpHistoryItem(WebHistoryItem *item, int indent, BOOL current) static void dumpFrameScrollPosition(WebFrame *f) { - NSPoint scrollPosition = [[[[f frameView] documentView] superview] bounds].origin; + WebScriptObject* scriptObject = [f windowObject]; + NSPoint scrollPosition = NSMakePoint( + [[scriptObject valueForKey:@"pageXOffset"] floatValue], + [[scriptObject valueForKey:@"pageYOffset"] floatValue]); if (ABS(scrollPosition.x) > 0.00000001 || ABS(scrollPosition.y) > 0.00000001) { if ([f parentFrame] != nil) printf("frame '%s' ", [[f name] UTF8String]); diff --git a/Tools/DumpRenderTree/pthreads/JavaScriptThreadingPthreads.cpp b/Tools/DumpRenderTree/pthreads/JavaScriptThreadingPthreads.cpp index 5a48b27..1266c02 100644 --- a/Tools/DumpRenderTree/pthreads/JavaScriptThreadingPthreads.cpp +++ b/Tools/DumpRenderTree/pthreads/JavaScriptThreadingPthreads.cpp @@ -37,6 +37,8 @@ #include #include +static JSContextGroupRef javaScriptThreadsGroup; + static pthread_mutex_t javaScriptThreadsMutex = PTHREAD_MUTEX_INITIALIZER; static bool javaScriptThreadsShouldTerminate; @@ -51,58 +53,68 @@ static ThreadSet* javaScriptThreads() return &staticJavaScriptThreads; } -// Loops forever, running a script and randomly respawning, until -// javaScriptThreadsShouldTerminate becomes true. +// This function exercises JSC in a loop until javaScriptThreadsShouldTerminate +// becomes true or it probabilistically decides to spawn a replacement thread and exit. void* runJavaScriptThread(void* arg) { - const char* const script = + static const char* const script = "var array = [];" - "for (var i = 0; i < 10; i++) {" + "for (var i = 0; i < 1024; i++) {" " array.push(String(i));" "}"; - while (1) { - JSGlobalContextRef ctx = JSGlobalContextCreate(0); - JSStringRef scriptRef = JSStringCreateWithUTF8CString(script); + pthread_mutex_lock(&javaScriptThreadsMutex); + JSGlobalContextRef ctx = JSGlobalContextCreateInGroup(javaScriptThreadsGroup, 0); + pthread_mutex_unlock(&javaScriptThreadsMutex); + + pthread_mutex_lock(&javaScriptThreadsMutex); + JSStringRef scriptRef = JSStringCreateWithUTF8CString(script); + pthread_mutex_unlock(&javaScriptThreadsMutex); + while (1) { + pthread_mutex_lock(&javaScriptThreadsMutex); JSValueRef exception = 0; JSEvaluateScript(ctx, scriptRef, 0, 0, 1, &exception); ASSERT(!exception); - - JSGarbageCollect(ctx); - JSGlobalContextRelease(ctx); - JSStringRelease(scriptRef); - - JSGarbageCollect(0); + pthread_mutex_unlock(&javaScriptThreadsMutex); pthread_mutex_lock(&javaScriptThreadsMutex); + size_t valuesCount = 1024; + JSValueRef values[valuesCount]; + for (size_t i = 0; i < valuesCount; ++i) + values[i] = JSObjectMake(ctx, 0, 0); + pthread_mutex_unlock(&javaScriptThreadsMutex); // Check for cancellation. - if (javaScriptThreadsShouldTerminate) { - javaScriptThreads()->remove(pthread_self()); - pthread_mutex_unlock(&javaScriptThreadsMutex); - return 0; - } + if (javaScriptThreadsShouldTerminate) + goto done; // Respawn probabilistically. if (random() % 5 == 0) { + pthread_mutex_lock(&javaScriptThreadsMutex); pthread_t pthread; pthread_create(&pthread, 0, &runJavaScriptThread, 0); pthread_detach(pthread); - - javaScriptThreads()->remove(pthread_self()); javaScriptThreads()->add(pthread); - pthread_mutex_unlock(&javaScriptThreadsMutex); - return 0; + goto done; } - - pthread_mutex_unlock(&javaScriptThreadsMutex); } + +done: + pthread_mutex_lock(&javaScriptThreadsMutex); + JSStringRelease(scriptRef); + JSGarbageCollect(ctx); + JSGlobalContextRelease(ctx); + javaScriptThreads()->remove(pthread_self()); + pthread_mutex_unlock(&javaScriptThreadsMutex); + return 0; } void startJavaScriptThreads() { + javaScriptThreadsGroup = JSContextGroupCreate(); + pthread_mutex_lock(&javaScriptThreadsMutex); for (int i = 0; i < javaScriptThreadsCount; i++) { @@ -121,8 +133,6 @@ void stopJavaScriptThreads() javaScriptThreadsShouldTerminate = true; - ASSERT(javaScriptThreads()->size() == javaScriptThreadsCount); - pthread_mutex_unlock(&javaScriptThreadsMutex); while (true) { diff --git a/Tools/DumpRenderTree/qt/DumpRenderTree.pro b/Tools/DumpRenderTree/qt/DumpRenderTree.pro index a7c6c26..d84af96 100644 --- a/Tools/DumpRenderTree/qt/DumpRenderTree.pro +++ b/Tools/DumpRenderTree/qt/DumpRenderTree.pro @@ -5,12 +5,12 @@ CONFIG += uitools BASEDIR = $$PWD/../ isEmpty(OUTPUT_DIR): OUTPUT_DIR = ../../.. -include(../../../WebKit.pri) +include(../../../Source/WebKit.pri) INCLUDEPATH += ../../../Source INCLUDEPATH += ../../../Source/JavaScriptCore INCLUDEPATH += ../../../Source/JavaScriptCore/ForwardingHeaders INCLUDEPATH += $$BASEDIR -DESTDIR = ../../../bin +DESTDIR = ../../bin unix:!mac:!symbian { CONFIG += link_pkgconfig diff --git a/Tools/DumpRenderTree/qt/DumpRenderTreeQt.cpp b/Tools/DumpRenderTree/qt/DumpRenderTreeQt.cpp index ce608cc..7d20f47 100644 --- a/Tools/DumpRenderTree/qt/DumpRenderTreeQt.cpp +++ b/Tools/DumpRenderTree/qt/DumpRenderTreeQt.cpp @@ -83,6 +83,8 @@ namespace WebCore { +const int databaseDefaultQuota = 5 * 1024 * 1024; + NetworkAccessManager::NetworkAccessManager(QObject* parent) : QNetworkAccessManager(parent) { @@ -520,7 +522,7 @@ void DumpRenderTree::dryRunPrint(QWebFrame* frame) #endif } -void DumpRenderTree::resetToConsistentStateBeforeTesting() +void DumpRenderTree::resetToConsistentStateBeforeTesting(const QUrl& url) { // reset so that any current loads are stopped // NOTE: that this has to be done before the layoutTestController is @@ -529,6 +531,10 @@ void DumpRenderTree::resetToConsistentStateBeforeTesting() m_page->triggerAction(QWebPage::Stop); m_page->blockSignals(false); + QList knownOrigins = QWebSecurityOrigin::allOrigins(); + for (int i = 0; i < knownOrigins.size(); ++i) + knownOrigins[i].setDatabaseQuota(databaseDefaultQuota); + // reset the layoutTestController at this point, so that we under no // circumstance dump (stop the waitUntilDone timer) during the reset // of the DRT. @@ -550,6 +556,14 @@ void DumpRenderTree::resetToConsistentStateBeforeTesting() m_page->mainFrame()->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAsNeeded); m_page->mainFrame()->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAsNeeded); + if (url.scheme() == "http" || url.scheme() == "https") { + // credentials may exist from previous tests. + m_page->setNetworkAccessManager(0); + delete m_networkAccessManager; + m_networkAccessManager = new NetworkAccessManager(this); + m_page->setNetworkAccessManager(m_networkAccessManager); + } + WorkQueue::shared()->clear(); WorkQueue::shared()->setFrozen(false); @@ -560,6 +574,7 @@ void DumpRenderTree::resetToConsistentStateBeforeTesting() QLocale::setDefault(QLocale::c()); + layoutTestController()->setDeveloperExtrasEnabled(true); #ifndef Q_OS_WINCE setlocale(LC_ALL, ""); #endif @@ -579,26 +594,16 @@ static bool isWebInspectorTest(const QUrl& url) return false; } -static bool shouldEnableDeveloperExtras(const QUrl& url) -{ - return true; -} - void DumpRenderTree::open(const QUrl& url) { DumpRenderTreeSupportQt::dumpResourceLoadCallbacksPath(QFileInfo(url.toString()).path()); - resetToConsistentStateBeforeTesting(); + resetToConsistentStateBeforeTesting(url); - if (shouldEnableDeveloperExtras(m_page->mainFrame()->url())) { + if (isWebInspectorTest(m_page->mainFrame()->url())) layoutTestController()->closeWebInspector(); - layoutTestController()->setDeveloperExtrasEnabled(false); - } - if (shouldEnableDeveloperExtras(url)) { - layoutTestController()->setDeveloperExtrasEnabled(true); - if (isWebInspectorTest(url)) - layoutTestController()->showWebInspector(); - } + if (isWebInspectorTest(url)) + layoutTestController()->showWebInspector(); if (isGlobalHistoryTest(url)) layoutTestController()->dumpHistoryCallbacks(); @@ -1028,7 +1033,7 @@ void DumpRenderTree::dumpDatabaseQuota(QWebFrame* frame, const QString& dbName) origin.host().toUtf8().data(), origin.port(), dbName.toUtf8().data()); - origin.setDatabaseQuota(5 * 1024 * 1024); + origin.setDatabaseQuota(databaseDefaultQuota); } void DumpRenderTree::dumpApplicationCacheQuota(QWebSecurityOrigin* origin, quint64 defaultOriginQuota) @@ -1085,9 +1090,6 @@ void DumpRenderTree::windowCloseRequested() QWebPage* page = qobject_cast(sender()); QObject* container = page->parent(); windows.removeAll(container); - // Our use of container->deleteLater() means we need to remove closed pages - // from the org.webkit.qt.DumpRenderTree group explicitly. - DumpRenderTreeSupportQt::webPageSetGroupName(page, ""); container->deleteLater(); } diff --git a/Tools/DumpRenderTree/qt/DumpRenderTreeQt.h b/Tools/DumpRenderTree/qt/DumpRenderTreeQt.h index 8cb5efb..5b53cd9 100644 --- a/Tools/DumpRenderTree/qt/DumpRenderTreeQt.h +++ b/Tools/DumpRenderTree/qt/DumpRenderTreeQt.h @@ -86,7 +86,7 @@ public: void setDumpPixels(bool); void closeRemainingWindows(); - void resetToConsistentStateBeforeTesting(); + void resetToConsistentStateBeforeTesting(const QUrl&); LayoutTestController *layoutTestController() const { return m_controller; } EventSender *eventSender() const { return m_eventSender; } diff --git a/Tools/DumpRenderTree/qt/EventSenderQt.cpp b/Tools/DumpRenderTree/qt/EventSenderQt.cpp index 6fb75a5..6449484 100644 --- a/Tools/DumpRenderTree/qt/EventSenderQt.cpp +++ b/Tools/DumpRenderTree/qt/EventSenderQt.cpp @@ -360,7 +360,7 @@ void EventSender::keyDown(const QString& string, const QStringList& modifiers, u sendEvent(m_page, &event2); } -void EventSender::contextClick() +QStringList EventSender::contextClick() { QMouseEvent event(QEvent::MouseButtonPress, m_mousePos, Qt::RightButton, Qt::RightButton, Qt::NoModifier); sendEvent(m_page, &event); @@ -378,6 +378,7 @@ void EventSender::contextClick() QContextMenuEvent ctxEvent(QContextMenuEvent::Mouse, m_mousePos); sendEvent(m_page->view(), &ctxEvent); } + return DumpRenderTreeSupportQt::contextMenu(m_page); } void EventSender::scheduleAsynchronousClick() diff --git a/Tools/DumpRenderTree/qt/EventSenderQt.h b/Tools/DumpRenderTree/qt/EventSenderQt.h index 4ba8382..ecb06e2 100644 --- a/Tools/DumpRenderTree/qt/EventSenderQt.h +++ b/Tools/DumpRenderTree/qt/EventSenderQt.h @@ -65,7 +65,7 @@ public slots: void leapForward(int ms); void keyDown(const QString& string, const QStringList& modifiers = QStringList(), unsigned int location = 0); void clearKillRing() {} - void contextClick(); + QStringList contextClick(); void scheduleAsynchronousClick(); void addTouchPoint(int x, int y); void updateTouchPoint(int index, int x, int y); diff --git a/Tools/DumpRenderTree/qt/ImageDiff.pro b/Tools/DumpRenderTree/qt/ImageDiff.pro index a218449..cdb067e 100644 --- a/Tools/DumpRenderTree/qt/ImageDiff.pro +++ b/Tools/DumpRenderTree/qt/ImageDiff.pro @@ -2,7 +2,7 @@ TARGET = ImageDiff CONFIG -= app_bundle isEmpty(OUTPUT_DIR): OUTPUT_DIR = ../../.. -include(../../../WebKit.pri) +include(../../../Source/WebKit.pri) INCLUDEPATH += ../../../Source/JavaScriptCore DESTDIR = $$OUTPUT_DIR/bin diff --git a/Tools/DumpRenderTree/qt/LayoutTestControllerQt.cpp b/Tools/DumpRenderTree/qt/LayoutTestControllerQt.cpp index 56406c2..a11bc60 100644 --- a/Tools/DumpRenderTree/qt/LayoutTestControllerQt.cpp +++ b/Tools/DumpRenderTree/qt/LayoutTestControllerQt.cpp @@ -87,6 +87,7 @@ void LayoutTestController::reset() DumpRenderTreeSupportQt::dumpHistoryCallbacks(false); DumpRenderTreeSupportQt::dumpVisitedLinksCallbacks(false); setIconDatabaseEnabled(false); + clearAllDatabases(); emit hidePage(); } @@ -830,5 +831,10 @@ QVariantList LayoutTestController::nodesFromRect(const QWebElement& document, in return DumpRenderTreeSupportQt::nodesFromRect(document, x, y, top, right, bottom, left, ignoreClipping); } +void LayoutTestController::addURLToRedirect(const QString& origin, const QString& destination) +{ + DumpRenderTreeSupportQt::addURLToRedirect(origin, destination); +} + const unsigned LayoutTestController::maxViewWidth = 800; const unsigned LayoutTestController::maxViewHeight = 600; diff --git a/Tools/DumpRenderTree/qt/LayoutTestControllerQt.h b/Tools/DumpRenderTree/qt/LayoutTestControllerQt.h index 0048a7e..bc62c51 100644 --- a/Tools/DumpRenderTree/qt/LayoutTestControllerQt.h +++ b/Tools/DumpRenderTree/qt/LayoutTestControllerQt.h @@ -230,6 +230,8 @@ public slots: QVariantList nodesFromRect(const QWebElement& document, int x, int y, unsigned top, unsigned right, unsigned bottom, unsigned left, bool ignoreClipping); + void addURLToRedirect(const QString& origin, const QString& destination); + /* Policy values: 'on', 'auto' or 'off'. Orientation values: 'vertical' or 'horizontal'. diff --git a/Tools/DumpRenderTree/qt/TestNetscapePlugin/TestNetscapePlugin.pro b/Tools/DumpRenderTree/qt/TestNetscapePlugin/TestNetscapePlugin.pro index 6f96d0a..176c4c4 100644 --- a/Tools/DumpRenderTree/qt/TestNetscapePlugin/TestNetscapePlugin.pro +++ b/Tools/DumpRenderTree/qt/TestNetscapePlugin/TestNetscapePlugin.pro @@ -3,7 +3,7 @@ TARGET = TestNetscapePlugIn VPATH = ../../unix/TestNetscapePlugin ../../TestNetscapePlugIn isEmpty(OUTPUT_DIR): OUTPUT_DIR = ../../../.. -include(../../../../WebKit.pri) +include(../../../../Source/WebKit.pri) DESTDIR = $$OUTPUT_DIR/lib/plugins diff --git a/Tools/DumpRenderTree/unix/TestNetscapePlugin/TestNetscapePlugin.cpp b/Tools/DumpRenderTree/unix/TestNetscapePlugin/TestNetscapePlugin.cpp index 2f7288a..4936efd 100644 --- a/Tools/DumpRenderTree/unix/TestNetscapePlugin/TestNetscapePlugin.cpp +++ b/Tools/DumpRenderTree/unix/TestNetscapePlugin/TestNetscapePlugin.cpp @@ -79,6 +79,9 @@ webkit_test_plugin_new_instance(NPMIMEType mimetype, else if (strcasecmp(argn[i], "src") == 0 && strcasecmp(argv[i], "data:application/x-webkit-test-netscape,returnerrorfromnewstream") == 0) obj->returnErrorFromNewStream = TRUE; + else if (!strcasecmp(argn[i], "src") + && !strcasecmp(argv[i], "data:application/x-webkit-test-netscape,alertwhenloaded")) + executeScript(obj, "alert('Plugin Loaded!')"); else if (strcasecmp(argn[i], "logfirstsetwindow") == 0) obj->logSetWindow = TRUE; else if (strcasecmp(argn[i], "testnpruntime") == 0) diff --git a/Tools/DumpRenderTree/win/AccessibilityControllerWin.cpp b/Tools/DumpRenderTree/win/AccessibilityControllerWin.cpp index f03c102..6e4ee46 100644 --- a/Tools/DumpRenderTree/win/AccessibilityControllerWin.cpp +++ b/Tools/DumpRenderTree/win/AccessibilityControllerWin.cpp @@ -213,6 +213,10 @@ void AccessibilityController::setLogScrollingStartEvents(bool logScrollingStartE ASSERT(m_scrollingStartEventHook); } +void AccessibilityController::setLogAccessibilityEvents(bool) +{ +} + static string stringEvent(DWORD event) { switch(event) { diff --git a/Tools/DumpRenderTree/win/ImageDiffCommon.vsprops b/Tools/DumpRenderTree/win/ImageDiffCommon.vsprops index 1156a72..5d41bf4 100644 --- a/Tools/DumpRenderTree/win/ImageDiffCommon.vsprops +++ b/Tools/DumpRenderTree/win/ImageDiffCommon.vsprops @@ -7,6 +7,7 @@ addItem(m_item); setFrameShape(QFrame::NoFrame); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - - connect(m_item, SIGNAL(titleChanged(QString)), this, SLOT(setWindowTitle(QString))); } void BrowserView::resizeEvent(QResizeEvent* event) diff --git a/Tools/MiniBrowser/qt/BrowserView.h b/Tools/MiniBrowser/qt/BrowserView.h index e19cc59..593006e 100644 --- a/Tools/MiniBrowser/qt/BrowserView.h +++ b/Tools/MiniBrowser/qt/BrowserView.h @@ -48,7 +48,6 @@ protected: private: QGraphicsWKView* m_item; - QWKContext* m_context; }; #endif diff --git a/Tools/MiniBrowser/qt/BrowserWindow.cpp b/Tools/MiniBrowser/qt/BrowserWindow.cpp index c63c9d6..0405e9e 100644 --- a/Tools/MiniBrowser/qt/BrowserWindow.cpp +++ b/Tools/MiniBrowser/qt/BrowserWindow.cpp @@ -28,34 +28,92 @@ #include "BrowserWindow.h" +#include "qwkpreferences.h" + static QWKPage* newPageFunction(QWKPage* page) { BrowserWindow* window = new BrowserWindow(page->context()); return window->page(); } -QGraphicsWKView::BackingStoreType BrowserWindow::backingStoreTypeForNewWindow = QGraphicsWKView::Simple; +QVector BrowserWindow::m_zoomLevels; -BrowserWindow::BrowserWindow(QWKContext* context) +BrowserWindow::BrowserWindow(QWKContext* context, WindowOptions* options) + : m_isZoomTextOnly(false) + , m_currentZoom(1) + , m_context(context) { - setAttribute(Qt::WA_DeleteOnClose); + if (options) + m_windowOptions = *options; + else { + WindowOptions tmpOptions; + m_windowOptions = tmpOptions; + } - m_menu = new QMenuBar(); - m_browser = new BrowserView(backingStoreTypeForNewWindow, context); - m_addressBar = new QLineEdit(); + if (m_windowOptions.useTiledBackingStore) + m_browser = new BrowserView(QGraphicsWKView::Tiled, context); + else + m_browser = new BrowserView(QGraphicsWKView::Simple, context); - m_menu->addAction("New Window", this, SLOT(newWindow())); - m_menu->addAction("Change User Agent", this, SLOT(showUserAgentDialog())); + setAttribute(Qt::WA_DeleteOnClose); - m_menu->addSeparator(); - m_menu->addAction("Quit", this, SLOT(close())); + connect(m_browser->view(), SIGNAL(loadProgress(int)), SLOT(loadProgress(int))); + connect(m_browser->view(), SIGNAL(titleChanged(const QString&)), SLOT(setWindowTitle(const QString&))); + connect(m_browser->view(), SIGNAL(urlChanged(const QUrl&)), SLOT(urlChanged(const QUrl&))); + this->setCentralWidget(m_browser); m_browser->setFocus(Qt::OtherFocusReason); + QMenu* fileMenu = menuBar()->addMenu("&File"); + fileMenu->addAction("New Window", this, SLOT(newWindow()), QKeySequence::New); + fileMenu->addAction("Open File", this, SLOT(openFile()), QKeySequence::Open); + fileMenu->addSeparator(); + fileMenu->addAction("Quit", this, SLOT(close())); + + QMenu* viewMenu = menuBar()->addMenu("&View"); + viewMenu->addAction(page()->action(QWKPage::Stop)); + viewMenu->addAction(page()->action(QWKPage::Reload)); + viewMenu->addSeparator(); + QAction* zoomIn = viewMenu->addAction("Zoom &In", this, SLOT(zoomIn())); + QAction* zoomOut = viewMenu->addAction("Zoom &Out", this, SLOT(zoomOut())); + QAction* resetZoom = viewMenu->addAction("Reset Zoom", this, SLOT(resetZoom())); + QAction* zoomText = viewMenu->addAction("Zoom Text Only", this, SLOT(toggleZoomTextOnly(bool))); + zoomText->setCheckable(true); + zoomText->setChecked(false); + viewMenu->addSeparator(); + viewMenu->addAction("Take Screen Shot...", this, SLOT(screenshot())); + + zoomIn->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Plus)); + zoomOut->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Minus)); + resetZoom->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_0)); + + QMenu* windowMenu = menuBar()->addMenu("&Window"); + QAction* toggleFullScreen = windowMenu->addAction("Toggle FullScreen", this, SIGNAL(enteredFullScreenMode(bool))); + toggleFullScreen->setShortcut(Qt::Key_F11); + toggleFullScreen->setCheckable(true); + toggleFullScreen->setChecked(false); + // When exit fullscreen mode by clicking on the exit area (bottom right corner) we must + // uncheck the Toggle FullScreen action. + toggleFullScreen->connect(this, SIGNAL(enteredFullScreenMode(bool)), SLOT(setChecked(bool))); + connect(this, SIGNAL(enteredFullScreenMode(bool)), this, SLOT(toggleFullScreenMode(bool))); + + QMenu* toolsMenu = menuBar()->addMenu("&Develop"); + QAction* toggleFrameFlattening = toolsMenu->addAction("Toggle Frame Flattening", this, SLOT(toggleFrameFlattening(bool))); + toggleFrameFlattening->setCheckable(true); + toggleFrameFlattening->setChecked(false); + toolsMenu->addSeparator(); + toolsMenu->addAction("Change User Agent", this, SLOT(showUserAgentDialog())); + + QMenu* settingsMenu = menuBar()->addMenu("&Settings"); + QAction* toggleAutoLoadImages = settingsMenu->addAction("Disable Auto Load Images", this, SLOT(toggleAutoLoadImages(bool))); + toggleAutoLoadImages->setCheckable(true); + toggleAutoLoadImages->setChecked(false); + QAction* toggleDisableJavaScript = settingsMenu->addAction("Disable JavaScript", this, SLOT(toggleDisableJavaScript(bool))); + toggleDisableJavaScript->setCheckable(true); + toggleDisableJavaScript->setChecked(false); + + m_addressBar = new QLineEdit(); connect(m_addressBar, SIGNAL(returnPressed()), SLOT(changeLocation())); - connect(m_browser->view(), SIGNAL(loadProgress(int)), SLOT(loadProgress(int))); - connect(m_browser->view(), SIGNAL(titleChanged(const QString&)), SLOT(titleChanged(const QString&))); - connect(m_browser->view(), SIGNAL(urlChanged(const QUrl&)), SLOT(urlChanged(const QUrl&))); QToolBar* bar = addToolBar("Navigation"); bar->addAction(page()->action(QWKPage::Back)); @@ -64,17 +122,19 @@ BrowserWindow::BrowserWindow(QWKContext* context) bar->addAction(page()->action(QWKPage::Stop)); bar->addWidget(m_addressBar); - this->setMenuBar(m_menu); - this->setCentralWidget(m_browser); - - m_browser->setFocus(Qt::OtherFocusReason); - QShortcut* selectAddressBar = new QShortcut(Qt::CTRL | Qt::Key_L, this); connect(selectAddressBar, SIGNAL(activated()), this, SLOT(openLocation())); page()->setCreateNewPageFunction(newPageFunction); - resize(960, 640); + // the zoom values are chosen to be like in Mozilla Firefox 3 + if (!m_zoomLevels.count()) { + m_zoomLevels << 0.3 << 0.5 << 0.67 << 0.8 << 0.9; + m_zoomLevels << 1; + m_zoomLevels << 1.1 << 1.2 << 1.33 << 1.5 << 1.7 << 2 << 2.4 << 3; + } + + resize(800, 600); show(); } @@ -91,7 +151,14 @@ QWKPage* BrowserWindow::page() BrowserWindow* BrowserWindow::newWindow(const QString& url) { - BrowserWindow* window = new BrowserWindow; + BrowserWindow* window; + if (m_windowOptions.useSeparateWebProcessPerWindow) { + QWKContext* context = new QWKContext(); + window = new BrowserWindow(context); + context->setParent(window); + } else + window = new BrowserWindow(m_context); + window->load(url); return window; } @@ -127,35 +194,113 @@ void BrowserWindow::loadProgress(int progress) m_addressBar->setPalette(pallete); } -void BrowserWindow::titleChanged(const QString& title) -{ - setWindowTitle(title); -} - void BrowserWindow::urlChanged(const QUrl& url) { m_addressBar->setText(url.toString()); } -void BrowserWindow::updateUserAgentList() +void BrowserWindow::openFile() { - QFile file(":/useragentlist.txt"); +#ifndef QT_NO_FILEDIALOG + static const QString filter("HTML Files (*.htm *.html *.xhtml);;Text Files (*.txt);;Image Files (*.gif *.jpg *.png);;SVG Files (*.svg);;All Files (*)"); + + QFileDialog fileDialog(this, tr("Open"), QString(), filter); + fileDialog.setAcceptMode(QFileDialog::AcceptOpen); + fileDialog.setFileMode(QFileDialog::ExistingFile); + fileDialog.setOptions(QFileDialog::ReadOnly); + + if (fileDialog.exec()) { + QString selectedFile = fileDialog.selectedFiles()[0]; + if (!selectedFile.isEmpty()) + load(selectedFile); + } +#endif +} - if (file.open(QIODevice::ReadOnly)) { - while (!file.atEnd()) { - QString agent = file.readLine().trimmed(); - if (!m_userAgentList.contains(agent)) - m_userAgentList << agent; - } - file.close(); +void BrowserWindow::screenshot() +{ + QPixmap pixmap = QPixmap::grabWidget(m_browser); + QLabel* label = 0; +#if !defined(Q_OS_SYMBIAN) + label = new QLabel; + label->setAttribute(Qt::WA_DeleteOnClose); + label->setWindowTitle("Screenshot - Preview"); + label->setPixmap(pixmap); + label->show(); +#endif + +#ifndef QT_NO_FILEDIALOG + QString fileName = QFileDialog::getSaveFileName(label, "Screenshot", QString(), QString("PNG File (.png)")); + if (!fileName.isEmpty()) { + QRegExp rx("*.png"); + rx.setCaseSensitivity(Qt::CaseInsensitive); + rx.setPatternSyntax(QRegExp::Wildcard); + + if (!rx.exactMatch(fileName)) + fileName += ".png"; + + pixmap.save(fileName, "png"); + if (label) + label->setWindowTitle(QString("Screenshot - Saved at %1").arg(fileName)); } +#endif +} - Q_ASSERT(!m_userAgentList.isEmpty()); - QWKPage* wkPage = page(); - if (!(wkPage->customUserAgent().isEmpty() || m_userAgentList.contains(wkPage->customUserAgent()))) - m_userAgentList << wkPage->customUserAgent(); +void BrowserWindow::zoomIn() +{ + if (m_isZoomTextOnly) + m_currentZoom = page()->textZoomFactor(); + else + m_currentZoom = page()->pageZoomFactor(); + + int i = m_zoomLevels.indexOf(m_currentZoom); + Q_ASSERT(i >= 0); + if (i < m_zoomLevels.count() - 1) + m_currentZoom = m_zoomLevels[i + 1]; + + applyZoom(); } +void BrowserWindow::zoomOut() +{ + if (m_isZoomTextOnly) + m_currentZoom = page()->textZoomFactor(); + else + m_currentZoom = page()->pageZoomFactor(); + + int i = m_zoomLevels.indexOf(m_currentZoom); + Q_ASSERT(i >= 0); + if (i > 0) + m_currentZoom = m_zoomLevels[i - 1]; + + applyZoom(); +} + +void BrowserWindow::resetZoom() +{ + m_currentZoom = 1; + applyZoom(); +} + +void BrowserWindow::toggleZoomTextOnly(bool b) +{ + m_isZoomTextOnly = b; +} + +void BrowserWindow::toggleFullScreenMode(bool enable) +{ + if (enable) + setWindowState(Qt::WindowFullScreen); + else + setWindowState(Qt::WindowNoState); +} + +void BrowserWindow::toggleFrameFlattening(bool toggle) +{ + page()->preferences()->setAttribute(QWKPreferences::FrameFlatteningEnabled, toggle); +} + + void BrowserWindow::showUserAgentDialog() { updateUserAgentList(); @@ -185,9 +330,45 @@ void BrowserWindow::showUserAgentDialog() page()->setCustomUserAgent(combo->currentText()); } +void BrowserWindow::toggleDisableJavaScript(bool enable) +{ + page()->preferences()->setAttribute(QWKPreferences::JavascriptEnabled, !enable); +} + +void BrowserWindow::toggleAutoLoadImages(bool enable) +{ + page()->preferences()->setAttribute(QWKPreferences::AutoLoadImages, !enable); +} + +void BrowserWindow::updateUserAgentList() +{ + QFile file(":/useragentlist.txt"); + + if (file.open(QIODevice::ReadOnly)) { + while (!file.atEnd()) { + QString agent = file.readLine().trimmed(); + if (!m_userAgentList.contains(agent)) + m_userAgentList << agent; + } + file.close(); + } + + Q_ASSERT(!m_userAgentList.isEmpty()); + QWKPage* wkPage = page(); + if (!(wkPage->customUserAgent().isEmpty() || m_userAgentList.contains(wkPage->customUserAgent()))) + m_userAgentList << wkPage->customUserAgent(); +} + +void BrowserWindow::applyZoom() +{ + if (m_isZoomTextOnly) + page()->setTextZoomFactor(m_currentZoom); + else + page()->setPageZoomFactor(m_currentZoom); +} + BrowserWindow::~BrowserWindow() { delete m_addressBar; delete m_browser; - delete m_menu; } diff --git a/Tools/MiniBrowser/qt/BrowserWindow.h b/Tools/MiniBrowser/qt/BrowserWindow.h index f984309..c79b2d0 100644 --- a/Tools/MiniBrowser/qt/BrowserWindow.h +++ b/Tools/MiniBrowser/qt/BrowserWindow.h @@ -30,38 +30,60 @@ #define BrowserWindow_h #include "BrowserView.h" + +#include "MiniBrowserApplication.h" #include #include -#include class BrowserWindow : public QMainWindow { Q_OBJECT public: - BrowserWindow(QWKContext* = 0); + BrowserWindow(QWKContext*, WindowOptions* = 0); ~BrowserWindow(); void load(const QString& url); QWKPage* page(); - static QGraphicsWKView::BackingStoreType backingStoreTypeForNewWindow; - public slots: BrowserWindow* newWindow(const QString& url = "about:blank"); void openLocation(); +signals: + void enteredFullScreenMode(bool on); + protected slots: void changeLocation(); void loadProgress(int progress); - void titleChanged(const QString&); void urlChanged(const QUrl&); + void openFile(); + + void zoomIn(); + void zoomOut(); + void resetZoom(); + void toggleZoomTextOnly(bool on); + void screenshot(); + + void toggleFullScreenMode(bool enable); + + void toggleFrameFlattening(bool); void showUserAgentDialog(); + void toggleAutoLoadImages(bool); + void toggleDisableJavaScript(bool); + private: void updateUserAgentList(); + void applyZoom(); + + static QVector m_zoomLevels; + bool m_isZoomTextOnly; + qreal m_currentZoom; + + QWKContext* m_context; + WindowOptions m_windowOptions; BrowserView* m_browser; - QMenuBar* m_menu; QLineEdit* m_addressBar; QStringList m_userAgentList; }; diff --git a/Tools/MiniBrowser/qt/MiniBrowser.pro b/Tools/MiniBrowser/qt/MiniBrowser.pro index c297207..cefa678 100644 --- a/Tools/MiniBrowser/qt/MiniBrowser.pro +++ b/Tools/MiniBrowser/qt/MiniBrowser.pro @@ -2,18 +2,22 @@ TEMPLATE = app TARGET = MiniBrowser SOURCES += \ - main.cpp \ BrowserView.cpp \ BrowserWindow.cpp \ + main.cpp \ + MiniBrowserApplication.cpp \ + UrlLoader.cpp \ + utils.cpp \ HEADERS += \ BrowserView.h \ BrowserWindow.h \ - -CONFIG += uitools + MiniBrowserApplication.h \ + UrlLoader.h \ + utils.h \ isEmpty(OUTPUT_DIR): OUTPUT_DIR = ../../.. -include(../../../WebKit.pri) +include(../../../Source/WebKit.pri) INCLUDEPATH += \ $$PWD/../../../Source/WebKit2/ \ @@ -55,4 +59,4 @@ contains(QT_CONFIG, opengl) { # We copy the resource file to the build directory. # The copier is defined in Tools/MiniBrowser/DerivedSources.pro. RESOURCES += \ - $$OUTPUT_DIR/Tools/MiniBrowser/qt/MiniBrowser.qrc + $$OUTPUT_DIR/MiniBrowser/qt/MiniBrowser.qrc diff --git a/Tools/MiniBrowser/qt/MiniBrowserApplication.cpp b/Tools/MiniBrowser/qt/MiniBrowserApplication.cpp new file mode 100644 index 0000000..4ed7895 --- /dev/null +++ b/Tools/MiniBrowser/qt/MiniBrowserApplication.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) + * Copyright (C) 2010 University of Szeged + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "MiniBrowserApplication.h" + +#include "utils.h" +#include + +MiniBrowserApplication::MiniBrowserApplication(int& argc, char** argv) + : QApplication(argc, argv, QApplication::GuiServer) + , m_windowOptions() + , m_isRobotized(false) + , m_robotTimeoutSeconds(0) + , m_robotExtraTimeSeconds(0) +{ + setOrganizationName("Nokia"); + setApplicationName("QtMiniBrowser"); + setApplicationVersion("0.1"); + + handleUserOptions(); +} + +void MiniBrowserApplication::handleUserOptions() +{ + QStringList args = arguments(); + QFileInfo program(args.at(0)); + QString programName("MiniBrowser"); + if (program.exists()) + programName = program.baseName(); + + if (args.contains("-help")) { + qDebug() << "Usage:" << programName.toLatin1().data() + << "[-r list]" + << "[-robot-timeout seconds]" + << "[-robot-extra-time seconds]" + << "[-tiled-backing-store]" + << "[-separate-web-process-per-window]" + << "URLs"; + appQuit(0); + } + + int robotIndex = args.indexOf("-r"); + if (robotIndex != -1) { + QString listFile = takeOptionValue(&args, robotIndex); + if (listFile.isEmpty()) + appQuit(1, "-r needs a list file to start in robotized mode"); + if (!QFile::exists(listFile)) + appQuit(1, "The list file supplied to -r does not exist."); + + m_isRobotized = true; + m_urls = QStringList(listFile); + } else { + int lastArg = args.lastIndexOf(QRegExp("^-.*")); + m_urls = (lastArg != -1) ? args.mid(++lastArg) : args.mid(1); + } + + int robotTimeoutIndex = args.indexOf("-robot-timeout"); + if (robotTimeoutIndex != -1) + m_robotTimeoutSeconds = takeOptionValue(&args, robotTimeoutIndex).toInt(); + + int robotExtraTimeIndex = args.indexOf("-robot-extra-time"); + if (robotExtraTimeIndex != -1) + m_robotExtraTimeSeconds = takeOptionValue(&args, robotExtraTimeIndex).toInt(); + + if (args.contains("-tiled-backing-store")) + m_windowOptions.useTiledBackingStore = true; + + if (args.contains("-separate-web-process-per-window")) + m_windowOptions.useSeparateWebProcessPerWindow = true; +} diff --git a/Tools/MiniBrowser/qt/MiniBrowserApplication.h b/Tools/MiniBrowser/qt/MiniBrowserApplication.h new file mode 100644 index 0000000..2039764 --- /dev/null +++ b/Tools/MiniBrowser/qt/MiniBrowserApplication.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) + * Copyright (C) 2010 University of Szeged + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MiniBrowserApplication_h +#define MiniBrowserApplication_h + +#include +#include + +struct WindowOptions { + WindowOptions() + : useTiledBackingStore(false) + , useSeparateWebProcessPerWindow(false) + { + } + + bool useTiledBackingStore; + bool useSeparateWebProcessPerWindow; +}; + +class MiniBrowserApplication : public QApplication { + Q_OBJECT + +public: + MiniBrowserApplication(int& argc, char** argv); + QStringList urls() const { return m_urls; } + bool isRobotized() const { return m_isRobotized; } + int robotTimeout() const { return m_robotTimeoutSeconds; } + int robotExtraTime() const { return m_robotExtraTimeSeconds; } + + WindowOptions m_windowOptions; + +private: + void handleUserOptions(); + +private: + bool m_isRobotized; + int m_robotTimeoutSeconds; + int m_robotExtraTimeSeconds; + QStringList m_urls; +}; + +#endif diff --git a/Tools/MiniBrowser/qt/UrlLoader.cpp b/Tools/MiniBrowser/qt/UrlLoader.cpp new file mode 100644 index 0000000..600d477 --- /dev/null +++ b/Tools/MiniBrowser/qt/UrlLoader.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) + * Copyright (C) 2009 University of Szeged + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "UrlLoader.h" + +#include +#include + +UrlLoader::UrlLoader(BrowserWindow* browserWindow, const QString& inputFileName, int timeoutSeconds, int extraTimeSeconds) + : m_browserWindow(browserWindow) + , m_stdOut(stdout) + , m_loaded(0) + , m_numFramesLoading(0) +{ + m_checkIfFinishedTimer.setInterval(200); + m_checkIfFinishedTimer.setSingleShot(true); + connect(&m_checkIfFinishedTimer, SIGNAL(timeout()), this, SLOT(checkIfFinished())); + // loadStarted and loadFinished on QWebPage is emitted for each frame/sub-frame + connect(m_browserWindow->page(), SIGNAL(loadStarted()), this, SLOT(frameLoadStarted())); + connect(m_browserWindow->page(), SIGNAL(loadFinished(bool)), this, SLOT(frameLoadFinished())); + + if (timeoutSeconds) { + m_timeoutTimer.setInterval(timeoutSeconds * 1000); + m_timeoutTimer.setSingleShot(true); + connect(m_browserWindow, SIGNAL(loadStarted()), &m_timeoutTimer, SLOT(start())); + connect(&m_timeoutTimer, SIGNAL(timeout()), this, SLOT(loadNext())); + } + if (extraTimeSeconds) { + m_extraTimeTimer.setInterval(extraTimeSeconds * 1000); + m_extraTimeTimer.setSingleShot(true); + connect(this, SIGNAL(pageLoadFinished()), &m_extraTimeTimer, SLOT(start())); + connect(&m_extraTimeTimer, SIGNAL(timeout()), this, SLOT(loadNext())); + } else + connect(this, SIGNAL(pageLoadFinished()), this, SLOT(loadNext())); + loadUrlList(inputFileName); +} + +void UrlLoader::loadNext() +{ + m_timeoutTimer.stop(); + m_extraTimeTimer.stop(); + m_checkIfFinishedTimer.stop(); + m_numFramesLoading = 0; + QString qstr; + if (getUrl(qstr)) { + QUrl url(qstr, QUrl::StrictMode); + if (url.isValid()) { + m_stdOut << "Loading " << qstr << " ......" << ++m_loaded << endl; + m_browserWindow->load(url.toString()); + } else + loadNext(); + } else + disconnect(m_browserWindow, 0, this, 0); +} + +void UrlLoader::checkIfFinished() +{ + if (!m_numFramesLoading) + emit pageLoadFinished(); +} + +void UrlLoader::frameLoadStarted() +{ + ++m_numFramesLoading; + m_checkIfFinishedTimer.stop(); +} + +void UrlLoader::frameLoadFinished() +{ + Q_ASSERT(m_numFramesLoading > 0); + --m_numFramesLoading; + // Once our frame has finished loading, wait a moment to call loadNext for cases + // where a sub-frame starts loading or another frame is loaded through JavaScript. + m_checkIfFinishedTimer.start(); +} + +void UrlLoader::loadUrlList(const QString& inputFileName) +{ + QFile inputFile(inputFileName); + if (inputFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + QTextStream stream(&inputFile); + QString line; + while (true) { + line = stream.readLine(); + if (line.isNull()) + break; + m_urls.append(line); + } + } else { + qDebug() << "Can't open list file"; + exit(0); + } + m_index = 0; + inputFile.close(); +} + +bool UrlLoader::getUrl(QString& qstr) +{ + if (m_index == m_urls.size()) + return false; + + qstr = m_urls[m_index++]; + return true; +} diff --git a/Tools/MiniBrowser/qt/UrlLoader.h b/Tools/MiniBrowser/qt/UrlLoader.h new file mode 100644 index 0000000..01e67c3 --- /dev/null +++ b/Tools/MiniBrowser/qt/UrlLoader.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) + * Copyright (C) 2009 University of Szeged + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UrlLoader_h +#define UrlLoader_h + +#include "BrowserWindow.h" + +#include +#include +#include + +class UrlLoader : public QObject { + Q_OBJECT + +public: + UrlLoader(BrowserWindow*, const QString&, int, int); + +public slots: + void loadNext(); + +private slots: + void checkIfFinished(); + void frameLoadStarted(); + void frameLoadFinished(); + +signals: + void pageLoadFinished(); + +private: + void loadUrlList(const QString& inputFileName); + bool getUrl(QString& qstr); + +private: + QVector m_urls; + int m_index; + BrowserWindow* m_browserWindow; + QTextStream m_stdOut; + int m_loaded; + QTimer m_timeoutTimer; + QTimer m_extraTimeTimer; + QTimer m_checkIfFinishedTimer; + int m_numFramesLoading; +}; + +#endif diff --git a/Tools/MiniBrowser/qt/main.cpp b/Tools/MiniBrowser/qt/main.cpp index 8c987c5..8f4c1ea 100644 --- a/Tools/MiniBrowser/qt/main.cpp +++ b/Tools/MiniBrowser/qt/main.cpp @@ -27,38 +27,46 @@ */ #include "BrowserWindow.h" + +#include "MiniBrowserApplication.h" +#include "UrlLoader.h" #include #include #include #include -int main(int argc, char** argv) { - QApplication app(argc, argv); - - QStringList args = QApplication::arguments(); - args.removeAt(0); +int main(int argc, char** argv) +{ + MiniBrowserApplication app(argc, argv); - QGraphicsWKView::BackingStoreType backingStoreTypeToUse = QGraphicsWKView::Simple; - int indexOfTiledOption; - if ((indexOfTiledOption = args.indexOf(QRegExp(QLatin1String("-tiled")))) != -1) { - backingStoreTypeToUse = QGraphicsWKView::Tiled; - args.removeAt(indexOfTiledOption); + if (app.isRobotized()) { + QWKContext* context = new QWKContext; + BrowserWindow* window = new BrowserWindow(context, &app.m_windowOptions); + UrlLoader loader(window, app.urls().at(0), app.robotTimeout(), app.robotExtraTime()); + loader.loadNext(); + window->show(); + return app.exec(); } - if (args.isEmpty()) { + QStringList urls = app.urls(); + + if (urls.isEmpty()) { QString defaultUrl = QString("file://%1/%2").arg(QDir::homePath()).arg(QLatin1String("index.html")); if (QDir(defaultUrl).exists()) - args.append(defaultUrl); + urls.append(defaultUrl); else - args.append("http://www.google.com"); + urls.append("http://www.google.com"); } - BrowserWindow::backingStoreTypeForNewWindow = backingStoreTypeToUse; - BrowserWindow* window = new BrowserWindow; - window->load(args[0]); + QWKContext* context = new QWKContext; + BrowserWindow* window = new BrowserWindow(context, &app.m_windowOptions); + if (app.m_windowOptions.useSeparateWebProcessPerWindow) + context->setParent(window); + + window->load(urls.at(0)); - for (int i = 1; i < args.size(); ++i) - window->newWindow(args[i]); + for (int i = 1; i < urls.size(); ++i) + window->newWindow(urls.at(i)); app.exec(); diff --git a/Tools/MiniBrowser/qt/utils.cpp b/Tools/MiniBrowser/qt/utils.cpp new file mode 100644 index 0000000..494da71 --- /dev/null +++ b/Tools/MiniBrowser/qt/utils.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) + * Copyright (C) 2011 University of Szeged + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "utils.h" + +QString takeOptionValue(QStringList* arguments, int index) +{ + QString result; + + if (++index < arguments->count() && !arguments->at(index).startsWith("-")) + result = arguments->takeAt(index); + + return result; +} + +QString formatKeys(QList keys) +{ + QString result; + for (int i = 0; i < keys.count() - 1; i++) + result.append(keys.at(i) + "|"); + result.append(keys.last()); + return result; +} + +QList enumToKeys(const QMetaObject o, const QString& name, const QString& strip) +{ + QList list; + + int enumIndex = o.indexOfEnumerator(name.toLatin1().data()); + QMetaEnum enumerator = o.enumerator(enumIndex); + + if (enumerator.isValid()) { + for (int i = 0; i < enumerator.keyCount(); i++) { + QString key(enumerator.valueToKey(i)); + list.append(key.remove(strip)); + } + } + + return list; +} + +void appQuit(int exitCode, const QString& msg) +{ + if (!msg.isEmpty()) { + if (exitCode > 0) + qDebug("ERROR: %s", msg.toLatin1().data()); + else + qDebug() << msg; + } + exit(exitCode); +} + +QUrl urlFromUserInput(const QString& string) +{ + QString input(string); + QFileInfo fi(input); + if (fi.exists() && fi.isRelative()) + input = fi.absoluteFilePath(); + + return QUrl::fromUserInput(input); +} diff --git a/Tools/MiniBrowser/qt/utils.h b/Tools/MiniBrowser/qt/utils.h new file mode 100644 index 0000000..2ec7af2 --- /dev/null +++ b/Tools/MiniBrowser/qt/utils.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) + * Copyright (C) 2011 University of Szeged + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef utils_h +#define utils_h + +#include + +#ifndef NO_RETURN +#if defined(__CC_ARM) || defined(__ARMCC__) +#define NO_RETURN __declspec(noreturn) +#elif defined(__GNUC__) +#define NO_RETURN __attribute((__noreturn__)) +#else +#define NO_RETURN +#endif +#endif + +// options handling +QString takeOptionValue(QStringList* arguments, int index); +QString formatKeys(QList keys); +QList enumToKeys(const QMetaObject, const QString&, const QString&); + +NO_RETURN void appQuit(int status, const QString& msg = QString()); + +QUrl urlFromUserInput(const QString& input); + +#endif diff --git a/Tools/QtTestBrowser/QtTestBrowser.pro b/Tools/QtTestBrowser/QtTestBrowser.pro index 62d2c02..6c8cdf4 100644 --- a/Tools/QtTestBrowser/QtTestBrowser.pro +++ b/Tools/QtTestBrowser/QtTestBrowser.pro @@ -25,7 +25,7 @@ HEADERS += \ CONFIG += uitools isEmpty(OUTPUT_DIR): OUTPUT_DIR = ../.. -include(../../WebKit.pri) +include(../../Source/WebKit.pri) DESTDIR = $$OUTPUT_DIR/bin !CONFIG(standalone_package): CONFIG -= app_bundle diff --git a/Tools/QtTestBrowser/launcherwindow.cpp b/Tools/QtTestBrowser/launcherwindow.cpp index 067b146..bd332fc 100644 --- a/Tools/QtTestBrowser/launcherwindow.cpp +++ b/Tools/QtTestBrowser/launcherwindow.cpp @@ -96,6 +96,7 @@ void LauncherWindow::initializeView() { delete m_view; + m_inputUrl = addressUrl(); QUrl url = page()->mainFrame()->url(); setPage(new WebPage(this)); page()->setQnamThreaded(m_windowOptions.useThreadedQnam); @@ -142,6 +143,10 @@ void LauncherWindow::initializeView() if (url.isValid()) page()->mainFrame()->load(url); + else { + setAddressUrl(m_inputUrl); + m_inputUrl = QString(); + } } void LauncherWindow::applyPrefs() @@ -259,6 +264,24 @@ void LauncherWindow::createChrome() toolsMenu->addSeparator(); + QAction* toggleLocalStorage = toolsMenu->addAction("Enable Local Storage", this, SLOT(toggleLocalStorage(bool))); + toggleLocalStorage->setCheckable(true); + toggleLocalStorage->setChecked(m_windowOptions.useLocalStorage); + + QAction* toggleOfflineStorageDatabase = toolsMenu->addAction("Enable Offline Storage Database", this, SLOT(toggleOfflineStorageDatabase(bool))); + toggleOfflineStorageDatabase->setCheckable(true); + toggleOfflineStorageDatabase->setChecked(m_windowOptions.useOfflineStorageDatabase); + + QAction* toggleOfflineWebApplicationCache = toolsMenu->addAction("Enable Offline Web Application Cache", this, SLOT(toggleOfflineWebApplicationCache(bool))); + toggleOfflineWebApplicationCache->setCheckable(true); + toggleOfflineWebApplicationCache->setChecked(m_windowOptions.useOfflineWebApplicationCache); + + QAction* offlineStorageDefaultQuotaAction = toolsMenu->addAction("Set Offline Storage Default Quota Size", this, SLOT(setOfflineStorageDefaultQuota())); + offlineStorageDefaultQuotaAction->setCheckable(true); + offlineStorageDefaultQuotaAction->setChecked(m_windowOptions.offlineStorageDefaultQuotaSize); + + toolsMenu->addSeparator(); + QAction* userAgentAction = toolsMenu->addAction("Change User Agent", this, SLOT(showUserAgentDialog())); userAgentAction->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_U)); @@ -367,6 +390,14 @@ void LauncherWindow::createChrome() QMenu* settingsMenu = menuBar()->addMenu("&Settings"); + QAction* toggleAutoLoadImages = settingsMenu->addAction("Disable Auto Load Images", this, SLOT(toggleAutoLoadImages(bool))); + toggleAutoLoadImages->setCheckable(true); + toggleAutoLoadImages->setChecked(false); + + QAction* togglePlugins = settingsMenu->addAction("Disable Plugins", this, SLOT(togglePlugins(bool))); + togglePlugins->setCheckable(true); + togglePlugins->setChecked(false); + QAction* toggleInterruptingJavaScripteEnabled = settingsMenu->addAction("Enable interrupting js scripts", this, SLOT(toggleInterruptingJavaScriptEnabled(bool))); toggleInterruptingJavaScripteEnabled->setCheckable(true); toggleInterruptingJavaScripteEnabled->setChecked(false); @@ -530,8 +561,13 @@ void LauncherWindow::loadStarted() void LauncherWindow::loadFinished() { QUrl url = page()->mainFrame()->url(); - setAddressUrl(url.toString(QUrl::RemoveUserInfo)); addCompleterEntry(url); + if (m_inputUrl.isEmpty()) + setAddressUrl(url.toString(QUrl::RemoveUserInfo)); + else { + setAddressUrl(m_inputUrl); + m_inputUrl = QString(); + } } void LauncherWindow::showLinkHover(const QString &link, const QString &toolTip) @@ -770,6 +806,16 @@ void LauncherWindow::toggleJavascriptCanOpenWindows(bool enable) page()->settings()->setAttribute(QWebSettings::JavascriptCanOpenWindows, enable); } +void LauncherWindow::toggleAutoLoadImages(bool enable) +{ + page()->settings()->setAttribute(QWebSettings::AutoLoadImages, !enable); +} + +void LauncherWindow::togglePlugins(bool enable) +{ + page()->settings()->setAttribute(QWebSettings::PluginsEnabled, !enable); +} + #if defined(QT_CONFIGURED_WITH_OPENGL) void LauncherWindow::toggleQGLWidgetViewport(bool enable) { @@ -874,6 +920,40 @@ void LauncherWindow::updateFPS(int fps) #endif } +void LauncherWindow::toggleLocalStorage(bool toggle) +{ + m_windowOptions.useLocalStorage = toggle; + page()->settings()->setAttribute(QWebSettings::LocalStorageEnabled, toggle); +} + +void LauncherWindow::toggleOfflineStorageDatabase(bool toggle) +{ + m_windowOptions.useOfflineStorageDatabase = toggle; + page()->settings()->setAttribute(QWebSettings::OfflineStorageDatabaseEnabled, toggle); +} + +void LauncherWindow::toggleOfflineWebApplicationCache(bool toggle) +{ + m_windowOptions.useOfflineWebApplicationCache = toggle; + page()->settings()->setAttribute(QWebSettings::OfflineWebApplicationCacheEnabled, toggle); +} + +void LauncherWindow::setOfflineStorageDefaultQuota() +{ + // For command line execution, quota size is taken from command line. + if (m_windowOptions.offlineStorageDefaultQuotaSize) + page()->settings()->setOfflineStorageDefaultQuota(m_windowOptions.offlineStorageDefaultQuotaSize); + else { +#ifndef QT_NO_INPUTDIALOG + bool ok; + // Maximum size is set to 25 * 1024 * 1024. + int quotaSize = QInputDialog::getInt(this, "Offline Storage Default Quota Size" , "Quota Size", 0, 0, 26214400, 1, &ok); + if (ok) + page()->settings()->setOfflineStorageDefaultQuota(quotaSize); +#endif + } +} + LauncherWindow* LauncherWindow::newWindow() { LauncherWindow* mw = new LauncherWindow(&m_windowOptions); diff --git a/Tools/QtTestBrowser/launcherwindow.h b/Tools/QtTestBrowser/launcherwindow.h index 849b15d..e13858e 100644 --- a/Tools/QtTestBrowser/launcherwindow.h +++ b/Tools/QtTestBrowser/launcherwindow.h @@ -94,6 +94,10 @@ public: , showFrameRate(false) , resizesToContents(false) , viewportUpdateMode(QGraphicsView::MinimalViewportUpdate) + , useLocalStorage(false) + , useOfflineStorageDatabase(false) + , useOfflineWebApplicationCache(false) + , offlineStorageDefaultQuotaSize(0) #if defined(QT_CONFIGURED_WITH_OPENGL) , useQGLWidgetViewport(false) #endif @@ -110,6 +114,10 @@ public: bool showFrameRate; bool resizesToContents; QGraphicsView::ViewportUpdateMode viewportUpdateMode; + bool useLocalStorage; + bool useOfflineStorageDatabase; + bool useOfflineWebApplicationCache; + quint64 offlineStorageDefaultQuotaSize; #if defined(QT_CONFIGURED_WITH_OPENGL) bool useQGLWidgetViewport; #endif @@ -163,6 +171,12 @@ protected slots: void toggleFrameFlattening(bool toggle); void toggleInterruptingJavaScriptEnabled(bool enable); void toggleJavascriptCanOpenWindows(bool enable); + void toggleAutoLoadImages(bool enable); + void togglePlugins(bool enable); + void toggleLocalStorage(bool toggle); + void toggleOfflineStorageDatabase(bool toggle); + void toggleOfflineWebApplicationCache(bool toggle); + void setOfflineStorageDefaultQuota(); #if defined(QT_CONFIGURED_WITH_OPENGL) void toggleQGLWidgetViewport(bool enable); @@ -206,6 +220,8 @@ private: QPropertyAnimation* m_zoomAnimation; QList m_touchPoints; bool m_touchMocking; + + QString m_inputUrl; }; #endif diff --git a/Tools/QtTestBrowser/locationedit.h b/Tools/QtTestBrowser/locationedit.h index 752497e..962b422 100644 --- a/Tools/QtTestBrowser/locationedit.h +++ b/Tools/QtTestBrowser/locationedit.h @@ -28,6 +28,8 @@ #ifndef locationedit_h #define locationedit_h +#include + #ifndef QT_NO_INPUTDIALOG #include diff --git a/Tools/QtTestBrowser/main.cpp b/Tools/QtTestBrowser/main.cpp index ec5f1d9..eecd9c3 100644 --- a/Tools/QtTestBrowser/main.cpp +++ b/Tools/QtTestBrowser/main.cpp @@ -164,18 +164,18 @@ void LauncherApplication::handleUserOptions() } if (args.contains("-local-storage-enabled")) - QWebSettings::globalSettings()->setAttribute(QWebSettings::LocalStorageEnabled, true); + windowOptions.useLocalStorage = true; if (args.contains("-offline-storage-database-enabled")) - QWebSettings::globalSettings()->setAttribute(QWebSettings::OfflineStorageDatabaseEnabled, true); + windowOptions.useOfflineStorageDatabase = true; if (args.contains("-offline-web-application-cache-enabled")) - QWebSettings::globalSettings()->setAttribute(QWebSettings::OfflineWebApplicationCacheEnabled, true); + windowOptions.useOfflineWebApplicationCache = true; int setOfflineStorageDefaultQuotaIndex = args.indexOf("-set-offline-storage-default-quota"); if (setOfflineStorageDefaultQuotaIndex != -1) { - int maxSize = takeOptionValue(&args, setOfflineStorageDefaultQuotaIndex).toInt(); - QWebSettings::globalSettings()->setOfflineStorageDefaultQuota(maxSize); + unsigned int maxSize = takeOptionValue(&args, setOfflineStorageDefaultQuotaIndex).toUInt(); + windowOptions.offlineStorageDefaultQuotaSize = maxSize; } if (defaultForAnimations) diff --git a/Tools/QtTestBrowser/mainwindow.cpp b/Tools/QtTestBrowser/mainwindow.cpp index 9f4aec5..e2ea41d 100644 --- a/Tools/QtTestBrowser/mainwindow.cpp +++ b/Tools/QtTestBrowser/mainwindow.cpp @@ -171,6 +171,14 @@ void MainWindow::load(const QUrl& url) page()->mainFrame()->load(url); } +QString MainWindow::addressUrl() const +{ +#ifndef QT_NO_INPUTDIALOG + return urlEdit->text(); +#endif + return QString(); +} + void MainWindow::changeLocation() { #ifndef QT_NO_INPUTDIALOG diff --git a/Tools/QtTestBrowser/mainwindow.h b/Tools/QtTestBrowser/mainwindow.h index 3a39d57..08fa81e 100644 --- a/Tools/QtTestBrowser/mainwindow.h +++ b/Tools/QtTestBrowser/mainwindow.h @@ -59,6 +59,9 @@ protected slots: void openLocation(); void changeLocation(); +protected: + QString addressUrl() const; + private: void buildUI(); diff --git a/Tools/QueueStatusServer/templates/submittoews.html b/Tools/QueueStatusServer/templates/submittoews.html index fb9d8aa..935d7bb 100644 --- a/Tools/QueueStatusServer/templates/submittoews.html +++ b/Tools/QueueStatusServer/templates/submittoews.html @@ -1,3 +1,3 @@
-Patch to submit: +Attachment id of patch to submit:
diff --git a/Tools/Scripts/build-webkit b/Tools/Scripts/build-webkit index 0b58113..74015ec 100755 --- a/Tools/Scripts/build-webkit +++ b/Tools/Scripts/build-webkit @@ -85,12 +85,14 @@ my ( $javaScriptDebuggerSupport, $linkPrefetchSupport, $mathmlSupport, + $mediaStatisticsSupport, $meterTagSupport, $netscapePluginSupport, $notificationsSupport, $offlineWebApplicationSupport, $orientationEventsSupport, $progressTagSupport, + $registerProtocolHandlerSupport, $sharedWorkersSupport, $svgSupport, $svgAnimationSupport, @@ -116,8 +118,8 @@ my ( ); my @features = ( - { option => "3d-canvas", desc => "Toggle 3D canvas support", - define => "ENABLE_3D_CANVAS", default => (isAppleMacWebKit() && !isTiger() && !isLeopard()), value => \$threeDCanvasSupport }, + { option => "3d-canvas", desc => "Toggle 3D canvas (WebGL) support", + define => "ENABLE_WEBGL", default => (isAppleMacWebKit() && !isTiger() && !isLeopard()), value => \$threeDCanvasSupport }, { option => "3d-rendering", desc => "Toggle 3D rendering support", define => "ENABLE_3D_RENDERING", default => (isAppleMacWebKit() && !isTiger()), value => \$threeDRenderingSupport }, @@ -194,6 +196,9 @@ my @features = ( { option => "mathml", desc => "Toggle MathML support", define => "ENABLE_MATHML", default => 1, value => \$mathmlSupport }, + { option => "media-statistics", desc => "Toggle Media Statistics support", + define => "ENABLE_MEDIA_STATISTICS", default => 0, value => \$mediaStatisticsSupport }, + { option => "meter-tag", desc => "Meter Tag support", define => "ENABLE_METER_TAG", default => !isGtk() && !isAppleWinWebKit(), value => \$meterTagSupport }, @@ -212,6 +217,9 @@ my @features = ( { option => "progress-tag", desc => "Progress Tag support", define => "ENABLE_PROGRESS_TAG", default => 1, value => \$progressTagSupport }, + { option => "register-protocol-handler", desc => "Register Protocol Handler support", + define => "ENABLE_REGISTER_PROTOCOL_HANDLER", default => 0, value => \$registerProtocolHandlerSupport }, + { option => "system-malloc", desc => "Toggle system allocator instead of TCmalloc", define => "USE_SYSTEM_MALLOC", default => 0, value => \$systemMallocSupport }, @@ -405,7 +413,7 @@ if (isGtk()) { { my ($feature, $isEnabled, $defaultValue) = @_; return "" if $defaultValue == $isEnabled; - return $feature . "=" . ($isEnabled ? $feature : " "); + return $feature . "=" . ($isEnabled ? $feature : ""); } foreach (@features) { diff --git a/Tools/Scripts/check-inspector-strings b/Tools/Scripts/check-inspector-strings index 82c08d7..0350aca 100755 --- a/Tools/Scripts/check-inspector-strings +++ b/Tools/Scripts/check-inspector-strings @@ -54,6 +54,9 @@ class StringsExtractor(ProcessorBase): def should_process(self, file_path): return file_path.endswith(".js") and (not file_path.endswith("InjectedScript.js")) + def decode_unicode_escapes(self, s): + return eval("ur\"" + s + "\"") + def process(self, lines, file_path, line_numbers=None): for line in lines: comment_start = line.find("//") @@ -63,7 +66,7 @@ class StringsExtractor(ProcessorBase): for pattern in self._patterns: line_strings = re.findall(pattern, line) for string in line_strings: - self.strings[index].append(string) + self.strings[index].append(self.decode_unicode_escapes(string)) index += 1 class LocalizedStringsExtractor: @@ -113,3 +116,10 @@ if __name__ == "__main__": unused_strings = old_strings - strings for s in unused_strings: _log.info("Unused: \"%s\"" % (s)) + + localized_strings_duplicates = {} + for s in localized_strings_extractor.localized_strings: + if s in localized_strings_duplicates: + _log.info("Duplicate: \"%s\"" % (s)) + else: + localized_strings_duplicates.setdefault(s) diff --git a/Tools/Scripts/do-webcore-rename b/Tools/Scripts/do-webcore-rename index aaa1eee..6dbfc1f 100755 --- a/Tools/Scripts/do-webcore-rename +++ b/Tools/Scripts/do-webcore-rename @@ -72,7 +72,7 @@ my @paths; find(\&wanted, "Source/JavaScriptCore"); find(\&wanted, "Source/JavaScriptGlue"); find(\&wanted, "Source/WebCore"); -find(\&wanted, "WebKit"); +find(\&wanted, "Source/WebKit"); find(\&wanted, "Source/WebKit2"); find(\&wanted, "Tools/DumpRenderTree"); diff --git a/Tools/Scripts/old-run-webkit-tests b/Tools/Scripts/old-run-webkit-tests index 79e2d9e..c56cb1c 100755 --- a/Tools/Scripts/old-run-webkit-tests +++ b/Tools/Scripts/old-run-webkit-tests @@ -77,6 +77,7 @@ use POSIX; sub buildPlatformResultHierarchy(); sub buildPlatformTestHierarchy(@); +sub captureSavedCrashLog($); sub checkPythonVersion(); sub closeCygpaths(); sub closeDumpTool(); @@ -89,6 +90,7 @@ sub dumpToolDidCrash(); sub epiloguesAndPrologues($$); sub expectedDirectoryForTest($;$;$); sub fileNameWithNumber($$); +sub findNewestFileMatchingGlob($); sub htmlForResultsSection(\@$&); sub isTextOnlyTest($); sub launchWithEnv(\@\%); @@ -189,6 +191,9 @@ my $actualTag = "actual"; my $prettyDiffTag = "pretty-diff"; my $diffsTag = "diffs"; my $errorTag = "stderr"; +my $crashLogTag = "crash-log"; + +my $windowsCrashLogFilePrefix = "CrashLog"; # These are defined here instead of closer to where they are used so that they # will always be accessible from the END block that uses them, even if the user @@ -1731,7 +1736,9 @@ sub testCrashedOrTimedOut($$$$$$) kill 9, $dumpToolPID unless $didCrash; closeDumpTool(); - + + captureSavedCrashLog($base) if $didCrash; + return unless isCygwin() && !$didCrash && $base =~ /^http/; # On Cygwin, http tests timing out can be a symptom of a non-responsive httpd. # If we timed out running an http test, try restarting httpd. @@ -1739,6 +1746,51 @@ sub testCrashedOrTimedOut($$$$$$) configureAndOpenHTTPDIfNeeded(); } +sub captureSavedCrashLog($) +{ + my ($base) = @_; + + my $crashLog; + + my $glob; + if (isCygwin()) { + $glob = File::Spec->catfile($testResultsDirectory, $windowsCrashLogFilePrefix . "*.txt"); + } elsif (isAppleMacWebKit()) { + $glob = File::Spec->catfile("~", "Library", "Logs", "CrashReporter", $dumpToolName . "_*.crash"); + + # Even though the dump tool has exited, CrashReporter might still be running. We need to + # wait for it to exit to ensure it has saved its crash log to disk. For simplicitly, we'll + # assume that the ReportCrash process with the highest PID is the one we want. + if (my @reportCrashPIDs = sort map { /^\s*(\d+)/; $1 } grep { /ReportCrash/ } `/bin/ps x`) { + my $reportCrashPID = $reportCrashPIDs[$#reportCrashPIDs]; + # We use kill instead of waitpid because ReportCrash is not one of our child processes. + usleep(250000) while kill(0, $reportCrashPID) > 0; + } + } + + # We assume that the newest crash log in matching the glob is the one that corresponds to the crash that just occurred. + if (my $newestCrashLog = findNewestFileMatchingGlob($glob)) { + # The crash log must have been created after this script started running. + $crashLog = $newestCrashLog if -M $newestCrashLog < 0; + } + + return unless $crashLog; + + move($crashLog, File::Spec->catfile($testResultsDirectory, "$base-$crashLogTag.txt")); +} + +sub findNewestFileMatchingGlob($) +{ + my ($glob) = @_; + + my @paths = glob $glob; + return unless @paths; + + my @pathsAndTimes = map { [$_, -M $_] } @paths; + @pathsAndTimes = sort { $b->[1] <=> $a->[1] } @pathsAndTimes; + return $pathsAndTimes[$#pathsAndTimes]->[0]; +} + sub printFailureMessageForTest($$) { my ($test, $description) = @_; @@ -1865,7 +1917,9 @@ sub htmlForResultsSection(\@$&) push @html, ""; push @html, "$test"; foreach my $link (@{&{$linkGetter}($test)}) { - push @html, "{href}\">$link->{text}"; + push @html, ""; + push @html, "{href}\">$link->{text}" if -f File::Spec->catfile($testResultsDirectory, $link->{href}); + push @html, ""; } push @html, ""; } @@ -1911,6 +1965,63 @@ sub linksForMismatchTest return \@links; } +sub crashLocation($) +{ + my ($base) = @_; + + my $crashLogFile = File::Spec->catfile($testResultsDirectory, "$base-$crashLogTag.txt"); + + if (isCygwin()) { + # We're looking for the following text: + # + # FOLLOWUP_IP: + # module!function+offset [file:line] + # + # The second contains the function that crashed (or the function that ended up jumping to a bad + # address, as in the case of a null function pointer). + + open LOG, "<", $crashLogFile or return; + while (my $line = ) { + last if $line =~ /^FOLLOWUP_IP:/; + } + my $desiredLine = ; + close LOG; + + return unless $desiredLine; + + # Just take everything up to the first space (which is where the file/line information should + # start). + $desiredLine =~ /^(\S+)/; + return $1; + } + + if (isAppleMacWebKit()) { + # We're looking for the following text: + # + # Thread M Crashed: + # N module address function + offset (file:line) + # + # Some lines might have a module of "???" if we've jumped to a bad address. We should skip + # past those. + + open LOG, "<", $crashLogFile or return; + while (my $line = ) { + last if $line =~ /^Thread \d+ Crashed:/; + } + my $location; + while (my $line = ) { + $line =~ /^\d+\s+(\S+)\s+\S+ (.* \+ \d+)/ or next; + my $module = $1; + my $functionAndOffset = $2; + next if $module eq "???"; + $location = "$module: $functionAndOffset"; + last; + } + close LOG; + return $location; + } +} + sub linksForErrorTest { my ($test) = @_; @@ -1919,8 +2030,14 @@ sub linksForErrorTest my $base = stripExtension($test); + my $crashLogText = "crash log"; + if (my $crashLocation = crashLocation($base)) { + $crashLogText .= " (" . $crashLocation . ")"; + } + push @links, @{linksForExpectedAndActualResults($base)}; push @links, { href => "$base-$errorTag.txt", text => "stderr" }; + push @links, { href => "$base-$crashLogTag.txt", text => $crashLogText }; return \@links; } @@ -1951,6 +2068,7 @@ sub deleteExpectedAndActualResults($) unlink "$testResultsDirectory/$base-$actualTag.txt"; unlink "$testResultsDirectory/$base-$diffsTag.txt"; unlink "$testResultsDirectory/$base-$errorTag.txt"; + unlink "$testResultsDirectory/$base-$crashLogTag.txt"; } sub recordActualResultsAndDiff($$) @@ -2450,6 +2568,37 @@ sub stopRunningTestsEarlyIfNeeded() return 0; } +# Store this at global scope so it won't be GCed (and thus unlinked) until the program exits. +my $debuggerTempDirectory; + +sub createDebuggerCommandFile() +{ + return unless isCygwin(); + + my @commands = ( + '.logopen /t "' . toWindowsPath($testResultsDirectory) . "\\" . $windowsCrashLogFilePrefix . '.txt"', + '.srcpath "' . toWindowsPath(sourceDir()) . '"', + '!analyze -vv', + '~*kpn', + 'q', + ); + + $debuggerTempDirectory = File::Temp->newdir; + + my $commandFile = File::Spec->catfile($debuggerTempDirectory, "debugger-commands.txt"); + unless (open COMMANDS, '>', $commandFile) { + print "Failed to open $commandFile. Crash logs will not be saved.\n"; + return; + } + print COMMANDS join("\n", @commands), "\n"; + unless (close COMMANDS) { + print "Failed to write to $commandFile. Crash logs will not be saved.\n"; + return; + } + + return $commandFile; +} + sub setUpWindowsCrashLogSaving() { return unless isCygwin(); @@ -2468,8 +2617,23 @@ sub setUpWindowsCrashLogSaving() } } + # If we used -c (instead of -cf) we could pass the commands directly on the command line. But + # when the commands include multiple quoted paths (e.g., for .logopen and .srcpath), Windows + # fails to invoke the post-mortem debugger at all (perhaps due to a bug in Windows's command + # line parsing). So we save the commands to a file instead and tell the debugger to execute them + # using -cf. + my $commandFile = createDebuggerCommandFile() or return; + + my @options = ( + '-p %ld', + '-e %ld', + '-g', + '-lines', + '-cf "' . toWindowsPath($commandFile) . '"', + ); + my %values = ( - Debugger => '"' . toWindowsPath($ntsdPath) . '" -p %ld -e %ld -g -lines -c ".logopen /t \"' . toWindowsPath($testResultsDirectory) . '\CrashLog.txt\";!analyze -vv;~*kpn;q"', + Debugger => '"' . toWindowsPath($ntsdPath) . '" ' . join(' ', @options), Auto => 1 ); diff --git a/Tools/Scripts/rebaseline-chromium-webkit-tests b/Tools/Scripts/rebaseline-chromium-webkit-tests index 8d14b86..806ca17 100755 --- a/Tools/Scripts/rebaseline-chromium-webkit-tests +++ b/Tools/Scripts/rebaseline-chromium-webkit-tests @@ -41,4 +41,4 @@ sys.path.append(os.path.join(webkitpy_directory, "thirdparty")) import rebaseline_chromium_webkit_tests if __name__ == '__main__': - rebaseline_chromium_webkit_tests.main() + rebaseline_chromium_webkit_tests.main(sys.argv[1:]) diff --git a/Tools/Scripts/run-chromium-webkit-unit-tests b/Tools/Scripts/run-chromium-webkit-unit-tests index 62646af..1e2b0aa 100755 --- a/Tools/Scripts/run-chromium-webkit-unit-tests +++ b/Tools/Scripts/run-chromium-webkit-unit-tests @@ -41,9 +41,9 @@ setConfiguration(); my $pathToBinary; if (isDarwin()) { - $pathToBinary = "WebKit/chromium/xcodebuild/" . configuration() . "/webkit_unit_tests"; + $pathToBinary = "Source/WebKit/chromium/xcodebuild/" . configuration() . "/webkit_unit_tests"; } elsif (isCygwin() || isWindows()) { - $pathToBinary = "WebKit/chromium/" . configuration() . "/webkit_unit_tests.exe"; + $pathToBinary = "Source/WebKit/chromium/" . configuration() . "/webkit_unit_tests.exe"; } elsif (isLinux()) { $pathToBinary = "out/" . configuration() . "/webkit_unit_tests"; } diff --git a/Tools/Scripts/run-webkit-httpd b/Tools/Scripts/run-webkit-httpd index 9ea2551..31b469e 100755 --- a/Tools/Scripts/run-webkit-httpd +++ b/Tools/Scripts/run-webkit-httpd @@ -2,6 +2,7 @@ # Copyright (C) 2005, 2006, 2007 Apple Inc. All rights reserved. # Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com) +# Copyright (C) 2011 Research In Motion Limited. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -69,6 +70,7 @@ setConfiguration(); my $productDir = productDir(); chdirWebKit(); my $testDirectory = File::Spec->catfile(getcwd(), "LayoutTests"); +$testDirectory = convertMsysPath($testDirectory) if isMsys(); my $listen = "127.0.0.1:$httpdPort"; $listen = "$httpdPort" if ($allInterfaces); @@ -82,14 +84,16 @@ print "Press Ctrl+C to stop it.\n\n"; my @args = ( "-C", "Listen $listen", - "-c", "CustomLog |/usr/bin/tee common", - "-c", "ErrorLog |/usr/bin/tee", - # Run in single-process mode, do not detach from the controlling terminal. - "-X", # Disable Keep-Alive support. Makes testing in multiple browsers easier (no need to wait # for another browser's connection to expire). "-c", "KeepAlive 0" ); +push @args, ( + "-c", "CustomLog |/usr/bin/tee common", + "-c", "ErrorLog |/usr/bin/tee", + # Run in single-process mode, do not detach from the controlling terminal. + "-X", +) unless isMsys(); my @defaultArgs = getDefaultConfigForTestDirectory($testDirectory); @args = (@defaultArgs, @args); diff --git a/Tools/Scripts/update-webkit b/Tools/Scripts/update-webkit index 6d3e0ee..aca56b8 100755 --- a/Tools/Scripts/update-webkit +++ b/Tools/Scripts/update-webkit @@ -50,9 +50,6 @@ determineIsChromium(); chdirWebKit(); -my $isGit = isGit(); -my $isSVN = isSVN(); - my $getOptionsResult = GetOptions( 'h|help' => \$showHelp, 'q|quiet' => \$quiet, @@ -77,14 +74,14 @@ push @svnOptions, '-q' if $quiet; push @svnOptions, qw(--accept postpone) if isSVNVersion16OrNewer(); print "Updating OpenSource\n" unless $quiet; -runSvnUpdate() if $isSVN; -runGitUpdate() if $isGit; +runSvnUpdate() if isSVN(); +runGitUpdate() if isGit(); if (-d "../Internal") { chdir("../Internal"); print "Updating Internal\n" unless $quiet; - runSvnUpdate() if $isSVN; - runGitUpdate() if $isGit; + runSvnUpdate() if isSVN(); + runGitUpdate() if isGit(); } elsif (isChromium()) { # Workaround for https://bugs.webkit.org/show_bug.cgi?id=38926 # We should remove the following "if" block when we find a right fix. diff --git a/Tools/Scripts/webkit-patch b/Tools/Scripts/webkit-patch index 007f919..1eb8476 100755 --- a/Tools/Scripts/webkit-patch +++ b/Tools/Scripts/webkit-patch @@ -33,6 +33,7 @@ import logging import os +import signal import sys from webkitpy.common.system.logutils import configure_logging @@ -66,5 +67,7 @@ def main(): if __name__ == "__main__": - - main() + try: + main() + except KeyboardInterrupt: + sys.exit(signal.SIGINT + 128) diff --git a/Tools/Scripts/webkitdirs.pm b/Tools/Scripts/webkitdirs.pm index 0ead831..aa7bab7 100644 --- a/Tools/Scripts/webkitdirs.pm +++ b/Tools/Scripts/webkitdirs.pm @@ -356,7 +356,7 @@ sub productDir sub jscProductDir { my $productDir = productDir(); - $productDir .= "/Source/JavaScriptCore" if isQt(); + $productDir .= "/JavaScriptCore" if isQt(); $productDir .= "/$configuration" if (isQt() && isWindows()); $productDir .= "/Programs" if (isGtk() || isEfl()); @@ -1009,14 +1009,18 @@ sub checkRequiredSystemConfig { if (isDarwin()) { chomp(my $productVersion = `sw_vers -productVersion`); - if ($productVersion lt "10.4") { + if (eval "v$productVersion" lt v10.4) { print "*************************************************************\n"; print "Mac OS X Version 10.4.0 or later is required to build WebKit.\n"; print "You have " . $productVersion . ", thus the build will most likely fail.\n"; print "*************************************************************\n"; } - my $xcodeVersion = `xcodebuild -version`; - if ($xcodeVersion !~ /DevToolsCore-(\d+)/ || $1 < 747) { + my $xcodebuildVersionOutput = `xcodebuild -version`; + my $devToolsCoreVersion = ($xcodebuildVersionOutput =~ /DevToolsCore-(\d+)/) ? $1 : undef; + my $xcodeVersion = ($xcodebuildVersionOutput =~ /Xcode ([0-9](\.[0-9]+)*)/) ? $1 : undef; + if (!$devToolsCoreVersion && !$xcodeVersion + || $devToolsCoreVersion && $devToolsCoreVersion < 747 + || $xcodeVersion && eval "v$xcodeVersion" lt v2.3) { print "*************************************************************\n"; print "Xcode Version 2.3 or later is required to build WebKit.\n"; print "You have an earlier version of Xcode, thus the build will\n"; @@ -1501,7 +1505,7 @@ sub buildCMakeProject($@) push @buildArgs, "-DCMAKE_BUILD_TYPE=Release"; } - push @buildArgs, sourceDir(); + push @buildArgs, sourceDir() . "/Source"; $dir = File::Spec->catfile($dir, $config); File::Path::mkpath($dir); @@ -1571,6 +1575,13 @@ sub buildQMakeProject($@) push @buildArgs, "INSTALL_HEADERS=" . $installHeaders if defined($installHeaders); push @buildArgs, "INSTALL_LIBS=" . $installLibs if defined($installLibs); my $dir = File::Spec->canonpath(productDir()); + + + # On Symbian qmake needs to run in the same directory where the pro file is located. + if (isSymbian()) { + $dir = $sourceDir . "/Source"; + } + File::Path::mkpath($dir); chdir $dir or die "Failed to cd into " . $dir . "\n"; @@ -1580,7 +1591,7 @@ sub buildQMakeProject($@) my @dsQmakeArgs = @buildArgs; push @dsQmakeArgs, "-r"; - push @dsQmakeArgs, sourceDir() . "/DerivedSources.pro"; + push @dsQmakeArgs, sourceDir() . "/Source/DerivedSources.pro"; push @dsQmakeArgs, "-o Makefile.DerivedSources"; print "Calling '$qmakebin @dsQmakeArgs' in " . $dir . "\n\n"; my $result = system "$qmakebin @dsQmakeArgs"; @@ -1588,18 +1599,29 @@ sub buildQMakeProject($@) die "Failed while running $qmakebin to generate derived sources!\n"; } - my $dsMakefile = "Makefile.DerivedSources"; - - # Iterate over different source directories manually to workaround a problem with qmake+extraTargets+s60 - my @subdirs = ("Source/JavaScriptCore", "Source/WebCore", "Source/WebKit/qt/Api"); + # FIXME: Iterate over different source directories manually to workaround a problem with qmake+extraTargets+s60 + # To avoid overwriting of Makefile.DerivedSources in the root dir use Makefile.DerivedSources.Tools for Tools + my @subdirs = ("JavaScriptCore", "WebCore", "WebKit/qt/Api"); if (grep { $_ eq "CONFIG+=webkit2"} @buildArgs) { - push @subdirs, "Source/WebKit2"; - push @subdirs, "Tools/WebKitTestRunner"; - push @subdirs, "Tools/MiniBrowser"; + push @subdirs, "WebKit2"; + if ( -e sourceDir() ."/Tools/DerivedSources.pro" ) { + @dsQmakeArgs = @buildArgs; + push @dsQmakeArgs, "-r"; + push @dsQmakeArgs, sourceDir() . "/Tools/DerivedSources.pro"; + push @dsQmakeArgs, "-o Makefile.DerivedSources.Tools"; + print "Calling '$qmakebin @dsQmakeArgs' in " . $dir . "\n\n"; + my $result = system "$qmakebin @dsQmakeArgs"; + if ($result ne 0) { + die "Failed while running $qmakebin to generate derived sources for Tools!\n"; + } + push @subdirs, "MiniBrowser"; + push @subdirs, "WebKitTestRunner"; + } } for my $subdir (@subdirs) { - print "Calling '$make $makeargs -f $dsMakefile generated_files' in " . $dir . "/$subdir\n\n"; + my $dsMakefile = "Makefile.DerivedSources"; + print "Calling '$make $makeargs -C $subdir -f $dsMakefile generated_files' in " . $dir . "/$subdir\n\n"; if ($make eq "nmake") { my $subdirWindows = $subdir; $subdirWindows =~ s:/:\\:g; @@ -1626,8 +1648,7 @@ sub buildQMakeProject($@) } } - push @buildArgs, sourceDir() . "/WebKit.pro"; - + push @buildArgs, sourceDir() . "/Source/WebKit.pro"; print "Calling '$qmakebin @buildArgs' in " . $dir . "\n\n"; print "Installation headers directory: $installHeaders\n" if(defined($installHeaders)); print "Installation libraries directory: $installLibs\n" if(defined($installLibs)); @@ -1637,8 +1658,24 @@ sub buildQMakeProject($@) die "Failed to setup build environment using $qmakebin!\n"; } + $buildArgs[-1] = sourceDir() . "/Tools/Tools.pro"; + my $makefile = "Makefile.Tools"; + + # On Symbian qmake needs to run in the same directory where the pro file is located. + if (isSymbian()) { + $dir = $sourceDir . "/Tools"; + chdir $dir or die "Failed to cd into " . $dir . "\n"; + $makefile = "bld.inf"; + } + + print "Calling '$qmakebin @buildArgs -o $makefile' in " . $dir . "\n\n"; + $result = system "$qmakebin @buildArgs -o $makefile"; + if ($result ne 0) { + die "Failed to setup build environment using $qmakebin!\n"; + } + # Manually create makefiles for the examples so we don't build by default - my $examplesDir = $dir . "/Source/WebKit/qt/examples"; + my $examplesDir = $dir . "/WebKit/qt/examples"; File::Path::mkpath($examplesDir); $buildArgs[-1] = sourceDir() . "/Source/WebKit/qt/examples/examples.pro"; chdir $examplesDir or die; @@ -1647,9 +1684,16 @@ sub buildQMakeProject($@) die "Failed to create makefiles for the examples!\n" if $result ne 0; chdir $dir or die; + my $makeTools = "echo No Makefile for Tools. Skipping make"; + + if (-e "$dir/$makefile") { + $makeTools = "$make $makeargs -f $makefile"; + } + if ($clean) { print "Calling '$make $makeargs distclean' in " . $dir . "\n\n"; $result = system "$make $makeargs distclean"; + $result = $result || system "$makeTools distclean"; } elsif (isSymbian()) { print "\n\nWebKit is now configured for building, but you have to make\n"; print "a choice about the target yourself. To start the build run:\n\n"; @@ -1657,6 +1701,7 @@ sub buildQMakeProject($@) } else { print "Calling '$make $makeargs' in " . $dir . "\n\n"; $result = system "$make $makeargs"; + $result = $result || system "$makeTools"; } chdir ".." or die; diff --git a/Tools/Scripts/webkitperl/httpd.pm b/Tools/Scripts/webkitperl/httpd.pm index b415db6..b73904d 100644 --- a/Tools/Scripts/webkitperl/httpd.pm +++ b/Tools/Scripts/webkitperl/httpd.pm @@ -1,6 +1,7 @@ # Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved # Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com) # Copyright (C) 2010 Andras Becsi (abecsi@inf.u-szeged.hu), University of Szeged +# Copyright (C) 2011 Research In Motion Limited. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -58,6 +59,7 @@ BEGIN { } my $tmpDir = "/tmp"; +$tmpDir = convertMsysPath($tmpDir) if isMsys(); my $httpdLockPrefix = "WebKitHttpd.lock."; my $myLockFile; my $exclusiveLockFile = File::Spec->catfile($tmpDir, "WebKit.lock"); @@ -76,6 +78,8 @@ sub getHTTPDPath { if (isDebianBased()) { $httpdPath = "/usr/sbin/apache2"; + } elsif (isMsys()) { + $httpdPath = 'c:\program files\apache software foundation\apache2.2\bin\httpd.exe'; } else { $httpdPath = "/usr/sbin/httpd"; } @@ -100,13 +104,16 @@ sub getDefaultConfigForTestDirectory # Setup a link to where the js test templates are stored, use -c so that mod_alias will already be loaded. "-c", "Alias /js-test-resources \"$jsTestResourcesDirectory\"", "-c", "TypesConfig \"$typesConfig\"", - # Apache wouldn't run CGIs with permissions==700 otherwise - "-c", "User \"#$<\"", - "-c", "LockFile \"$httpdLockFile\"", "-c", "PidFile \"$httpdPidFile\"", "-c", "ScoreBoardFile \"$httpdScoreBoardFile\"", ); + push @httpdArgs, ( + # Apache wouldn't run CGIs with permissions==700 otherwise + "-c", "User \"#$<\"", + "-c", "LockFile \"$httpdLockFile\"" + ) unless isMsys(); + # FIXME: Enable this on Windows once is fixed # The version of Apache we use with Cygwin does not support SSL my $sslCertificate = "$testDirectory/http/conf/webkit-httpd.pem"; @@ -129,6 +136,8 @@ sub getHTTPDConfigPathForTestDirectory chmod(0755, "/usr/lib/apache/libphp4.dll"); } $httpdConfig = "$windowsConfDirectory/cygwin-httpd.conf"; + } elsif (isMsys()) { + $httpdConfig = "$testDirectory/http/conf/apache2-msys-httpd.conf"; } elsif (isDebianBased()) { $httpdConfig = "$testDirectory/http/conf/apache2-debian-httpd.conf"; } elsif (isFedoraBased()) { @@ -319,3 +328,13 @@ sub getWaitTime } return $waitTime; } + +sub convertMsysPath +{ + my ($path) = @_; + return unless isMsys(); + + $path = `cmd.exe //c echo $path`; + $path =~ s/\r\n$//; + return $path; +} diff --git a/Tools/Scripts/webkitpy/common/checkout/api.py b/Tools/Scripts/webkitpy/common/checkout/api.py index a87bb5a..170b822 100644 --- a/Tools/Scripts/webkitpy/common/checkout/api.py +++ b/Tools/Scripts/webkitpy/common/checkout/api.py @@ -33,6 +33,7 @@ from webkitpy.common.config import urls from webkitpy.common.checkout.changelog import ChangeLog from webkitpy.common.checkout.commitinfo import CommitInfo from webkitpy.common.checkout.scm import CommitMessage +from webkitpy.common.checkout.deps import DEPS from webkitpy.common.memoized import memoized from webkitpy.common.net.bugzilla import parse_bug_id from webkitpy.common.system.executive import Executive, run_command, ScriptError @@ -148,6 +149,9 @@ class Checkout(object): except ScriptError, e: pass # We might not have ChangeLogs. + def chromium_deps(self): + return DEPS(os.path.join(self._scm.checkout_root, "Source", "WebKit", "chromium", "DEPS")) + def apply_patch(self, patch, force=False): # It's possible that the patch was not made from the root directory. # We should detect and handle that case. diff --git a/Tools/Scripts/webkitpy/common/checkout/changelog.py b/Tools/Scripts/webkitpy/common/checkout/changelog.py index 07f905d..c81318c 100644 --- a/Tools/Scripts/webkitpy/common/checkout/changelog.py +++ b/Tools/Scripts/webkitpy/common/checkout/changelog.py @@ -36,9 +36,7 @@ import textwrap from webkitpy.common.system.deprecated_logging import log from webkitpy.common.config.committers import CommitterList -from webkitpy.common.config import urls from webkitpy.common.net.bugzilla import parse_bug_id -from webkitpy.tool.grammar import join_with_separators class ChangeLogEntry(object): @@ -145,29 +143,14 @@ class ChangeLog(object): lines = [self._wrap_line(line) for line in message.splitlines()] return "\n".join(lines) - # This probably does not belong in changelogs.py - def _message_for_revert(self, revision_list, reason, bug_url): - message = "Unreviewed, rolling out %s.\n" % join_with_separators(['r' + str(revision) for revision in revision_list]) - for revision in revision_list: - message += "%s\n" % urls.view_revision_url(revision) - if bug_url: - message += "%s\n" % bug_url - # Add an extra new line after the rollout links, before any reason. - message += "\n" - if reason: - message += "%s\n\n" % reason - return self._wrap_lines(message) - - def update_for_revert(self, revision_list, reason, bug_url=None): + def update_with_unreviewed_message(self, message): reviewed_by_regexp = re.compile( "%sReviewed by NOBODY \(OOPS!\)\." % self._changelog_indent) removing_boilerplate = False # inplace=1 creates a backup file and re-directs stdout to the file for line in fileinput.FileInput(self.path, inplace=1): if reviewed_by_regexp.search(line): - message_lines = self._message_for_revert(revision_list, - reason, - bug_url) + message_lines = self._wrap_lines(message) print reviewed_by_regexp.sub(message_lines, line), # Remove all the ChangeLog boilerplate between the Reviewed by # line and the first changed file. diff --git a/Tools/Scripts/webkitpy/common/checkout/changelog_unittest.py b/Tools/Scripts/webkitpy/common/checkout/changelog_unittest.py index 20c6cfa..299d509 100644 --- a/Tools/Scripts/webkitpy/common/checkout/changelog_unittest.py +++ b/Tools/Scripts/webkitpy/common/checkout/changelog_unittest.py @@ -142,89 +142,3 @@ class ChangeLogTest(unittest.TestCase): expected_contents = changelog_contents.replace("Need a short description and bug URL (OOPS!)", expected_message) os.remove(changelog_path) self.assertEquals(actual_contents, expected_contents) - - _revert_message = """ Unreviewed, rolling out r12345. - http://trac.webkit.org/changeset/12345 - http://example.com/123 - - This is a very long reason which should be long enough so that - _message_for_revert will need to wrap it. We'll also include - a - https://veryveryveryveryverylongbugurl.com/reallylongbugthingy.cgi?bug_id=12354 - link so that we can make sure we wrap that right too. -""" - - def test_message_for_revert(self): - changelog = ChangeLog("/fake/path") - long_reason = "This is a very long reason which should be long enough so that _message_for_revert will need to wrap it. We'll also include a https://veryveryveryveryverylongbugurl.com/reallylongbugthingy.cgi?bug_id=12354 link so that we can make sure we wrap that right too." - message = changelog._message_for_revert([12345], long_reason, "http://example.com/123") - self.assertEquals(message, self._revert_message) - - _revert_entry_with_bug_url = '''2009-08-19 Eric Seidel - - Unreviewed, rolling out r12345. - http://trac.webkit.org/changeset/12345 - http://example.com/123 - - Reason - - * Scripts/bugzilla-tool: -''' - - _revert_entry_without_bug_url = '''2009-08-19 Eric Seidel - - Unreviewed, rolling out r12345. - http://trac.webkit.org/changeset/12345 - - Reason - - * Scripts/bugzilla-tool: -''' - - _multiple_revert_entry_with_bug_url = '''2009-08-19 Eric Seidel - - Unreviewed, rolling out r12345, r12346, and r12347. - http://trac.webkit.org/changeset/12345 - http://trac.webkit.org/changeset/12346 - http://trac.webkit.org/changeset/12347 - http://example.com/123 - - Reason - - * Scripts/bugzilla-tool: -''' - - _multiple_revert_entry_without_bug_url = '''2009-08-19 Eric Seidel - - Unreviewed, rolling out r12345, r12346, and r12347. - http://trac.webkit.org/changeset/12345 - http://trac.webkit.org/changeset/12346 - http://trac.webkit.org/changeset/12347 - - Reason - - * Scripts/bugzilla-tool: -''' - - def _assert_update_for_revert_output(self, args, expected_entry): - changelog_contents = u"%s\n%s" % (self._new_entry_boilerplate, self._example_changelog) - changelog_path = self._write_tmp_file_with_contents(changelog_contents.encode("utf-8")) - changelog = ChangeLog(changelog_path) - changelog.update_for_revert(*args) - actual_entry = changelog.latest_entry() - os.remove(changelog_path) - self.assertEquals(actual_entry.contents(), expected_entry) - self.assertEquals(actual_entry.reviewer_text(), None) - # These checks could be removed to allow this to work on other entries: - self.assertEquals(actual_entry.author_name(), "Eric Seidel") - self.assertEquals(actual_entry.author_email(), "eric@webkit.org") - - def test_update_for_revert(self): - self._assert_update_for_revert_output([[12345], "Reason"], self._revert_entry_without_bug_url) - self._assert_update_for_revert_output([[12345], "Reason", "http://example.com/123"], self._revert_entry_with_bug_url) - self._assert_update_for_revert_output([[12345, 12346, 12347], "Reason"], self._multiple_revert_entry_without_bug_url) - self._assert_update_for_revert_output([[12345, 12346, 12347], "Reason", "http://example.com/123"], self._multiple_revert_entry_with_bug_url) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tools/Scripts/webkitpy/common/checkout/deps.py b/Tools/Scripts/webkitpy/common/checkout/deps.py new file mode 100644 index 0000000..6b87ff1 --- /dev/null +++ b/Tools/Scripts/webkitpy/common/checkout/deps.py @@ -0,0 +1,61 @@ +# Copyright (C) 2011, Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# WebKit's Python module for parsing and modifying ChangeLog files + +import codecs +import fileinput +import os.path +import re +import textwrap + + +class DEPS(object): + + _variable_regexp = r"\s+'%s':\s+'(?P\d+)'" + + def __init__(self, path): + self._path = path + + def read_variable(self, name): + pattern = re.compile(self._variable_regexp % name) + for line in fileinput.FileInput(self._path): + match = pattern.match(line) + if match: + return int(match.group("value")) + + def write_variable(self, name, value): + pattern = re.compile(self._variable_regexp % name) + replacement_line = " '%s': '%s'" % (name, value) + # inplace=1 creates a backup file and re-directs stdout to the file + for line in fileinput.FileInput(self._path, inplace=1): + if pattern.match(line): + print replacement_line + continue + # Trailing comma suppresses printing newline + print line, diff --git a/Tools/Scripts/webkitpy/common/checkout/scm.py b/Tools/Scripts/webkitpy/common/checkout/scm.py index 421c0dc..3fa2db5 100644 --- a/Tools/Scripts/webkitpy/common/checkout/scm.py +++ b/Tools/Scripts/webkitpy/common/checkout/scm.py @@ -34,10 +34,10 @@ import re import sys import shutil -from webkitpy.common.system.executive import Executive, run_command, ScriptError -from webkitpy.common.system.deprecated_logging import error, log -import webkitpy.common.system.ospath as ospath from webkitpy.common.memoized import memoized +from webkitpy.common.system.deprecated_logging import error, log +from webkitpy.common.system.executive import Executive, run_command, ScriptError +from webkitpy.common.system import ospath def find_checkout_root(): @@ -746,6 +746,22 @@ class Git(SCM): def display_name(self): return "git" + def prepend_svn_revision(self, diff): + revision = None + tries = 0 + while not revision and tries < 10: + # If the git checkout is not tracking an SVN repo, then svn_revision_from_git_commit throws. + try: + revision = self.svn_revision_from_git_commit('HEAD~' + str(tries)) + except: + return diff + tries += 1 + + if not revision: + return diff + + return "Subversion Revision: " + str(revision) + '\n' + diff + def create_patch(self, git_commit=None, changed_files=None): """Returns a byte array (str()) representing the patch file. Patch files are effectively binary since they may contain @@ -753,7 +769,7 @@ class Git(SCM): command = ['git', 'diff', '--binary', "--no-ext-diff", "--full-index", "-M", self.merge_base(git_commit), "--"] if changed_files: command += changed_files - return self.run(command, decode_output=False, cwd=self.checkout_root) + return self.prepend_svn_revision(self.run(command, decode_output=False, cwd=self.checkout_root)) def _run_git_svn_find_rev(self, arg): # git svn find-rev always exits 0, even when the revision or commit is not found. diff --git a/Tools/Scripts/webkitpy/common/checkout/scm_unittest.py b/Tools/Scripts/webkitpy/common/checkout/scm_unittest.py index 64122b4..decfae0 100644 --- a/Tools/Scripts/webkitpy/common/checkout/scm_unittest.py +++ b/Tools/Scripts/webkitpy/common/checkout/scm_unittest.py @@ -810,6 +810,16 @@ class GitTest(SCMTest): run_command(['git', 'config', '--add', 'svn-remote.svn.fetch', 'trunk:remote2']) self.assertEqual(self.tracking_scm.remote_branch_ref(), 'remote1') + def test_create_patch(self): + write_into_file_at_path('test_file_commit1', 'contents') + run_command(['git', 'add', 'test_file_commit1']) + scm = detect_scm_system(self.untracking_checkout_path) + scm.commit_locally_with_message('message') + + patch = scm.create_patch() + self.assertFalse(re.search(r'Subversion Revision:', patch)) + + class GitSVNTest(SCMTest): def _setup_git_checkout(self): @@ -1126,6 +1136,7 @@ class GitSVNTest(SCMTest): patch = scm.create_patch() self.assertTrue(re.search(r'test_file_commit2', patch)) self.assertTrue(re.search(r'test_file_commit1', patch)) + self.assertTrue(re.search(r'Subversion Revision: 5', patch)) def test_create_patch_with_changed_files(self): self._one_local_commit_plus_working_copy_changes() diff --git a/Tools/Scripts/webkitpy/common/config/committers.py b/Tools/Scripts/webkitpy/common/config/committers.py index f7d59fe..5c571ab 100644 --- a/Tools/Scripts/webkitpy/common/config/committers.py +++ b/Tools/Scripts/webkitpy/common/config/committers.py @@ -91,16 +91,17 @@ committers_unable_to_review = [ Committer("Cameron McCormack", "cam@webkit.org", "heycam"), Committer("Carlos Garcia Campos", ["cgarcia@igalia.com", "carlosgc@gnome.org", "carlosgc@webkit.org"], "KaL"), Committer("Carol Szabo", "carol.szabo@nokia.com"), - Committer("Chang Shu", "Chang.Shu@nokia.com"), + Committer("Chang Shu", ["Chang.Shu@nokia.com", "cshu@webkit.org"], "cshu"), Committer("Chris Evans", "cevans@google.com"), Committer("Chris Petersen", "cpetersen@apple.com", "cpetersen"), Committer("Chris Rogers", "crogers@google.com", "crogers"), Committer("Christian Dywan", ["christian@twotoasts.de", "christian@webkit.org"]), Committer("Collin Jackson", "collinj@webkit.org"), + Committer("Daniel Cheng", "dcheng@chromium.org", "dcheng"), Committer("David Smith", ["catfish.man@gmail.com", "dsmith@webkit.org"], "catfishman"), Committer("Dean Jackson", "dino@apple.com", "dino"), Committer("Diego Gonzalez", ["diegohcg@webkit.org", "diego.gonzalez@openbossa.org"], "diegohcg"), - Committer("Dirk Pranke", "dpranke@chromium.org"), + Committer("Dirk Pranke", "dpranke@chromium.org", "dpranke"), Committer("Drew Wilson", "atwilson@chromium.org", "atwilson"), Committer("Eli Fidler", "eli@staikos.net", "QBin"), Committer("Enrica Casucci", "enrica@apple.com"), @@ -113,6 +114,7 @@ committers_unable_to_review = [ Committer("Feng Qian", "feng@chromium.org"), Committer("Fumitoshi Ukai", "ukai@chromium.org", "ukai"), Committer("Gabor Loki", "loki@webkit.org", "loki04"), + Committer("Gabor Rapcsanyi", ["rgabor@webkit.org", "rgabor@inf.u-szeged.hu"], "rgabor"), Committer("Girish Ramakrishnan", ["girish@forwardbias.in", "ramakrishnan.girish@gmail.com"]), Committer("Graham Dennis", ["Graham.Dennis@gmail.com", "gdennis@webkit.org"]), Committer("Greg Bolsinga", "bolsinga@apple.com"), @@ -121,6 +123,7 @@ committers_unable_to_review = [ Committer("Hayato Ito", "hayato@chromium.org", "hayato"), Committer("Hin-Chung Lam", ["hclam@google.com", "hclam@chromium.org"]), Committer("Ilya Tikhonovsky", "loislo@chromium.org", "loislo"), + Committer("Ivan Krsti\u0107", "ike@apple.com"), Committer("Jakob Petsovits", ["jpetsovits@rim.com", "jpetso@gmx.at"], "jpetso"), Committer("Jakub Wieczorek", "jwieczorek@webkit.org", "fawek"), Committer("James Hawkins", ["jhawkins@chromium.org", "jhawkins@google.com"], "jhawkins"), @@ -137,6 +140,7 @@ committers_unable_to_review = [ Committer("John Gregg", ["johnnyg@google.com", "johnnyg@chromium.org"], "johnnyg"), Committer("John Knottenbelt", ["jknotten@chromium.org"], "jknotten"), Committer("Johnny Ding", ["jnd@chromium.org", "johnnyding.webkit@gmail.com"], "johnnyding"), + Committer("Joone Hur", ["joone.hur@collabora.co.uk", "joone@kldp.org", "joone@webkit.org"], "joone"), Committer("Joost de Valk", ["joost@webkit.org", "webkit-dev@joostdevalk.nl"], "Altha"), Committer("Julie Parent", ["jparent@google.com", "jparent@chromium.org"], "jparent"), Committer("Julien Chaffraix", ["jchaffraix@webkit.org", "julien.chaffraix@gmail.com"]), diff --git a/Tools/Scripts/webkitpy/common/config/urls.py b/Tools/Scripts/webkitpy/common/config/urls.py index dfa6d69..ddaef97 100644 --- a/Tools/Scripts/webkitpy/common/config/urls.py +++ b/Tools/Scripts/webkitpy/common/config/urls.py @@ -34,5 +34,6 @@ def view_source_url(local_path): def view_revision_url(revision_number): return "http://trac.webkit.org/changeset/%s" % revision_number +chromium_lkgr_url = "http://chromium-status.appspot.com/lkgr" contribution_guidelines = "http://webkit.org/coding/contributing.html" diff --git a/Tools/Scripts/webkitpy/common/net/buildbot/buildbot.py b/Tools/Scripts/webkitpy/common/net/buildbot/buildbot.py index 3cb6da5..76cd31d 100644 --- a/Tools/Scripts/webkitpy/common/net/buildbot/buildbot.py +++ b/Tools/Scripts/webkitpy/common/net/buildbot/buildbot.py @@ -266,10 +266,11 @@ class BuildBot(object): # See https://bugs.webkit.org/show_bug.cgi?id=33296 and related bugs. self.core_builder_names_regexps = [ "SnowLeopard.*Build", - "SnowLeopard.*\(Test", # Exclude WebKit2 for now. + "SnowLeopard.*\(Test", + "SnowLeopard.*\(WebKit2 Test", "Leopard", - "Tiger", "Windows.*Build", + "EFL", "GTK.*32", "GTK.*64.*Debug", # Disallow the 64-bit Release bot which is broken. "Qt", diff --git a/Tools/Scripts/webkitpy/common/net/buildbot/buildbot_unittest.py b/Tools/Scripts/webkitpy/common/net/buildbot/buildbot_unittest.py index 57290d1..f158827 100644 --- a/Tools/Scripts/webkitpy/common/net/buildbot/buildbot_unittest.py +++ b/Tools/Scripts/webkitpy/common/net/buildbot/buildbot_unittest.py @@ -222,7 +222,6 @@ class BuildBotTest(unittest.TestCase): # For complete testing, this list should match the list of builders at build.webkit.org: example_builders = [ - {'name': u'Tiger Intel Release', }, {'name': u'Leopard Intel Release (Build)', }, {'name': u'Leopard Intel Release (Tests)', }, {'name': u'Leopard Intel Debug (Build)', }, @@ -256,22 +255,23 @@ class BuildBotTest(unittest.TestCase): name_regexps = [ "SnowLeopard.*Build", "SnowLeopard.*\(Test", + "SnowLeopard.*\(WebKit2 Test", "Leopard", - "Tiger", "Windows.*Build", + "EFL", "GTK.*32", "GTK.*64.*Debug", # Disallow the 64-bit Release bot which is broken. "Qt", "Chromium.*Release$", ] expected_builders = [ - {'name': u'Tiger Intel Release', }, {'name': u'Leopard Intel Release (Build)', }, {'name': u'Leopard Intel Release (Tests)', }, {'name': u'Leopard Intel Debug (Build)', }, {'name': u'Leopard Intel Debug (Tests)', }, {'name': u'SnowLeopard Intel Release (Build)', }, {'name': u'SnowLeopard Intel Release (Tests)', }, + {'name': u'SnowLeopard Intel Release (WebKit2 Tests)', }, {'name': u'Windows Release (Build)', }, {'name': u'Windows Debug (Build)', }, {'name': u'GTK Linux 32-bit Release', }, diff --git a/Tools/Scripts/webkitpy/common/net/irc/ircbot.py b/Tools/Scripts/webkitpy/common/net/irc/ircbot.py index f742867..061a43c 100644 --- a/Tools/Scripts/webkitpy/common/net/irc/ircbot.py +++ b/Tools/Scripts/webkitpy/common/net/irc/ircbot.py @@ -26,7 +26,7 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import webkitpy.common.config.irc as config_irc +from webkitpy.common.config import irc as config_irc from webkitpy.common.thread.messagepump import MessagePump, MessagePumpDelegate from webkitpy.thirdparty.autoinstalled.irc import ircbot diff --git a/Tools/Scripts/webkitpy/common/net/layouttestresults.py b/Tools/Scripts/webkitpy/common/net/layouttestresults.py index 28caad4..249ecc9 100644 --- a/Tools/Scripts/webkitpy/common/net/layouttestresults.py +++ b/Tools/Scripts/webkitpy/common/net/layouttestresults.py @@ -51,11 +51,13 @@ class LayoutTestResults(object): timeout_key = u'Tests that timed out:' crash_key = u'Tests that caused the DumpRenderTree tool to crash:' missing_key = u'Tests that had no expected results (probably new):' + webprocess_crash_key = u'Tests that caused the Web process to crash:' expected_keys = [ stderr_key, fail_key, crash_key, + webprocess_crash_key, timeout_key, missing_key, ] @@ -87,6 +89,8 @@ class LayoutTestResults(object): return cls._failures_from_fail_row(row) if table_title == cls.crash_key: return [test_failures.FailureCrash()] + if table_title == cls.webprocess_crash_key: + return [test_failures.FailureCrash()] if table_title == cls.timeout_key: return [test_failures.FailureTimeout()] if table_title == cls.missing_key: diff --git a/Tools/Scripts/webkitpy/common/net/testoutput.py b/Tools/Scripts/webkitpy/common/net/testoutput.py new file mode 100644 index 0000000..37c1445 --- /dev/null +++ b/Tools/Scripts/webkitpy/common/net/testoutput.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python +# Copyright (C) 2010 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import os +import re + + +class NaiveImageDiffer(object): + def same_image(self, img1, img2): + return img1 == img2 + + +class TestOutput(object): + """Represents the output that a single layout test generates when it is run + on a particular platform. + Note that this is the raw output that is produced when the layout test is + run, not the results of the subsequent comparison between that output and + the expected output.""" + def __init__(self, platform, output_type, files): + self._output_type = output_type + self._files = files + file = files[0] # Pick some file to do test name calculation. + self._name = self._extract_test_name(file.name()) + self._is_actual = '-actual.' in file.name() + + self._platform = platform or self._extract_platform(file.name()) + + def _extract_platform(self, filename): + """Calculates the platform from the name of the file if it isn't known already""" + path = re.split(os.path.sep, filename) + if 'platform' in path: + return path[path.index('platform') + 1] + return None + + def _extract_test_name(self, filename): + path = re.split(os.path.sep, filename) + if 'LayoutTests' in path: + path = path[1 + path.index('LayoutTests'):] + if 'layout-test-results' in path: + path = path[1 + path.index('layout-test-results'):] + if 'platform' in path: + path = path[2 + path.index('platform'):] + + filename = path[-1] + filename = re.sub('-expected\..*$', '', filename) + filename = re.sub('-actual\..*$', '', filename) + path[-1] = filename + return os.path.sep.join(path) + + def save_to(self, path): + """Have the files in this TestOutput write themselves to the disk at the specified location.""" + for file in self._files: + file.save_to(path) + + def is_actual(self): + """Is this output the actual output of a test? (As opposed to expected output.)""" + return self._is_actual + + def name(self): + """The name of this test (doesn't include extension)""" + return self._name + + def __eq__(self, other): + return (other != None and + self.name() == other.name() and + self.type() == other.type() and + self.platform() == other.platform() and + self.is_actual() == other.is_actual() and + self.same_content(other)) + + def __hash__(self): + return hash(str(self.name()) + str(self.type()) + str(self.platform())) + + def is_new_baseline_for(self, other): + return (self.name() == other.name() and + self.type() == other.type() and + self.platform() == other.platform() and + self.is_actual() and + (not other.is_actual())) + + def __str__(self): + actual_str = '[A] ' if self.is_actual() else '' + return "TestOutput[%s/%s] %s%s" % (self._platform, self._output_type, actual_str, self.name()) + + def type(self): + return self._output_type + + def platform(self): + return self._platform + + def _path_to_platform(self): + """Returns the path that tests for this platform are stored in.""" + if self._platform is None: + return "" + else: + return os.path.join("self._platform", self._platform) + + def _save_expected_result(self, file, path): + path = os.path.join(path, self._path_to_platform()) + extension = os.path.splitext(file.name())[1] + filename = self.name() + '-expected' + extension + file.save_to(path, filename) + + def save_expected_results(self, path_to_layout_tests): + """Save the files of this TestOutput to the appropriate directory + inside the LayoutTests directory. Typically this means that these files + will be saved in "LayoutTests/platform//, or simply + LayoutTests if the platform is None.""" + for file in self._files: + self._save_expected_result(file, path_to_layout_tests) + + def delete(self): + """Deletes the files that comprise this TestOutput from disk. This + fails if the files are virtual files (eg: the files may reside inside a + remote zip file).""" + for file in self._files: + file.delete() + + +class TextTestOutput(TestOutput): + """Represents a text output of a single test on a single platform""" + def __init__(self, platform, text_file): + self._text_file = text_file + TestOutput.__init__(self, platform, 'text', [text_file]) + + def same_content(self, other): + return self._text_file.contents() == other._text_file.contents() + + def retarget(self, platform): + return TextTestOutput(platform, self._text_file) + + +class ImageTestOutput(TestOutput): + image_differ = NaiveImageDiffer() + """Represents an image output of a single test on a single platform""" + def __init__(self, platform, image_file, checksum_file): + self._checksum_file = checksum_file + self._image_file = image_file + files = filter(bool, [self._checksum_file, self._image_file]) + TestOutput.__init__(self, platform, 'image', files) + + def has_checksum(self): + return self._checksum_file is not None + + def same_content(self, other): + # FIXME This should not assume that checksums are up to date. + if self.has_checksum() and other.has_checksum(): + return self._checksum_file.contents() == other._checksum_file.contents() + else: + self_contents = self._image_file.contents() + other_contents = other._image_file.contents() + return ImageTestOutput.image_differ.same_image(self_contents, other_contents) + + def retarget(self, platform): + return ImageTestOutput(platform, self._image_file, self._checksum_file) + + def checksum(self): + return self._checksum_file.contents() diff --git a/Tools/Scripts/webkitpy/common/net/testoutput_unittest.py b/Tools/Scripts/webkitpy/common/net/testoutput_unittest.py new file mode 100644 index 0000000..ad38ca6 --- /dev/null +++ b/Tools/Scripts/webkitpy/common/net/testoutput_unittest.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python +# Copyright (C) 2010 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import re +import testoutput +import unittest + + +class FakeFile(object): + def __init__(self, filename, contents="fake contents"): + self._filename = filename + self._contents = contents + + def name(self): + return self._filename + + def contents(self): + return self._contents + + +class FakeTestOutput(testoutput.TestOutput): + def __init__(self, platform, output_type, contents, is_expected=False): + self._output_type = output_type + self._contents = contents + self._is_expected = is_expected + actual = 'actual' + if is_expected: + actual = 'expected' + test_name = 'anonymous-test-%s.txt' % actual + file = FakeFile(test_name, contents) + super(FakeTestOutput, self).__init__(platform, output_type, [file]) + + def contents(self): + return self._contents + + def retarget(self, platform): + return FakeTestOutput(platform, self._output_type, self._contents, self._is_expected) + + +class TestOutputTest(unittest.TestCase): + def _check_name(self, filename, expected_test_name): + r = testoutput.TextTestOutput(None, FakeFile(filename)) + self.assertEquals(expected_test_name, r.name()) + + def _check_platform(self, filename, expected_platform): + r = testoutput.TextTestOutput(None, FakeFile(filename)) + self.assertEquals(expected_platform, r.platform()) + + def test_extracts_name_correctly(self): + self._check_name('LayoutTests/fast/dom/a-expected.txt', 'fast/dom/a') + self._check_name('LayoutTests/fast/dom/a-actual.txt', 'fast/dom/a') + self._check_name('LayoutTests/platform/win/fast/a-expected.txt', 'fast/a') + self._check_name('LayoutTests/platform/win/fast/a-expected.checksum', 'fast/a') + self._check_name('fast/dom/test-expected.txt', 'fast/dom/test') + self._check_name('layout-test-results/fast/a-actual.checksum', 'fast/a') + + def test_extracts_platform_correctly(self): + self._check_platform('LayoutTests/platform/win/fast/a-expected.txt', 'win') + self._check_platform('platform/win/fast/a-expected.txt', 'win') + self._check_platform('platform/mac/fast/a-expected.txt', 'mac') + self._check_platform('fast/a-expected.txt', None) + + def test_outputs_from_an_actual_file_are_marked_as_such(self): + r = testoutput.TextTestOutput(None, FakeFile('test-actual.txt')) + self.assertTrue(r.is_actual()) + + def test_outputs_from_an_expected_file_are_not_actual(self): + r = testoutput.TextTestOutput(None, FakeFile('test-expected.txt')) + self.assertFalse(r.is_actual()) + + def test_is_new_baseline_for(self): + expected = testoutput.TextTestOutput('mac', FakeFile('test-expected.txt')) + actual = testoutput.TextTestOutput('mac', FakeFile('test-actual.txt')) + self.assertTrue(actual.is_new_baseline_for(expected)) + self.assertFalse(expected.is_new_baseline_for(actual)) + + def test__eq__(self): + r1 = testoutput.TextTestOutput('mac', FakeFile('test-expected.txt', 'contents')) + r2 = testoutput.TextTestOutput('mac', FakeFile('test-expected.txt', 'contents')) + r3 = testoutput.TextTestOutput('win', FakeFile('test-expected.txt', 'contents')) + + self.assertEquals(r1, r2) + self.assertEquals(r1, r2.retarget('mac')) + self.assertNotEquals(r1, r2.retarget('win')) + + def test__hash__(self): + r1 = testoutput.TextTestOutput('mac', FakeFile('test-expected.txt', 'contents')) + r2 = testoutput.TextTestOutput('mac', FakeFile('test-expected.txt', 'contents')) + r3 = testoutput.TextTestOutput(None, FakeFile('test-expected.txt', None)) + + x = set([r1, r2]) + self.assertEquals(1, len(set([r1, r2]))) + self.assertEquals(2, len(set([r1, r2, r3]))) + + def test_image_diff_is_invoked_for_image_outputs_without_checksum(self): + r1 = testoutput.ImageTestOutput('mac', FakeFile('test-expected.png', 'asdf'), FakeFile('test-expected.checksum', 'check')) + r2 = testoutput.ImageTestOutput('mac', FakeFile('test-expected.png', 'asdf'), None) + + # Default behaviour is to just compare on image contents. + self.assertTrue(r1.same_content(r2)) + + class AllImagesAreDifferent(object): + def same_image(self, image1, image2): + return False + + # But we can install other image differs. + testoutput.ImageTestOutput.image_differ = AllImagesAreDifferent() + + self.assertFalse(r1.same_content(r2)) + +if __name__ == "__main__": + unittest.main() diff --git a/Tools/Scripts/webkitpy/common/net/testoutputset.py b/Tools/Scripts/webkitpy/common/net/testoutputset.py new file mode 100644 index 0000000..4074686 --- /dev/null +++ b/Tools/Scripts/webkitpy/common/net/testoutputset.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python +# Copyright (C) 2010 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from webkitpy.common.system.directoryfileset import DirectoryFileSet +from webkitpy.common.system.zipfileset import ZipFileSet +import re +import testoutput +import urllib + + +class TestOutputSet(object): + def __init__(self, name, platform, zip_file, **kwargs): + self._name = name + self._platform = platform + self._zip_file = zip_file + self._include_expected = kwargs.get('include_expected', True) + + @classmethod + def from_zip_url(cls, platform, zip_path): + return TestOutputSet('local zip %s builder' % platform, platform, ZipFileSet(zip_path)) + + @classmethod + def from_zip(cls, platform, zip): + return TestOutputSet('local zip %s builder' % platform, platform, zip) + + @classmethod + def from_zip_map(cls, zip_map): + output_sets = [] + for k, v in zip_map.items(): + output_sets.append(TestOutputSet.from_zip(k, v)) + return AggregateTestOutputSet(output_sets) + + @classmethod + def from_path(self, path, platform=None): + return TestOutputSet('local %s builder' % platform, platform, DirectoryFileSet(path)) + + def name(self): + return self._name + + def set_platform(self, platform): + self._platform = platform + + def files(self): + return [self._zip_file.open(filename) for filename in self._zip_file.namelist()] + + def _extract_output_files(self, name, exact_match): + name_matcher = re.compile(name) + actual_matcher = re.compile(r'-actual\.') + expected_matcher = re.compile(r'-expected\.') + + checksum_files = [] + text_files = [] + image_files = [] + for output_file in self.files(): + name_match = name_matcher.search(output_file.name()) + actual_match = actual_matcher.search(output_file.name()) + expected_match = expected_matcher.search(output_file.name()) + if not (name_match and (actual_match or (self._include_expected and expected_match))): + continue + if output_file.name().endswith('.checksum'): + checksum_files.append(output_file) + elif output_file.name().endswith('.txt'): + text_files.append(output_file) + elif output_file.name().endswith('.png'): + image_files.append(output_file) + + return (checksum_files, text_files, image_files) + + def _extract_file_with_name(self, name, files): + for file in files: + if file.name() == name: + return file + return None + + def _make_output_from_image(self, image_file, checksum_files): + checksum_file_name = re.sub('\.png', '.checksum', image_file.name()) + checksum_file = self._extract_file_with_name(checksum_file_name, checksum_files) + return testoutput.ImageTestOutput(self._platform, image_file, checksum_file) + + def outputs_for(self, name, **kwargs): + target_type = kwargs.get('target_type', None) + exact_match = kwargs.get('exact_match', False) + if re.search(r'\.x?html', name): + name = name[:name.rindex('.')] + + (checksum_files, text_files, image_files) = self._extract_output_files(name, exact_match) + + outputs = [self._make_output_from_image(image_file, checksum_files) for image_file in image_files] + + outputs += [testoutput.TextTestOutput(self._platform, text_file) for text_file in text_files] + + if exact_match: + outputs = filter(lambda output: output.name() == name, outputs) + + outputs = filter(lambda r: target_type in [None, r.type()], outputs) + + return outputs + + +class AggregateTestOutputSet(object): + """Set of test outputs from a list of builders""" + def __init__(self, builders): + self._builders = builders + + def outputs_for(self, name, **kwargs): + return sum([builder.outputs_for(name, **kwargs) for builder in self._builders], []) + + def builders(self): + return self._builders diff --git a/Tools/Scripts/webkitpy/common/net/testoutputset_unittest.py b/Tools/Scripts/webkitpy/common/net/testoutputset_unittest.py new file mode 100644 index 0000000..a70a539 --- /dev/null +++ b/Tools/Scripts/webkitpy/common/net/testoutputset_unittest.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python +# Copyright (C) 2010 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from webkitpy.common.system.zip_mock import MockZip +import testoutputset +import unittest + + +class TestOutputSetTest(unittest.TestCase): + def _outputset_with_zip(self, zip, **kwargs): + return testoutputset.TestOutputSet('', '', zip, **kwargs) + + def test_text_files_get_interpreted_as_text_outputs(self): + zip = MockZip() + zip.insert('fast/dom/some-test-actual.txt', 'actual outputs') + b = self._outputset_with_zip(zip) + self.assertEquals(1, len(b.outputs_for('fast/dom/some-test'))) + self.assertEquals('fast/dom/some-test', b.outputs_for('fast/dom/some-test.html')[0].name()) + + def test_image_and_checksum_files_get_interpreted_as_a_single_image_output(self): + zip = MockZip() + zip.insert('fast/dom/some-test-actual.checksum', 'abc123') + zip.insert('fast/dom/some-test-actual.png', '') + b = self._outputset_with_zip(zip) + outputs = b.outputs_for('fast/dom/some-test') + self.assertEquals(1, len(outputs)) + output = outputs[0] + self.assertEquals('image', output.type()) + self.assertEquals('abc123', output.checksum()) + + def test_multiple_image_outputs_are_detected(self): + zip = MockZip() + zip.insert('platform/win/fast/dom/some-test-actual.checksum', 'checksum1') + zip.insert('platform/win/fast/dom/some-test-actual.png', '') + zip.insert('platform/mac/fast/dom/some-test-actual.checksum', 'checksum2') + zip.insert('platform/mac/fast/dom/some-test-actual.png', '') + b = self._outputset_with_zip(zip) + outputs = b.outputs_for('fast/dom/some-test') + self.assertEquals(2, len(outputs)) + self.assertFalse(outputs[0].same_content(outputs[1])) + + def test_aggregate_output_set_correctly_retrieves_tests_from_multiple_output_sets(self): + outputset1_zip = MockZip() + outputset1_zip.insert('fast/dom/test-actual.txt', 'linux text output') + outputset1 = testoutputset.TestOutputSet('linux-outputset', 'linux', outputset1_zip) + outputset2_zip = MockZip() + outputset2_zip.insert('fast/dom/test-actual.txt', 'windows text output') + outputset2 = testoutputset.TestOutputSet('win-outputset', 'win', outputset2_zip) + + b = testoutputset.AggregateTestOutputSet([outputset1, outputset2]) + self.assertEquals(2, len(b.outputs_for('fast/dom/test'))) + + def test_can_infer_platform_from_path_if_none_provided(self): + zip = MockZip() + zip.insert('platform/win/some-test-expected.png', '') + zip.insert('platform/win/some-test-expected.checksum', 'abc123') + b = testoutputset.TestOutputSet('local LayoutTests outputset', None, zip) + + outputs = b.outputs_for('some-test') + self.assertEquals(1, len(outputs)) + self.assertEquals('win', outputs[0].platform()) + + def test_test_extension_is_ignored(self): + zip = MockZip() + zip.insert('test/test-a-actual.txt', 'actual outputs') + b = self._outputset_with_zip(zip) + outputs = b.outputs_for('test/test-a.html') + self.assertEquals(1, len(outputs)) + self.assertEquals('test/test-a', outputs[0].name()) + + def test_existing_outputs_are_marked_as_such(self): + zip = MockZip() + zip.insert('test/test-a-expected.txt', 'expected outputs') + b = self._outputset_with_zip(zip) + outputs = b.outputs_for('test/test-a.html') + self.assertEquals(1, len(outputs)) + self.assertFalse(outputs[0].is_actual()) + + def test_only_returns_outputs_of_specified_type(self): + zip = MockZip() + zip.insert('test/test-a-expected.txt', 'expected outputs') + zip.insert('test/test-a-expected.checksum', 'expected outputs') + zip.insert('test/test-a-expected.png', 'expected outputs') + b = self._outputset_with_zip(zip) + + outputs = b.outputs_for('test/test-a.html') + text_outputs = b.outputs_for('test/test-a.html', target_type='text') + image_outputs = b.outputs_for('test/test-a.html', target_type='image') + + self.assertEquals(2, len(outputs)) + self.assertEquals(1, len(text_outputs)) + self.assertEquals(1, len(image_outputs)) + self.assertEquals('text', text_outputs[0].type()) + self.assertEquals('image', image_outputs[0].type()) + + def test_exclude_expected_outputs_works(self): + zip = MockZip() + zip.insert('test-expected.txt', 'expected outputs stored on server for some reason') + b = self._outputset_with_zip(zip, include_expected=False) + outputs = b.outputs_for('test', target_type=None) + self.assertEquals(0, len(outputs)) + +if __name__ == "__main__": + unittest.main() diff --git a/Tools/Scripts/webkitpy/common/system/directoryfileset.py b/Tools/Scripts/webkitpy/common/system/directoryfileset.py index 11acaf4..a5cab0e 100644 --- a/Tools/Scripts/webkitpy/common/system/directoryfileset.py +++ b/Tools/Scripts/webkitpy/common/system/directoryfileset.py @@ -21,14 +21,8 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from __future__ import with_statement - -import os -import shutil - from webkitpy.common.system.fileset import FileSetFileHandle from webkitpy.common.system.filesystem import FileSystem -import webkitpy.common.system.ospath as ospath class DirectoryFileSet(object): @@ -36,8 +30,8 @@ class DirectoryFileSet(object): def __init__(self, path, filesystem=None): self._path = path self._filesystem = filesystem or FileSystem() - if not self._path.endswith(os.path.sep): - self._path += os.path.sep + if not self._path.endswith(self._filesystem.sep): + self._path += self._filesystem.sep def _full_path(self, filename): assert self._is_under(self._path, filename) @@ -52,7 +46,7 @@ class DirectoryFileSet(object): return self._filesystem.files_under(self._path) def _is_under(self, dir, filename): - return bool(ospath.relpath(self._filesystem.join(dir, filename), dir)) + return bool(self._filesystem.relpath(self._filesystem.join(dir, filename), dir)) def open(self, filename): return FileSetFileHandle(self, filename, self._filesystem) @@ -69,7 +63,7 @@ class DirectoryFileSet(object): dest = self._filesystem.join(path, filename) # As filename may have slashes in it, we must ensure that the same # directory hierarchy exists at the output path. - self._filesystem.maybe_make_directory(os.path.split(dest)[0]) + self._filesystem.maybe_make_directory(self._filesystem.dirname(dest)) self._filesystem.copyfile(src, dest) def delete(self, filename): diff --git a/Tools/Scripts/webkitpy/common/system/fileset.py b/Tools/Scripts/webkitpy/common/system/fileset.py index 22f7c4d..598c1c5 100644 --- a/Tools/Scripts/webkitpy/common/system/fileset.py +++ b/Tools/Scripts/webkitpy/common/system/fileset.py @@ -22,7 +22,6 @@ # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from __future__ import with_statement -import os from webkitpy.common.system.filesystem import FileSystem @@ -38,6 +37,9 @@ class FileSetFileHandle(object): def __str__(self): return "%s:%s" % (self._fileset, self._filename) + def close(self): + pass + def contents(self): if self._contents is None: self._contents = self._fileset.read(self._filename) @@ -61,4 +63,4 @@ class FileSetFileHandle(object): return self._filename def splitext(self): - return os.path.splitext(self.name()) + return self._filesystem.splitext(self.name()) diff --git a/Tools/Scripts/webkitpy/common/system/filesystem.py b/Tools/Scripts/webkitpy/common/system/filesystem.py index 05513a9..b876807 100644 --- a/Tools/Scripts/webkitpy/common/system/filesystem.py +++ b/Tools/Scripts/webkitpy/common/system/filesystem.py @@ -39,13 +39,20 @@ import shutil import tempfile import time +from webkitpy.common.system import ospath + class FileSystem(object): """FileSystem interface for webkitpy. Unless otherwise noted, all paths are allowed to be either absolute or relative.""" def __init__(self): - self.sep = os.sep + self._sep = os.sep + + def _get_sep(self): + return self._sep + + sep = property(_get_sep, doc="pathname separator") def abspath(self, path): return os.path.abspath(path) @@ -165,8 +172,8 @@ class FileSystem(object): if e.errno != errno.EEXIST: raise - def move(self, src, dest): - shutil.move(src, dest) + def move(self, source, destination): + shutil.move(source, destination) def mtime(self, path): return os.stat(path).st_mtime @@ -200,6 +207,9 @@ class FileSystem(object): with codecs.open(path, 'r', 'utf8') as f: return f.read() + def relpath(self, path, start='.'): + return ospath.relpath(path, start) + class _WindowsError(exceptions.OSError): """Fake exception for Linux and Mac.""" pass diff --git a/Tools/Scripts/webkitpy/common/system/filesystem_mock.py b/Tools/Scripts/webkitpy/common/system/filesystem_mock.py index 0004944..aa79a8c 100644 --- a/Tools/Scripts/webkitpy/common/system/filesystem_mock.py +++ b/Tools/Scripts/webkitpy/common/system/filesystem_mock.py @@ -28,9 +28,11 @@ import errno import os -import path import re +from webkitpy.common.system import path +from webkitpy.common.system import ospath + class MockFileSystem(object): def __init__(self, files=None): @@ -44,17 +46,23 @@ class MockFileSystem(object): """ self.files = files or {} self.written_files = {} - self.sep = '/' + self._sep = '/' self.current_tmpno = 0 + def _get_sep(self): + return self._sep + + sep = property(_get_sep, doc="pathname separator") + def _raise_not_found(self, path): raise IOError(errno.ENOENT, path, os.strerror(errno.ENOENT)) def _split(self, path): - idx = path.rfind('/') - return (path[0:idx], path[idx + 1:]) + return path.rsplit(self.sep, 1) def abspath(self, path): + if path.endswith(self.sep): + return path[:-1] return path def basename(self, path): @@ -69,6 +77,7 @@ class MockFileSystem(object): raise IOError(errno.EISDIR, destination, os.strerror(errno.ISDIR)) self.files[destination] = self.files[source] + self.written_files[destination] = self.files[source] def dirname(self, path): return self._split(path)[0] @@ -90,10 +99,10 @@ class MockFileSystem(object): if self.basename(path) in dirs_to_skip: return [] - if not path.endswith('/'): - path += '/' + if not path.endswith(self.sep): + path += self.sep - dir_substrings = ['/' + d + '/' for d in dirs_to_skip] + dir_substrings = [self.sep + d + self.sep for d in dirs_to_skip] for filename in self.files: if not filename.startswith(path): continue @@ -117,7 +126,7 @@ class MockFileSystem(object): return [f for f in self.files if f == path] def isabs(self, path): - return path.startswith('/') + return path.startswith(self.sep) def isfile(self, path): return path in self.files and self.files[path] is not None @@ -125,8 +134,8 @@ class MockFileSystem(object): def isdir(self, path): if path in self.files: return False - if not path.endswith('/'): - path += '/' + if not path.endswith(self.sep): + path += self.sep # We need to use a copy of the keys here in order to avoid switching # to a different thread and potentially modifying the dict in @@ -135,22 +144,24 @@ class MockFileSystem(object): return any(f.startswith(path) for f in files) def join(self, *comps): - return re.sub(re.escape(os.path.sep), '/', os.path.join(*comps)) + # FIXME: might want tests for this and/or a better comment about how + # it works. + return re.sub(re.escape(os.path.sep), self.sep, os.path.join(*comps)) def listdir(self, path): if not self.isdir(path): raise OSError("%s is not a directory" % path) - if not path.endswith('/'): - path += '/' + if not path.endswith(self.sep): + path += self.sep dirs = [] files = [] for f in self.files: if self.exists(f) and f.startswith(path): remaining = f[len(path):] - if '/' in remaining: - dir = remaining[:remaining.index('/')] + if self.sep in remaining: + dir = remaining[:remaining.index(self.sep)] if not dir in dirs: dirs.append(dir) else: @@ -164,7 +175,7 @@ class MockFileSystem(object): def _mktemp(self, suffix='', prefix='tmp', dir=None, **kwargs): if dir is None: - dir = '/__im_tmp' + dir = self.sep + '__im_tmp' curno = self.current_tmpno self.current_tmpno += 1 return self.join(dir, "%s_%u_%s" % (prefix, curno, suffix)) @@ -196,24 +207,26 @@ class MockFileSystem(object): # FIXME: Implement such that subsequent calls to isdir() work? pass - def move(self, src, dst): - if self.files[src] is None: - self._raise_not_found(src) - self.files[dst] = self.files[src] - self.files[src] = None + def move(self, source, destination): + if self.files[source] is None: + self._raise_not_found(source) + self.files[destination] = self.files[source] + self.written_files[destination] = self.files[destination] + self.files[source] = None + self.written_files[source] = None def normpath(self, path): return path - def open_binary_tempfile(self, suffix): + def open_binary_tempfile(self, suffix=''): path = self._mktemp(suffix) - return WritableFileObject(self, path), path + return (WritableFileObject(self, path), path) def open_text_file_for_writing(self, path, append=False): return WritableFileObject(self, path, append) def read_text_file(self, path): - return self.read_binary_file(path) + return self.read_binary_file(path).decode('utf-8') def read_binary_file(self, path): # Intentionally raises KeyError if we don't recognize the path. @@ -221,14 +234,18 @@ class MockFileSystem(object): self._raise_not_found(path) return self.files[path] + def relpath(self, path, start='.'): + return ospath.relpath(path, start, self.abspath, self.sep) + def remove(self, path): if self.files[path] is None: self._raise_not_found(path) self.files[path] = None + self.written_files[path] = None def rmtree(self, path): - if not path.endswith('/'): - path += '/' + if not path.endswith(self.sep): + path += self.sep for f in self.files: if f.startswith(path): @@ -241,7 +258,7 @@ class MockFileSystem(object): return (path[0:idx], path[idx:]) def write_text_file(self, path, contents): - return self.write_binary_file(path, contents) + return self.write_binary_file(path, contents.encode('utf-8')) def write_binary_file(self, path, contents): self.files[path] = contents diff --git a/Tools/Scripts/webkitpy/common/system/filesystem_unittest.py b/Tools/Scripts/webkitpy/common/system/filesystem_unittest.py index 267ca13..8455d72 100644 --- a/Tools/Scripts/webkitpy/common/system/filesystem_unittest.py +++ b/Tools/Scripts/webkitpy/common/system/filesystem_unittest.py @@ -167,6 +167,19 @@ class FileSystemTest(unittest.TestCase): self.assertTrue(fs.remove('filename', remove_with_exception)) self.assertEquals(-1, FileSystemTest._remove_failures) + def test_sep(self): + fs = FileSystem() + + self.assertEquals(fs.sep, os.sep) + self.assertEquals(fs.join("foo", "bar"), + os.path.join("foo", "bar")) + + def test_sep__is_readonly(self): + def assign_sep(): + fs.sep = ' ' + fs = FileSystem() + self.assertRaises(AttributeError, assign_sep) + if __name__ == '__main__': unittest.main() diff --git a/Tools/Scripts/webkitpy/common/system/logutils.py b/Tools/Scripts/webkitpy/common/system/logutils.py index cd4e60f..eef4636 100644 --- a/Tools/Scripts/webkitpy/common/system/logutils.py +++ b/Tools/Scripts/webkitpy/common/system/logutils.py @@ -83,7 +83,7 @@ def get_logger(path): Sample usage: - import webkitpy.common.system.logutils as logutils + from webkitpy.common.system import logutils _log = logutils.get_logger(__file__) diff --git a/Tools/Scripts/webkitpy/common/system/logutils_unittest.py b/Tools/Scripts/webkitpy/common/system/logutils_unittest.py index b77c284..f1b494d 100644 --- a/Tools/Scripts/webkitpy/common/system/logutils_unittest.py +++ b/Tools/Scripts/webkitpy/common/system/logutils_unittest.py @@ -28,7 +28,7 @@ import unittest from webkitpy.common.system.logtesting import LogTesting from webkitpy.common.system.logtesting import TestLogStream -import webkitpy.common.system.logutils as logutils +from webkitpy.common.system import logutils class GetLoggerTest(unittest.TestCase): diff --git a/Tools/Scripts/webkitpy/common/system/ospath.py b/Tools/Scripts/webkitpy/common/system/ospath.py index aed7a3d..2504645 100644 --- a/Tools/Scripts/webkitpy/common/system/ospath.py +++ b/Tools/Scripts/webkitpy/common/system/ospath.py @@ -32,7 +32,7 @@ import os # # It should behave essentially the same as os.path.relpath(), except for # returning None on paths not contained in abs_start_path. -def relpath(path, start_path, os_path_abspath=None): +def relpath(path, start_path, os_path_abspath=None, sep=None): """Return a path relative to the given start path, or None. Returns None if the path is not contained in the directory start_path. @@ -44,10 +44,12 @@ def relpath(path, start_path, os_path_abspath=None): os_path_abspath: A replacement function for unit testing. This function should strip trailing slashes just like os.path.abspath(). Defaults to os.path.abspath. + sep: Path separator. Defaults to os.path.sep """ if os_path_abspath is None: os_path_abspath = os.path.abspath + sep = sep or os.sep # Since os_path_abspath() calls os.path.normpath()-- # @@ -67,11 +69,11 @@ def relpath(path, start_path, os_path_abspath=None): if not rel_path: # Then the paths are the same. pass - elif rel_path[0] == os.sep: + elif rel_path[0] == sep: # It is probably sufficient to remove just the first character # since os.path.normpath() collapses separators, but we use # lstrip() just to be sure. - rel_path = rel_path.lstrip(os.sep) + rel_path = rel_path.lstrip(sep) else: # We are in the case typified by the following example: # diff --git a/Tools/Scripts/webkitpy/common/system/stack_utils.py b/Tools/Scripts/webkitpy/common/system/stack_utils.py new file mode 100644 index 0000000..a343807 --- /dev/null +++ b/Tools/Scripts/webkitpy/common/system/stack_utils.py @@ -0,0 +1,67 @@ +# Copyright (C) 2011 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Simple routines for logging, obtaining thread stack information.""" + +import sys +import traceback + + +def log_thread_state(logger, name, thread_id, msg=''): + """Log information about the given thread state.""" + stack = _find_thread_stack(thread_id) + assert(stack is not None) + logger("") + logger("%s (tid %d) %s" % (name, thread_id, msg)) + _log_stack(logger, stack) + logger("") + + +def _find_thread_stack(thread_id): + """Returns a stack object that can be used to dump a stack trace for + the given thread id (or None if the id is not found).""" + for tid, stack in sys._current_frames().items(): + if tid == thread_id: + return stack + return None + + +def _log_stack(logger, stack): + """Log a stack trace to the logger callback.""" + for filename, lineno, name, line in traceback.extract_stack(stack): + logger('File: "%s", line %d, in %s' % (filename, lineno, name)) + if line: + logger(' %s' % line.strip()) + + +def log_traceback(logger, tb): + stack = traceback.extract_tb(tb) + for frame_str in traceback.format_list(stack): + for line in frame_str.split('\n'): + if line: + logger(" %s" % line) diff --git a/Tools/Scripts/webkitpy/common/system/stack_utils_unittest.py b/Tools/Scripts/webkitpy/common/system/stack_utils_unittest.py new file mode 100644 index 0000000..b21319f --- /dev/null +++ b/Tools/Scripts/webkitpy/common/system/stack_utils_unittest.py @@ -0,0 +1,76 @@ +# Copyright (C) 2011 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import sys +import unittest + +from webkitpy.common.system import outputcapture +from webkitpy.common.system import stack_utils + + +def current_thread_id(): + thread_id, _ = sys._current_frames().items()[0] + return thread_id + + +class Test(unittest.TestCase): + def test_find_thread_stack_found(self): + thread_id = current_thread_id() + found_stack = stack_utils._find_thread_stack(thread_id) + self.assertNotEqual(found_stack, None) + + def test_find_thread_stack_not_found(self): + found_stack = stack_utils._find_thread_stack(0) + self.assertEqual(found_stack, None) + + def test_log_thread_state(self): + msgs = [] + + def logger(msg): + msgs.append(msg) + + thread_id = current_thread_id() + stack_utils.log_thread_state(logger, "test-thread", thread_id, + "is tested") + self.assertTrue(msgs) + + def test_log_traceback(self): + msgs = [] + + def logger(msg): + msgs.append(msg) + + try: + raise ValueError + except: + stack_utils.log_traceback(logger, sys.exc_info()[2]) + self.assertTrue(msgs) + + +if __name__ == '__main__': + unittest.main() diff --git a/Tools/Scripts/webkitpy/common/system/urlfetcher.py b/Tools/Scripts/webkitpy/common/system/urlfetcher.py new file mode 100644 index 0000000..2d9e5ec --- /dev/null +++ b/Tools/Scripts/webkitpy/common/system/urlfetcher.py @@ -0,0 +1,55 @@ +# Copyright (C) 2011 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Wrapper module for fetching URLs.""" + +import urllib + + +class UrlFetcher(object): + """Class with restricted interface to fetch URLs (makes testing easier)""" + def __init__(self, filesystem): + self._filesystem = filesystem + + def fetch(self, url): + """Fetches the contents of the URL as a string.""" + file_object = urllib.urlopen(url) + content = file_object.read() + file_object.close() + return content + + def fetch_into_file(self, url): + """Fetches the contents of the URL into a temporary file and return the filename. + + This is the equivalent of urllib.retrieve() except that we don't return any headers. + """ + file_object, filename = self._filesystem.open_binary_tempfile('-fetched') + contents = self.fetch(url) + file_object.write(contents) + file_object.close() + return filename diff --git a/Tools/Scripts/webkitpy/common/system/urlfetcher_mock.py b/Tools/Scripts/webkitpy/common/system/urlfetcher_mock.py new file mode 100644 index 0000000..e8a7532 --- /dev/null +++ b/Tools/Scripts/webkitpy/common/system/urlfetcher_mock.py @@ -0,0 +1,46 @@ +# Copyright (C) 2011 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +def make_fetcher_cls(urls): + """UrlFetcher factory routine that simulates network access + using a dict of URLs -> contents.""" + class MockFetcher(object): + def __init__(self, filesystem): + self._filesystem = filesystem + + def fetch(self, url): + return urls[url] + + def fetch_into_file(self, url): + f, fn = self._filesystem.open_binary_tempfile('mockfetcher') + f.write(self.fetch(url)) + f.close() + return fn + + return MockFetcher diff --git a/Tools/Scripts/webkitpy/common/system/zip_mock.py b/Tools/Scripts/webkitpy/common/system/zip_mock.py new file mode 100644 index 0000000..dcfaba7 --- /dev/null +++ b/Tools/Scripts/webkitpy/common/system/zip_mock.py @@ -0,0 +1,55 @@ +# Copyright (C) 2010 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from webkitpy.common.system.fileset import FileSetFileHandle +from webkitpy.common.system.filesystem_mock import MockFileSystem + + +class MockZip(object): + """A mock zip file that can have new files inserted into it.""" + def __init__(self, filesystem=None): + self._filesystem = filesystem or MockFileSystem() + self._files = {} + + def __str__(self): + return "MockZip" + + def insert(self, filename, content): + self._files[filename] = content + + def namelist(self): + return self._files.keys() + + def open(self, filename): + return FileSetFileHandle(self, filename) + + def read(self, filename): + return self._files[filename] + + def extract(self, filename, path): + full_path = self._filesystem.join(path, filename) + contents = self.open(filename).contents() + self._filesystem.write_text_file(full_path, contents) + + def delete(self, filename): + self._files[filename] = None diff --git a/Tools/Scripts/webkitpy/common/system/zipfileset.py b/Tools/Scripts/webkitpy/common/system/zipfileset.py index fa2b762..5cf3616 100644 --- a/Tools/Scripts/webkitpy/common/system/zipfileset.py +++ b/Tools/Scripts/webkitpy/common/system/zipfileset.py @@ -33,22 +33,28 @@ class ZipFileSet(object): """The set of files in a zip file that resides at a URL (local or remote)""" def __init__(self, zip_url, filesystem=None, zip_factory=None): self._zip_url = zip_url + self._temp_file = None self._zip_file = None self._filesystem = filesystem or FileSystem() self._zip_factory = zip_factory or self._retrieve_zip_file def _retrieve_zip_file(self, zip_url): temp_file = NetworkTransaction().run(lambda: urllib.urlretrieve(zip_url)[0]) - return zipfile.ZipFile(temp_file) + return (temp_file, zipfile.ZipFile(temp_file)) def _load(self): if self._zip_file is None: - self._zip_file = self._zip_factory(self._zip_url) + self._temp_file, self._zip_file = self._zip_factory(self._zip_url) def open(self, filename): self._load() return FileSetFileHandle(self, filename, self._filesystem) + def close(self): + if self._temp_file: + self._filesystem.remove(self._temp_file) + self._temp_file = None + def namelist(self): self._load() return self._zip_file.namelist() diff --git a/Tools/Scripts/webkitpy/common/system/zipfileset_mock.py b/Tools/Scripts/webkitpy/common/system/zipfileset_mock.py new file mode 100644 index 0000000..24ac8cb --- /dev/null +++ b/Tools/Scripts/webkitpy/common/system/zipfileset_mock.py @@ -0,0 +1,51 @@ +# Copyright (C) 2011 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +def make_factory(ziphashes): + """ZipFileSet factory routine that looks up zipfiles in a dict; + each zipfile should also be a dict of member names -> contents.""" + class MockZipFileSet(object): + def __init__(self, url): + self._url = url + self._ziphash = ziphashes[url] + + def namelist(self): + return self._ziphash.keys() + + def read(self, member): + return self._ziphash[member] + + def close(self): + pass + + def maker(url): + # We return None because there's no tempfile to delete. + return (None, MockZipFileSet(url)) + + return maker diff --git a/Tools/Scripts/webkitpy/common/system/zipfileset_unittest.py b/Tools/Scripts/webkitpy/common/system/zipfileset_unittest.py index a9ba5ad..6801406 100644 --- a/Tools/Scripts/webkitpy/common/system/zipfileset_unittest.py +++ b/Tools/Scripts/webkitpy/common/system/zipfileset_unittest.py @@ -64,13 +64,17 @@ class ZipFileSetTest(unittest.TestCase): result = FakeZip(self._filesystem) result.add_file('some-file', 'contents') result.add_file('a/b/some-other-file', 'other contents') - return result + return (None, result) def test_open(self): file = self._zip.open('a/b/some-other-file') self.assertEquals('a/b/some-other-file', file.name()) self.assertEquals('other contents', file.contents()) + def test_close(self): + zipfileset = ZipFileSet('blah', self._filesystem, self.make_fake_zip) + zipfileset.close() + def test_read(self): self.assertEquals('contents', self._zip.read('some-file')) diff --git a/Tools/Scripts/webkitpy/layout_tests/deduplicate_tests.py b/Tools/Scripts/webkitpy/layout_tests/deduplicate_tests.py index 51dcac8..86649b6 100644 --- a/Tools/Scripts/webkitpy/layout_tests/deduplicate_tests.py +++ b/Tools/Scripts/webkitpy/layout_tests/deduplicate_tests.py @@ -36,11 +36,12 @@ import os import subprocess import sys import re -import webkitpy.common.checkout.scm as scm -import webkitpy.common.system.executive as executive -import webkitpy.common.system.logutils as logutils -import webkitpy.common.system.ospath as ospath -import webkitpy.layout_tests.port.factory as port_factory + +from webkitpy.common.checkout import scm +from webkitpy.common.system import executive +from webkitpy.common.system import logutils +from webkitpy.common.system import ospath +from webkitpy.layout_tests.port import factory as port_factory _log = logutils.get_logger(__file__) diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py index 050eefa..7ddd7b0 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py @@ -28,17 +28,11 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -"""A Thread object for running DumpRenderTree and processing URLs from a -shared queue. +"""This module implements a shared-memory, thread-based version of the worker +task in new-run-webkit-tests: it receives a list of tests from TestShellThread +and passes them one at a time to SingleTestRunner to execute.""" -Each thread runs a separate instance of the DumpRenderTree binary and validates -the output. When there are no more URLs to process in the shared queue, the -thread exits. -""" - -import copy import logging -import os import Queue import signal import sys @@ -46,199 +40,13 @@ import thread import threading import time - -from webkitpy.layout_tests.test_types import image_diff -from webkitpy.layout_tests.test_types import test_type_base -from webkitpy.layout_tests.test_types import text_diff - -import test_failures -import test_output -import test_results +from webkitpy.layout_tests.layout_package.single_test_runner import SingleTestRunner _log = logging.getLogger("webkitpy.layout_tests.layout_package." "dump_render_tree_thread") -def _expected_test_output(port, filename): - """Returns an expected TestOutput object.""" - return test_output.TestOutput(port.expected_text(filename), - port.expected_image(filename), - port.expected_checksum(filename)) - -def _process_output(port, options, test_input, test_types, test_args, - test_output, worker_name): - """Receives the output from a DumpRenderTree process, subjects it to a - number of tests, and returns a list of failure types the test produced. - - Args: - port: port-specific hooks - options: command line options argument from optparse - proc: an active DumpRenderTree process - test_input: Object containing the test filename and timeout - test_types: list of test types to subject the output to - test_args: arguments to be passed to each test - test_output: a TestOutput object containing the output of the test - worker_name: worker name for logging - - Returns: a TestResult object - """ - failures = [] - fs = port._filesystem - - if test_output.crash: - failures.append(test_failures.FailureCrash()) - if test_output.timeout: - failures.append(test_failures.FailureTimeout()) - - test_name = port.relative_test_filename(test_input.filename) - if test_output.crash: - _log.debug("%s Stacktrace for %s:\n%s" % (worker_name, test_name, - test_output.error)) - filename = fs.join(options.results_directory, test_name) - filename = fs.splitext(filename)[0] + "-stack.txt" - fs.maybe_make_directory(fs.dirname(filename)) - fs.write_text_file(filename, test_output.error) - elif test_output.error: - _log.debug("%s %s output stderr lines:\n%s" % (worker_name, test_name, - test_output.error)) - - expected_test_output = _expected_test_output(port, test_input.filename) - - # Check the output and save the results. - start_time = time.time() - time_for_diffs = {} - for test_type in test_types: - start_diff_time = time.time() - new_failures = test_type.compare_output(port, test_input.filename, - test_args, test_output, - expected_test_output) - # Don't add any more failures if we already have a crash, so we don't - # double-report those tests. We do double-report for timeouts since - # we still want to see the text and image output. - if not test_output.crash: - failures.extend(new_failures) - time_for_diffs[test_type.__class__.__name__] = ( - time.time() - start_diff_time) - - total_time_for_all_diffs = time.time() - start_diff_time - return test_results.TestResult(test_input.filename, failures, test_output.test_time, - total_time_for_all_diffs, time_for_diffs) - - -def _pad_timeout(timeout): - """Returns a safe multiple of the per-test timeout value to use - to detect hung test threads. - - """ - # When we're running one test per DumpRenderTree process, we can - # enforce a hard timeout. The DumpRenderTree watchdog uses 2.5x - # the timeout; we want to be larger than that. - return timeout * 3 - - -def _milliseconds_to_seconds(msecs): - return float(msecs) / 1000.0 - - -def _should_fetch_expected_checksum(options): - return options.pixel_tests and not (options.new_baseline or options.reset_results) - - -def _run_single_test(port, options, test_input, test_types, test_args, driver, worker_name): - # FIXME: Pull this into TestShellThread._run(). - - # The image hash is used to avoid doing an image dump if the - # checksums match, so it should be set to a blank value if we - # are generating a new baseline. (Otherwise, an image from a - # previous run will be copied into the baseline.""" - if _should_fetch_expected_checksum(options): - test_input.image_hash = port.expected_checksum(test_input.filename) - test_output = driver.run_test(test_input) - return _process_output(port, options, test_input, test_types, test_args, - test_output, worker_name) - - -class SingleTestThread(threading.Thread): - """Thread wrapper for running a single test file.""" - - def __init__(self, port, options, worker_number, worker_name, - test_input, test_types, test_args): - """ - Args: - port: object implementing port-specific hooks - options: command line argument object from optparse - worker_number: worker number for tests - worker_name: for logging - test_input: Object containing the test filename and timeout - test_types: A list of TestType objects to run the test output - against. - test_args: A TestArguments object to pass to each TestType. - """ - - threading.Thread.__init__(self) - self._port = port - self._options = options - self._test_input = test_input - self._test_types = test_types - self._test_args = test_args - self._driver = None - self._worker_number = worker_number - self._name = worker_name - - def run(self): - self._covered_run() - - def _covered_run(self): - # FIXME: this is a separate routine to work around a bug - # in coverage: see http://bitbucket.org/ned/coveragepy/issue/85. - self._driver = self._port.create_driver(self._worker_number) - self._driver.start() - self._test_result = _run_single_test(self._port, self._options, - self._test_input, self._test_types, - self._test_args, self._driver, - self._name) - self._driver.stop() - - def get_test_result(self): - return self._test_result - - -class WatchableThread(threading.Thread): - """This class abstracts an interface used by - run_webkit_tests.TestRunner._wait_for_threads_to_finish for thread - management.""" - def __init__(self): - threading.Thread.__init__(self) - self._canceled = False - self._exception_info = None - self._next_timeout = None - self._thread_id = None - - def cancel(self): - """Set a flag telling this thread to quit.""" - self._canceled = True - - def clear_next_timeout(self): - """Mark a flag telling this thread to stop setting timeouts.""" - self._timeout = 0 - - def exception_info(self): - """If run() terminated on an uncaught exception, return it here - ((type, value, traceback) tuple). - Returns None if run() terminated normally. Meant to be called after - joining this thread.""" - return self._exception_info - - def id(self): - """Return a thread identifier.""" - return self._thread_id - - def next_timeout(self): - """Return the time the test is supposed to finish by.""" - return self._next_timeout - - -class TestShellThread(WatchableThread): +class TestShellThread(threading.Thread): def __init__(self, port, options, worker_number, worker_name, filename_list_queue, result_queue): """Initialize all the local state for this DumpRenderTree thread. @@ -253,50 +61,51 @@ class TestShellThread(WatchableThread): result_queue: A thread safe Queue class that will contain serialized TestResult objects. """ - WatchableThread.__init__(self) + threading.Thread.__init__(self) + self._canceled = False + self._exception_info = None + self._next_timeout = None + self._thread_id = None self._port = port self._options = options self._worker_number = worker_number self._name = worker_name self._filename_list_queue = filename_list_queue self._result_queue = result_queue + self._current_group = None self._filename_list = [] - self._driver = None self._test_group_timing_stats = {} self._test_results = [] self._num_tests = 0 self._start_time = 0 self._stop_time = 0 - self._have_http_lock = False self._http_lock_wait_begin = 0 self._http_lock_wait_end = 0 - self._test_types = [] - for cls in self._get_test_type_classes(): - self._test_types.append(cls(self._port, - self._options.results_directory)) - self._test_args = self._get_test_args(worker_number) + def cancel(self): + """Set a flag telling this thread to quit.""" + self._canceled = True - # Current group of tests we're running. - self._current_group = None - # Number of tests in self._current_group. - self._num_tests_in_current_group = None - # Time at which we started running tests from self._current_group. - self._current_group_start_time = None + def clear_next_timeout(self): + """Mark a flag telling this thread to stop setting timeouts.""" + self._timeout = 0 - def _get_test_args(self, worker_number): - """Returns the tuple of arguments for tests and for DumpRenderTree.""" - test_args = test_type_base.TestArguments() - test_args.new_baseline = self._options.new_baseline - test_args.reset_results = self._options.reset_results + def exception_info(self): + """If run() terminated on an uncaught exception, return it here + ((type, value, traceback) tuple). + Returns None if run() terminated normally. Meant to be called after + joining this thread.""" + return self._exception_info - return test_args + def id(self): + """Return a thread identifier.""" + return self._thread_id - def _get_test_type_classes(self): - classes = [text_diff.TestTextDiff] - if self._options.pixel_tests: - classes.append(image_diff.ImageDiff) - return classes + def next_timeout(self): + """Return the time the test is supposed to finish by.""" + if self._next_timeout: + return self._next_timeout + self._http_lock_wait_time() + return self._next_timeout def get_test_group_timing_stats(self): """Returns a dictionary mapping test group to a tuple of @@ -352,17 +161,6 @@ class TestShellThread(WatchableThread): do multi-threaded debugging.""" self._run(test_runner, result_summary) - def cancel(self): - """Clean up http lock and set a flag telling this thread to quit.""" - self._stop_servers_with_lock() - WatchableThread.cancel(self) - - def next_timeout(self): - """Return the time the test is supposed to finish by.""" - if self._next_timeout: - return self._next_timeout + self._http_lock_wait_time() - return self._next_timeout - def _http_lock_wait_time(self): """Return the time what http locking takes.""" if self._http_lock_wait_begin == 0: @@ -377,18 +175,23 @@ class TestShellThread(WatchableThread): If test_runner is not None, then we call test_runner.UpdateSummary() with the results of each test.""" + single_test_runner = SingleTestRunner(self._options, self._port, + self._name, self._worker_number) + batch_size = self._options.batch_size batch_count = 0 # Append tests we're running to the existing tests_run.txt file. # This is created in run_webkit_tests.py:_PrepareListsAndPrintOutput. tests_run_filename = self._port._filesystem.join(self._options.results_directory, - "tests_run.txt") + "tests_run%d.txt" % self._worker_number) tests_run_file = self._port._filesystem.open_text_file_for_writing(tests_run_filename, append=False) + while True: if self._canceled: _log.debug('Testing cancelled') tests_run_file.close() + single_test_runner.cleanup() return if len(self._filename_list) is 0: @@ -401,15 +204,16 @@ class TestShellThread(WatchableThread): self._current_group, self._filename_list = \ self._filename_list_queue.get_nowait() except Queue.Empty: - self._stop_servers_with_lock() - self._kill_dump_render_tree() tests_run_file.close() + single_test_runner.cleanup() return if self._current_group == "tests_to_http_lock": - self._start_servers_with_lock() - elif self._have_http_lock: - self._stop_servers_with_lock() + self._http_lock_wait_begin = time.time() + single_test_runner.start_servers_with_lock() + self._http_lock_wait_end = time.time() + elif single_test_runner.has_http_lock: + single_test_runner.stop_servers_with_lock() self._num_tests_in_current_group = len(self._filename_list) self._current_group_start_time = time.time() @@ -419,145 +223,31 @@ class TestShellThread(WatchableThread): # We have a url, run tests. batch_count += 1 self._num_tests += 1 - if self._options.run_singly: - result = self._run_test_in_another_thread(test_input) - else: - result = self._run_test_in_this_thread(test_input) - filename = test_input.filename - tests_run_file.write(filename + "\n") + timeout = single_test_runner.timeout(test_input) + result = single_test_runner.run_test(test_input, timeout) + + tests_run_file.write(test_input.filename + "\n") + test_name = self._port.relative_test_filename(test_input.filename) if result.failures: # Check and kill DumpRenderTree if we need to. - if len([1 for f in result.failures - if f.should_kill_dump_render_tree()]): - self._kill_dump_render_tree() + if any([f.should_kill_dump_render_tree() for f in result.failures]): + single_test_runner.kill_dump_render_tree() # Reset the batch count since the shell just bounced. batch_count = 0 + # Print the error message(s). - error_str = '\n'.join([' ' + f.message() for - f in result.failures]) - _log.debug("%s %s failed:\n%s" % (self.getName(), - self._port.relative_test_filename(filename), - error_str)) + _log.debug("%s %s failed:" % (self._name, test_name)) + for f in result.failures: + _log.debug("%s %s" % (self._name, f.message())) else: - _log.debug("%s %s passed" % (self.getName(), - self._port.relative_test_filename(filename))) + _log.debug("%s %s passed" % (self._name, test_name)) self._result_queue.put(result.dumps()) if batch_size > 0 and batch_count >= batch_size: # Bounce the shell and reset count. - self._kill_dump_render_tree() + single_test_runner.kill_dump_render_tree() batch_count = 0 if test_runner: test_runner.update_summary(result_summary) - - def _run_test_in_another_thread(self, test_input): - """Run a test in a separate thread, enforcing a hard time limit. - - Since we can only detect the termination of a thread, not any internal - state or progress, we can only run per-test timeouts when running test - files singly. - - Args: - test_input: Object containing the test filename and timeout - - Returns: - A TestResult - """ - worker = SingleTestThread(self._port, - self._options, - self._worker_number, - self._name, - test_input, - self._test_types, - self._test_args) - - worker.start() - - thread_timeout = _milliseconds_to_seconds( - _pad_timeout(int(test_input.timeout))) - thread._next_timeout = time.time() + thread_timeout - worker.join(thread_timeout) - if worker.isAlive(): - # If join() returned with the thread still running, the - # DumpRenderTree is completely hung and there's nothing - # more we can do with it. We have to kill all the - # DumpRenderTrees to free it up. If we're running more than - # one DumpRenderTree thread, we'll end up killing the other - # DumpRenderTrees too, introducing spurious crashes. We accept - # that tradeoff in order to avoid losing the rest of this - # thread's results. - _log.error('Test thread hung: killing all DumpRenderTrees') - if worker._driver: - worker._driver.stop() - - try: - result = worker.get_test_result() - except AttributeError, e: - # This gets raised if the worker thread has already exited. - _log.error('Cannot get results of test: %s' % test_input.filename) - # FIXME: Seems we want a unique failure type here. - result = test_results.TestResult(test_input.filename) - - return result - - def _run_test_in_this_thread(self, test_input): - """Run a single test file using a shared DumpRenderTree process. - - Args: - test_input: Object containing the test filename, uri and timeout - - Returns: a TestResult object. - """ - self._ensure_dump_render_tree_is_running() - thread_timeout = _milliseconds_to_seconds( - _pad_timeout(int(test_input.timeout))) - self._next_timeout = time.time() + thread_timeout - test_result = _run_single_test(self._port, self._options, test_input, - self._test_types, self._test_args, - self._driver, self._name) - self._test_results.append(test_result) - return test_result - - def _ensure_dump_render_tree_is_running(self): - """Start the shared DumpRenderTree, if it's not running. - - This is not for use when running tests singly, since those each start - a separate DumpRenderTree in their own thread. - - """ - # poll() is not threadsafe and can throw OSError due to: - # http://bugs.python.org/issue1731717 - if not self._driver or self._driver.poll() is not None: - self._driver = self._port.create_driver(self._worker_number) - self._driver.start() - - def _start_servers_with_lock(self): - """Acquire http lock and start the servers.""" - self._http_lock_wait_begin = time.time() - _log.debug('Acquire http lock ...') - self._port.acquire_http_lock() - _log.debug('Starting HTTP server ...') - self._port.start_http_server() - _log.debug('Starting WebSocket server ...') - self._port.start_websocket_server() - self._http_lock_wait_end = time.time() - self._have_http_lock = True - - def _stop_servers_with_lock(self): - """Stop the servers and release http lock.""" - if self._have_http_lock: - _log.debug('Stopping HTTP server ...') - self._port.stop_http_server() - _log.debug('Stopping WebSocket server ...') - self._port.stop_websocket_server() - _log.debug('Release http lock ...') - self._port.release_http_lock() - self._have_http_lock = False - - def _kill_dump_render_tree(self): - """Kill the DumpRenderTree process if it's running.""" - if self._driver: - self._driver.stop() - self._driver = None diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py index 3267fb7..8226ed0 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py @@ -55,8 +55,7 @@ class JSONLayoutResultsGenerator(json_results_generator.JSONResultsGeneratorBase def __init__(self, port, builder_name, build_name, build_number, results_file_base_path, builder_base_url, test_timings, expectations, result_summary, all_tests, - generate_incremental_results=False, test_results_server=None, - test_type="", master_name=""): + test_results_server=None, test_type="", master_name=""): """Modifies the results.json file. Grabs it off the archive directory if it is not found locally. @@ -67,8 +66,7 @@ class JSONLayoutResultsGenerator(json_results_generator.JSONResultsGeneratorBase super(JSONLayoutResultsGenerator, self).__init__( port, builder_name, build_name, build_number, results_file_base_path, builder_base_url, {}, port.test_repository_paths(), - generate_incremental_results, test_results_server, - test_type, master_name) + test_results_server, test_type, master_name) self._expectations = expectations diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py index 32ffd71..05662c2 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py @@ -114,13 +114,16 @@ class JSONResultsGeneratorBase(object): URL_FOR_TEST_LIST_JSON = \ "http://%s/testfile?builder=%s&name=%s&testlistjson=1&testtype=%s" + # FIXME: Remove generate_incremental_results once the reference to it in + # http://src.chromium.org/viewvc/chrome/trunk/tools/build/scripts/slave/gtest_slave_utils.py + # has been removed. def __init__(self, port, builder_name, build_name, build_number, results_file_base_path, builder_base_url, test_results_map, svn_repositories=None, - generate_incremental_results=False, test_results_server=None, test_type="", - master_name=""): + master_name="", + generate_incremental_results=None): """Modifies the results.json file. Grabs it off the archive directory if it is not found locally. @@ -137,8 +140,6 @@ class JSONResultsGeneratorBase(object): svn_repositories: A (json_field_name, svn_path) pair for SVN repositories that tests rely on. The SVN revision will be included in the JSON with the given json_field_name. - generate_incremental_results: If true, generate incremental json file - from current run results. test_results_server: server that hosts test results json. test_type: test type string (e.g. 'layout-tests'). master_name: the name of the buildbot master. @@ -157,7 +158,6 @@ class JSONResultsGeneratorBase(object): self._test_results_map = test_results_map self._test_results = test_results_map.values() - self._generate_incremental_results = generate_incremental_results self._svn_repositories = svn_repositories if not self._svn_repositories: @@ -167,39 +167,20 @@ class JSONResultsGeneratorBase(object): self._test_type = test_type self._master_name = master_name - self._json = None self._archived_results = None def generate_json_output(self): - """Generates the JSON output file.""" - - # Generate the JSON output file that has full results. - # FIXME: stop writing out the full results file once all bots use - # incremental results. - if not self._json: - self._json = self.get_json() - if self._json: - self._generate_json_file(self._json, self._results_file_path) - - # Generate the JSON output file that only has incremental results. - if self._generate_incremental_results: - json = self.get_json(incremental=True) - if json: - self._generate_json_file( - json, self._incremental_results_file_path) - - def get_json(self, incremental=False): + json = self.get_json() + if json: + self._generate_json_file( + json, self._incremental_results_file_path) + + def get_json(self): """Gets the results for the results.json file.""" results_json = {} - if not incremental: - if self._json: - return self._json - - if self._archived_results: - results_json = self._archived_results if not results_json: - results_json, error = self._get_archived_json_results(incremental) + results_json, error = self._get_archived_json_results() if error: # If there was an error don't write a results.json # file at all as it would lose all the information on the @@ -231,7 +212,7 @@ class JSONResultsGeneratorBase(object): all_failing_tests = self._get_failed_test_names() all_failing_tests.update(tests.iterkeys()) for test in all_failing_tests: - self._insert_test_time_and_result(test, tests, incremental) + self._insert_test_time_and_result(test, tests) return results_json @@ -340,52 +321,39 @@ class JSONResultsGeneratorBase(object): return "" return "" - def _get_archived_json_results(self, for_incremental=False): - """Reads old results JSON file if it exists. - Returns (archived_results, error) tuple where error is None if results - were successfully read. - - if for_incremental is True, download JSON file that only contains test + def _get_archived_json_results(self): + """Download JSON file that only contains test name list from test-results server. This is for generating incremental JSON so the file generated has info for tests that failed before but pass or are skipped from current run. + + Returns (archived_results, error) tuple where error is None if results + were successfully read. """ results_json = {} old_results = None error = None - if self._fs.exists(self._results_file_path) and not for_incremental: - old_results = self._fs.read_text_file(self._results_file_path) - elif self._builder_base_url or for_incremental: - if for_incremental: - if not self._test_results_server: - # starting from fresh if no test results server specified. - return {}, None - - results_file_url = (self.URL_FOR_TEST_LIST_JSON % - (urllib2.quote(self._test_results_server), - urllib2.quote(self._builder_name), - self.RESULTS_FILENAME, - urllib2.quote(self._test_type))) - else: - # Check if we have the archived JSON file on the buildbot - # server. - results_file_url = (self._builder_base_url + - self._build_name + "/" + self.RESULTS_FILENAME) - _log.error("Local results.json file does not exist. Grabbing " - "it off the archive at " + results_file_url) + if not self._test_results_server: + return {}, None - try: - results_file = urllib2.urlopen(results_file_url) - info = results_file.info() - old_results = results_file.read() - except urllib2.HTTPError, http_error: - # A non-4xx status code means the bot is hosed for some reason - # and we can't grab the results.json file off of it. - if (http_error.code < 400 and http_error.code >= 500): - error = http_error - except urllib2.URLError, url_error: - error = url_error + results_file_url = (self.URL_FOR_TEST_LIST_JSON % + (urllib2.quote(self._test_results_server), + urllib2.quote(self._builder_name), + self.RESULTS_FILENAME, + urllib2.quote(self._test_type))) + + try: + results_file = urllib2.urlopen(results_file_url) + info = results_file.info() + old_results = results_file.read() + except urllib2.HTTPError, http_error: + # A non-4xx status code means the bot is hosed for some reason + # and we can't grab the results.json file off of it. + if (http_error.code < 400 and http_error.code >= 500): + error = http_error + except urllib2.URLError, url_error: + error = url_error if old_results: # Strip the prefix and suffix so we can get the actual JSON object. @@ -490,7 +458,7 @@ class JSONResultsGeneratorBase(object): int(time.time()), self.TIME) - def _insert_test_time_and_result(self, test_name, tests, incremental=False): + def _insert_test_time_and_result(self, test_name, tests): """ Insert a test item with its results to the given tests dictionary. Args: @@ -514,11 +482,6 @@ class JSONResultsGeneratorBase(object): else: thisTest[self.TIMES] = [[1, time]] - # Don't normalize the incremental results json because we need results - # for tests that pass or have no data from current run. - if not incremental: - self._normalize_results_json(thisTest, test_name, tests) - def _convert_json_to_current_version(self, results_json): """If the JSON does not match the current version, converts it to the current version and adds in the new version number. diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator_unittest.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator_unittest.py index ce99765..95da8fb 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator_unittest.py @@ -94,7 +94,7 @@ class JSONGeneratorTest(unittest.TestCase): failed_count_map = dict([(t, 1) for t in failed_tests]) # Test incremental json results - incremental_json = generator.get_json(incremental=True) + incremental_json = generator.get_json() self._verify_json_results( tests_set, test_timings, @@ -106,33 +106,6 @@ class JSONGeneratorTest(unittest.TestCase): incremental_json, 1) - # Test aggregated json results - generator.set_archived_results(self._json) - json = generator.get_json(incremental=False) - self._json = json - self._num_runs += 1 - self._tests_set |= tests_set - self._test_timings.update(test_timings) - self._PASS_count += len(PASS_tests) - self._DISABLED_count += len(DISABLED_tests) - self._FLAKY_count += len(FLAKY_tests) - self._fixable_count += len(DISABLED_tests | failed_tests) - - get = self._failed_count_map.get - for test in failed_count_map.iterkeys(): - self._failed_count_map[test] = get(test, 0) + 1 - - self._verify_json_results( - self._tests_set, - self._test_timings, - self._failed_count_map, - self._PASS_count, - self._DISABLED_count, - self._FLAKY_count, - self._fixable_count, - self._json, - self._num_runs) - def _verify_json_results(self, tests_set, test_timings, failed_count_map, PASS_count, DISABLED_count, FLAKY_count, fixable_count, diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/manager_worker_broker.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/manager_worker_broker.py new file mode 100644 index 0000000..a0f252c --- /dev/null +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/manager_worker_broker.py @@ -0,0 +1,282 @@ +# Copyright (C) 2011 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Module for handling messages and concurrency for run-webkit-tests. + +This module implements a message broker that connects the manager +(TestRunner2) to the workers: it provides a messaging abstraction and +message loops (building on top of message_broker2), and handles starting +workers by launching threads and/or processes depending on the +requested configuration. + +There are a lot of classes and objects involved in a fully connected system. +They interact more or less like: + +TestRunner2 --> _InlineManager ---> _InlineWorker <-> Worker + ^ \ / ^ + | v v | + \-------------------- MessageBroker -------------/ +""" + +import logging +import optparse +import Queue +import thread +import threading +import time + + +# Handle Python < 2.6 where multiprocessing isn't available. +# +# _Multiprocessing_Process is needed so that _MultiProcessWorker +# can be defined with or without multiprocessing. +try: + import multiprocessing + _Multiprocessing_Process = multiprocessing.Process +except ImportError: + multiprocessing = None + _Multiprocessing_Process = threading.Thread + + +from webkitpy.layout_tests import port +from webkitpy.layout_tests.layout_package import message_broker2 + + +_log = logging.getLogger(__name__) + +# +# Topic names for Manager <-> Worker messaging +# +MANAGER_TOPIC = 'managers' +ANY_WORKER_TOPIC = 'workers' + + +def runtime_options(): + """Return a list of optparse.Option objects for any runtime values used + by this module.""" + options = [ + optparse.make_option("--worker-model", action="store", + help=("controls worker model. Valid values are " + "'inline', 'threads', and 'processes'.")), + ] + return options + + +def get(port, options, client, worker_class): + """Return a connection to a manager/worker message_broker + + Args: + port - handle to layout_tests/port object for port-specific stuff + options - optparse argument for command-line options + client - message_broker2.BrokerClient implementation to dispatch + replies to. + worker_class - type of workers to create. This class must implement + the methods in AbstractWorker. + Returns: + A handle to an object that will talk to a message broker configured + for the normal manager/worker communication. + """ + worker_model = options.worker_model + if worker_model == 'inline': + queue_class = Queue.Queue + manager_class = _InlineManager + elif worker_model == 'threads': + queue_class = Queue.Queue + manager_class = _ThreadedManager + elif worker_model == 'processes' and multiprocessing: + queue_class = multiprocessing.Queue + manager_class = _MultiProcessManager + else: + raise ValueError("unsupported value for --worker-model: %s" % + worker_model) + + broker = message_broker2.Broker(options, queue_class) + return manager_class(broker, port, options, client, worker_class) + + +class AbstractWorker(message_broker2.BrokerClient): + def __init__(self, broker_connection, worker_number, options): + """The constructor should be used to do any simple initialization + necessary, but should not do anything that creates data structures + that cannot be Pickled or sent across processes (like opening + files or sockets). Complex initialization should be done at the + start of the run() call. + + Args: + broker_connection - handle to the BrokerConnection object creating + the worker and that can be used for messaging. + worker_number - identifier for this particular worker + options - command-line argument object from optparse""" + + raise NotImplementedError + + def run(self, port): + """Callback for the worker to start executing. Typically does any + remaining initialization and then calls broker_connection.run_message_loop().""" + raise NotImplementedError + + def cancel(self): + """Called when possible to indicate to the worker to stop processing + messages and shut down. Note that workers may be stopped without this + method being called, so clients should not rely solely on this.""" + raise NotImplementedError + + +class _ManagerConnection(message_broker2.BrokerConnection): + def __init__(self, broker, options, client, worker_class): + """Base initialization for all Manager objects. + + Args: + broker: handle to the message_broker2 object + options: command line options object + client: callback object (the caller) + worker_class: class object to use to create workers. + """ + message_broker2.BrokerConnection.__init__(self, broker, client, + MANAGER_TOPIC, ANY_WORKER_TOPIC) + self._options = options + self._worker_class = worker_class + + def start_worker(self, worker_number): + raise NotImplementedError + + +class _InlineManager(_ManagerConnection): + def __init__(self, broker, port, options, client, worker_class): + _ManagerConnection.__init__(self, broker, options, client, worker_class) + self._port = port + self._inline_worker = None + + def start_worker(self, worker_number): + self._inline_worker = _InlineWorkerConnection(self._broker, self._port, + self._client, self._worker_class, worker_number) + return self._inline_worker + + def run_message_loop(self, delay_secs=None): + # Note that delay_secs is ignored in this case since we can't easily + # implement it. + self._inline_worker.run() + self._broker.run_all_pending(MANAGER_TOPIC, self._client) + + +class _ThreadedManager(_ManagerConnection): + def __init__(self, broker, port, options, client, worker_class): + _ManagerConnection.__init__(self, broker, options, client, worker_class) + self._port = port + + def start_worker(self, worker_number): + worker_connection = _ThreadedWorkerConnection(self._broker, self._port, + self._worker_class, worker_number) + worker_connection.start() + return worker_connection + + +class _MultiProcessManager(_ManagerConnection): + def __init__(self, broker, port, options, client, worker_class): + # Note that this class does not keep a handle to the actual port + # object, because it isn't Picklable. Instead it keeps the port + # name and recreates the port in the child process from the name + # and options. + _ManagerConnection.__init__(self, broker, options, client, worker_class) + self._platform_name = port.real_name() + + def start_worker(self, worker_number): + worker_connection = _MultiProcessWorkerConnection(self._broker, self._platform_name, + self._worker_class, worker_number, self._options) + worker_connection.start() + return worker_connection + + +class _WorkerConnection(message_broker2.BrokerConnection): + def __init__(self, broker, worker_class, worker_number, options): + self._client = worker_class(self, worker_number, options) + self.name = self._client.name() + message_broker2.BrokerConnection.__init__(self, broker, self._client, + ANY_WORKER_TOPIC, MANAGER_TOPIC) + + def yield_to_broker(self): + pass + + +class _InlineWorkerConnection(_WorkerConnection): + def __init__(self, broker, port, manager_client, worker_class, worker_number): + _WorkerConnection.__init__(self, broker, worker_class, worker_number, port._options) + self._port = port + self._manager_client = manager_client + + def run(self): + self._client.run(self._port) + + def yield_to_broker(self): + self._broker.run_all_pending(MANAGER_TOPIC, self._manager_client) + + +class _Thread(threading.Thread): + def __init__(self, worker_connection, port, client): + threading.Thread.__init__(self) + self._worker_connection = worker_connection + self._port = port + self._client = client + + def run(self): + # FIXME: We can remove this once everyone is on 2.6. + if not hasattr(self, 'ident'): + self.ident = thread.get_ident() + self._client.run(self._port) + + +class _ThreadedWorkerConnection(_WorkerConnection): + def __init__(self, broker, port, worker_class, worker_number): + _WorkerConnection.__init__(self, broker, worker_class, worker_number, port._options) + self._thread = _Thread(self, port, self._client) + + def start(self): + self._thread.start() + + +class _Process(_Multiprocessing_Process): + def __init__(self, worker_connection, platform_name, options, client): + _Multiprocessing_Process.__init__(self) + self._worker_connection = worker_connection + self._platform_name = platform_name + self._options = options + self._client = client + + def run(self): + logging.basicConfig() + port_obj = port.get(self._platform_name, self._options) + self._client.run(port_obj) + + +class _MultiProcessWorkerConnection(_WorkerConnection): + def __init__(self, broker, platform_name, worker_class, worker_number, options): + _WorkerConnection.__init__(self, broker, worker_class, worker_number, options) + self._proc = _Process(self, platform_name, options, self._client) + + def start(self): + self._proc.start() diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/manager_worker_broker_unittest.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/manager_worker_broker_unittest.py new file mode 100644 index 0000000..ffbe081 --- /dev/null +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/manager_worker_broker_unittest.py @@ -0,0 +1,227 @@ +# Copyright (C) 2010 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import optparse +import Queue +import sys +import unittest + +try: + import multiprocessing +except ImportError: + multiprocessing = None + + +from webkitpy.common.system import outputcapture + +from webkitpy.layout_tests import port +from webkitpy.layout_tests.layout_package import manager_worker_broker +from webkitpy.layout_tests.layout_package import message_broker2 + + +class TestWorker(manager_worker_broker.AbstractWorker): + def __init__(self, broker_connection, worker_number, options): + self._broker_connection = broker_connection + self._options = options + self._worker_number = worker_number + self._name = 'TestWorker/%d' % worker_number + self._stopped = False + + def handle_stop(self, src): + self._stopped = True + + def handle_test(self, src, an_int, a_str): + assert an_int == 1 + assert a_str == "hello, world" + self._broker_connection.post_message('test', 2, 'hi, everybody') + + def is_done(self): + return self._stopped + + def name(self): + return self._name + + def start(self): + pass + + def run(self, port): + try: + self._broker_connection.run_message_loop() + self._broker_connection.yield_to_broker() + self._broker_connection.post_message('done') + except Exception, e: + self._broker_connection.post_message('exception', (type(e), str(e), None)) + + +def get_options(worker_model): + option_list = manager_worker_broker.runtime_options() + parser = optparse.OptionParser(option_list=option_list) + options, args = parser.parse_args(args=['--worker-model', worker_model]) + return options + + +def make_broker(manager, worker_model): + options = get_options(worker_model) + return manager_worker_broker.get(port.get("test"), options, manager, + TestWorker) + + +class FunctionTests(unittest.TestCase): + def test_get__inline(self): + self.assertTrue(make_broker(self, 'inline') is not None) + + def test_get__threads(self): + self.assertTrue(make_broker(self, 'threads') is not None) + + def test_get__processes(self): + if multiprocessing: + self.assertTrue(make_broker(self, 'processes') is not None) + else: + self.assertRaises(ValueError, make_broker, self, 'processes') + + def test_get__unknown(self): + self.assertRaises(ValueError, make_broker, self, 'unknown') + + +class _TestsMixin(object): + """Mixin class that implements a series of tests to enforce the + contract all implementations must follow.""" + + # + # Methods to implement the Manager side of the ClientInterface + # + def name(self): + return 'Tester' + + def is_done(self): + return self._done + + # + # Handlers for the messages the TestWorker may send. + # + def handle_done(self, src): + self._done = True + + def handle_test(self, src, an_int, a_str): + self._an_int = an_int + self._a_str = a_str + + def handle_exception(self, src, exc_info): + self._exception = exc_info + self._done = True + + # + # Testing helper methods + # + def setUp(self): + self._an_int = None + self._a_str = None + self._broker = None + self._done = False + self._exception = None + self._worker_model = None + + def make_broker(self): + self._broker = make_broker(self, self._worker_model) + + # + # Actual unit tests + # + def test_done(self): + if not self._worker_model: + return + self.make_broker() + worker = self._broker.start_worker(0) + self._broker.post_message('test', 1, 'hello, world') + self._broker.post_message('stop') + self._broker.run_message_loop() + self.assertTrue(self.is_done()) + self.assertEqual(self._an_int, 2) + self.assertEqual(self._a_str, 'hi, everybody') + + def test_unknown_message(self): + if not self._worker_model: + return + self.make_broker() + worker = self._broker.start_worker(0) + self._broker.post_message('unknown') + self._broker.run_message_loop() + + self.assertTrue(self.is_done()) + self.assertEquals(self._exception[0], ValueError) + self.assertEquals(self._exception[1], + "TestWorker/0: received message 'unknown' it couldn't handle") + + +class InlineBrokerTests(_TestsMixin, unittest.TestCase): + def setUp(self): + _TestsMixin.setUp(self) + self._worker_model = 'inline' + + +class MultiProcessBrokerTests(_TestsMixin, unittest.TestCase): + def setUp(self): + _TestsMixin.setUp(self) + if multiprocessing: + self._worker_model = 'processes' + else: + self._worker_model = None + + def queue(self): + return multiprocessing.Queue() + + +class ThreadedBrokerTests(_TestsMixin, unittest.TestCase): + def setUp(self): + _TestsMixin.setUp(self) + self._worker_model = 'threads' + + +class FunctionsTest(unittest.TestCase): + def test_runtime_options(self): + option_list = manager_worker_broker.runtime_options() + parser = optparse.OptionParser(option_list=option_list) + options, args = parser.parse_args([]) + self.assertTrue(options) + + +class InterfaceTest(unittest.TestCase): + # These tests mostly exist to pacify coverage. + + # FIXME: There must be a better way to do this and also verify + # that classes do implement every abstract method in an interface. + def test_managerconnection_is_abstract(self): + # Test that all the base class methods are abstract and have the + # signature we expect. + broker = make_broker(self, 'inline') + obj = manager_worker_broker._ManagerConnection(broker._broker, None, self, None) + self.assertRaises(NotImplementedError, obj.start_worker, 0) + + +if __name__ == '__main__': + unittest.main() diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/message_broker.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/message_broker.py index 481c617..66a7aa8 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/message_broker.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/message_broker.py @@ -41,9 +41,9 @@ requested configuration. """ import logging -import sys import time -import traceback + +from webkitpy.common.system import stack_utils import dump_render_tree_thread @@ -137,6 +137,7 @@ class MultiThreadedBroker(WorkerMessageBroker): def run_message_loop(self): threads = self._threads() + wedged_threads = set() # Loop through all the threads waiting for them to finish. some_thread_is_alive = True @@ -145,11 +146,15 @@ class MultiThreadedBroker(WorkerMessageBroker): t = time.time() for thread in threads: if thread.isAlive(): + if thread in wedged_threads: + continue + some_thread_is_alive = True next_timeout = thread.next_timeout() if next_timeout and t > next_timeout: - log_wedged_worker(thread.getName(), thread.id()) + stack_utils.log_thread_state(_log.error, thread.getName(), thread.id(), "is wedged") thread.clear_next_timeout() + wedged_threads.add(thread) exception_info = thread.exception_info() if exception_info is not None: @@ -164,34 +169,10 @@ class MultiThreadedBroker(WorkerMessageBroker): if some_thread_is_alive: time.sleep(0.01) + if wedged_threads: + _log.warning("All remaining threads are wedged, bailing out.") + def cancel_workers(self): threads = self._threads() for thread in threads: thread.cancel() - - -def log_wedged_worker(name, id): - """Log information about the given worker state.""" - stack = _find_thread_stack(id) - assert(stack is not None) - _log.error("") - _log.error("%s (tid %d) is wedged" % (name, id)) - _log_stack(stack) - _log.error("") - - -def _find_thread_stack(id): - """Returns a stack object that can be used to dump a stack trace for - the given thread id (or None if the id is not found).""" - for thread_id, stack in sys._current_frames().items(): - if thread_id == id: - return stack - return None - - -def _log_stack(stack): - """Log a stack trace to log.error().""" - for filename, lineno, name, line in traceback.extract_stack(stack): - _log.error('File: "%s", line %d, in %s' % (filename, lineno, name)) - if line: - _log.error(' %s' % line.strip()) diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/message_broker2.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/message_broker2.py new file mode 100644 index 0000000..ec3c970 --- /dev/null +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/message_broker2.py @@ -0,0 +1,196 @@ +# Copyright (C) 2011 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Module for handling messaging for run-webkit-tests. + +This module implements a simple message broker abstraction that will be +used to coordinate messages between the main run-webkit-tests thread +(aka TestRunner) and the individual worker threads (previously known as +dump_render_tree_threads). + +The broker simply distributes messages onto topics (named queues); the actual +queues themselves are provided by the caller, as the queue's implementation +requirements varies vary depending on the desired concurrency model +(none/threads/processes). + +In order for shared-nothing messaging between processing to be possible, +Messages must be picklable. + +The module defines one interface and two classes. Callers of this package +must implement the BrokerClient interface, and most callers will create +BrokerConnections as well as Brokers. + +The classes relate to each other as: + + BrokerClient ------> BrokerConnection + ^ | + | v + \---------------- Broker + +(The BrokerClient never calls broker directly after it is created, only +BrokerConnection. BrokerConnection passes a reference to BrokerClient to +Broker, and Broker only invokes that reference, never talking directly to +BrokerConnection). +""" + +import cPickle +import logging +import Queue +import time + + +_log = logging.getLogger(__name__) + + +class BrokerClient(object): + """Abstract base class / interface that all message broker clients must + implement. In addition to the methods below, by convention clients + implement routines of the signature type + + handle_MESSAGE_NAME(self, src, ...): + + where MESSAGE_NAME matches the string passed to post_message(), and + src indicates the name of the sender. If the message contains values in + the message body, those will be provided as optparams.""" + + def __init__(self, *optargs, **kwargs): + raise NotImplementedError + + def is_done(self): + """Called from inside run_message_loop() to indicate whether to exit.""" + raise NotImplementedError + + def name(self): + """Return a name that identifies the client.""" + raise NotImplementedError + + +class Broker(object): + """Brokers provide the basic model of a set of topics. Clients can post a + message to any topic using post_message(), and can process messages on one + topic at a time using run_message_loop().""" + + def __init__(self, options, queue_maker): + """Args: + options: a runtime option class from optparse + queue_maker: a factory method that returns objects implementing a + Queue interface (put()/get()). + """ + self._options = options + self._queue_maker = queue_maker + self._topics = {} + + def add_topic(self, topic_name): + if topic_name not in self._topics: + self._topics[topic_name] = self._queue_maker() + + def _get_queue_for_topic(self, topic_name): + return self._topics[topic_name] + + def post_message(self, client, topic_name, message_name, *message_args): + """Post a message to the appropriate topic name. + + Messages have a name and a tuple of optional arguments. Both must be picklable.""" + message = _Message(client.name(), topic_name, message_name, message_args) + queue = self._get_queue_for_topic(topic_name) + queue.put(_Message.dumps(message)) + + def run_message_loop(self, topic_name, client, delay_secs=None): + """Loop processing messages until client.is_done() or delay passes. + + To run indefinitely, set delay_secs to None.""" + assert delay_secs is None or delay_secs > 0 + self._run_loop(topic_name, client, block=True, delay_secs=delay_secs) + + def run_all_pending(self, topic_name, client): + """Process messages until client.is_done() or caller would block.""" + self._run_loop(topic_name, client, block=False, delay_secs=None) + + def _run_loop(self, topic_name, client, block, delay_secs): + queue = self._get_queue_for_topic(topic_name) + while not client.is_done(): + try: + s = queue.get(block, delay_secs) + except Queue.Empty: + return + msg = _Message.loads(s) + self._dispatch_message(msg, client) + + def _dispatch_message(self, message, client): + if not hasattr(client, 'handle_' + message.name): + raise ValueError( + "%s: received message '%s' it couldn't handle" % + (client.name(), message.name)) + optargs = message.args + message_handler = getattr(client, 'handle_' + message.name) + message_handler(message.src, *optargs) + + +class _Message(object): + @staticmethod + def loads(str): + obj = cPickle.loads(str) + assert(isinstance(obj, _Message)) + return obj + + def __init__(self, src, topic_name, message_name, message_args): + self.src = src + self.topic_name = topic_name + self.name = message_name + self.args = message_args + + def dumps(self): + return cPickle.dumps(self) + + def __repr__(self): + return ("_Message(from='%s', topic_name='%s', message_name='%s')" % + (self.src, self.topic_name, self.name)) + + +class BrokerConnection(object): + """BrokerConnection provides a connection-oriented facade on top of a + Broker, so that callers don't have to repeatedly pass the same topic + names over and over.""" + + def __init__(self, broker, client, run_topic, post_topic): + """Create a BrokerConnection on top of a Broker. Note that the Broker + is passed in rather than created so that a single Broker can be used + by multiple BrokerConnections.""" + self._broker = broker + self._client = client + self._post_topic = post_topic + self._run_topic = run_topic + broker.add_topic(run_topic) + broker.add_topic(post_topic) + + def run_message_loop(self, delay_secs=None): + self._broker.run_message_loop(self._run_topic, self._client, delay_secs) + + def post_message(self, message_name, *message_args): + self._broker.post_message(self._client, self._post_topic, + message_name, *message_args) diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/message_broker2_unittest.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/message_broker2_unittest.py new file mode 100644 index 0000000..0e0a88d --- /dev/null +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/message_broker2_unittest.py @@ -0,0 +1,83 @@ +# Copyright (C) 2011 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import unittest + +from webkitpy.layout_tests.layout_package import message_broker2 + +# This file exists to test routines that aren't necessarily covered elsewhere; +# most of the testing of message_broker2 will be covered under the tests in +# the manager_worker_broker module. + + +class MessageTest(unittest.TestCase): + def test__no_body(self): + msg = message_broker2._Message('src', 'topic_name', 'message_name', None) + self.assertTrue(repr(msg)) + s = msg.dumps() + new_msg = message_broker2._Message.loads(s) + self.assertEqual(new_msg.name, 'message_name') + self.assertEqual(new_msg.args, None) + self.assertEqual(new_msg.topic_name, 'topic_name') + self.assertEqual(new_msg.src, 'src') + + def test__body(self): + msg = message_broker2._Message('src', 'topic_name', 'message_name', + ('body', 0)) + self.assertTrue(repr(msg)) + s = msg.dumps() + new_msg = message_broker2._Message.loads(s) + self.assertEqual(new_msg.name, 'message_name') + self.assertEqual(new_msg.args, ('body', 0)) + self.assertEqual(new_msg.topic_name, 'topic_name') + self.assertEqual(new_msg.src, 'src') + + +class InterfaceTest(unittest.TestCase): + # These tests mostly exist to pacify coverage. + + # FIXME: There must be a better way to do this and also verify + # that classes do implement every abstract method in an interface. + + def test_brokerclient_is_abstract(self): + # Test that we can't create an instance directly. + self.assertRaises(NotImplementedError, message_broker2.BrokerClient) + + class TestClient(message_broker2.BrokerClient): + def __init__(self): + pass + + # Test that all the base class methods are abstract and have the + # signature we expect. + obj = TestClient() + self.assertRaises(NotImplementedError, obj.is_done) + self.assertRaises(NotImplementedError, obj.name) + + +if __name__ == '__main__': + unittest.main() diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/message_broker_unittest.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/message_broker_unittest.py index 6f04fd3..f4cb5d2 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/message_broker_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/message_broker_unittest.py @@ -84,7 +84,6 @@ class TestThread(threading.Thread): def next_timeout(self): if self._timeout: - self._timeout_queue.put('done') return time.time() - 10 return time.time() @@ -125,7 +124,12 @@ class MultiThreadedBrokerTest(unittest.TestCase): child_thread.start() started_msg = starting_queue.get() stopping_queue.put(msg) - return broker.run_message_loop() + res = broker.run_message_loop() + if msg == 'Timeout': + child_thread._timeout_queue.put('done') + child_thread.join(1.0) + self.assertFalse(child_thread.isAlive()) + return res def test_basic(self): interrupted = self.run_one_thread('') @@ -135,48 +139,22 @@ class MultiThreadedBrokerTest(unittest.TestCase): self.assertRaises(KeyboardInterrupt, self.run_one_thread, 'KeyboardInterrupt') def test_timeout(self): + # Because the timeout shows up as a wedged thread, this also tests + # log_wedged_worker(). oc = outputcapture.OutputCapture() - oc.capture_output() - interrupted = self.run_one_thread('Timeout') - self.assertFalse(interrupted) - oc.restore_output() - - def test_exception(self): - self.assertRaises(ValueError, self.run_one_thread, 'Exception') - - -class Test(unittest.TestCase): - def test_find_thread_stack_found(self): - id, stack = sys._current_frames().items()[0] - found_stack = message_broker._find_thread_stack(id) - self.assertNotEqual(found_stack, None) - - def test_find_thread_stack_not_found(self): - found_stack = message_broker._find_thread_stack(0) - self.assertEqual(found_stack, None) - - def test_log_wedged_worker(self): - oc = outputcapture.OutputCapture() - oc.capture_output() + stdout, stderr = oc.capture_output() logger = message_broker._log astream = array_stream.ArrayStream() handler = TestHandler(astream) logger.addHandler(handler) + interrupted = self.run_one_thread('Timeout') + stdout, stderr = oc.restore_output() + self.assertFalse(interrupted) + logger.handlers.remove(handler) + self.assertTrue('All remaining threads are wedged, bailing out.' in astream.get()) - starting_queue = Queue.Queue() - stopping_queue = Queue.Queue() - child_thread = TestThread(starting_queue, stopping_queue) - child_thread.start() - msg = starting_queue.get() - - message_broker.log_wedged_worker(child_thread.getName(), - child_thread.id()) - stopping_queue.put('') - child_thread.join(timeout=1.0) - - self.assertFalse(astream.empty()) - self.assertFalse(child_thread.isAlive()) - oc.restore_output() + def test_exception(self): + self.assertRaises(ValueError, self.run_one_thread, 'Exception') if __name__ == '__main__': diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/printing_unittest.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/printing_unittest.py index 12a786e..7ab6da8 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/printing_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/printing_unittest.py @@ -144,7 +144,7 @@ class Testprinter(unittest.TestCase): test in tests] expectations = test_expectations.TestExpectations( self._port, test_paths, expectations_str, - self._port.test_platform_name(), is_debug_mode=False, + self._port.test_configuration(), is_lint_mode=False) rs = result_summary.ResultSummary(expectations, test_paths) @@ -363,7 +363,7 @@ class Testprinter(unittest.TestCase): def test_print_progress__detailed(self): tests = ['passes/text.html', 'failures/expected/timeout.html', 'failures/expected/crash.html'] - expectations = 'failures/expected/timeout.html = TIMEOUT' + expectations = 'BUGX : failures/expected/timeout.html = TIMEOUT' # first, test that it is disabled properly # should still print one-line-progress @@ -569,8 +569,8 @@ class Testprinter(unittest.TestCase): self.assertFalse(out.empty()) expectations = """ -failures/expected/crash.html = CRASH -failures/expected/timeout.html = TIMEOUT +BUGX : failures/expected/crash.html = CRASH +BUGX : failures/expected/timeout.html = TIMEOUT """ err.reset() out.reset() diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/single_test_runner.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/single_test_runner.py new file mode 100644 index 0000000..96e3ee6 --- /dev/null +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/single_test_runner.py @@ -0,0 +1,322 @@ +# Copyright (C) 2011 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +import logging +import threading +import time + +from webkitpy.layout_tests.port import base + +from webkitpy.layout_tests.test_types import text_diff +from webkitpy.layout_tests.test_types import image_diff + +from webkitpy.layout_tests.layout_package import test_failures +from webkitpy.layout_tests.layout_package.test_results import TestResult + + +_log = logging.getLogger(__name__) + + +class ExpectedDriverOutput: + """Groups information about an expected driver output.""" + def __init__(self, text, image, image_hash): + self.text = text + self.image = image + self.image_hash = image_hash + + +class SingleTestRunner: + + def __init__(self, options, port, worker_name, worker_number): + self._options = options + self._port = port + self._worker_name = worker_name + self._worker_number = worker_number + self._driver = None + self._test_types = [] + self.has_http_lock = False + for cls in self._get_test_type_classes(): + self._test_types.append(cls(self._port, + self._options.results_directory)) + + def cleanup(self): + self.kill_dump_render_tree() + if self.has_http_lock: + self.stop_servers_with_lock() + + def _get_test_type_classes(self): + classes = [text_diff.TestTextDiff] + if self._options.pixel_tests: + classes.append(image_diff.ImageDiff) + return classes + + def timeout(self, test_input): + # We calculate how long we expect the test to take. + # + # The DumpRenderTree watchdog uses 2.5x the timeout; we want to be + # larger than that. We also add a little more padding if we're + # running tests in a separate thread. + # + # Note that we need to convert the test timeout from a + # string value in milliseconds to a float for Python. + driver_timeout_sec = 3.0 * float(test_input.timeout) / 1000.0 + if not self._options.run_singly: + return driver_timeout_sec + + thread_padding_sec = 1.0 + thread_timeout_sec = driver_timeout_sec + thread_padding_sec + return thread_timeout_sec + + def run_test(self, test_input, timeout): + if self._options.run_singly: + return self._run_test_in_another_thread(test_input, timeout) + else: + return self._run_test_in_this_thread(test_input) + return result + + def _run_test_in_another_thread(self, test_input, thread_timeout_sec): + """Run a test in a separate thread, enforcing a hard time limit. + + Since we can only detect the termination of a thread, not any internal + state or progress, we can only run per-test timeouts when running test + files singly. + + Args: + test_input: Object containing the test filename and timeout + thread_timeout_sec: time to wait before killing the driver process. + Returns: + A TestResult + """ + worker = self + result = None + + driver = worker._port.create_driver(worker._worker_number) + driver.start() + + class SingleTestThread(threading.Thread): + def run(self): + result = worker.run(test_input, driver) + + thread = SingleTestThread() + thread.start() + thread.join(thread_timeout_sec) + if thread.isAlive(): + # If join() returned with the thread still running, the + # DumpRenderTree is completely hung and there's nothing + # more we can do with it. We have to kill all the + # DumpRenderTrees to free it up. If we're running more than + # one DumpRenderTree thread, we'll end up killing the other + # DumpRenderTrees too, introducing spurious crashes. We accept + # that tradeoff in order to avoid losing the rest of this + # thread's results. + _log.error('Test thread hung: killing all DumpRenderTrees') + + driver.stop() + + if not result: + result = TestResult(test_input.filename, failures=[], + test_run_time=0, total_time_for_all_diffs=0, time_for_diffs={}) + return result + + def _run_test_in_this_thread(self, test_input): + """Run a single test file using a shared DumpRenderTree process. + + Args: + test_input: Object containing the test filename, uri and timeout + + Returns: a TestResult object. + """ + # poll() is not threadsafe and can throw OSError due to: + # http://bugs.python.org/issue1731717 + if not self._driver or self._driver.poll() is not None: + self._driver = self._port.create_driver(self._worker_number) + self._driver.start() + return self._run(self._driver, test_input) + + def _expected_driver_output(self): + return ExpectedDriverOutput(self._port.expected_text(self._filename), + self._port.expected_image(self._filename), + self._port.expected_checksum(self._filename)) + + def _should_fetch_expected_checksum(self): + return (self._options.pixel_tests and + not (self._options.new_baseline or self._options.reset_results)) + + def _driver_input(self, test_input): + self._filename = test_input.filename + self._timeout = test_input.timeout + self._testname = self._port.relative_test_filename(test_input.filename) + + # The image hash is used to avoid doing an image dump if the + # checksums match, so it should be set to a blank value if we + # are generating a new baseline. (Otherwise, an image from a + # previous run will be copied into the baseline.""" + image_hash = None + if self._should_fetch_expected_checksum(): + image_hash = self._port.expected_checksum(self._filename) + return base.DriverInput(self._filename, self._timeout, image_hash) + + def _run(self, driver, test_input): + if self._options.new_baseline or self._options.reset_results: + return self._run_rebaseline(driver, test_input) + return self._run_compare_test(driver, test_input) + + def _run_compare_test(self, driver, test_input): + driver_output = self._driver.run_test(self._driver_input(test_input)) + return self._process_output(driver_output) + + def _run_rebaseline(self, driver, test_input): + driver_output = self._driver.run_test(self._driver_input(test_input)) + failures = self._handle_error(driver_output) + # FIXME: It the test crashed or timed out, it might be bettter to avoid + # to write new baselines. + self._save_baselines(driver_output) + return TestResult(self._filename, failures, driver_output.test_time) + + def _save_baselines(self, driver_output): + # Although all test_shell/DumpRenderTree output should be utf-8, + # we do not ever decode it inside run-webkit-tests. For some tests + # DumpRenderTree may not output utf-8 text (e.g. webarchives). + self._save_baseline_data(driver_output.text, ".txt", + generate_new_baseline=self._options.new_baseline) + if self._options.pixel_tests and driver_output.image_hash: + self._save_baseline_data(driver_output.image, ".png", + generate_new_baseline=self._options.new_baseline) + self._save_baseline_data(driver_output.image_hash, ".checksum", + generate_new_baseline=self._options.new_baseline) + + def _save_baseline_data(self, data, modifier, generate_new_baseline=True): + """Saves a new baseline file into the port's baseline directory. + + The file will be named simply "-expected", suitable for + use as the expected results in a later run. + + Args: + data: result to be saved as the new baseline + modifier: type of the result file, e.g. ".txt" or ".png" + generate_new_baseline: whether to enerate a new, platform-specific + baseline, or update the existing one + """ + + port = self._port + fs = port._filesystem + if generate_new_baseline: + relative_dir = fs.dirname(self._testname) + baseline_path = port.baseline_path() + output_dir = fs.join(baseline_path, relative_dir) + output_file = fs.basename(fs.splitext(self._filename)[0] + + "-expected" + modifier) + fs.maybe_make_directory(output_dir) + output_path = fs.join(output_dir, output_file) + _log.debug('writing new baseline result "%s"' % (output_path)) + else: + output_path = port.expected_filename(self._filename, modifier) + _log.debug('resetting baseline result "%s"' % output_path) + + port.update_baseline(output_path, data) + + def _handle_error(self, driver_output): + failures = [] + fs = self._port._filesystem + if driver_output.timeout: + failures.append(test_failures.FailureTimeout()) + if driver_output.crash: + failures.append(test_failures.FailureCrash()) + _log.debug("%s Stacktrace for %s:\n%s" % (self._worker_name, self._testname, + driver_output.error)) + stack_filename = fs.join(self._options.results_directory, self._testname) + stack_filename = fs.splitext(stack_filename)[0] + "-stack.txt" + fs.maybe_make_directory(fs.dirname(stack_filename)) + fs.write_text_file(stack_filename, driver_output.error) + elif driver_output.error: + _log.debug("%s %s output stderr lines:\n%s" % (self._worker_name, self._testname, + driver_output.error)) + return failures + + def _run_test(self): + driver_output = self._driver.run_test(self._driver_input()) + return self._process_output(driver_output) + + def _process_output(self, driver_output): + """Receives the output from a DumpRenderTree process, subjects it to a + number of tests, and returns a list of failure types the test produced. + Args: + driver_output: a DriverOutput object containing the output from the driver + + Returns: a TestResult object + """ + fs = self._port._filesystem + failures = self._handle_error(driver_output) + expected_driver_output = self._expected_driver_output() + + # Check the output and save the results. + start_time = time.time() + time_for_diffs = {} + for test_type in self._test_types: + start_diff_time = time.time() + new_failures = test_type.compare_output( + self._port, self._filename, self._options, driver_output, + expected_driver_output) + # Don't add any more failures if we already have a crash, so we don't + # double-report those tests. We do double-report for timeouts since + # we still want to see the text and image output. + if not driver_output.crash: + failures.extend(new_failures) + time_for_diffs[test_type.__class__.__name__] = ( + time.time() - start_diff_time) + + total_time_for_all_diffs = time.time() - start_diff_time + return TestResult(self._filename, failures, driver_output.test_time, + total_time_for_all_diffs, time_for_diffs) + + def start_servers_with_lock(self): + _log.debug('Acquiring http lock ...') + self._port.acquire_http_lock() + _log.debug('Starting HTTP server ...') + self._port.start_http_server() + _log.debug('Starting WebSocket server ...') + self._port.start_websocket_server() + self.has_http_lock = True + + def stop_servers_with_lock(self): + """Stop the servers and release http lock.""" + if self.has_http_lock: + _log.debug('Stopping HTTP server ...') + self._port.stop_http_server() + _log.debug('Stopping WebSocket server ...') + self._port.stop_websocket_server() + _log.debug('Releasing server lock ...') + self._port.release_http_lock() + self.has_http_lock = False + + def kill_dump_render_tree(self): + """Kill the DumpRenderTree process if it's running.""" + if self._driver: + self._driver.stop() + self._driver = None diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_expectations.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_expectations.py index 806b663..494395a 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_expectations.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_expectations.py @@ -31,6 +31,7 @@ for layout tests. """ +import itertools import logging import re @@ -84,18 +85,16 @@ def remove_pixel_failures(expected_results): class TestExpectations: TEST_LIST = "test_expectations.txt" - def __init__(self, port, tests, expectations, test_platform_name, - is_debug_mode, is_lint_mode, overrides=None): + def __init__(self, port, tests, expectations, test_config, + is_lint_mode, overrides=None): """Loads and parses the test expectations given in the string. Args: port: handle to object containing platform-specific functionality - test: list of all of the test files + tests: list of all of the test files expectations: test expectations as a string - test_platform_name: name of the platform to match expectations - against. Note that this may be different than - port.test_platform_name() when is_lint_mode is True. - is_debug_mode: whether to use the DEBUG or RELEASE modifiers - in the expectations + test_config: specific values to check against when + parsing the file (usually port.test_config(), + but may be different when linting or doing other things). is_lint_mode: If True, just parse the expectations string looking for errors. overrides: test expectations that are allowed to override any @@ -104,7 +103,7 @@ class TestExpectations: and downstream expectations). """ self._expected_failures = TestExpectationsFile(port, expectations, - tests, test_platform_name, is_debug_mode, is_lint_mode, + tests, test_config, is_lint_mode, overrides=overrides) # TODO(ojan): Allow for removing skipped tests when getting the list of @@ -197,7 +196,7 @@ class ParseError(Exception): return '\n'.join(map(str, self.errors)) def __repr__(self): - return 'ParseError(fatal=%s, errors=%s)' % (fatal, errors) + return 'ParseError(fatal=%s, errors=%s)' % (self.fatal, self.errors) class ModifiersAndExpectations: @@ -302,29 +301,15 @@ class TestExpectationsFile: 'fail': FAIL, 'flaky': FLAKY} - def __init__(self, port, expectations, full_test_list, test_platform_name, - is_debug_mode, is_lint_mode, overrides=None): - """ - expectations: Contents of the expectations file - full_test_list: The list of all tests to be run pending processing of - the expections for those tests. - test_platform_name: name of the platform to match expectations - against. Note that this may be different than - port.test_platform_name() when is_lint_mode is True. - is_debug_mode: Whether we testing a test_shell built debug mode. - is_lint_mode: Whether this is just linting test_expecatations.txt. - overrides: test expectations that are allowed to override any - entries in |expectations|. This is used by callers - that need to manage two sets of expectations (e.g., upstream - and downstream expectations). - """ + def __init__(self, port, expectations, full_test_list, + test_config, is_lint_mode, overrides=None): + # See argument documentation in TestExpectation(), above. self._port = port self._fs = port._filesystem self._expectations = expectations self._full_test_list = full_test_list - self._test_platform_name = test_platform_name - self._is_debug_mode = is_debug_mode + self._test_config = test_config self._is_lint_mode = is_lint_mode self._overrides = overrides self._errors = [] @@ -332,7 +317,9 @@ class TestExpectationsFile: # Maps relative test paths as listed in the expectations file to a # list of maps containing modifiers and expectations for each time - # the test is listed in the expectations file. + # the test is listed in the expectations file. We use this to + # keep a representation of the entire list of expectations, even + # invalid ones. self._all_expectations = {} # Maps a test to its list of expectations. @@ -345,7 +332,8 @@ class TestExpectationsFile: # the options minus any bug or platform strings self._test_to_modifiers = {} - # Maps a test to the base path that it was listed with in the list. + # Maps a test to the base path that it was listed with in the list and + # the number of matches that base path had. self._test_list_paths = {} self._modifier_to_tests = self._dict_of_sets(self.MODIFIERS) @@ -372,13 +360,7 @@ class TestExpectationsFile: def _handle_any_read_errors(self): if len(self._errors) or len(self._non_fatal_errors): - if self._is_debug_mode: - build_type = 'DEBUG' - else: - build_type = 'RELEASE' - _log.error('') - _log.error("FAILURES FOR PLATFORM: %s, BUILD_TYPE: %s" % - (self._test_platform_name.upper(), build_type)) + _log.error("FAILURES FOR %s" % str(self._test_config)) for error in self._errors: _log.error(error) @@ -394,11 +376,12 @@ class TestExpectationsFile: expectations = set([PASS]) options = [] modifiers = [] + num_matches = 0 if self._full_test_list: for test in self._full_test_list: if not test in self._test_list_paths: - self._add_test(test, modifiers, expectations, options, - overrides_allowed=False) + self._add_test(test, modifiers, num_matches, expectations, + options, overrides_allowed=False) def _dict_of_sets(self, strings_to_constants): """Takes a dict of strings->constants and returns a dict mapping @@ -505,7 +488,8 @@ class TestExpectationsFile: _log.info(' new: %s', new_line) elif action == ADD_PLATFORMS_EXCEPT_THIS: parts = line.split(':') - new_options = parts[0] + _log.info('Test updated: ') + _log.info(' old: %s', line) for p in self._port.test_platform_names(): p = p.upper() # This is a temp solution for rebaselining tool. @@ -515,13 +499,11 @@ class TestExpectationsFile: # TODO(victorw): Remove WIN-VISTA and WIN-7 once we have # reliable Win 7 and Win Vista buildbots setup. if not p in (platform.upper(), 'WIN-VISTA', 'WIN-7'): - new_options += p + ' ' - new_line = ('%s:%s' % (new_options, parts[1])) - f_new.append(new_line) + new_options = parts[0] + p + ' ' + new_line = ('%s:%s' % (new_options, parts[1])) + f_new.append(new_line) + _log.info(' new: %s', new_line) tests_updated += 1 - _log.info('Test updated: ') - _log.info(' old: %s', line) - _log.info(' new: %s', new_line) _log.info('Total tests removed: %d', tests_removed) _log.info('Total tests updated: %d', tests_updated) @@ -537,12 +519,15 @@ class TestExpectationsFile: options = [] if line.find(":") is -1: - test_and_expectation = line.split("=") - else: - parts = line.split(":") - options = self._get_options_list(parts[0]) - test_and_expectation = parts[1].split('=') + self._add_error(lineno, "Missing a ':'", line) + return (None, None, None) + parts = line.split(':') + + # FIXME: verify that there is exactly one colon in the line. + + options = self._get_options_list(parts[0]) + test_and_expectation = parts[1].split('=') test = test_and_expectation[0].strip() if (len(test_and_expectation) is not 2): self._add_error(lineno, "Missing expectations.", @@ -588,69 +573,6 @@ class TestExpectationsFile: return REMOVE_TEST - def _has_valid_modifiers_for_current_platform(self, options, lineno, - test_and_expectations, modifiers): - """Returns true if the current platform is in the options list or if - no platforms are listed and if there are no fatal errors in the - options list. - - Args: - options: List of lowercase options. - lineno: The line in the file where the test is listed. - test_and_expectations: The path and expectations for the test. - modifiers: The set to populate with modifiers. - """ - has_any_platform = False - has_bug_id = False - for option in options: - if option in self.MODIFIERS: - modifiers.add(option) - elif option in self._port.test_platform_names(): - has_any_platform = True - elif re.match(r'bug\d', option) != None: - self._add_error(lineno, 'Bug must be either BUGCR, BUGWK, or BUGV8_ for test: %s' % - option, test_and_expectations) - elif option.startswith('bug'): - has_bug_id = True - elif option not in self.BUILD_TYPES: - self._add_error(lineno, 'Invalid modifier for test: %s' % - option, test_and_expectations) - - if has_any_platform and not self._match_platform(options): - return False - - if not has_bug_id and 'wontfix' not in options: - # TODO(ojan): Turn this into an AddError call once all the - # tests have BUG identifiers. - self._log_non_fatal_error(lineno, 'Test lacks BUG modifier.', - test_and_expectations) - - if 'release' in options or 'debug' in options: - if self._is_debug_mode and 'debug' not in options: - return False - if not self._is_debug_mode and 'release' not in options: - return False - - if self._is_lint_mode and 'rebaseline' in options: - self._add_error(lineno, - 'REBASELINE should only be used for running rebaseline.py. ' - 'Cannot be checked in.', test_and_expectations) - - return True - - def _match_platform(self, options): - """Match the list of options against our specified platform. If any - of the options prefix-match self._platform, return True. This handles - the case where a test is marked WIN and the platform is WIN-VISTA. - - Args: - options: list of options - """ - for opt in options: - if self._test_platform_name.startswith(opt): - return True - return False - def _add_to_all_expectations(self, test, options, expectations): # Make all paths unix-style so the dashboard doesn't need to. test = test.replace('\\', '/') @@ -663,54 +585,43 @@ class TestExpectationsFile: """For each test in an expectations iterable, generate the expectations for it.""" lineno = 0 + matcher = ModifierMatcher(self._test_config) for line in expectations: lineno += 1 + self._process_line(line, lineno, matcher, overrides_allowed) - test_list_path, options, expectations = \ - self.parse_expectations_line(line, lineno) - if not expectations: - continue + def _process_line(self, line, lineno, matcher, overrides_allowed): + test_list_path, options, expectations = \ + self.parse_expectations_line(line, lineno) + if not expectations: + return - self._add_to_all_expectations(test_list_path, - " ".join(options).upper(), - " ".join(expectations).upper()) + self._add_to_all_expectations(test_list_path, + " ".join(options).upper(), + " ".join(expectations).upper()) - modifiers = set() - if options and not self._has_valid_modifiers_for_current_platform( - options, lineno, test_list_path, modifiers): - continue + num_matches = self._check_options(matcher, options, lineno, + test_list_path) + if num_matches == ModifierMatcher.NO_MATCH: + return - expectations = self._parse_expectations(expectations, lineno, - test_list_path) + expectations = self._parse_expectations(expectations, lineno, + test_list_path) - if 'slow' in options and TIMEOUT in expectations: - self._add_error(lineno, - 'A test can not be both slow and timeout. If it times out ' - 'indefinitely, then it should be just timeout.', - test_list_path) + self._check_options_against_expectations(options, expectations, + lineno, test_list_path) - full_path = self._fs.join(self._port.layout_tests_dir(), - test_list_path) - full_path = self._fs.normpath(full_path) - # WebKit's way of skipping tests is to add a -disabled suffix. - # So we should consider the path existing if the path or the - # -disabled version exists. - if (not self._port.path_exists(full_path) - and not self._port.path_exists(full_path + '-disabled')): - # Log a non fatal error here since you hit this case any - # time you update test_expectations.txt without syncing - # the LayoutTests directory - self._log_non_fatal_error(lineno, 'Path does not exist.', - test_list_path) - continue + if self._check_path_does_not_exist(lineno, test_list_path): + return - if not self._full_test_list: - tests = [test_list_path] - else: - tests = self._expand_tests(test_list_path) + if not self._full_test_list: + tests = [test_list_path] + else: + tests = self._expand_tests(test_list_path) - self._add_tests(tests, expectations, test_list_path, lineno, - modifiers, options, overrides_allowed) + modifiers = [o for o in options if o in self.MODIFIERS] + self._add_tests(tests, expectations, test_list_path, lineno, + modifiers, num_matches, options, overrides_allowed) def _get_options_list(self, listString): return [part.strip().lower() for part in listString.strip().split(' ')] @@ -726,6 +637,65 @@ class TestExpectationsFile: result.add(expectation) return result + def _check_options(self, matcher, options, lineno, test_list_path): + match_result = self._check_syntax(matcher, options, lineno, + test_list_path) + self._check_semantics(options, lineno, test_list_path) + return match_result.num_matches + + def _check_syntax(self, matcher, options, lineno, test_list_path): + match_result = matcher.match(options) + for error in match_result.errors: + self._add_error(lineno, error, test_list_path) + for warning in match_result.warnings: + self._log_non_fatal_error(lineno, warning, test_list_path) + return match_result + + def _check_semantics(self, options, lineno, test_list_path): + has_wontfix = 'wontfix' in options + has_bug = False + for opt in options: + if opt.startswith('bug'): + has_bug = True + if re.match('bug\d+', opt): + self._add_error(lineno, + 'BUG\d+ is not allowed, must be one of ' + 'BUGCR\d+, BUGWK\d+, BUGV8_\d+, ' + 'or a non-numeric bug identifier.', test_list_path) + + if not has_bug and not has_wontfix: + self._log_non_fatal_error(lineno, 'Test lacks BUG modifier.', + test_list_path) + + if self._is_lint_mode and 'rebaseline' in options: + self._add_error(lineno, + 'REBASELINE should only be used for running rebaseline.py. ' + 'Cannot be checked in.', test_list_path) + + def _check_options_against_expectations(self, options, expectations, + lineno, test_list_path): + if 'slow' in options and TIMEOUT in expectations: + self._add_error(lineno, + 'A test can not be both SLOW and TIMEOUT. If it times out ' + 'indefinitely, then it should be just TIMEOUT.', test_list_path) + + def _check_path_does_not_exist(self, lineno, test_list_path): + full_path = self._fs.join(self._port.layout_tests_dir(), + test_list_path) + full_path = self._fs.normpath(full_path) + # WebKit's way of skipping tests is to add a -disabled suffix. + # So we should consider the path existing if the path or the + # -disabled version exists. + if (not self._port.path_exists(full_path) + and not self._port.path_exists(full_path + '-disabled')): + # Log a non fatal error here since you hit this case any + # time you update test_expectations.txt without syncing + # the LayoutTests directory + self._log_non_fatal_error(lineno, 'Path does not exist.', + test_list_path) + return True + return False + def _expand_tests(self, test_list_path): """Convert the test specification to an absolute, normalized path and make sure directories end with the OS path separator.""" @@ -751,27 +721,30 @@ class TestExpectationsFile: return result def _add_tests(self, tests, expectations, test_list_path, lineno, - modifiers, options, overrides_allowed): + modifiers, num_matches, options, overrides_allowed): for test in tests: - if self._already_seen_test(test, test_list_path, lineno, - overrides_allowed): + if self._already_seen_better_match(test, test_list_path, + num_matches, lineno, overrides_allowed): continue self._clear_expectations_for_test(test, test_list_path) - self._add_test(test, modifiers, expectations, options, + self._test_list_paths[test] = (self._fs.normpath(test_list_path), + num_matches, lineno) + self._add_test(test, modifiers, num_matches, expectations, options, overrides_allowed) - def _add_test(self, test, modifiers, expectations, options, + def _add_test(self, test, modifiers, num_matches, expectations, options, overrides_allowed): """Sets the expected state for a given test. This routine assumes the test has not been added before. If it has, - use _ClearExpectationsForTest() to reset the state prior to + use _clear_expectations_for_test() to reset the state prior to calling this. Args: test: test to add modifiers: sequence of modifier keywords ('wontfix', 'slow', etc.) + num_matches: number of modifiers that matched the configuration expectations: sequence of expectations (PASS, IMAGE, etc.) options: sequence of keywords and bug identifiers. overrides_allowed: whether we're parsing the regular expectations @@ -828,32 +801,70 @@ class TestExpectationsFile: if test in set_of_tests: set_of_tests.remove(test) - def _already_seen_test(self, test, test_list_path, lineno, - allow_overrides): - """Returns true if we've already seen a more precise path for this test - than the test_list_path. + def _already_seen_better_match(self, test, test_list_path, num_matches, + lineno, overrides_allowed): + """Returns whether we've seen a better match already in the file. + + Returns True if we've already seen a test_list_path that matches more of the test + than this path does """ + # FIXME: See comment below about matching test configs and num_matches. + if not test in self._test_list_paths: + # We've never seen this test before. return False - prev_base_path = self._test_list_paths[test] - if (prev_base_path == self._fs.normpath(test_list_path)): - if (not allow_overrides or test in self._overridding_tests): - if allow_overrides: - expectation_source = "override" - else: - expectation_source = "expectation" - self._add_error(lineno, 'Duplicate %s.' % expectation_source, - test) - return True - else: - # We have seen this path, but that's okay because its - # in the overrides and the earlier path was in the - # expectations. - return False + prev_base_path, prev_num_matches, prev_lineno = self._test_list_paths[test] + base_path = self._fs.normpath(test_list_path) + + if len(prev_base_path) > len(base_path): + # The previous path matched more of the test. + return True + + if len(prev_base_path) < len(base_path): + # This path matches more of the test. + return False + + if overrides_allowed and test not in self._overridding_tests: + # We have seen this path, but that's okay because it is + # in the overrides and the earlier path was in the + # expectations (not the overrides). + return False + + # At this point we know we have seen a previous exact match on this + # base path, so we need to check the two sets of modifiers. - # Check if we've already seen a more precise path. - return prev_base_path.startswith(self._fs.normpath(test_list_path)) + if overrides_allowed: + expectation_source = "override" + else: + expectation_source = "expectation" + + # FIXME: This code was originally designed to allow lines that matched + # more modifiers to override lines that matched fewer modifiers. + # However, we currently view these as errors. If we decide to make + # this policy permanent, we can probably simplify this code + # and the ModifierMatcher code a fair amount. + # + # To use the "more modifiers wins" policy, change the "_add_error" lines for overrides + # to _log_non_fatal_error() and change the commented-out "return False". + + if prev_num_matches == num_matches: + self._add_error(lineno, + 'Duplicate or ambiguous %s.' % expectation_source, + test) + return True + + if prev_num_matches < num_matches: + self._add_error(lineno, + 'More specific entry on line %d overrides line %d' % + (lineno, prev_lineno), test_list_path) + # FIXME: return False if we want more specific to win. + return True + + self._add_error(lineno, + 'More specific entry on line %d overrides line %d' % + (prev_lineno, lineno), test_list_path) + return True def _add_error(self, lineno, msg, path): """Reports an error that will prevent running the tests. Does not @@ -865,3 +876,188 @@ class TestExpectationsFile: """Reports an error that will not prevent running the tests. These are still errors, but not bad enough to warrant breaking test running.""" self._non_fatal_errors.append('Line:%s %s %s' % (lineno, msg, path)) + + +class ModifierMatchResult(object): + def __init__(self, options): + self.num_matches = ModifierMatcher.NO_MATCH + self.options = options + self.errors = [] + self.warnings = [] + self.modifiers = [] + self._matched_regexes = set() + self._matched_macros = set() + + +class ModifierMatcher(object): + + """ + This class manages the interpretation of the "modifiers" for a given + line in the expectations file. Modifiers are the tokens that appear to the + left of the colon on a line. For example, "BUG1234", "DEBUG", and "WIN" are + all modifiers. This class gets what the valid modifiers are, and which + modifiers are allowed to exist together on a line, from the + TestConfiguration object that is passed in to the call. + + This class detects *intra*-line errors like unknown modifiers, but + does not detect *inter*-line modifiers like duplicate expectations. + + More importantly, this class is also used to determine if a given line + matches the port in question. Matches are ranked according to the number + of modifiers that match on a line. A line with no modifiers matches + everything and has a score of zero. A line with one modifier matches only + ports that have that modifier and gets a score of 1, and so one. Ports + that don't match at all get a score of -1. + + Given two lines in a file that apply to the same test, if both expectations + match the current config, then the expectation is considered ambiguous, + even if one expectation matches more of the config than the other. For + example, in: + + BUG1 RELEASE : foo.html = FAIL + BUG1 WIN RELEASE : foo.html = PASS + BUG2 WIN : bar.html = FAIL + BUG2 DEBUG : bar.html = PASS + + lines 1 and 2 would produce an error on a Win XP Release bot (the scores + would be 1 and 2, respectively), and lines three and four would produce + a duplicate expectation on a Win Debug bot since both the 'win' and the + 'debug' expectations would apply (both had scores of 1). + + In addition to the definitions of all of the modifiers, the class + supports "macros" that are expanded prior to interpretation, and "ignore + regexes" that can be used to skip over modifiers like the BUG* modifiers. + """ + MACROS = { + 'mac-snowleopard': ['mac', 'snowleopard'], + 'mac-leopard': ['mac', 'leopard'], + 'win-xp': ['win', 'xp'], + 'win-vista': ['win', 'vista'], + 'win-7': ['win', 'win7'], + } + + # We don't include the "none" modifier because it isn't actually legal. + REGEXES_TO_IGNORE = (['bug\w+'] + + TestExpectationsFile.MODIFIERS.keys()[:-1]) + DUPLICATE_REGEXES_ALLOWED = ['bug\w+'] + + # Magic value returned when the options don't match. + NO_MATCH = -1 + + # FIXME: The code currently doesn't detect combinations of modifiers + # that are syntactically valid but semantically invalid, like + # 'MAC XP'. See ModifierMatchTest.test_invalid_combinations() in the + # _unittest.py file. + + def __init__(self, test_config): + """Initialize a ModifierMatcher argument with the TestConfiguration it + should be matched against.""" + self.test_config = test_config + self.allowed_configurations = test_config.all_test_configurations() + self.macros = self.MACROS + + self.regexes_to_ignore = {} + for regex_str in self.REGEXES_TO_IGNORE: + self.regexes_to_ignore[regex_str] = re.compile(regex_str) + + # Keep a set of all of the legal modifiers for quick checking. + self._all_modifiers = set() + + # Keep a dict mapping values back to their categories. + self._categories_for_modifiers = {} + for config in self.allowed_configurations: + for category, modifier in config.items(): + self._categories_for_modifiers[modifier] = category + self._all_modifiers.add(modifier) + + def match(self, options): + """Checks a list of options against the config set in the constructor. + Options may be either actual modifier strings, "macro" strings + that get expanded to a list of modifiers, or strings that are allowed + to be ignored. All of the options must be passed in in lower case. + + Returns the number of matching categories, or NO_MATCH (-1) if it + doesn't match or there were errors found. Matches are prioritized + by the number of matching categories, because the more specific + the options list, the more categories will match. + + The results of the most recent match are available in the 'options', + 'modifiers', 'num_matches', 'errors', and 'warnings' properties. + """ + result = ModifierMatchResult(options) + self._parse(result) + if result.errors: + return result + self._count_matches(result) + return result + + def _parse(self, result): + # FIXME: Should we warn about lines having every value in a category? + for option in result.options: + self._parse_one(option, result) + + def _parse_one(self, option, result): + if option in self._all_modifiers: + self._add_modifier(option, result) + elif option in self.macros: + self._expand_macro(option, result) + elif not self._matches_any_regex(option, result): + result.errors.append("Unrecognized option '%s'" % option) + + def _add_modifier(self, option, result): + if option in result.modifiers: + result.errors.append("More than one '%s'" % option) + else: + result.modifiers.append(option) + + def _expand_macro(self, macro, result): + if macro in result._matched_macros: + result.errors.append("More than one '%s'" % macro) + return + + mods = [] + for modifier in self.macros[macro]: + if modifier in result.options: + result.errors.append("Can't specify both modifier '%s' and " + "macro '%s'" % (modifier, macro)) + else: + mods.append(modifier) + result._matched_macros.add(macro) + result.modifiers.extend(mods) + + def _matches_any_regex(self, option, result): + for regex_str, pattern in self.regexes_to_ignore.iteritems(): + if pattern.match(option): + self._handle_regex_match(regex_str, result) + return True + return False + + def _handle_regex_match(self, regex_str, result): + if (regex_str in result._matched_regexes and + regex_str not in self.DUPLICATE_REGEXES_ALLOWED): + result.errors.append("More than one option matching '%s'" % + regex_str) + else: + result._matched_regexes.add(regex_str) + + def _count_matches(self, result): + """Returns the number of modifiers that match the test config.""" + categorized_modifiers = self._group_by_category(result.modifiers) + result.num_matches = 0 + for category, modifier in self.test_config.items(): + if category in categorized_modifiers: + if modifier in categorized_modifiers[category]: + result.num_matches += 1 + else: + result.num_matches = self.NO_MATCH + return + + def _group_by_category(self, modifiers): + # Returns a dict of category name -> list of modifiers. + modifiers_by_category = {} + for m in modifiers: + modifiers_by_category.setdefault(self._category(m), []).append(m) + return modifiers_by_category + + def _category(self, modifier): + return self._categories_for_modifiers[modifier] diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_expectations_unittest.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_expectations_unittest.py index 8f9e5dd..05d805d 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_expectations_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_expectations_unittest.py @@ -32,6 +32,7 @@ import unittest from webkitpy.layout_tests import port +from webkitpy.layout_tests.port import base from webkitpy.layout_tests.layout_package.test_expectations import * class FunctionsTest(unittest.TestCase): @@ -78,8 +79,11 @@ class FunctionsTest(unittest.TestCase): class Base(unittest.TestCase): + # Note that all of these tests are written assuming the configuration + # being tested is Windows XP, Release build. + def __init__(self, testFunc, setUp=None, tearDown=None, description=None): - self._port = port.get('test', None) + self._port = port.get('test-win-xp', None) self._fs = self._port._filesystem self._exp = None unittest.TestCase.__init__(self, testFunc) @@ -101,16 +105,15 @@ BUG_TEST : failures/expected/text.html = TEXT BUG_TEST WONTFIX SKIP : failures/expected/crash.html = CRASH BUG_TEST REBASELINE : failures/expected/missing_image.html = MISSING BUG_TEST WONTFIX : failures/expected/image_checksum.html = IMAGE -BUG_TEST WONTFIX WIN : failures/expected/image.html = IMAGE +BUG_TEST WONTFIX MAC : failures/expected/image.html = IMAGE """ - def parse_exp(self, expectations, overrides=None, is_lint_mode=False, - is_debug_mode=False): + def parse_exp(self, expectations, overrides=None, is_lint_mode=False): + test_config = self._port.test_configuration() self._exp = TestExpectations(self._port, tests=self.get_basic_tests(), expectations=expectations, - test_platform_name=self._port.test_platform_name(), - is_debug_mode=is_debug_mode, + test_config=test_config, is_lint_mode=is_lint_mode, overrides=overrides) @@ -119,7 +122,7 @@ BUG_TEST WONTFIX WIN : failures/expected/image.html = IMAGE set([result])) -class TestExpectationsTest(Base): +class BasicTests(Base): def test_basic(self): self.parse_exp(self.get_basic_expectations()) self.assert_exp('failures/expected/text.html', TEXT) @@ -127,23 +130,14 @@ class TestExpectationsTest(Base): self.assert_exp('passes/text.html', PASS) self.assert_exp('failures/expected/image.html', PASS) + +class MiscTests(Base): def test_multiple_results(self): self.parse_exp('BUGX : failures/expected/text.html = TEXT CRASH') self.assertEqual(self._exp.get_expectations( self.get_test('failures/expected/text.html')), set([TEXT, CRASH])) - def test_precedence(self): - # This tests handling precedence of specific lines over directories - # and tests expectations covering entire directories. - exp_str = """ -BUGX : failures/expected/text.html = TEXT -BUGX WONTFIX : failures/expected = IMAGE -""" - self.parse_exp(exp_str) - self.assert_exp('failures/expected/text.html', TEXT) - self.assert_exp('failures/expected/crash.html', IMAGE) - def test_category_expectations(self): # This test checks unknown tests are not present in the # expectations and that known test part of a test category is @@ -158,20 +152,6 @@ BUGX WONTFIX : failures/expected = IMAGE unknown_test) self.assert_exp('failures/expected/crash.html', IMAGE) - def test_release_mode(self): - self.parse_exp('BUGX DEBUG : failures/expected/text.html = TEXT', - is_debug_mode=True) - self.assert_exp('failures/expected/text.html', TEXT) - self.parse_exp('BUGX RELEASE : failures/expected/text.html = TEXT', - is_debug_mode=True) - self.assert_exp('failures/expected/text.html', PASS) - self.parse_exp('BUGX DEBUG : failures/expected/text.html = TEXT', - is_debug_mode=False) - self.assert_exp('failures/expected/text.html', PASS) - self.parse_exp('BUGX RELEASE : failures/expected/text.html = TEXT', - is_debug_mode=False) - self.assert_exp('failures/expected/text.html', TEXT) - def test_get_options(self): self.parse_exp(self.get_basic_expectations()) self.assertEqual(self._exp.get_options( @@ -216,7 +196,7 @@ SKIP : failures/expected/image.html""") self.assertFalse(True, "ParseError wasn't raised") except ParseError, e: self.assertTrue(e.fatal) - exp_errors = [u'Line:1 Invalid modifier for test: foo failures/expected/text.html', + exp_errors = [u"Line:1 Unrecognized option 'foo' failures/expected/text.html", u"Line:2 Missing expectations. [' failures/expected/image.html']"] self.assertEqual(str(e), '\n'.join(map(str, exp_errors))) self.assertEqual(e.errors, exp_errors) @@ -232,77 +212,167 @@ SKIP : failures/expected/image.html""") self.assertEqual(str(e), '\n'.join(map(str, exp_errors))) self.assertEqual(e.errors, exp_errors) - def test_syntax_missing_expectation(self): + def test_overrides(self): + self.parse_exp("BUG_EXP: failures/expected/text.html = TEXT", + "BUG_OVERRIDE : failures/expected/text.html = IMAGE") + self.assert_exp('failures/expected/text.html', IMAGE) + + def test_overrides__duplicate(self): + self.assertRaises(ParseError, self.parse_exp, + "BUG_EXP: failures/expected/text.html = TEXT", + """ +BUG_OVERRIDE : failures/expected/text.html = IMAGE +BUG_OVERRIDE : failures/expected/text.html = CRASH +""") + + def test_pixel_tests_flag(self): + def match(test, result, pixel_tests_enabled): + return self._exp.matches_an_expected_result( + self.get_test(test), result, pixel_tests_enabled) + + self.parse_exp(self.get_basic_expectations()) + self.assertTrue(match('failures/expected/text.html', TEXT, True)) + self.assertTrue(match('failures/expected/text.html', TEXT, False)) + self.assertFalse(match('failures/expected/text.html', CRASH, True)) + self.assertFalse(match('failures/expected/text.html', CRASH, False)) + self.assertTrue(match('failures/expected/image_checksum.html', IMAGE, + True)) + self.assertTrue(match('failures/expected/image_checksum.html', PASS, + False)) + self.assertTrue(match('failures/expected/crash.html', SKIP, False)) + self.assertTrue(match('passes/text.html', PASS, False)) + + def test_more_specific_override_resets_skip(self): + self.parse_exp("BUGX SKIP : failures/expected = TEXT\n" + "BUGX : failures/expected/text.html = IMAGE\n") + self.assert_exp('failures/expected/text.html', IMAGE) + self.assertFalse(self._port._filesystem.join(self._port.layout_tests_dir(), + 'failures/expected/text.html') in + self._exp.get_tests_with_result_type(SKIP)) + +class ExpectationSyntaxTests(Base): + def test_missing_expectation(self): # This is missing the expectation. self.assertRaises(ParseError, self.parse_exp, - 'BUG_TEST: failures/expected/text.html', - is_debug_mode=True) + 'BUG_TEST: failures/expected/text.html') - def test_syntax_invalid_option(self): + def test_missing_colon(self): + # This is missing the modifiers and the ':' self.assertRaises(ParseError, self.parse_exp, - 'BUG_TEST FOO: failures/expected/text.html = PASS') + 'failures/expected/text.html = TEXT') - def test_syntax_invalid_expectation(self): - # This is missing the expectation. + def disabled_test_too_many_colons(self): + # FIXME: Enable this test and fix the underlying bug. + self.assertRaises(ParseError, self.parse_exp, + 'BUG_TEST: failures/expected/text.html = PASS :') + + def test_too_many_equals_signs(self): self.assertRaises(ParseError, self.parse_exp, - 'BUG_TEST: failures/expected/text.html = FOO') + 'BUG_TEST: failures/expected/text.html = TEXT = IMAGE') + + def test_unrecognized_expectation(self): + self.assertRaises(ParseError, self.parse_exp, + 'BUG_TEST: failures/expected/text.html = UNKNOWN') + + def test_macro(self): + exp_str = """ +BUG_TEST WIN-XP : failures/expected/text.html = TEXT +""" + self.parse_exp(exp_str) + self.assert_exp('failures/expected/text.html', TEXT) + + +class SemanticTests(Base): + def test_bug_format(self): + self.assertRaises(ParseError, self.parse_exp, 'BUG1234 : failures/expected/text.html = TEXT') - def test_syntax_missing_bugid(self): + def test_missing_bugid(self): # This should log a non-fatal error. self.parse_exp('SLOW : failures/expected/text.html = TEXT') self.assertEqual( len(self._exp._expected_failures.get_non_fatal_errors()), 1) - def test_semantic_slow_and_timeout(self): + def test_slow_and_timeout(self): # A test cannot be SLOW and expected to TIMEOUT. self.assertRaises(ParseError, self.parse_exp, 'BUG_TEST SLOW : failures/expected/timeout.html = TIMEOUT') - def test_semantic_rebaseline(self): + def test_rebaseline(self): # Can't lint a file w/ 'REBASELINE' in it. self.assertRaises(ParseError, self.parse_exp, 'BUG_TEST REBASELINE : failures/expected/text.html = TEXT', is_lint_mode=True) - def test_semantic_duplicates(self): + def test_duplicates(self): self.assertRaises(ParseError, self.parse_exp, """ -BUG_TEST : failures/expected/text.html = TEXT -BUG_TEST : failures/expected/text.html = IMAGE""") +BUG_EXP : failures/expected/text.html = TEXT +BUG_EXP : failures/expected/text.html = IMAGE""") self.assertRaises(ParseError, self.parse_exp, - self.get_basic_expectations(), """ -BUG_TEST : failures/expected/text.html = TEXT -BUG_TEST : failures/expected/text.html = IMAGE""") + self.get_basic_expectations(), overrides=""" +BUG_OVERRIDE : failures/expected/text.html = TEXT +BUG_OVERRIDE : failures/expected/text.html = IMAGE""", ) - def test_semantic_missing_file(self): + def test_missing_file(self): # This should log a non-fatal error. self.parse_exp('BUG_TEST : missing_file.html = TEXT') self.assertEqual( len(self._exp._expected_failures.get_non_fatal_errors()), 1) - def test_overrides(self): - self.parse_exp(self.get_basic_expectations(), """ -BUG_OVERRIDE : failures/expected/text.html = IMAGE""") - self.assert_exp('failures/expected/text.html', IMAGE) +class PrecedenceTests(Base): + def test_file_over_directory(self): + # This tests handling precedence of specific lines over directories + # and tests expectations covering entire directories. + exp_str = """ +BUGX : failures/expected/text.html = TEXT +BUGX WONTFIX : failures/expected = IMAGE +""" + self.parse_exp(exp_str) + self.assert_exp('failures/expected/text.html', TEXT) + self.assert_exp('failures/expected/crash.html', IMAGE) - def test_matches_an_expected_result(self): + exp_str = """ +BUGX WONTFIX : failures/expected = IMAGE +BUGX : failures/expected/text.html = TEXT +""" + self.parse_exp(exp_str) + self.assert_exp('failures/expected/text.html', TEXT) + self.assert_exp('failures/expected/crash.html', IMAGE) - def match(test, result, pixel_tests_enabled): - return self._exp.matches_an_expected_result( - self.get_test(test), result, pixel_tests_enabled) + def test_ambiguous(self): + self.assertRaises(ParseError, self.parse_exp, """ +BUG_TEST RELEASE : passes/text.html = PASS +BUG_TEST WIN : passes/text.html = FAIL +""") - self.parse_exp(self.get_basic_expectations()) - self.assertTrue(match('failures/expected/text.html', TEXT, True)) - self.assertTrue(match('failures/expected/text.html', TEXT, False)) - self.assertFalse(match('failures/expected/text.html', CRASH, True)) - self.assertFalse(match('failures/expected/text.html', CRASH, False)) - self.assertTrue(match('failures/expected/image_checksum.html', IMAGE, - True)) - self.assertTrue(match('failures/expected/image_checksum.html', PASS, - False)) - self.assertTrue(match('failures/expected/crash.html', SKIP, False)) - self.assertTrue(match('passes/text.html', PASS, False)) + def test_more_modifiers(self): + exp_str = """ +BUG_TEST RELEASE : passes/text.html = PASS +BUG_TEST WIN RELEASE : passes/text.html = TEXT +""" + self.assertRaises(ParseError, self.parse_exp, exp_str) + + def test_order_in_file(self): + exp_str = """ +BUG_TEST WIN RELEASE : passes/text.html = TEXT +BUG_TEST RELEASE : passes/text.html = PASS +""" + self.assertRaises(ParseError, self.parse_exp, exp_str) + + def test_version_overrides(self): + exp_str = """ +BUG_TEST WIN : passes/text.html = PASS +BUG_TEST WIN XP : passes/text.html = TEXT +""" + self.assertRaises(ParseError, self.parse_exp, exp_str) + + def test_macro_overrides(self): + exp_str = """ +BUG_TEST WIN : passes/text.html = PASS +BUG_TEST WIN-XP : passes/text.html = TEXT +""" + self.assertRaises(ParseError, self.parse_exp, exp_str) class RebaseliningTest(Base): @@ -327,7 +397,8 @@ BUG_TEST REBASELINE : failures/expected/text.html = TEXT def test_remove_expand(self): self.assertRemove('mac', 'BUGX REBASELINE : failures/expected/text.html = TEXT\n', - 'BUGX REBASELINE WIN : failures/expected/text.html = TEXT\n') + 'BUGX REBASELINE WIN : failures/expected/text.html = TEXT\n' + 'BUGX REBASELINE WIN-XP : failures/expected/text.html = TEXT\n') def test_remove_mac_win(self): self.assertRemove('mac', @@ -345,5 +416,85 @@ BUG_TEST REBASELINE : failures/expected/text.html = TEXT '\n\n') +class ModifierTests(unittest.TestCase): + def setUp(self): + port_obj = port.get('test-win-xp', None) + self.config = port_obj.test_configuration() + self.matcher = ModifierMatcher(self.config) + + def match(self, modifiers, expected_num_matches=-1, values=None, num_errors=0): + matcher = self.matcher + if values: + matcher = ModifierMatcher(self.FakeTestConfiguration(values)) + match_result = matcher.match(modifiers) + self.assertEqual(len(match_result.warnings), 0) + self.assertEqual(len(match_result.errors), num_errors) + self.assertEqual(match_result.num_matches, expected_num_matches, + 'match(%s, %s) returned -> %d, expected %d' % + (modifiers, str(self.config.values()), + match_result.num_matches, expected_num_matches)) + + def test_bad_match_modifier(self): + self.match(['foo'], num_errors=1) + + def test_none(self): + self.match([], 0) + + def test_one(self): + self.match(['xp'], 1) + self.match(['win'], 1) + self.match(['release'], 1) + self.match(['cpu'], 1) + self.match(['x86'], 1) + self.match(['leopard'], -1) + self.match(['gpu'], -1) + self.match(['debug'], -1) + + def test_two(self): + self.match(['xp', 'release'], 2) + self.match(['win7', 'release'], -1) + self.match(['win7', 'xp'], 1) + + def test_three(self): + self.match(['win7', 'xp', 'release'], 2) + self.match(['xp', 'debug', 'x86'], -1) + self.match(['xp', 'release', 'x86'], 3) + self.match(['xp', 'cpu', 'release'], 3) + + def test_four(self): + self.match(['xp', 'release', 'cpu', 'x86'], 4) + self.match(['win7', 'xp', 'release', 'cpu'], 3) + self.match(['win7', 'xp', 'debug', 'cpu'], -1) + + def test_case_insensitivity(self): + self.match(['Win'], num_errors=1) + self.match(['WIN'], num_errors=1) + self.match(['win'], 1) + + def test_duplicates(self): + self.match(['release', 'release'], num_errors=1) + self.match(['win-xp', 'xp'], num_errors=1) + self.match(['win-xp', 'win-xp'], num_errors=1) + self.match(['xp', 'release', 'xp', 'release'], num_errors=2) + self.match(['rebaseline', 'rebaseline'], num_errors=1) + + def test_unknown_option(self): + self.match(['vms'], num_errors=1) + + def test_duplicate_bugs(self): + # BUG* regexes can appear multiple times. + self.match(['bugfoo', 'bugbar'], 0) + + def test_invalid_combinations(self): + # FIXME: This should probably raise an error instead of NO_MATCH. + self.match(['mac', 'xp'], num_errors=0) + + def test_regexes_are_ignored(self): + self.match(['bug123xy', 'rebaseline', 'wontfix', 'slow', 'skip'], 0) + + def test_none_is_invalid(self): + self.match(['none'], num_errors=1) + + if __name__ == '__main__': unittest.main() diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_input.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_input.py index 4b027c0..0aed1dd 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_input.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_input.py @@ -41,7 +41,3 @@ class TestInput: # FIXME: filename should really be test_name as a relative path. self.filename = filename self.timeout = timeout - # The image_hash is used to avoid doing an image dump if the - # checksums match. The image_hash is set later, and only if it is needed - # for the test. - self.image_hash = None diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_output.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_output.py deleted file mode 100644 index e809be6..0000000 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_output.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -class TestOutput(object): - """Groups information about a test output for easy passing of data. - - This is used not only for a actual test output, but also for grouping - expected test output. - """ - - def __init__(self, text, image, image_hash, - crash=None, test_time=None, timeout=None, error=None): - """Initializes a TestOutput object. - - Args: - text: a text output - image: an image output - image_hash: a string containing the checksum of the image - crash: a boolean indicating whether the driver crashed on the test - test_time: a time which the test has taken - timeout: a boolean indicating whehter the test timed out - error: any unexpected or additional (or error) text output - """ - self.text = text - self.image = image - self.image_hash = image_hash - self.crash = crash - self.test_time = test_time - self.timeout = timeout - self.error = error diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_runner.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_runner.py index 6c07850..e3bd4ad 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_runner.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_runner.py @@ -214,21 +214,13 @@ class TestRunner: def lint(self): lint_failed = False - - # Creating the expecations for each platform/configuration pair does - # all the test list parsing and ensures it's correct syntax (e.g. no - # dupes). - for platform_name in self._port.test_platform_names(): - try: - self.parse_expectations(platform_name, is_debug_mode=True) - except test_expectations.ParseError: - lint_failed = True + for test_configuration in self._port.all_test_configurations(): try: - self.parse_expectations(platform_name, is_debug_mode=False) + self.lint_expectations(test_configuration) except test_expectations.ParseError: lint_failed = True + self._printer.write("") - self._printer.write("") if lint_failed: _log.error("Lint failed.") return -1 @@ -236,22 +228,28 @@ class TestRunner: _log.info("Lint succeeded.") return 0 - def parse_expectations(self, test_platform_name, is_debug_mode): + def lint_expectations(self, config): + port = self._port + test_expectations.TestExpectations( + port, + None, + port.test_expectations(), + config, + self._options.lint_test_files, + port.test_expectations_overrides()) + + def parse_expectations(self): """Parse the expectations from the test_list files and return a data structure holding them. Throws an error if the test_list files have invalid syntax.""" - if self._options.lint_test_files: - test_files = None - else: - test_files = self._test_files - - expectations_str = self._port.test_expectations() - overrides_str = self._port.test_expectations_overrides() + port = self._port self._expectations = test_expectations.TestExpectations( - self._port, test_files, expectations_str, test_platform_name, - is_debug_mode, self._options.lint_test_files, - overrides=overrides_str) - return self._expectations + port, + self._test_files, + port.test_expectations(), + port.test_configuration(), + self._options.lint_test_files, + port.test_expectations_overrides()) # FIXME: This method is way too long and needs to be broken into pieces. def prepare_lists_and_print_output(self): @@ -358,9 +356,7 @@ class TestRunner: self._test_files_list = files + skip_chunk_list self._test_files = set(self._test_files_list) - self._expectations = self.parse_expectations( - self._port.test_platform_name(), - self._options.configuration == 'Debug') + self.parse_expectations() self._test_files = set(files) self._test_files_list = files @@ -691,6 +687,8 @@ class TestRunner: self._expectations, result_summary, retry_summary) self._printer.print_unexpected_results(unexpected_results) + # FIXME: remove record_results. It's just used for testing. There's no need + # for it to be a commandline argument. if (self._options.record_results and not self._options.dry_run and not interrupted): # Write the same data to log files and upload generated JSON files @@ -731,28 +729,31 @@ class TestRunner: except Queue.Empty: return - expected = self._expectations.matches_an_expected_result( - result.filename, result.type, self._options.pixel_tests) - result_summary.add(result, expected) - exp_str = self._expectations.get_expectations_string( - result.filename) - got_str = self._expectations.expectation_to_string(result.type) - self._printer.print_test_result(result, expected, exp_str, got_str) - self._printer.print_progress(result_summary, self._retrying, - self._test_files_list) - - def interrupt_if_at_failure_limit(limit, count, message): - if limit and count >= limit: - raise TestRunInterruptedException(message % count) - - interrupt_if_at_failure_limit( - self._options.exit_after_n_failures, - result_summary.unexpected_failures, - "Aborting run since %d failures were reached") - interrupt_if_at_failure_limit( - self._options.exit_after_n_crashes_or_timeouts, - result_summary.unexpected_crashes_or_timeouts, - "Aborting run since %d crashes or timeouts were reached") + self._update_summary_with_result(result_summary, result) + + def _update_summary_with_result(self, result_summary, result): + expected = self._expectations.matches_an_expected_result( + result.filename, result.type, self._options.pixel_tests) + result_summary.add(result, expected) + exp_str = self._expectations.get_expectations_string( + result.filename) + got_str = self._expectations.expectation_to_string(result.type) + self._printer.print_test_result(result, expected, exp_str, got_str) + self._printer.print_progress(result_summary, self._retrying, + self._test_files_list) + + def interrupt_if_at_failure_limit(limit, count, message): + if limit and count >= limit: + raise TestRunInterruptedException(message % count) + + interrupt_if_at_failure_limit( + self._options.exit_after_n_failures, + result_summary.unexpected_failures, + "Aborting run since %d failures were reached") + interrupt_if_at_failure_limit( + self._options.exit_after_n_crashes_or_timeouts, + result_summary.unexpected_crashes_or_timeouts, + "Aborting run since %d crashes or timeouts were reached") def _clobber_old_results(self): # Just clobber the actual test results directories since the other @@ -789,7 +790,7 @@ class TestRunner: return failed_results def _upload_json_files(self, unexpected_results, result_summary, - individual_test_timings): + individual_test_timings): """Writes the results of the test run as JSON files into the results dir and upload the files to the appengine server. @@ -825,18 +826,13 @@ class TestRunner: self._options.build_number, self._options.results_directory, BUILDER_BASE_URL, individual_test_timings, self._expectations, result_summary, self._test_files_list, - not self._options.upload_full_results, self._options.test_results_server, "layout-tests", self._options.master_name) _log.debug("Finished writing JSON files.") - json_files = ["expectations.json"] - if self._options.upload_full_results: - json_files.append("results.json") - else: - json_files.append("incremental_results.json") + json_files = ["expectations.json", "incremental_results.json"] generator.upload_json_files(json_files) @@ -844,6 +840,7 @@ class TestRunner: """Prints the configuration for the test run.""" p = self._printer p.print_config("Using port '%s'" % self._port.name()) + p.print_config("Test configuration: %s" % self._port.test_configuration()) p.print_config("Placing test results in %s" % self._options.results_directory) if self._options.new_baseline: diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_runner2.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_runner2.py new file mode 100644 index 0000000..f097b83 --- /dev/null +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_runner2.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python +# Copyright (C) 2011 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" +The TestRunner2 package is an alternate implementation of the TestRunner +class that uses the manager_worker_broker module to send sets of tests to +workers and receive their completion messages accordingly. +""" + +import logging + + +from webkitpy.layout_tests.layout_package import manager_worker_broker +from webkitpy.layout_tests.layout_package import test_runner +from webkitpy.layout_tests.layout_package import worker + +_log = logging.getLogger(__name__) + + +class TestRunner2(test_runner.TestRunner): + def __init__(self, port, options, printer): + test_runner.TestRunner.__init__(self, port, options, printer) + self._all_results = [] + self._group_stats = {} + self._current_result_summary = None + self._done = False + + def is_done(self): + return self._done + + def name(self): + return 'TestRunner2' + + def _run_tests(self, file_list, result_summary): + """Runs the tests in the file_list. + + Return: A tuple (keyboard_interrupted, thread_timings, test_timings, + individual_test_timings) + keyboard_interrupted is whether someone typed Ctrl^C + thread_timings is a list of dicts with the total runtime + of each thread with 'name', 'num_tests', 'total_time' properties + test_timings is a list of timings for each sharded subdirectory + of the form [time, directory_name, num_tests] + individual_test_timings is a list of run times for each test + in the form {filename:filename, test_run_time:test_run_time} + result_summary: summary object to populate with the results + """ + self._current_result_summary = result_summary + + # FIXME: shard properly. + + # FIXME: should shard_tests return a list of objects rather than tuples? + test_lists = self._shard_tests(file_list, False) + + manager_connection = manager_worker_broker.get(self._port, self._options, self, worker.Worker) + + # FIXME: start all of the workers. + manager_connection.start_worker(0) + + for test_list in test_lists: + manager_connection.post_message('test_list', test_list[0], test_list[1]) + + manager_connection.post_message('stop') + + keyboard_interrupted = False + interrupted = False + if not self._options.dry_run: + while not self._check_if_done(): + manager_connection.run_message_loop(delay_secs=1.0) + + # FIXME: implement stats. + thread_timings = [] + + # FIXME: should this be a class instead of a tuple? + return (keyboard_interrupted, interrupted, thread_timings, + self._group_stats, self._all_results) + + def _check_if_done(self): + """Returns true iff all the workers have either completed or wedged.""" + # FIXME: implement to check for wedged workers. + return self._done + + def handle_started_test(self, src, test_info, hang_timeout): + # FIXME: implement + pass + + def handle_done(self, src): + # FIXME: implement properly to handle multiple workers. + self._done = True + pass + + def handle_exception(self, src, exception_info): + raise exception_info + + def handle_finished_list(self, src, list_name, num_tests, elapsed_time): + # FIXME: update stats + pass + + def handle_finished_test(self, src, result, elapsed_time): + self._update_summary_with_result(self._current_result_summary, result) + + # FIXME: update stats. + self._all_results.append(result) diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/worker.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/worker.py new file mode 100644 index 0000000..47d4fbd --- /dev/null +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/worker.py @@ -0,0 +1,104 @@ +# Copyright (C) 2011 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Handle messages from the TestRunner and execute actual tests.""" + +import logging +import sys +import time + +from webkitpy.common.system import stack_utils + +from webkitpy.layout_tests.layout_package import manager_worker_broker +from webkitpy.layout_tests.layout_package import test_results + + +_log = logging.getLogger(__name__) + + +class Worker(manager_worker_broker.AbstractWorker): + def __init__(self, worker_connection, worker_number, options): + self._worker_connection = worker_connection + self._worker_number = worker_number + self._options = options + self._name = 'worker/%d' % worker_number + self._done = False + self._port = None + + def _deferred_init(self, port): + self._port = port + + def is_done(self): + return self._done + + def name(self): + return self._name + + def run(self, port): + self._deferred_init(port) + + _log.debug("%s starting" % self._name) + + # FIXME: need to add in error handling, better logging. + self._worker_connection.run_message_loop() + self._worker_connection.post_message('done') + + def handle_test_list(self, src, list_name, test_list): + # FIXME: check to see if we need to get the http lock. + + start_time = time.time() + num_tests = 0 + for test_input in test_list: + self._run_test(test_input) + num_tests += 1 + self._worker_connection.yield_to_broker() + + elapsed_time = time.time() - start_time + self._worker_connection.post_message('finished_list', list_name, num_tests, elapsed_time) + + # FIXME: release the lock if necessary + + def handle_stop(self, src): + self._done = True + + def _run_test(self, test_input): + + # FIXME: get real timeout value from SingleTestRunner + test_timeout_sec = int(test_input.timeout) / 1000 + start = time.time() + self._worker_connection.post_message('started_test', test_input, test_timeout_sec) + + # FIXME: actually run the test. + result = test_results.TestResult(test_input.filename, failures=[], + test_run_time=0, total_time_for_all_diffs=0, time_for_diffs={}) + + elapsed_time = time.time() - start + + # FIXME: update stats, check for failures. + + self._worker_connection.post_message('finished_test', result, elapsed_time) diff --git a/Tools/Scripts/webkitpy/layout_tests/port/base.py b/Tools/Scripts/webkitpy/layout_tests/port/base.py index 6e5fabc..5ff4bff 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/base.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/base.py @@ -121,15 +121,18 @@ class Port(object): # certainly won't be available, so it's a good test to keep us # from erroring out later. self._pretty_patch_available = self._filesystem.exists(self._pretty_patch_path) - self.set_option_default('configuration', None) - if self._options.configuration is None: + if not hasattr(self._options, 'configuration') or self._options.configuration is None: self._options.configuration = self.default_configuration() + self._test_configuration = None def default_child_processes(self): """Return the number of DumpRenderTree instances to use for this port.""" return self._executive.cpu_count() + def default_worker_model(self): + return 'old-threads' + def baseline_path(self): """Return the absolute path to the directory to store new baselines in for this port.""" @@ -315,7 +318,7 @@ class Port(object): path = self.expected_filename(test, '.checksum') if not self.path_exists(path): return None - return self._filesystem.read_text_file(path) + return self._filesystem.read_binary_file(path) def expected_image(self, test): """Returns the image we expect the test to produce.""" @@ -393,7 +396,7 @@ class Port(object): driver = self.create_driver(0) return driver.cmd_line() - def update_baseline(self, path, data, encoding): + def update_baseline(self, path, data): """Updates the baseline for a test. Args: @@ -401,14 +404,8 @@ class Port(object): the test. This function is used to update either generic or platform-specific baselines, but we can't infer which here. data: contents of the baseline. - encoding: file encoding to use for the baseline. """ - # FIXME: remove the encoding parameter in favor of text/binary - # functions. - if encoding is None: - self._filesystem.write_binary_file(path, data) - else: - self._filesystem.write_text_file(path, data) + self._filesystem.write_binary_file(path, data) def uri_to_test_name(self, uri): """Return the base layout test name for a given URI. @@ -465,6 +462,15 @@ class Port(object): may be different (e.g., 'win-xp' instead of 'chromium-win-xp'.""" return self._name + def graphics_type(self): + """Returns whether the port uses accelerated graphics ('gpu') or not + ('cpu').""" + return 'cpu' + + def real_name(self): + """Returns the actual name of the port, not the delegate's.""" + return self.name() + def get_option(self, name, default_value=None): # FIXME: Eventually we should not have to do a test for # hasattr(), and we should be able to just do @@ -496,9 +502,16 @@ class Port(object): """Relative unix-style path for a filename under the LayoutTests directory. Filenames outside the LayoutTests directory should raise an error.""" + # FIXME: On Windows, does this return test_names with forward slashes, + # or windows-style relative paths? assert filename.startswith(self.layout_tests_dir()), "%s did not start with %s" % (filename, self.layout_tests_dir()) return filename[len(self.layout_tests_dir()) + 1:] + def abspath_for_test(self, test_name): + """Returns the full path to the file for a given test name. This is the + inverse of relative_test_filename().""" + return self._filesystem.normpath(self._filesystem.join(self.layout_tests_dir(), test_name)) + def results_directory(self): """Absolute path to the place to store the test results.""" raise NotImplementedError('Port.results_directory') @@ -577,12 +590,25 @@ class Port(object): if self._http_lock: self._http_lock.cleanup_http_lock() + # + # TEST EXPECTATION-RELATED METHODS + # + + def test_configuration(self): + """Returns the current TestConfiguration for the port.""" + if not self._test_configuration: + self._test_configuration = TestConfiguration(self) + return self._test_configuration + + def all_test_configurations(self): + return self.test_configuration().all_test_configurations() + def test_expectations(self): """Returns the test expectations for this port. Basically this string should contain the equivalent of a test_expectations file. See test_expectations.py for more details.""" - raise NotImplementedError('Port.test_expectations') + return self._filesystem.read_text_file(self.path_to_test_expectations_file()) def test_expectations_overrides(self): """Returns an optional set of overrides for the test_expectations. @@ -593,18 +619,6 @@ class Port(object): sync up the two repos.""" return None - def test_base_platform_names(self): - """Return a list of the 'base' platforms on your port. The base - platforms represent different architectures, operating systems, - or implementations (as opposed to different versions of a single - platform). For example, 'mac' and 'win' might be different base - platforms, wherease 'mac-tiger' and 'mac-leopard' might be - different platforms. This routine is used by the rebaselining tool - and the dashboards, and the strings correspond to the identifiers - in your test expectations (*not* necessarily the platform names - themselves).""" - raise NotImplementedError('Port.base_test_platforms') - def test_platform_name(self): """Returns the string that corresponds to the given platform name in the test expectations. This may be the same as name(), or it @@ -810,6 +824,48 @@ class Port(object): platform) +class DriverInput(object): + """Holds the input parameters for a driver.""" + + def __init__(self, filename, timeout, image_hash): + """Initializes a DriverInput object. + + Args: + filename: Full path to the test. + timeout: Timeout in msecs the driver should use while running the test + image_hash: A image checksum which is used to avoid doing an image dump if + the checksums match. + """ + self.filename = filename + self.timeout = timeout + self.image_hash = image_hash + + +class DriverOutput(object): + """Groups information about a output from driver for easy passing of data.""" + + def __init__(self, text, image, image_hash, + crash=False, test_time=None, timeout=False, error=''): + """Initializes a TestOutput object. + + Args: + text: a text output + image: an image output + image_hash: a string containing the checksum of the image + crash: a boolean indicating whether the driver crashed on the test + test_time: a time which the test has taken + timeout: a boolean indicating whehter the test timed out + error: any unexpected or additional (or error) text output + """ + self.text = text + self.image = image + self.image_hash = image_hash + self.crash = crash + self.test_time = test_time + self.timeout = timeout + self.error = error + + class Driver: """Abstract interface for the DumpRenderTree interface.""" @@ -824,7 +880,7 @@ class Driver: """ raise NotImplementedError('Driver.__init__') - def run_test(self, test_input): + def run_test(self, driver_input): """Run a single test and return the results. Note that it is okay if a test times out or crashes and leaves @@ -832,9 +888,9 @@ class Driver: are responsible for cleaning up and ensuring things are okay. Args: - test_input: a TestInput object + driver_input: a DriverInput object - Returns a TestOutput object. + Returns a DriverOutput object. """ raise NotImplementedError('Driver.run_test') @@ -863,3 +919,68 @@ class Driver: def stop(self): raise NotImplementedError('Driver.stop') + + +class TestConfiguration(object): + def __init__(self, port=None, os=None, version=None, architecture=None, + build_type=None, graphics_type=None): + + # FIXME: We can get the O/S and version from test_platform_name() + # and version() for now, but those should go away and be cleaned up + # with more generic methods like operation_system() and os_version() + # or something. Note that we need to strip the leading '-' off the + # version string if it is present. + if port: + port_version = port.version() + self.os = os or port.test_platform_name().replace(port_version, '') + self.version = version or port_version[1:] + self.architecture = architecture or 'x86' + self.build_type = build_type or port._options.configuration.lower() + self.graphics_type = graphics_type or port.graphics_type() + + def items(self): + return self.__dict__.items() + + def keys(self): + return self.__dict__.keys() + + def __str__(self): + return ("<%(os)s, %(version)s, %(build_type)s, %(graphics_type)s>" % + self.__dict__) + + def __repr__(self): + return "TestConfig(os='%(os)s', version='%(version)s', architecture='%(architecture)s', build_type='%(build_type)s', graphics_type='%(graphics_type)s')" % self.__dict__ + + def values(self): + """Returns the configuration values of this instance as a tuple.""" + return self.__dict__.values() + + def all_test_configurations(self): + """Returns a sequence of the TestConfigurations the port supports.""" + # By default, we assume we want to test every graphics type in + # every configuration on every system. + test_configurations = [] + for system in self.all_systems(): + for build_type in self.all_build_types(): + for graphics_type in self.all_graphics_types(): + test_configurations.append(TestConfiguration( + os=system[0], + version=system[1], + architecture=system[2], + build_type=build_type, + graphics_type=graphics_type)) + return test_configurations + + def all_systems(self): + return (('mac', 'leopard', 'x86'), + ('mac', 'snowleopard', 'x86'), + ('win', 'xp', 'x86'), + ('win', 'vista', 'x86'), + ('win', 'win7', 'x86'), + ('linux', 'hardy', 'x86')) + + def all_build_types(self): + return ('debug', 'release') + + def all_graphics_types(self): + return ('cpu', 'gpu') diff --git a/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py index 72f2d05..ef90484 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py @@ -224,19 +224,6 @@ class PortTest(unittest.TestCase): port = base.Port() self.assertEqual(port.get_option('foo', 'bar'), 'bar') - def test_set_option_default__unset(self): - port = base.Port() - port.set_option_default('foo', 'bar') - self.assertEqual(port.get_option('foo'), 'bar') - - def test_set_option_default__set(self): - options, args = optparse.OptionParser().parse_args([]) - options.foo = 'bar' - port = base.Port(options=options) - # This call should have no effect. - port.set_option_default('foo', 'new_bar') - self.assertEqual(port.get_option('foo'), 'bar') - def test_name__unset(self): port = base.Port() self.assertEqual(port.name(), None) @@ -263,7 +250,6 @@ class VirtualTest(unittest.TestCase): self.assertVirtual(port.test_platform_name) self.assertVirtual(port.results_directory) self.assertVirtual(port.test_expectations) - self.assertVirtual(port.test_base_platform_names) self.assertVirtual(port.test_platform_name) self.assertVirtual(port.test_platforms) self.assertVirtual(port.test_platform_name_to_name, None) diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium.py index ad1bea6..7d56fa2 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/chromium.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium.py @@ -41,16 +41,9 @@ import webbrowser from webkitpy.common.system import executive from webkitpy.common.system.path import cygpath from webkitpy.layout_tests.layout_package import test_expectations -from webkitpy.layout_tests.layout_package import test_output - -import base -import http_server - -# Chromium DRT on OSX uses WebKitDriver. -if sys.platform == 'darwin': - import webkit - -import websocket_server +from webkitpy.layout_tests.port import base +from webkitpy.layout_tests.port import http_server +from webkitpy.layout_tests.port import websocket_server _log = logging.getLogger("webkitpy.layout_tests.port.chromium") @@ -176,8 +169,6 @@ class ChromiumPort(base.Port): return result def driver_name(self): - if self._options.use_test_shell: - return "test_shell" return "DumpRenderTree" def path_from_chromium_base(self, *comps): @@ -189,7 +180,7 @@ class ChromiumPort(base.Port): if offset == -1: self._chromium_base_dir = self._filesystem.join( abspath[0:abspath.find('Tools')], - 'WebKit', 'chromium') + 'Source', 'WebKit', 'chromium') else: self._chromium_base_dir = abspath[0:offset] return self._filesystem.join(self._chromium_base_dir, *comps) @@ -217,8 +208,6 @@ class ChromiumPort(base.Port): def create_driver(self, worker_number): """Starts a new Driver and returns a handle to it.""" - if not self.get_option('use_test_shell') and sys.platform == 'darwin': - return webkit.WebKitDriver(self, worker_number) return ChromiumDriver(self, worker_number) def start_helper(self): @@ -241,9 +230,6 @@ class ChromiumPort(base.Port): # http://bugs.python.org/issue1731717 self._helper.wait() - def test_base_platform_names(self): - return ('linux', 'mac', 'win') - def test_expectations(self): """Returns the test expectations for this port. @@ -273,15 +259,14 @@ class ChromiumPort(base.Port): all_test_files.update(extra_test_files) expectations = test_expectations.TestExpectations( - self, all_test_files, expectations_str, test_platform_name, - is_debug_mode, is_lint_mode=False, overrides=overrides_str) + self, all_test_files, expectations_str, self.test_configuration(), + is_lint_mode=False, overrides=overrides_str) tests_dir = self.layout_tests_dir() return [self.relative_test_filename(test) for test in expectations.get_tests_with_result_type(test_expectations.SKIP)] def test_platform_names(self): - return self.test_base_platform_names() + ('win-xp', - 'win-vista', 'win-7') + return ('mac', 'win', 'linux', 'win-xp', 'win-vista', 'win-7') def test_platform_name_to_name(self, test_platform_name): if test_platform_name in self.test_platform_names(): @@ -340,13 +325,11 @@ class ChromiumPort(base.Port): def _path_to_image_diff(self): binary_name = 'ImageDiff' - if self.get_option('use_test_shell'): - binary_name = 'image_diff' return self._build_path(self.get_option('configuration'), binary_name) class ChromiumDriver(base.Driver): - """Abstract interface for test_shell.""" + """Abstract interface for DRT.""" def __init__(self, port, worker_number): self._port = port @@ -365,10 +348,7 @@ class ChromiumDriver(base.Driver): cmd.append("--pixel-tests=" + self._port._convert_path(self._image_path)) - if self._port.get_option('use_test_shell'): - cmd.append('--layout-tests') - else: - cmd.append('--test-shell') + cmd.append('--test-shell') if self._port.get_option('startup_dialog'): cmd.append('--testshell-startup-dialog') @@ -385,14 +365,12 @@ class ChromiumDriver(base.Driver): if self._port.get_option('stress_deopt'): cmd.append('--stress-deopt') - # test_shell does not support accelerated compositing. - if not self._port.get_option("use_test_shell"): - if self._port.get_option('accelerated_compositing'): - cmd.append('--enable-accelerated-compositing') - if self._port.get_option('accelerated_2d_canvas'): - cmd.append('--enable-accelerated-2d-canvas') - if self._port.get_option('enable_hardware_gpu'): - cmd.append('--enable-hardware-gpu') + if self._port.get_option('accelerated_compositing'): + cmd.append('--enable-accelerated-compositing') + if self._port.get_option('accelerated_2d_canvas'): + cmd.append('--enable-accelerated-2d-canvas') + if self._port.get_option('enable_hardware_gpu'): + cmd.append('--enable-hardware-gpu') return cmd def start(self): @@ -420,17 +398,17 @@ class ChromiumDriver(base.Driver): try: if input: if isinstance(input, unicode): - # TestShell expects utf-8 + # DRT expects utf-8 input = input.encode("utf-8") self._proc.stdin.write(input) # DumpRenderTree text output is always UTF-8. However some tests # (e.g. webarchive) may spit out binary data instead of text so we - # don't bother to decode the output (for either DRT or test_shell). + # don't bother to decode the output. line = self._proc.stdout.readline() # We could assert() here that line correctly decodes as UTF-8. return (line, False) except IOError, e: - _log.error("IOError communicating w/ test_shell: " + str(e)) + _log.error("IOError communicating w/ DRT: " + str(e)) return (None, True) def _test_shell_command(self, uri, timeoutms, checksum): @@ -465,7 +443,7 @@ class ChromiumDriver(base.Driver): raise e return self._output_image() - def run_test(self, test_input): + def run_test(self, driver_input): output = [] error = [] crash = False @@ -475,9 +453,9 @@ class ChromiumDriver(base.Driver): start_time = time.time() - uri = self._port.filename_to_uri(test_input.filename) - cmd = self._test_shell_command(uri, test_input.timeout, - test_input.image_hash) + uri = self._port.filename_to_uri(driver_input.filename) + cmd = self._test_shell_command(uri, driver_input.timeout, + driver_input.image_hash) (line, crash) = self._write_command_and_read_line(input=cmd) while not crash and line.rstrip() != "#EOF": @@ -485,7 +463,7 @@ class ChromiumDriver(base.Driver): if line == '' and self.poll() is not None: # This is hex code 0xc000001d, which is used for abrupt # termination. This happens if we hit ctrl+c from the prompt - # and we happen to be waiting on test_shell. + # and we happen to be waiting on DRT. # sdoyon: Not sure for which OS and in what circumstances the # above code is valid. What works for me under Linux to detect # ctrl+c is for the subprocess returncode to be negative @@ -519,7 +497,7 @@ class ChromiumDriver(base.Driver): (line, crash) = self._write_command_and_read_line(input=None) run_time = time.time() - start_time - return test_output.TestOutput( + return base.DriverOutput( ''.join(output), self._output_image_with_retry(), actual_checksum, crash, run_time, timeout, ''.join(error)) @@ -532,8 +510,8 @@ class ChromiumDriver(base.Driver): if sys.platform not in ('win32', 'cygwin'): # Closing stdin/stdout/stderr hangs sometimes on OS X, # (see __init__(), above), and anyway we don't want to hang - # the harness if test_shell is buggy, so we wait a couple - # seconds to give test_shell a chance to clean up, but then + # the harness if DRT is buggy, so we wait a couple + # seconds to give DRT a chance to clean up, but then # force-kill the process if necessary. KILL_TIMEOUT = 3.0 timeout = time.time() + KILL_TIMEOUT diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py index b88d8aa..e8c75c4 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py @@ -30,64 +30,64 @@ import chromium_linux import chromium_mac import chromium_win +from webkitpy.layout_tests.port import test_files -def get(**kwargs): + +def get(platform=None, port_name='chromium-gpu', **kwargs): """Some tests have slightly different results when run while using hardware acceleration. In those cases, we prepend an additional directory to the baseline paths.""" - port_name = kwargs.get('port_name', None) + platform = platform or sys.platform if port_name == 'chromium-gpu': - if sys.platform in ('cygwin', 'win32'): + if platform in ('cygwin', 'win32'): port_name = 'chromium-gpu-win' - elif sys.platform == 'linux2': + elif platform == 'linux2': port_name = 'chromium-gpu-linux' - elif sys.platform == 'darwin': + elif platform == 'darwin': port_name = 'chromium-gpu-mac' else: - raise NotImplementedError('unsupported platform: %s' % - sys.platform) + raise NotImplementedError('unsupported platform: %s' % platform) if port_name == 'chromium-gpu-linux': - return ChromiumGpuLinuxPort(**kwargs) - - if port_name.startswith('chromium-gpu-mac'): - return ChromiumGpuMacPort(**kwargs) - - if port_name.startswith('chromium-gpu-win'): - return ChromiumGpuWinPort(**kwargs) - + return ChromiumGpuLinuxPort(port_name=port_name, **kwargs) + if port_name == 'chromium-gpu-mac': + return ChromiumGpuMacPort(port_name=port_name, **kwargs) + if port_name == 'chromium-gpu-win': + return ChromiumGpuWinPort(port_name=port_name, **kwargs) raise NotImplementedError('unsupported port: %s' % port_name) -def _set_gpu_options(options): - if options: - if options.accelerated_compositing is None: - options.accelerated_compositing = True - if options.accelerated_2d_canvas is None: - options.accelerated_2d_canvas = True +# FIXME: These should really be a mixin class. - # FIXME: Remove this after http://codereview.chromium.org/5133001/ is enabled - # on the bots. - if options.builder_name is not None and not ' - GPU' in options.builder_name: - options.builder_name = options.builder_name + ' - GPU' +def _set_gpu_options(port): + if port.get_option('accelerated_compositing') is None: + port._options.accelerated_compositing = True + if port.get_option('accelerated_2d_canvas') is None: + port._options.accelerated_2d_canvas = True + # FIXME: Remove this after http://codereview.chromium.org/5133001/ is enabled + # on the bots. + if port.get_option('builder_name') is not None and not ' - GPU' in port._options.builder_name: + port._options.builder_name += ' - GPU' -def _gpu_overrides(port): - try: - overrides_path = port.path_from_chromium_base('webkit', 'tools', - 'layout_tests', 'test_expectations_gpu.txt') - except AssertionError: - return None - if not port._filesystem.exists(overrides_path): - return None - return port._filesystem.read_text_file(overrides_path) + +def _tests(port, paths): + if not paths: + paths = ['compositing', 'platform/chromium/compositing'] + if not port.name().startswith('chromium-gpu-mac'): + # Canvas is not yet accelerated on the Mac, so there's no point + # in running the tests there. + paths += ['fast/canvas', 'canvas/philip'] + # invalidate_rect.html tests a bug in the compositor. + # See https://bugs.webkit.org/show_bug.cgi?id=53117 + paths += ['plugins/invalidate_rect.html'] + return test_files.find(port, paths) class ChromiumGpuLinuxPort(chromium_linux.ChromiumLinuxPort): - def __init__(self, **kwargs): - kwargs.setdefault('port_name', 'chromium-gpu-linux') - _set_gpu_options(kwargs.get('options')) - chromium_linux.ChromiumLinuxPort.__init__(self, **kwargs) + def __init__(self, port_name='chromium-gpu-linux', **kwargs): + chromium_linux.ChromiumLinuxPort.__init__(self, port_name=port_name, **kwargs) + _set_gpu_options(self) def baseline_search_path(self): # Mimic the Linux -> Win expectations fallback in the ordinary Chromium port. @@ -97,19 +97,18 @@ class ChromiumGpuLinuxPort(chromium_linux.ChromiumLinuxPort): def default_child_processes(self): return 1 - def path_to_test_expectations_file(self): - return self.path_from_webkit_base('LayoutTests', 'platform', - 'chromium-gpu', 'test_expectations.txt') + def graphics_type(self): + return 'gpu' + + def tests(self, paths): + return _tests(self, paths) - def test_expectations_overrides(self): - return _gpu_overrides(self) class ChromiumGpuMacPort(chromium_mac.ChromiumMacPort): - def __init__(self, **kwargs): - kwargs.setdefault('port_name', 'chromium-gpu-mac') - _set_gpu_options(kwargs.get('options')) - chromium_mac.ChromiumMacPort.__init__(self, **kwargs) + def __init__(self, port_name='chromium-gpu-mac', **kwargs): + chromium_mac.ChromiumMacPort.__init__(self, port_name=port_name, **kwargs) + _set_gpu_options(self) def baseline_search_path(self): return (map(self._webkit_baseline_path, ['chromium-gpu-mac', 'chromium-gpu']) + @@ -118,19 +117,18 @@ class ChromiumGpuMacPort(chromium_mac.ChromiumMacPort): def default_child_processes(self): return 1 - def path_to_test_expectations_file(self): - return self.path_from_webkit_base('LayoutTests', 'platform', - 'chromium-gpu', 'test_expectations.txt') + def graphics_type(self): + return 'gpu' + + def tests(self, paths): + return _tests(self, paths) - def test_expectations_overrides(self): - return _gpu_overrides(self) class ChromiumGpuWinPort(chromium_win.ChromiumWinPort): - def __init__(self, **kwargs): - kwargs.setdefault('port_name', 'chromium-gpu-win' + self.version()) - _set_gpu_options(kwargs.get('options')) - chromium_win.ChromiumWinPort.__init__(self, **kwargs) + def __init__(self, port_name='chromium-gpu-win', **kwargs): + chromium_win.ChromiumWinPort.__init__(self, port_name=port_name, **kwargs) + _set_gpu_options(self) def baseline_search_path(self): return (map(self._webkit_baseline_path, ['chromium-gpu-win', 'chromium-gpu']) + @@ -139,9 +137,8 @@ class ChromiumGpuWinPort(chromium_win.ChromiumWinPort): def default_child_processes(self): return 1 - def path_to_test_expectations_file(self): - return self.path_from_webkit_base('LayoutTests', 'platform', - 'chromium-gpu', 'test_expectations.txt') + def graphics_type(self): + return 'gpu' - def test_expectations_overrides(self): - return _gpu_overrides(self) + def tests(self, paths): + return _tests(self, paths) diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py index 0bfb127..96962ec 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py @@ -40,20 +40,34 @@ class ChromiumGpuTest(unittest.TestCase): def test_get_chromium_gpu_win(self): self.assertOverridesWorked('chromium-gpu-win') - def assertOverridesWorked(self, port_name): + def test_get_chromium_gpu__on_linux(self): + self.assertOverridesWorked('chromium-gpu-linux', 'chromium-gpu', 'linux2') + + def test_get_chromium_gpu__on_mac(self): + self.assertOverridesWorked('chromium-gpu-mac', 'chromium-gpu', 'darwin') + + def test_get_chromium_gpu__on_win(self): + self.assertOverridesWorked('chromium-gpu-win', 'chromium-gpu', 'win32') + self.assertOverridesWorked('chromium-gpu-win', 'chromium-gpu', 'cygwin') + + def assertOverridesWorked(self, port_name, input_name=None, platform=None): # test that we got the right port mock_options = mocktool.MockOptions(accelerated_compositing=None, accelerated_2d_canvas=None, builder_name='foo', child_processes=None) - port = chromium_gpu.get(port_name=port_name, options=mock_options) + if input_name and platform: + port = chromium_gpu.get(platform=platform, port_name=input_name, + options=mock_options) + else: + port = chromium_gpu.get(port_name=port_name, options=mock_options) self.assertTrue(port._options.accelerated_compositing) self.assertTrue(port._options.accelerated_2d_canvas) self.assertEqual(port.default_child_processes(), 1) self.assertEqual(port._options.builder_name, 'foo - GPU') - # we use startswith() instead of Equal to gloss over platform versions. - self.assertTrue(port.name().startswith(port_name)) + # We don't support platform-specific versions of the GPU port yet. + self.assertEqual(port.name(), port_name) # test that it has the right directories in front of the search path. paths = port.baseline_search_path() @@ -64,9 +78,24 @@ class ChromiumGpuTest(unittest.TestCase): else: self.assertEqual(port._webkit_baseline_path('chromium-gpu'), paths[1]) - # Test that we have the right expectations file. - self.assertTrue('chromium-gpu' in - port.path_to_test_expectations_file()) + + # Test that we're limiting to the correct directories. + # These two tests are picked mostly at random, but we make sure they + # exist separately from being filtered out by the port. + files = port.tests(None) + + path = port.abspath_for_test('compositing/checkerboard.html') + self.assertTrue(port._filesystem.exists(path)) + self.assertTrue(path in files) + + path = port.abspath_for_test('fast/html/keygen.html') + self.assertTrue(port._filesystem.exists(path)) + self.assertFalse(path in files) + if port_name.startswith('chromium-gpu-mac'): + path = port.abspath_for_test('fast/canvas/set-colors.html') + self.assertTrue(port._filesystem.exists(path)) + self.assertFalse(path in files) + if __name__ == '__main__': unittest.main() diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py index c1c85f8..c3c5a21 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py @@ -85,7 +85,7 @@ class ChromiumLinuxPort(chromium.ChromiumPort): base = self.path_from_chromium_base() if self._filesystem.exists(self._filesystem.join(base, 'sconsbuild')): return self._filesystem.join(base, 'sconsbuild', *comps) - if self._filesystem.exists(self._filesystem.join(base, 'out', *comps)) or self.get_option('use_test_shell'): + if self._filesystem.exists(self._filesystem.join(base, 'out', *comps)): return self._filesystem.join(base, 'out', *comps) base = self.path_from_webkit_base() if self._filesystem.exists(self._filesystem.join(base, 'sconsbuild')): @@ -153,8 +153,6 @@ class ChromiumLinuxPort(chromium.ChromiumPort): if not configuration: configuration = self.get_option('configuration') binary_name = 'DumpRenderTree' - if self.get_option('use_test_shell'): - binary_name = 'test_shell' return self._build_path(configuration, binary_name) def _path_to_helper(self): diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py index 5360ab3..17862a2 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py @@ -69,18 +69,18 @@ class ChromiumMacPort(chromium.ChromiumPort): return result def default_child_processes(self): - # FIXME: we need to run single-threaded for now. See - # https://bugs.webkit.org/show_bug.cgi?id=38553. Unfortunately this - # routine is called right before the logger is configured, so if we - # try to _log.warning(), it gets thrown away. - import sys - sys.stderr.write("Defaulting to one child - see https://bugs.webkit.org/show_bug.cgi?id=38553\n") - return 1 + if self.get_option('worker_model') == 'old-threads': + # FIXME: we need to run single-threaded for now. See + # https://bugs.webkit.org/show_bug.cgi?id=38553. Unfortunately this + # routine is called right before the logger is configured, so if we + # try to _log.warning(), it gets thrown away. + import sys + sys.stderr.write("Defaulting to one child - see https://bugs.webkit.org/show_bug.cgi?id=38553\n") + return 1 + + return chromium.ChromiumPort.default_child_processes(self) def driver_name(self): - """name for this port's equivalent of DumpRenderTree.""" - if self.get_option('use_test_shell'): - return "TestShell" return "DumpRenderTree" def test_platform_name(self): @@ -110,7 +110,7 @@ class ChromiumMacPort(chromium.ChromiumPort): *comps) path = self.path_from_chromium_base('xcodebuild', *comps) - if self._filesystem.exists(path) or self.get_option('use_test_shell'): + if self._filesystem.exists(path): return path return self.path_from_webkit_base( 'Source', 'WebKit', 'chromium', 'xcodebuild', *comps) @@ -154,8 +154,6 @@ class ChromiumMacPort(chromium.ChromiumPort): def _path_to_helper(self): binary_name = 'LayoutTestHelper' - if self.get_option('use_test_shell'): - binary_name = 'layout_test_helper' return self._build_path(self.get_option('configuration'), binary_name) def _path_to_wdiff(self): diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py index 6c8987b..b89c8cc 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py @@ -116,13 +116,6 @@ class ChromiumPortTest(unittest.TestCase): port = ChromiumPortTest.TestMacPort(options=mock_options) self.assertTrue(port._path_to_image_diff().endswith( '/xcodebuild/default/ImageDiff')) - mock_options = mocktool.MockOptions(use_test_shell=True) - port = ChromiumPortTest.TestLinuxPort(options=mock_options) - self.assertTrue(port._path_to_image_diff().endswith( - '/out/default/image_diff'), msg=port._path_to_image_diff()) - port = ChromiumPortTest.TestMacPort(options=mock_options) - self.assertTrue(port._path_to_image_diff().endswith( - '/xcodebuild/default/image_diff')) # FIXME: Figure out how this is going to work on Windows. #port = chromium_win.ChromiumWinPort('test-port', options=MockOptions()) diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_win.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_win.py index 14f2777..f4cbf80 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_win.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_win.py @@ -113,9 +113,9 @@ class ChromiumWinPort(chromium.ChromiumPort): if self._filesystem.exists(p): return p p = self.path_from_chromium_base('chrome', *comps) - if self._filesystem.exists(p) or self.get_option('use_test_shell'): + if self._filesystem.exists(p): return p - return self._filesystem.join(self.path_from_webkit_base(), 'WebKit', 'chromium', *comps) + return self._filesystem.join(self.path_from_webkit_base(), 'Source', 'WebKit', 'chromium', *comps) def _lighttpd_path(self, *comps): return self.path_from_chromium_base('third_party', 'lighttpd', 'win', @@ -141,20 +141,14 @@ class ChromiumWinPort(chromium.ChromiumPort): if not configuration: configuration = self.get_option('configuration') binary_name = 'DumpRenderTree.exe' - if self.get_option('use_test_shell'): - binary_name = 'test_shell.exe' return self._build_path(configuration, binary_name) def _path_to_helper(self): binary_name = 'LayoutTestHelper.exe' - if self.get_option('use_test_shell'): - binary_name = 'layout_test_helper.exe' return self._build_path(self.get_option('configuration'), binary_name) def _path_to_image_diff(self): binary_name = 'ImageDiff.exe' - if self.get_option('use_test_shell'): - binary_name = 'image_diff.exe' return self._build_path(self.get_option('configuration'), binary_name) def _path_to_wdiff(self): diff --git a/Tools/Scripts/webkitpy/layout_tests/port/dryrun.py b/Tools/Scripts/webkitpy/layout_tests/port/dryrun.py index 4ed34e6..6b3bd51 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/dryrun.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/dryrun.py @@ -50,8 +50,6 @@ import os import sys import time -from webkitpy.layout_tests.layout_package import test_output - import base import factory @@ -71,6 +69,12 @@ class DryRunPort(object): def __getattr__(self, name): return getattr(self.__delegate, name) + def acquire_http_lock(self): + pass + + def release_http_lock(self): + pass + def check_build(self, needs_http): return True @@ -112,18 +116,18 @@ class DryrunDriver(base.Driver): def poll(self): return None - def run_test(self, test_input): + def run_test(self, driver_input): start_time = time.time() - text_output = self._port.expected_text(test_input.filename) + text_output = self._port.expected_text(driver_input.filename) - if test_input.image_hash is not None: - image = self._port.expected_image(test_input.filename) - hash = self._port.expected_checksum(test_input.filename) + if driver_input.image_hash is not None: + image = self._port.expected_image(driver_input.filename) + hash = self._port.expected_checksum(driver_input.filename) else: image = None hash = None - return test_output.TestOutput(text_output, image, hash, False, - time.time() - start_time, False, None) + return base.DriverOutput(text_output, image, hash, False, + time.time() - start_time, False, '') def start(self): pass diff --git a/Tools/Scripts/webkitpy/layout_tests/port/factory.py b/Tools/Scripts/webkitpy/layout_tests/port/factory.py index 6935744..7ae6eb6 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/factory.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/factory.py @@ -70,12 +70,15 @@ def _get_kwargs(**kwargs): raise NotImplementedError('unknown port; sys.platform = "%s"' % sys.platform) - if port_to_use == 'test': + if port_to_use.startswith('test'): import test maker = test.TestPort elif port_to_use.startswith('dryrun'): import dryrun maker = dryrun.DryRunPort + elif port_to_use.startswith('mock-'): + import mock_drt + maker = mock_drt.MockDRTPort elif port_to_use.startswith('mac'): import mac maker = mac.MacPort diff --git a/Tools/Scripts/webkitpy/layout_tests/port/http_server_base.py b/Tools/Scripts/webkitpy/layout_tests/port/http_server_base.py index 52a0403..2a43e81 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/http_server_base.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/http_server_base.py @@ -67,7 +67,7 @@ class HttpServerBase(object): url = 'http%s://127.0.0.1:%d/' % (http_suffix, mapping['port']) try: - response = urllib.urlopen(url) + response = urllib.urlopen(url, proxies={}) _log.debug("Server running at %s" % url) except IOError, e: _log.debug("Server NOT running at %s: %s" % (url, e)) diff --git a/Tools/Scripts/webkitpy/layout_tests/port/mac.py b/Tools/Scripts/webkitpy/layout_tests/port/mac.py index 0622196..1398ed3 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/mac.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/mac.py @@ -33,9 +33,7 @@ import os import platform import signal -import webkitpy.common.system.ospath as ospath -import webkitpy.layout_tests.port.server_process as server_process -from webkitpy.layout_tests.port.webkit import WebKitPort, WebKitDriver +from webkitpy.layout_tests.port.webkit import WebKitPort _log = logging.getLogger("webkitpy.layout_tests.port.mac") @@ -52,7 +50,7 @@ class MacPort(WebKitPort): # four threads in parallel. # See https://bugs.webkit.org/show_bug.cgi?id=36622 child_processes = WebKitPort.default_child_processes(self) - if child_processes > 4: + if self.get_option('worker_model') == 'old-threads' and child_processes > 4: return 4 return child_processes diff --git a/Tools/Scripts/webkitpy/layout_tests/port/mac_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/mac_unittest.py index d383a4c..ef04679 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/mac_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/mac_unittest.py @@ -35,23 +35,31 @@ import port_testcase class MacTest(port_testcase.PortTestCase): - def make_port(self, options=port_testcase.mock_options): + def make_port(self, port_name=None, options=port_testcase.mock_options): if sys.platform != 'darwin': return None - port_obj = mac.MacPort(options=options) + port_obj = mac.MacPort(port_name=port_name, options=options) port_obj._options.results_directory = port_obj.results_directory() port_obj._options.configuration = 'Release' return port_obj - def test_skipped_file_paths(self): - port = self.make_port() + def assert_skipped_files_for_version(self, port_name, expected_paths): + port = self.make_port(port_name) if not port: return skipped_paths = port._skipped_file_paths() # FIXME: _skipped_file_paths should return WebKit-relative paths. # So to make it unit testable, we strip the WebKit directory from the path. relative_paths = [path[len(port.path_from_webkit_base()):] for path in skipped_paths] - self.assertEqual(relative_paths, ['LayoutTests/platform/mac-leopard/Skipped', 'LayoutTests/platform/mac/Skipped']) + self.assertEqual(relative_paths, expected_paths) + + def test_skipped_file_paths(self): + self.assert_skipped_files_for_version('mac', + ['/LayoutTests/platform/mac/Skipped']) + self.assert_skipped_files_for_version('mac-snowleopard', + ['/LayoutTests/platform/mac-snowleopard/Skipped', '/LayoutTests/platform/mac/Skipped']) + self.assert_skipped_files_for_version('mac-leopard', + ['/LayoutTests/platform/mac-leopard/Skipped', '/LayoutTests/platform/mac/Skipped']) example_skipped_file = u""" # fast/events/mouseout-on-window.html needs mac DRT to issue mouse out events @@ -69,12 +77,11 @@ svg/batik/text/smallFonts.svg "svg/batik/text/smallFonts.svg", ] - def test_skipped_file_paths(self): + def test_tests_from_skipped_file_contents(self): port = self.make_port() if not port: return - skipped_file = StringIO.StringIO(self.example_skipped_file) - self.assertEqual(port._tests_from_skipped_file(skipped_file), self.example_skipped_tests) + self.assertEqual(port._tests_from_skipped_file_contents(self.example_skipped_file), self.example_skipped_tests) if __name__ == '__main__': diff --git a/Tools/Scripts/webkitpy/layout_tests/port/mock_drt.py b/Tools/Scripts/webkitpy/layout_tests/port/mock_drt.py new file mode 100644 index 0000000..1147846 --- /dev/null +++ b/Tools/Scripts/webkitpy/layout_tests/port/mock_drt.py @@ -0,0 +1,280 @@ +#!/usr/bin/env python +# Copyright (C) 2011 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the Google name nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" +This is an implementation of the Port interface that overrides other +ports and changes the Driver binary to "MockDRT". +""" + +import logging +import optparse +import os +import sys + +from webkitpy.common.system import filesystem + +from webkitpy.layout_tests.port import base +from webkitpy.layout_tests.port import factory + +_log = logging.getLogger(__name__) + + +class MockDRTPort(object): + """MockPort implementation of the Port interface.""" + + def __init__(self, **kwargs): + prefix = 'mock-' + if 'port_name' in kwargs: + kwargs['port_name'] = kwargs['port_name'][len(prefix):] + self.__delegate = factory.get(**kwargs) + self.__real_name = prefix + self.__delegate.name() + + def real_name(self): + return self.__real_name + + def __getattr__(self, name): + return getattr(self.__delegate, name) + + def acquire_http_lock(self): + pass + + def release_http_lock(self): + pass + + def check_build(self, needs_http): + return True + + def check_sys_deps(self, needs_http): + return True + + def driver_cmd_line(self): + driver = self.create_driver(0) + return driver.cmd_line() + + def _path_to_driver(self): + return os.path.abspath(__file__) + + def create_driver(self, worker_number): + # We need to create a driver object as the delegate would, but + # overwrite the path to the driver binary in its command line. We do + # this by actually overwriting its cmd_line() method with a proxy + # method that splices in the mock_drt path and command line arguments + # in place of the actual path to the driver binary. + + def overriding_cmd_line(): + cmd = self.__original_driver_cmd_line() + index = cmd.index(self.__delegate._path_to_driver()) + cmd[index:index + 1] = [sys.executable, self._path_to_driver(), + '--platform', self.name()] + return cmd + + delegated_driver = self.__delegate.create_driver(worker_number) + self.__original_driver_cmd_line = delegated_driver.cmd_line + delegated_driver.cmd_line = overriding_cmd_line + return delegated_driver + + def start_helper(self): + pass + + def start_http_server(self): + pass + + def start_websocket_server(self): + pass + + def stop_helper(self): + pass + + def stop_http_server(self): + pass + + def stop_websocket_server(self): + pass + + +def main(argv, fs, stdin, stdout, stderr): + """Run the tests.""" + + options, args = parse_options(argv) + if options.chromium: + drt = MockChromiumDRT(options, args, fs, stdin, stdout, stderr) + else: + drt = MockDRT(options, args, fs, stdin, stdout, stderr) + return drt.run() + + +def parse_options(argv): + # FIXME: We have to do custom arg parsing instead of using the optparse + # module. First, Chromium and non-Chromium DRTs have a different argument + # syntax. Chromium uses --pixel-tests=, and non-Chromium uses + # --pixel-tests as a boolean flag. Second, we don't want to have to list + # every command line flag DRT accepts, but optparse complains about + # unrecognized flags. At some point it might be good to share a common + # DRT options class between this file and webkit.py and chromium.py + # just to get better type checking. + platform_index = argv.index('--platform') + platform = argv[platform_index + 1] + + pixel_tests = False + pixel_path = None + chromium = False + if platform.startswith('chromium'): + chromium = True + for arg in argv: + if arg.startswith('--pixel-tests'): + pixel_tests = True + pixel_path = arg[len('--pixel-tests='):] + else: + pixel_tests = '--pixel-tests' in argv + options = base.DummyOptions(chromium=chromium, + platform=platform, + pixel_tests=pixel_tests, + pixel_path=pixel_path) + return (options, []) + + +# FIXME: Should probably change this to use DriverInput after +# https://bugs.webkit.org/show_bug.cgi?id=53004 lands. +class _DRTInput(object): + def __init__(self, line): + vals = line.strip().split("'") + if len(vals) == 1: + self.uri = vals[0] + self.checksum = None + else: + self.uri = vals[0] + self.checksum = vals[1] + + +class MockDRT(object): + def __init__(self, options, args, filesystem, stdin, stdout, stderr): + self._options = options + self._args = args + self._filesystem = filesystem + self._stdout = stdout + self._stdin = stdin + self._stderr = stderr + + port_name = None + if options.platform: + port_name = options.platform + self._port = factory.get(port_name, options=options, filesystem=filesystem) + + def run(self): + while True: + line = self._stdin.readline() + if not line: + break + self.run_one_test(self.parse_input(line)) + return 0 + + def parse_input(self, line): + return _DRTInput(line) + + def run_one_test(self, test_input): + port = self._port + if test_input.uri.startswith('http'): + test_name = port.uri_to_test_name(test_input.uri) + test_path = self._filesystem.join(port.layout_tests_dir(), test_name) + else: + test_path = test_input.uri + + actual_text = port.expected_text(test_path) + if self._options.pixel_tests and test_input.checksum: + actual_checksum = port.expected_checksum(test_path) + actual_image = port.expected_image(test_path) + + self._stdout.write('Content-Type: text/plain\n') + + # FIXME: Note that we don't ensure there is a trailing newline! + # This mirrors actual (Mac) DRT behavior but is a bug. + self._stdout.write(actual_text) + self._stdout.write('#EOF\n') + + if self._options.pixel_tests and test_input.checksum: + self._stdout.write('\n') + self._stdout.write('ActualHash: %s\n' % actual_checksum) + self._stdout.write('ExpectedHash: %s\n' % test_input.checksum) + if actual_checksum != test_input.checksum: + self._stdout.write('Content-Type: image/png\n') + self._stdout.write('Content-Length: %s\n\n' % len(actual_image)) + self._stdout.write(actual_image) + self._stdout.write('#EOF\n') + self._stdout.flush() + self._stderr.flush() + + +# FIXME: Should probably change this to use DriverInput after +# https://bugs.webkit.org/show_bug.cgi?id=53004 lands. +class _ChromiumDRTInput(_DRTInput): + def __init__(self, line): + vals = line.strip().split() + if len(vals) == 3: + self.uri, self.timeout, self.checksum = vals + else: + self.uri = vals[0] + self.timeout = vals[1] + self.checksum = None + + +class MockChromiumDRT(MockDRT): + def parse_input(self, line): + return _ChromiumDRTInput(line) + + def run_one_test(self, test_input): + port = self._port + test_name = self._port.uri_to_test_name(test_input.uri) + test_path = self._filesystem.join(port.layout_tests_dir(), test_name) + + actual_text = port.expected_text(test_path) + actual_image = '' + actual_checksum = '' + if self._options.pixel_tests and test_input.checksum: + actual_checksum = port.expected_checksum(test_path) + if actual_checksum != test_input.checksum: + actual_image = port.expected_image(test_path) + + self._stdout.write("#URL:%s\n" % test_input.uri) + if self._options.pixel_tests and test_input.checksum: + self._stdout.write("#MD5:%s\n" % actual_checksum) + self._filesystem.write_binary_file(self._options.pixel_path, + actual_image) + self._stdout.write(actual_text) + + # FIXME: (See above FIXME as well). Chromium DRT appears to always + # ensure the text output has a trailing newline. Mac DRT does not. + if not actual_text.endswith('\n'): + self._stdout.write('\n') + self._stdout.write('#EOF\n') + self._stdout.flush() + + +if __name__ == '__main__': + fs = filesystem.FileSystem() + sys.exit(main(sys.argv[1:], fs, sys.stdin, sys.stdout, sys.stderr)) diff --git a/Tools/Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py new file mode 100644 index 0000000..1506315 --- /dev/null +++ b/Tools/Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python +# Copyright (C) 2011 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Unit tests for MockDRT.""" + +import unittest + +from webkitpy.common import newstringio + +from webkitpy.layout_tests.port import mock_drt +from webkitpy.layout_tests.port import factory +from webkitpy.layout_tests.port import port_testcase +from webkitpy.layout_tests.port import test + + +class MockDRTPortTest(port_testcase.PortTestCase): + def make_port(self): + return mock_drt.MockDRTPort() + + def test_port_name_in_constructor(self): + self.assertTrue(mock_drt.MockDRTPort(port_name='mock-test')) + + def test_acquire_http_lock(self): + # Only checking that no exception is raised. + self.make_port().acquire_http_lock() + + def test_release_http_lock(self): + # Only checking that no exception is raised. + self.make_port().release_http_lock() + + def test_check_build(self): + port = self.make_port() + self.assertTrue(port.check_build(True)) + + def test_check_sys_deps(self): + port = self.make_port() + self.assertTrue(port.check_sys_deps(True)) + + def test_start_helper(self): + # Only checking that no exception is raised. + self.make_port().start_helper() + + def test_start_http_server(self): + # Only checking that no exception is raised. + self.make_port().start_http_server() + + def test_start_websocket_server(self): + # Only checking that no exception is raised. + self.make_port().start_websocket_server() + + def test_stop_helper(self): + # Only checking that no exception is raised. + self.make_port().stop_helper() + + def test_stop_http_server(self): + # Only checking that no exception is raised. + self.make_port().stop_http_server() + + def test_stop_websocket_server(self): + # Only checking that no exception is raised. + self.make_port().stop_websocket_server() + + +class MockDRTTest(unittest.TestCase): + def to_path(self, port, test_name): + return port._filesystem.join(port.layout_tests_dir(), test_name) + + def input_line(self, port, test_name, checksum=None): + url = port.filename_to_uri(self.to_path(port, test_name)) + # FIXME: we shouldn't have to work around platform-specific issues + # here. + if url.startswith('file:////'): + url = url[len('file:////') - 1:] + if url.startswith('file:///'): + url = url[len('file:///') - 1:] + + if checksum: + return url + "'" + checksum + '\n' + return url + '\n' + + def extra_args(self, pixel_tests): + if pixel_tests: + return ['--pixel-tests', '-'] + return ['-'] + + def make_drt(self, options, args, filesystem, stdin, stdout, stderr): + return mock_drt.MockDRT(options, args, filesystem, stdin, stdout, stderr) + + def make_input_output(self, port, test_name, pixel_tests, + expected_checksum, drt_output, drt_input=None): + path = self.to_path(port, test_name) + if pixel_tests: + if not expected_checksum: + expected_checksum = port.expected_checksum(path) + if not drt_input: + drt_input = self.input_line(port, test_name, expected_checksum) + text_output = port.expected_text(path) + + if not drt_output: + drt_output = self.expected_output(port, test_name, pixel_tests, + text_output, expected_checksum) + return (drt_input, drt_output) + + def expected_output(self, port, test_name, pixel_tests, text_output, expected_checksum): + if pixel_tests and expected_checksum: + return ['Content-Type: text/plain\n', + text_output, + '#EOF\n', + '\n', + 'ActualHash: %s\n' % expected_checksum, + 'ExpectedHash: %s\n' % expected_checksum, + '#EOF\n'] + else: + return ['Content-Type: text/plain\n', + text_output, + '#EOF\n', + '#EOF\n'] + + def assertTest(self, test_name, pixel_tests, expected_checksum=None, + drt_output=None, filesystem=None): + platform = 'test' + filesystem = filesystem or test.unit_test_filesystem() + port = factory.get(platform, filesystem=filesystem) + drt_input, drt_output = self.make_input_output(port, test_name, + pixel_tests, expected_checksum, drt_output) + + args = ['--platform', 'test'] + self.extra_args(pixel_tests) + stdin = newstringio.StringIO(drt_input) + stdout = newstringio.StringIO() + stderr = newstringio.StringIO() + options, args = mock_drt.parse_options(args) + + drt = self.make_drt(options, args, filesystem, stdin, stdout, stderr) + res = drt.run() + + self.assertEqual(res, 0) + + # We use the StringIO.buflist here instead of getvalue() because + # the StringIO might be a mix of unicode/ascii and 8-bit strings. + self.assertEqual(stdout.buflist, drt_output) + self.assertEqual(stderr.getvalue(), '') + + def test_main(self): + filesystem = test.unit_test_filesystem() + stdin = newstringio.StringIO() + stdout = newstringio.StringIO() + stderr = newstringio.StringIO() + res = mock_drt.main(['--platform', 'test'] + self.extra_args(False), + filesystem, stdin, stdout, stderr) + self.assertEqual(res, 0) + self.assertEqual(stdout.getvalue(), '') + self.assertEqual(stderr.getvalue(), '') + self.assertEqual(filesystem.written_files, {}) + + def test_pixeltest_passes(self): + # This also tests that we handle HTTP: test URLs properly. + self.assertTest('http/tests/passes/text.html', True) + + def test_pixeltest__fails(self): + self.assertTest('failures/expected/checksum.html', pixel_tests=True, + expected_checksum='wrong-checksum', + drt_output=['Content-Type: text/plain\n', + 'checksum-txt', + '#EOF\n', + '\n', + 'ActualHash: checksum-checksum\n', + 'ExpectedHash: wrong-checksum\n', + 'Content-Type: image/png\n', + 'Content-Length: 13\n\n', + 'checksum\x8a-png', + '#EOF\n']) + + def test_textonly(self): + self.assertTest('passes/image.html', False) + + +class MockChromiumDRTTest(MockDRTTest): + def extra_args(self, pixel_tests): + if pixel_tests: + return ['--pixel-tests=/tmp/png_result0.png'] + return [] + + def make_drt(self, options, args, filesystem, stdin, stdout, stderr): + options.chromium = True + + # We have to set these by hand because --platform test won't trigger + # the Chromium code paths. + options.pixel_path = '/tmp/png_result0.png' + options.pixel_tests = True + + return mock_drt.MockChromiumDRT(options, args, filesystem, stdin, stdout, stderr) + + def input_line(self, port, test_name, checksum=None): + url = port.filename_to_uri(self.to_path(port, test_name)) + if checksum: + return url + ' 6000 ' + checksum + '\n' + return url + ' 6000\n' + + def expected_output(self, port, test_name, pixel_tests, text_output, expected_checksum): + url = port.filename_to_uri(self.to_path(port, test_name)) + if pixel_tests and expected_checksum: + return ['#URL:%s\n' % url, + '#MD5:%s\n' % expected_checksum, + text_output, + '\n', + '#EOF\n'] + else: + return ['#URL:%s\n' % url, + text_output, + '\n', + '#EOF\n'] + + def test_pixeltest__fails(self): + filesystem = test.unit_test_filesystem() + self.assertTest('failures/expected/checksum.html', pixel_tests=True, + expected_checksum='wrong-checksum', + drt_output=['#URL:file:///test.checkout/LayoutTests/failures/expected/checksum.html\n', + '#MD5:checksum-checksum\n', + 'checksum-txt', + '\n', + '#EOF\n'], + filesystem=filesystem) + self.assertEquals(filesystem.written_files, + {'/tmp/png_result0.png': 'checksum\x8a-png'}) + + def test_chromium_parse_options(self): + options, args = mock_drt.parse_options(['--platform', 'chromium-mac', + '--pixel-tests=/tmp/png_result0.png']) + self.assertTrue(options.chromium) + self.assertTrue(options.pixel_tests) + self.assertEquals(options.pixel_path, '/tmp/png_result0.png') + + +if __name__ == '__main__': + unittest.main() diff --git a/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py b/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py index 0b03b4c..4146d40 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py @@ -88,3 +88,15 @@ class PortTestCase(unittest.TestCase): return port.start_websocket_server() port.stop_websocket_server() + + def test_test_configuration(self): + port = self.make_port() + if not port: + return + self.assertTrue(port.test_configuration()) + + def test_all_test_configurations(self): + port = self.make_port() + if not port: + return + self.assertTrue(len(port.all_test_configurations()) > 0) diff --git a/Tools/Scripts/webkitpy/layout_tests/port/server_process.py b/Tools/Scripts/webkitpy/layout_tests/port/server_process.py index 5a0a40c..7974f94 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/server_process.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/server_process.py @@ -115,7 +115,11 @@ class ServerProcess: if is not already running.""" if not self._proc: self._start() - self._proc.stdin.write(input) + try: + self._proc.stdin.write(input) + except IOError, e: + self.stop() + self.crashed = True def read_line(self, timeout): """Read a single line from the subprocess, waiting until the deadline. diff --git a/Tools/Scripts/webkitpy/layout_tests/port/server_process_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/server_process_unittest.py new file mode 100644 index 0000000..f3429cb --- /dev/null +++ b/Tools/Scripts/webkitpy/layout_tests/port/server_process_unittest.py @@ -0,0 +1,77 @@ +# Copyright (C) 2011 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import unittest + +from webkitpy.layout_tests.port import server_process + + +class MockFile(object): + def __init__(self, server_process): + self._server_process = server_process + + def fileno(self): + return 1 + + def write(self, line): + self._server_process.broken_pipes.append(self) + raise IOError + + def close(self): + pass + + +class MockProc(object): + def __init__(self, server_process): + self.stdin = MockFile(server_process) + self.stdout = MockFile(server_process) + self.stderr = MockFile(server_process) + self.pid = 1 + + def poll(self): + return 1 + + +class FakeServerProcess(server_process.ServerProcess): + def _start(self): + self._proc = MockProc(self) + self.stdin = self._proc.stdin + self.broken_pipes = [] + + +class TestServerProcess(unittest.TestCase): + def test_broken_pipe(self): + server_process = FakeServerProcess(port_obj=None, name="test", cmd=["test"]) + server_process.write("should break") + self.assertTrue(server_process.crashed) + self.assertEquals(server_process._proc, None) + self.assertEquals(server_process.broken_pipes, [server_process.stdin]) + + +if __name__ == '__main__': + unittest.main() diff --git a/Tools/Scripts/webkitpy/layout_tests/port/test.py b/Tools/Scripts/webkitpy/layout_tests/port/test.py index 5df5c2d..b94c378 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/test.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/test.py @@ -33,8 +33,7 @@ from __future__ import with_statement import time from webkitpy.common.system import filesystem_mock - -from webkitpy.layout_tests.layout_package import test_output +from webkitpy.tool import mocktool import base @@ -51,9 +50,17 @@ class TestInstance: self.keyboard = False self.error = '' self.timeout = False - self.actual_text = self.base + '-txt\n' - self.actual_checksum = self.base + '-checksum\n' - self.actual_image = self.base + '-png\n' + + # The values of each field are treated as raw byte strings. They + # will be converted to unicode strings where appropriate using + # MockFileSystem.read_text_file(). + self.actual_text = self.base + '-txt' + self.actual_checksum = self.base + '-checksum' + + # We add the '\x8a' for the image file to prevent the value from + # being treated as UTF-8 (the character is invalid) + self.actual_image = self.base + '\x8a' + '-png' + self.expected_text = self.actual_text self.expected_checksum = self.actual_checksum self.expected_image = self.actual_image @@ -84,53 +91,44 @@ class TestList: def unit_test_list(): tests = TestList() tests.add('failures/expected/checksum.html', - actual_checksum='checksum_fail-checksum') + actual_checksum='checksum_fail-checksum') tests.add('failures/expected/crash.html', crash=True) tests.add('failures/expected/exception.html', exception=True) tests.add('failures/expected/timeout.html', timeout=True) tests.add('failures/expected/hang.html', hang=True) - tests.add('failures/expected/missing_text.html', - expected_text=None) + tests.add('failures/expected/missing_text.html', expected_text=None) tests.add('failures/expected/image.html', - actual_image='image_fail-png', - expected_image='image-png') + actual_image='image_fail-png', + expected_image='image-png') tests.add('failures/expected/image_checksum.html', - actual_checksum='image_checksum_fail-checksum', - actual_image='image_checksum_fail-png') - tests.add('failures/expected/keyboard.html', - keyboard=True) - tests.add('failures/expected/missing_check.html', - expected_checksum=None) - tests.add('failures/expected/missing_image.html', - expected_image=None) - tests.add('failures/expected/missing_text.html', - expected_text=None) + actual_checksum='image_checksum_fail-checksum', + actual_image='image_checksum_fail-png') + tests.add('failures/expected/keyboard.html', keyboard=True) + tests.add('failures/expected/missing_check.html', expected_checksum=None) + tests.add('failures/expected/missing_image.html', expected_image=None) + tests.add('failures/expected/missing_text.html', expected_text=None) tests.add('failures/expected/newlines_leading.html', - expected_text="\nfoo\n", - actual_text="foo\n") + expected_text="\nfoo\n", actual_text="foo\n") tests.add('failures/expected/newlines_trailing.html', - expected_text="foo\n\n", - actual_text="foo\n") + expected_text="foo\n\n", actual_text="foo\n") tests.add('failures/expected/newlines_with_excess_CR.html', - expected_text="foo\r\r\r\n", - actual_text="foo\n") - tests.add('failures/expected/text.html', - actual_text='text_fail-png') + expected_text="foo\r\r\r\n", actual_text="foo\n") + tests.add('failures/expected/text.html', actual_text='text_fail-png') tests.add('failures/unexpected/crash.html', crash=True) tests.add('failures/unexpected/text-image-checksum.html', - actual_text='text-image-checksum_fail-txt', - actual_checksum='text-image-checksum_fail-checksum') + actual_text='text-image-checksum_fail-txt', + actual_checksum='text-image-checksum_fail-checksum') tests.add('failures/unexpected/timeout.html', timeout=True) tests.add('http/tests/passes/text.html') tests.add('http/tests/ssl/text.html') tests.add('passes/error.html', error='stuff going to stderr') tests.add('passes/image.html') tests.add('passes/platform_image.html') + # Text output files contain "\r\n" on Windows. This may be # helpfully filtered to "\r\r\n" by our Python/Cygwin tooling. tests.add('passes/text.html', - expected_text='\nfoo\n\n', - actual_text='\nfoo\r\n\r\r\n') + expected_text='\nfoo\n\n', actual_text='\nfoo\r\n\r\r\n') tests.add('websocket/tests/passes/text.html') return tests @@ -184,6 +182,9 @@ WONTFIX SKIP : failures/expected/keyboard.html = CRASH WONTFIX SKIP : failures/expected/exception.html = CRASH """ + # Add in a file should be ignored by test_files.find(). + files[LAYOUT_TEST_DIR + 'userscripts/resources/iframe.html'] = 'iframe' + fs = filesystem_mock.MockFileSystem(files) fs._tests = test_list return fs @@ -192,30 +193,31 @@ WONTFIX SKIP : failures/expected/exception.html = CRASH class TestPort(base.Port): """Test implementation of the Port interface.""" - def __init__(self, **kwargs): - # FIXME: what happens if we're not passed in the test filesystem - # and the tests don't match what's in the filesystem? - # - # We'll leave as is for now to avoid unnecessary dependencies while - # converting all of the unit tests over to using - # unit_test_filesystem(). If things get out of sync the tests should - # fail in fairly obvious ways. Eventually we want to just do: - # - # assert kwargs['filesystem']._tests - # self._tests = kwargs['filesystem']._tests + def __init__(self, port_name=None, user=None, filesystem=None, **kwargs): + if not filesystem: + filesystem = unit_test_filesystem() + + assert filesystem._tests + self._tests = filesystem._tests + + if not user: + user = mocktool.MockUser() - if 'filesystem' not in kwargs or kwargs['filesystem'] is None: - kwargs['filesystem'] = unit_test_filesystem() - self._tests = kwargs['filesystem']._tests - else: - self._tests = unit_test_list() + if not port_name or port_name == 'test': + port_name = 'test-mac' - kwargs.setdefault('port_name', 'test') - base.Port.__init__(self, **kwargs) + self._expectations_path = LAYOUT_TEST_DIR + '/platform/test/test_expectations.txt' + base.Port.__init__(self, port_name=port_name, filesystem=filesystem, user=user, + **kwargs) + + def _path_to_driver(self): + # This routine shouldn't normally be called, but it is called by + # the mock_drt Driver. We return something, but make sure it's useless. + return 'junk' def baseline_path(self): - return self._filesystem.join(self.layout_tests_dir(), 'platform', - self.name() + self.version()) + # We don't bother with a fallback path. + return self._filesystem.join(self.layout_tests_dir(), 'platform', self.name()) def baseline_search_path(self): return [self.baseline_path()] @@ -223,11 +225,14 @@ class TestPort(base.Port): def check_build(self, needs_http): return True + def default_configuration(self): + return 'Release' + def diff_image(self, expected_contents, actual_contents, diff_filename=None): diffed = actual_contents != expected_contents if diffed and diff_filename: - self._filesystem.write_text_file(diff_filename, + self._filesystem.write_binary_file(diff_filename, "< %s\n---\n> %s\n" % (expected_contents, actual_contents)) return diffed @@ -261,23 +266,98 @@ class TestPort(base.Port): def stop_websocket_server(self): pass - def test_base_platform_names(self): - return ('mac', 'win') - - def test_expectations(self): - return self._filesystem.read_text_file(LAYOUT_TEST_DIR + '/platform/test/test_expectations.txt') + def path_to_test_expectations_file(self): + return self._expectations_path def test_platform_name(self): - return 'mac' + name_map = { + 'test-mac': 'mac', + 'test-win': 'win', + 'test-win-xp': 'win-xp', + } + return name_map[self._name] def test_platform_names(self): - return self.test_base_platform_names() + return ('mac', 'win', 'win-xp') def test_platform_name_to_name(self, test_platform_name): - return test_platform_name + name_map = { + 'mac': 'test-mac', + 'win': 'test-win', + 'win-xp': 'test-win-xp', + } + return name_map[test_platform_name] + + # FIXME: These next two routines are copied from base.py with + # the calls to path.abspath_to_uri() removed. We shouldn't have + # to do this. + def filename_to_uri(self, filename): + """Convert a test file (which is an absolute path) to a URI.""" + LAYOUTTEST_HTTP_DIR = "http/tests/" + LAYOUTTEST_WEBSOCKET_DIR = "http/tests/websocket/tests/" + + relative_path = self.relative_test_filename(filename) + port = None + use_ssl = False + + if (relative_path.startswith(LAYOUTTEST_WEBSOCKET_DIR) + or relative_path.startswith(LAYOUTTEST_HTTP_DIR)): + relative_path = relative_path[len(LAYOUTTEST_HTTP_DIR):] + port = 8000 + + # Make http/tests/local run as local files. This is to mimic the + # logic in run-webkit-tests. + # + # TODO(dpranke): remove the media reference and the SSL reference? + if (port and not relative_path.startswith("local/") and + not relative_path.startswith("media/")): + if relative_path.startswith("ssl/"): + port += 443 + protocol = "https" + else: + protocol = "http" + return "%s://127.0.0.1:%u/%s" % (protocol, port, relative_path) + + return "file://" + self._filesystem.abspath(filename) + + def uri_to_test_name(self, uri): + """Return the base layout test name for a given URI. + + This returns the test name for a given URI, e.g., if you passed in + "file:///src/LayoutTests/fast/html/keygen.html" it would return + "fast/html/keygen.html". + + """ + test = uri + if uri.startswith("file:///"): + prefix = "file://" + self.layout_tests_dir() + "/" + return test[len(prefix):] + + if uri.startswith("http://127.0.0.1:8880/"): + # websocket tests + return test.replace('http://127.0.0.1:8880/', '') + + if uri.startswith("http://"): + # regular HTTP test + return test.replace('http://127.0.0.1:8000/', 'http/tests/') + + if uri.startswith("https://"): + return test.replace('https://127.0.0.1:8443/', 'http/tests/') + + raise NotImplementedError('unknown url type: %s' % uri) def version(self): - return '' + version_map = { + 'test-win-xp': '-xp', + 'test-win': '-7', + 'test-mac': '-leopard', + } + return version_map[self._name] + + def test_configuration(self): + if not self._test_configuration: + self._test_configuration = TestTestConfiguration(self) + return self._test_configuration class TestDriver(base.Driver): @@ -287,7 +367,7 @@ class TestDriver(base.Driver): self._port = port def cmd_line(self): - return ['None'] + return [self._port._path_to_driver()] def poll(self): return True @@ -302,13 +382,20 @@ class TestDriver(base.Driver): raise ValueError('exception from ' + test_name) if test.hang: time.sleep((float(test_input.timeout) * 4) / 1000.0) - return test_output.TestOutput(test.actual_text, test.actual_image, - test.actual_checksum, test.crash, - time.time() - start_time, test.timeout, - test.error) + return base.DriverOutput(test.actual_text, test.actual_image, + test.actual_checksum, test.crash, + time.time() - start_time, test.timeout, + test.error) def start(self): pass def stop(self): pass + + +class TestTestConfiguration(base.TestConfiguration): + def all_systems(self): + return (('mac', 'leopard', 'x86'), + ('win', 'xp', 'x86'), + ('win', 'win7', 'x86')) diff --git a/Tools/Scripts/webkitpy/layout_tests/port/test_files.py b/Tools/Scripts/webkitpy/layout_tests/port/test_files.py index 41d918f..534462a 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/test_files.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/test_files.py @@ -49,37 +49,47 @@ _supported_file_extensions = set(['.html', '.shtml', '.xml', '.xhtml', '.xhtmlmp _skipped_directories = set(['.svn', '_svn', 'resources', 'script-tests']) -def find(port, paths): - """Finds the set of tests under port.layout_tests_dir(). +def find(port, paths=None): + """Finds the set of tests under a given list of sub-paths. Args: - paths: a list of command line paths relative to the layout_tests_dir() - to limit the search to. glob patterns are ok. + paths: a list of path expressions relative to port.layout_tests_dir() + to search. Glob patterns are ok, as are path expressions with + forward slashes on Windows. If paths is empty, we look at + everything under the layout_tests_dir(). + """ + paths = paths or ['*'] + filesystem = port._filesystem + return normalized_find(filesystem, normalize(filesystem, port.layout_tests_dir(), paths)) + + +def normalize(filesystem, base_dir, paths): + return [filesystem.normpath(filesystem.join(base_dir, path)) for path in paths] + + +def normalized_find(filesystem, paths): + """Finds the set of tests under the list of paths. + + Args: + paths: a list of absolute path expressions to search. + Glob patterns are ok. """ - fs = port._filesystem gather_start_time = time.time() paths_to_walk = set() - # if paths is empty, provide a pre-defined list. - if paths: - _log.debug("Gathering tests from: %s relative to %s" % (paths, port.layout_tests_dir())) - for path in paths: - # If there's an * in the name, assume it's a glob pattern. - path = fs.join(port.layout_tests_dir(), path) - if path.find('*') > -1: - filenames = fs.glob(path) - paths_to_walk.update(filenames) - else: - paths_to_walk.add(path) - else: - _log.debug("Gathering tests from: %s" % port.layout_tests_dir()) - paths_to_walk.add(port.layout_tests_dir()) + for path in paths: + # If there's an * in the name, assume it's a glob pattern. + if path.find('*') > -1: + filenames = filesystem.glob(path) + paths_to_walk.update(filenames) + else: + paths_to_walk.add(path) # FIXME: I'm not sure there's much point in this being a set. A list would # probably be faster. test_files = set() for path in paths_to_walk: - files = fs.files_under(path, _skipped_directories, _is_test_file) + files = filesystem.files_under(path, _skipped_directories, _is_test_file) test_files.update(set(files)) gather_time = time.time() - gather_start_time @@ -88,10 +98,10 @@ def find(port, paths): return test_files -def _has_supported_extension(fs, filename): +def _has_supported_extension(filesystem, filename): """Return true if filename is one of the file extensions we want to run a test on.""" - extension = fs.splitext(filename)[1] + extension = filesystem.splitext(filename)[1] return extension in _supported_file_extensions @@ -104,7 +114,7 @@ def _is_reference_html_file(filename): return False -def _is_test_file(fs, dirname, filename): +def _is_test_file(filesystem, dirname, filename): """Return true if the filename points to a test file.""" - return (_has_supported_extension(fs, filename) and + return (_has_supported_extension(filesystem, filename) and not _is_reference_html_file(filename)) diff --git a/Tools/Scripts/webkitpy/layout_tests/port/test_files_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/test_files_unittest.py index a68950a..a29ba49 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/test_files_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/test_files_unittest.py @@ -26,44 +26,41 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import sys import unittest -import base +from webkitpy.layout_tests.port import test import test_files - class TestFilesTest(unittest.TestCase): def test_find_no_paths_specified(self): - port = base.Port() + port = test.TestPort() layout_tests_dir = port.layout_tests_dir() - port.layout_tests_dir = lambda: port._filesystem.join(layout_tests_dir, - 'fast', 'html') tests = test_files.find(port, []) - self.assertNotEqual(tests, 0) + self.assertNotEqual(len(tests), 0) def test_find_one_test(self): - port = base.Port() - # This is just a test picked at random but known to exist. - tests = test_files.find(port, ['fast/html/keygen.html']) + port = test.TestPort() + tests = test_files.find(port, ['failures/expected/image.html']) self.assertEqual(len(tests), 1) def test_find_glob(self): - port = base.Port() - tests = test_files.find(port, ['fast/html/key*']) - self.assertEqual(len(tests), 1) + port = test.TestPort() + tests = test_files.find(port, ['failures/expected/im*']) + self.assertEqual(len(tests), 2) def test_find_with_skipped_directories(self): - port = base.Port() + port = test.TestPort() tests = port.tests('userscripts') - self.assertTrue('userscripts/resources/frame1.html' not in tests) + self.assertTrue('userscripts/resources/iframe.html' not in tests) def test_find_with_skipped_directories_2(self): - port = base.Port() + port = test.TestPort() tests = test_files.find(port, ['userscripts/resources']) self.assertEqual(tests, set([])) def test_is_test_file(self): - port = base.Port() + port = test.TestPort() fs = port._filesystem self.assertTrue(test_files._is_test_file(fs, '', 'foo.html')) self.assertTrue(test_files._is_test_file(fs, '', 'foo.shtml')) @@ -72,5 +69,33 @@ class TestFilesTest(unittest.TestCase): self.assertFalse(test_files._is_test_file(fs, '', 'foo-expected-mismatch.html')) +class MockWinFileSystem(object): + def join(self, *paths): + return '\\'.join(paths) + + def normpath(self, path): + return path.replace('/', '\\') + + +class TestWinNormalize(unittest.TestCase): + def assert_filesystem_normalizes(self, filesystem): + self.assertEquals(test_files.normalize(filesystem, "c:\\foo", + ['fast/html', 'fast/canvas/*', 'compositing/foo.html']), + ['c:\\foo\\fast\html', 'c:\\foo\\fast\canvas\*', 'c:\\foo\compositing\\foo.html']) + + def test_mocked_win(self): + # This tests test_files.normalize, using portable behavior emulating + # what we think Windows is supposed to do. This test will run on all + # platforms. + self.assert_filesystem_normalizes(MockWinFileSystem()) + + def test_win(self): + # This tests the actual windows platform, to ensure we get the same + # results that we get in test_mocked_win(). + if sys.platform != 'win': + return + self.assert_filesystem_normalizes(FileSystem()) + + if __name__ == '__main__': unittest.main() diff --git a/Tools/Scripts/webkitpy/layout_tests/port/webkit.py b/Tools/Scripts/webkitpy/layout_tests/port/webkit.py index 577acd4..65a047d 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/webkit.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/webkit.py @@ -40,10 +40,9 @@ import sys import time import webbrowser -import webkitpy.common.system.ospath as ospath -import webkitpy.layout_tests.layout_package.test_output as test_output -import webkitpy.layout_tests.port.base as base -import webkitpy.layout_tests.port.server_process as server_process +from webkitpy.common.system import ospath +from webkitpy.layout_tests.port import base +from webkitpy.layout_tests.port import server_process _log = logging.getLogger("webkitpy.layout_tests.port.webkit") @@ -57,7 +56,8 @@ class WebKitPort(base.Port): # FIXME: disable pixel tests until they are run by default on the # build machines. - self.set_option_default('pixel_tests', False) + if not hasattr(self._options, "pixel_tests") or self._options.pixel_tests == None: + self._options.pixel_tests = False def baseline_path(self): return self._webkit_baseline_path(self._name) @@ -120,9 +120,9 @@ class WebKitPort(base.Port): return self._diff_image_reply(sp, diff_filename) def _diff_image_request(self, expected_contents, actual_contents): - # FIXME: use self.get_option('tolerance') and - # self.set_option_default('tolerance', 0.1) once that behaves correctly - # with default values. + # FIXME: There needs to be a more sane way of handling default + # values for options so that you can distinguish between a default + # value of None and a default value that wasn't set. if self.get_option('tolerance') is not None: tolerance = self.get_option('tolerance') else: @@ -159,7 +159,7 @@ class WebKitPort(base.Port): if m.group(2) == 'passed': result = False elif output and diff_filename: - self._filesystem.write_text_file(diff_filename, output) + self._filesystem.write_binary_file(diff_filename, output) elif sp.timed_out: _log.error("ImageDiff timed out") elif sp.crashed: @@ -179,11 +179,6 @@ class WebKitPort(base.Port): def create_driver(self, worker_number): return WebKitDriver(self, worker_number) - def test_base_platform_names(self): - # At the moment we don't use test platform names, but we have - # to return something. - return ('mac', 'win') - def _tests_for_other_platforms(self): raise NotImplementedError('WebKitPort._tests_for_other_platforms') # The original run-webkit-tests builds up a "whitelist" of tests to @@ -283,9 +278,9 @@ class WebKitPort(base.Port): unsupported_feature_tests = self._skipped_tests_for_unsupported_features() return disabled_feature_tests + webarchive_tests + unsupported_feature_tests - def _tests_from_skipped_file(self, skipped_file): + def _tests_from_skipped_file_contents(self, skipped_file_contents): tests_to_skip = [] - for line in skipped_file.readlines(): + for line in skipped_file_contents.split('\n'): line = line.strip() if line.startswith('#') or not len(line): continue @@ -301,7 +296,8 @@ class WebKitPort(base.Port): if not self._filesystem.exists(filename): _log.warn("Failed to open Skipped file: %s" % filename) continue - skipped_file = self._filesystem.read_text_file(filename) + skipped_file_contents = self._filesystem.read_text_file(filename) + tests_to_skip.extend(self._tests_from_skipped_file_contents(skipped_file_contents)) return tests_to_skip def test_expectations(self): @@ -335,8 +331,7 @@ class WebKitPort(base.Port): return self._name + self.version() def test_platform_names(self): - return self.test_base_platform_names() + ( - 'mac-tiger', 'mac-leopard', 'mac-snowleopard') + return ('mac', 'win', 'mac-tiger', 'mac-leopard', 'mac-snowleopard') def _build_path(self, *comps): return self._filesystem.join(self._config.build_directory( @@ -409,15 +404,15 @@ class WebKitDriver(base.Driver): return # FIXME: This function is huge. - def run_test(self, test_input): - uri = self._port.filename_to_uri(test_input.filename) + def run_test(self, driver_input): + uri = self._port.filename_to_uri(driver_input.filename) if uri.startswith("file:///"): command = uri[7:] else: command = uri - if test_input.image_hash: - command += "'" + test_input.image_hash + if driver_input.image_hash: + command += "'" + driver_input.image_hash command += "\n" start_time = time.time() @@ -428,7 +423,7 @@ class WebKitDriver(base.Driver): output = str() # Use a byte array for output, even though it should be UTF-8. image = str() - timeout = int(test_input.timeout) / 1000.0 + timeout = int(driver_input.timeout) / 1000.0 deadline = time.time() + timeout line = self._server_process.read_line(timeout) while (not self._server_process.timed_out @@ -475,11 +470,11 @@ class WebKitDriver(base.Driver): # FIXME: This seems like the wrong section of code to be doing # this reset in. self._server_process.error = "" - return test_output.TestOutput(output, image, actual_image_hash, - self._server_process.crashed, - time.time() - start_time, - self._server_process.timed_out, - error) + return base.DriverOutput(output, image, actual_image_hash, + self._server_process.crashed, + time.time() - start_time, + self._server_process.timed_out, + error) def stop(self): if self._server_process: diff --git a/Tools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py index 7b68310..c72a411 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py @@ -1,5 +1,6 @@ #!/usr/bin/env python # Copyright (C) 2010 Gabor Rapcsanyi , University of Szeged +# Copyright (C) 2010 Google Inc. All rights reserved. # # All rights reserved. # @@ -26,13 +27,19 @@ import unittest +from webkitpy.common.system import filesystem_mock + from webkitpy.layout_tests.port.webkit import WebKitPort class TestWebKitPort(WebKitPort): - def __init__(self, symbol_list=None, feature_list=None): + def __init__(self, symbol_list=None, feature_list=None, + expectations_file=None, skips_file=None, **kwargs): self.symbol_list = symbol_list self.feature_list = feature_list + self.expectations_file = expectations_file + self.skips_file = skips_file + WebKitPort.__init__(self, **kwargs) def _runtime_feature_list(self): return self.feature_list @@ -46,7 +53,14 @@ class TestWebKitPort(WebKitPort): def _tests_for_disabled_features(self): return ["accessibility", ] + def path_to_test_expectations_file(self): + if self.expectations_file: + return self.expectations_file + return WebKitPort.path_to_test_expectations_file(self) + def _skipped_file_paths(self): + if self.skips_file: + return [self.skips_file] return [] class WebKitPortTest(unittest.TestCase): @@ -66,3 +80,23 @@ class WebKitPortTest(unittest.TestCase): def test_skipped_layout_tests(self): self.assertEqual(TestWebKitPort(None, None).skipped_layout_tests(), set(["media", "accessibility"])) + + def test_test_expectations(self): + # Check that we read both the expectations file and anything in a + # Skipped file, and that we include the feature and platform checks. + files = { + '/tmp/test_expectations.txt': 'BUG_TESTEXPECTATIONS SKIP : fast/html/article-element.html = FAIL\n', + '/tmp/Skipped': 'fast/html/keygen.html', + } + mock_fs = filesystem_mock.MockFileSystem(files) + port = TestWebKitPort(expectations_file='/tmp/test_expectations.txt', + skips_file='/tmp/Skipped', filesystem=mock_fs) + self.assertEqual(port.test_expectations(), + """BUG_TESTEXPECTATIONS SKIP : fast/html/article-element.html = FAIL +BUG_SKIPPED SKIP : fast/html/keygen.html = FAIL +BUG_SKIPPED SKIP : media = FAIL +BUG_SKIPPED SKIP : accessibility = FAIL""") + + +if __name__ == '__main__': + unittest.main() diff --git a/Tools/Scripts/webkitpy/layout_tests/port/websocket_server.py b/Tools/Scripts/webkitpy/layout_tests/port/websocket_server.py index 926bc04..713ad21 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/websocket_server.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/websocket_server.py @@ -73,7 +73,7 @@ def url_is_alive(url): wait_time = 10 while wait_time > 0: try: - response = urllib.urlopen(url) + response = urllib.urlopen(url, proxies={}) # Server is up and responding. return True except IOError: diff --git a/Tools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py b/Tools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py index c852186..567975c 100644 --- a/Tools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py +++ b/Tools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py @@ -47,18 +47,17 @@ import optparse import re import sys import time -import urllib -import zipfile from webkitpy.common.checkout import scm +from webkitpy.common.system import zipfileset from webkitpy.common.system import path +from webkitpy.common.system import urlfetcher from webkitpy.common.system.executive import ScriptError -import port -from layout_package import test_expectations +from webkitpy.layout_tests import port +from webkitpy.layout_tests.layout_package import test_expectations -_log = logging.getLogger("webkitpy.layout_tests." - "rebaseline_chromium_webkit_tests") +_log = logging.getLogger(__name__) BASELINE_SUFFIXES = ['.txt', '.png', '.checksum'] REBASELINE_PLATFORM_ORDER = ['mac', 'win', 'win-xp', 'win-vista', 'linux'] @@ -142,7 +141,7 @@ class Rebaseliner(object): REVISION_REGEX = r'' - def __init__(self, running_port, target_port, platform, options): + def __init__(self, running_port, target_port, platform, options, url_fetcher, zip_factory, scm): """ Args: running_port: the Port the script is running on. @@ -150,14 +149,20 @@ class Rebaseliner(object): configuration information like the test_expectations.txt file location and the list of test platforms. platform: the test platform to rebaseline - options: the command-line options object.""" + options: the command-line options object. + url_fetcher: object that can fetch objects from URLs + zip_factory: optional object that can fetch zip files from URLs + scm: scm object for adding new baselines + """ self._platform = platform self._options = options self._port = running_port self._filesystem = running_port._filesystem self._target_port = target_port + self._rebaseline_port = port.get( - self._target_port.test_platform_name_to_name(platform), options) + self._target_port.test_platform_name_to_name(platform), options, + filesystem=self._filesystem) self._rebaselining_tests = [] self._rebaselined_tests = [] @@ -170,10 +175,11 @@ class Rebaseliner(object): test_expectations.TestExpectations(self._rebaseline_port, None, expectations_str, - self._platform, - False, + self._rebaseline_port.test_configuration(), False) - self._scm = scm.default_scm() + self._url_fetcher = url_fetcher + self._zip_factory = zip_factory + self._scm = scm def run(self, backup): """Run rebaseline process.""" @@ -192,8 +198,11 @@ class Rebaseliner(object): log_dashed_string('Extracting and adding new baselines', self._platform) if not self._extract_and_add_new_baselines(archive_file): + archive_file.close() return False + archive_file.close() + log_dashed_string('Updating rebaselined tests in file', self._platform) self._update_rebaselined_tests_in_file(backup) @@ -254,9 +263,7 @@ class Rebaseliner(object): _log.debug('Url to retrieve revision: "%s"', url) - f = urllib.urlopen(url) - content = f.read() - f.close() + content = self._url_fetcher.fetch(url) revisions = re.findall(self.REVISION_REGEX, content) if not revisions: @@ -313,33 +320,24 @@ class Rebaseliner(object): return archive_url def _download_buildbot_archive(self): - """Download layout test archive file from buildbot. - - Returns: - True if download succeeded or - False otherwise. - """ - + """Download layout test archive file from buildbot and return a handle to it.""" url = self._get_archive_url() if url is None: return None - fn = urllib.urlretrieve(url)[0] - _log.info('Archive downloaded and saved to file: "%s"', fn) - return fn + archive_file = zipfileset.ZipFileSet(url, filesystem=self._filesystem, + zip_factory=self._zip_factory) + _log.info('Archive downloaded') + return archive_file - def _extract_and_add_new_baselines(self, archive_file): - """Extract new baselines from archive and add them to SVN repository. - - Args: - archive_file: full path to the archive file. + def _extract_and_add_new_baselines(self, zip_file): + """Extract new baselines from the zip file and add them to SVN repository. Returns: List of tests that have been rebaselined or None on failure. """ - zip_file = zipfile.ZipFile(archive_file, 'r') zip_namelist = zip_file.namelist() _log.debug('zip file namelist:') @@ -419,7 +417,6 @@ class Rebaseliner(object): test_no += 1 zip_file.close() - self._filesystem.remove(archive_file) return self._rebaselined_tests @@ -857,18 +854,9 @@ def parse_options(args): return (options, target_options) -def main(): - """Main function to produce new baselines.""" - - (options, target_options) = parse_options(sys.argv[1:]) - - # We need to create three different Port objects over the life of this - # script. |target_port_obj| is used to determine configuration information: - # location of the expectations file, names of ports to rebaseline, etc. - # |port_obj| is used for runtime functionality like actually diffing - # Then we create a rebaselining port to actual find and manage the - # baselines. - target_port_obj = port.get(None, target_options) +def main(args): + """Bootstrap function that sets up the object references we need and calls real_main().""" + options, target_options = parse_options(args) # Set up our logging format. log_level = logging.INFO @@ -879,20 +867,53 @@ def main(): '%(levelname)s %(message)s'), datefmt='%y%m%d %H:%M:%S') + target_port_obj = port.get(None, target_options) host_port_obj = get_host_port_object(options) - if not host_port_obj: - sys.exit(1) + if not host_port_obj or not target_port_obj: + return 1 + + url_fetcher = urlfetcher.UrlFetcher(host_port_obj._filesystem) + scm_obj = scm.default_scm() + + # We use the default zip factory method. + zip_factory = None + + return real_main(options, target_options, host_port_obj, target_port_obj, url_fetcher, + zip_factory, scm_obj) + +def real_main(options, target_options, host_port_obj, target_port_obj, url_fetcher, + zip_factory, scm_obj): + """Main function to produce new baselines. The Rebaseliner object uses two + different Port objects - one to represent the machine the object is running + on, and one to represent the port whose expectations are being updated. + E.g., you can run the script on a mac and rebaseline the 'win' port. + + Args: + options: command-line argument used for the host_port_obj (see below) + target_options: command_line argument used for the target_port_obj. + This object may have slightly different values than |options|. + host_port_obj: a Port object for the platform the script is running + on. This is used to produce image and text diffs, mostly, and + is usually acquired from get_host_port_obj(). + target_port_obj: a Port obj representing the port getting rebaselined. + This is used to find the expectations file, the baseline paths, + etc. + url_fetcher: object used to download the build archives from the bots + zip_factory: factory function used to create zip file objects for + the archives. + scm_obj: object used to add new baselines to the source control system. + """ # Verify 'platforms' option is valid. if not options.platforms: _log.error('Invalid "platforms" option. --platforms must be ' 'specified in order to rebaseline.') - sys.exit(1) + return 1 platforms = [p.strip().lower() for p in options.platforms.split(',')] for platform in platforms: if not platform in REBASELINE_PLATFORM_ORDER: _log.error('Invalid platform: "%s"' % (platform)) - sys.exit(1) + return 1 # Adjust the platform order so rebaseline tool is running at the order of # 'mac', 'win' and 'linux'. This is in same order with layout test baseline @@ -909,7 +930,8 @@ def main(): backup = options.backup for platform in rebaseline_platforms: rebaseliner = Rebaseliner(host_port_obj, target_port_obj, - platform, options) + platform, options, url_fetcher, zip_factory, + scm_obj) _log.info('') log_dashed_string('Rebaseline started', platform) @@ -934,7 +956,8 @@ def main(): html_generator.show_html() log_dashed_string('Rebaselining result comparison done', None) - sys.exit(0) + return 0 + if '__main__' == __name__: - main() + sys.exit(main(sys.argv[1:])) diff --git a/Tools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py b/Tools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py index 50c0204..730220b 100644 --- a/Tools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py @@ -32,11 +32,14 @@ import unittest from webkitpy.tool import mocktool +from webkitpy.common.system import urlfetcher_mock from webkitpy.common.system import filesystem_mock +from webkitpy.common.system import zipfileset_mock +from webkitpy.common.system import outputcapture from webkitpy.common.system.executive import Executive, ScriptError -import port -import rebaseline_chromium_webkit_tests +from webkitpy.layout_tests import port +from webkitpy.layout_tests import rebaseline_chromium_webkit_tests class MockPort(object): @@ -53,6 +56,57 @@ def get_mock_get(config_expectations): return mock_get +ARCHIVE_URL = 'http://localhost/layout_test_results' + + +def test_options(): + return mocktool.MockOptions(configuration=None, + backup=False, + html_directory='/tmp', + archive_url=ARCHIVE_URL, + force_archive_url=None, + webkit_canary=True, + use_drt=False, + target_platform='chromium', + verbose=False, + quiet=False, + platforms='mac,win,win-xp') + + +def test_host_port_and_filesystem(options, expectations): + filesystem = port.unit_test_filesystem() + host_port_obj = port.get('test', options, filesystem=filesystem, + user=mocktool.MockUser()) + + expectations_path = host_port_obj.path_to_test_expectations_file() + filesystem.write_text_file(expectations_path, expectations) + return (host_port_obj, filesystem) + + +def test_url_fetcher(filesystem): + urls = { + ARCHIVE_URL + '/Webkit_Mac10_5/': '', + ARCHIVE_URL + '/Webkit_Win/': '', + } + return urlfetcher_mock.make_fetcher_cls(urls)(filesystem) + + +def test_zip_factory(): + ziphashes = { + ARCHIVE_URL + '/Webkit_Mac10_5/2/layout-test-results.zip': { + 'layout-test-results/failures/expected/image-actual.txt': 'new-image-txt', + 'layout-test-results/failures/expected/image-actual.checksum': 'new-image-checksum', + 'layout-test-results/failures/expected/image-actual.png': 'new-image-png', + }, + ARCHIVE_URL + '/Webkit_Win/1/layout-test-results.zip': { + 'layout-test-results/failures/expected/image-actual.txt': 'win-image-txt', + 'layout-test-results/failures/expected/image-actual.checksum': 'win-image-checksum', + 'layout-test-results/failures/expected/image-actual.png': 'win-image-png', + }, + } + return zipfileset_mock.make_factory(ziphashes) + + class TestGetHostPortObject(unittest.TestCase): def assert_result(self, release_present, debug_present, valid_port_obj): # Tests whether we get a valid port object returned when we claim @@ -60,9 +114,8 @@ class TestGetHostPortObject(unittest.TestCase): port.get = get_mock_get({'Release': release_present, 'Debug': debug_present}) options = mocktool.MockOptions(configuration=None, - html_directory=None) - port_obj = rebaseline_chromium_webkit_tests.get_host_port_object( - options) + html_directory='/tmp') + port_obj = rebaseline_chromium_webkit_tests.get_host_port_object(options) if valid_port_obj: self.assertNotEqual(port_obj, None) else: @@ -84,18 +137,7 @@ class TestGetHostPortObject(unittest.TestCase): port.get = old_get -class TestRebaseliner(unittest.TestCase): - def make_rebaseliner(self): - options = mocktool.MockOptions(configuration=None, - html_directory=None) - filesystem = filesystem_mock.MockFileSystem() - host_port_obj = port.get('test', options, filesystem=filesystem) - target_options = options - target_port_obj = port.get('test', target_options, filesystem=filesystem) - platform = 'test' - return rebaseline_chromium_webkit_tests.Rebaseliner( - host_port_obj, target_port_obj, platform, options) - +class TestOptions(unittest.TestCase): def test_parse_options(self): (options, target_options) = rebaseline_chromium_webkit_tests.parse_options([]) self.assertTrue(target_options.chromium) @@ -105,39 +147,113 @@ class TestRebaseliner(unittest.TestCase): self.assertFalse(hasattr(target_options, 'chromium')) self.assertEqual(options.tolerance, 0) + +class TestRebaseliner(unittest.TestCase): + def make_rebaseliner(self, expectations): + options = test_options() + host_port_obj, filesystem = test_host_port_and_filesystem(options, expectations) + + target_options = options + target_port_obj = port.get('test', target_options, + filesystem=filesystem) + target_port_obj._expectations = expectations + platform = target_port_obj.test_platform_name() + + url_fetcher = test_url_fetcher(filesystem) + zip_factory = test_zip_factory() + mock_scm = mocktool.MockSCM() + rebaseliner = rebaseline_chromium_webkit_tests.Rebaseliner(host_port_obj, + target_port_obj, platform, options, url_fetcher, zip_factory, mock_scm) + return rebaseliner, filesystem + def test_noop(self): # this method tests that was can at least instantiate an object, even # if there is nothing to do. - rebaseliner = self.make_rebaseliner() - self.assertNotEqual(rebaseliner, None) + rebaseliner, filesystem = self.make_rebaseliner("") + rebaseliner.run(False) + self.assertEqual(len(filesystem.written_files), 1) + + def test_one_platform(self): + rebaseliner, filesystem = self.make_rebaseliner( + "BUGX REBASELINE MAC : failures/expected/image.html = IMAGE") + rebaseliner.run(False) + # We expect to have written 12 files over the course of this rebaseline: + # *) 3 files in /__im_tmp for the extracted archive members + # *) 3 new baselines under '/test.checkout/LayoutTests' + # *) 4 files in /tmp for the new and old baselines in the result file + # (-{old,new}.{txt,png} + # *) 1 text diff in /tmp for the result file (-diff.txt). We don't + # create image diffs (FIXME?) and don't display the checksums. + # *) 1 updated test_expectations file + self.assertEqual(len(filesystem.written_files), 12) + self.assertEqual(filesystem.files['/test.checkout/LayoutTests/platform/test/test_expectations.txt'], '') + self.assertEqual(filesystem.files['/test.checkout/LayoutTests/platform/test-mac/failures/expected/image-expected.checksum'], 'new-image-checksum') + self.assertEqual(filesystem.files['/test.checkout/LayoutTests/platform/test-mac/failures/expected/image-expected.png'], 'new-image-png') + self.assertEqual(filesystem.files['/test.checkout/LayoutTests/platform/test-mac/failures/expected/image-expected.txt'], 'new-image-txt') + + def test_all_platforms(self): + rebaseliner, filesystem = self.make_rebaseliner( + "BUGX REBASELINE : failures/expected/image.html = IMAGE") + rebaseliner.run(False) + # See comment in test_one_platform for an explanation of the 12 written tests. + # Note that even though the rebaseline is marked for all platforms, each + # rebaseliner only ever does one. + self.assertEqual(len(filesystem.written_files), 12) + self.assertEqual(filesystem.files['/test.checkout/LayoutTests/platform/test/test_expectations.txt'], + 'BUGX REBASELINE WIN : failures/expected/image.html = IMAGE\n' + 'BUGX REBASELINE WIN-XP : failures/expected/image.html = IMAGE\n') + self.assertEqual(filesystem.files['/test.checkout/LayoutTests/platform/test-mac/failures/expected/image-expected.checksum'], 'new-image-checksum') + self.assertEqual(filesystem.files['/test.checkout/LayoutTests/platform/test-mac/failures/expected/image-expected.png'], 'new-image-png') + self.assertEqual(filesystem.files['/test.checkout/LayoutTests/platform/test-mac/failures/expected/image-expected.txt'], 'new-image-txt') def test_diff_baselines_txt(self): - rebaseliner = self.make_rebaseliner() - output = rebaseliner._port.expected_text( - rebaseliner._port._filesystem.join(rebaseliner._port.layout_tests_dir(), - 'passes/text.html')) + rebaseliner, filesystem = self.make_rebaseliner("") + port = rebaseliner._port + output = port.expected_text( + port._filesystem.join(port.layout_tests_dir(), 'passes/text.html')) self.assertFalse(rebaseliner._diff_baselines(output, output, is_image=False)) def test_diff_baselines_png(self): - rebaseliner = self.make_rebaseliner() - image = rebaseliner._port.expected_image( - rebaseliner._port._filesystem.join(rebaseliner._port.layout_tests_dir(), - 'passes/image.html')) + rebaseliner, filesystem = self.make_rebaseliner('') + port = rebaseliner._port + image = port.expected_image( + port._filesystem.join(port.layout_tests_dir(), 'passes/image.html')) self.assertFalse(rebaseliner._diff_baselines(image, image, is_image=True)) +class TestRealMain(unittest.TestCase): + def test_all_platforms(self): + expectations = "BUGX REBASELINE : failures/expected/image.html = IMAGE" + + options = test_options() + + host_port_obj, filesystem = test_host_port_and_filesystem(options, expectations) + url_fetcher = test_url_fetcher(filesystem) + zip_factory = test_zip_factory() + mock_scm = mocktool.MockSCM() + oc = outputcapture.OutputCapture() + oc.capture_output() + rebaseline_chromium_webkit_tests.real_main(options, options, host_port_obj, + host_port_obj, url_fetcher, zip_factory, mock_scm) + oc.restore_output() + + # We expect to have written 35 files over the course of this rebaseline: + # *) 11 files * 3 ports for the new baselines and the diffs (see breakdown + # under test_one_platform, above) + # *) the updated test_expectations file + # *) the rebaseline results html file + self.assertEqual(len(filesystem.written_files), 35) + self.assertEqual(filesystem.files['/test.checkout/LayoutTests/platform/test/test_expectations.txt'], '') + + class TestHtmlGenerator(unittest.TestCase): def make_generator(self, files, tests): options = mocktool.MockOptions(configuration=None, html_directory='/tmp') - host_port = port.get('test', options, filesystem=filesystem_mock.MockFileSystem(files)) - generator = rebaseline_chromium_webkit_tests.HtmlGenerator( - host_port, - target_port=None, - options=options, - platforms=['mac'], - rebaselining_tests=tests) + host_port = port.get('test', options, filesystem=port.unit_test_filesystem(files)) + generator = rebaseline_chromium_webkit_tests.HtmlGenerator(host_port, + target_port=None, options=options, platforms=['mac'], rebaselining_tests=tests) return generator, host_port def test_generate_baseline_links(self): diff --git a/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py b/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py index 17b6e89..2d55b93 100755 --- a/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py +++ b/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py @@ -30,8 +30,6 @@ """Run layout tests.""" -from __future__ import with_statement - import errno import logging import optparse @@ -41,6 +39,7 @@ import sys from layout_package import printing from layout_package import test_runner +from layout_package import test_runner2 from webkitpy.common.system import user from webkitpy.thirdparty import simplejson @@ -89,7 +88,11 @@ def run(port, options, args, regular_output=sys.stderr, # in a try/finally to ensure that we clean up the logging configuration. num_unexpected_results = -1 try: - runner = test_runner.TestRunner(port, options, printer) + if options.worker_model in ('inline', 'threads', 'processes'): + runner = test_runner2.TestRunner2(port, options, printer) + else: + runner = test_runner.TestRunner(port, options, printer) + runner._print_config() printer.print_update("Collecting tests ...") @@ -100,11 +103,11 @@ def run(port, options, args, regular_output=sys.stderr, return -1 raise - printer.print_update("Parsing expectations ...") if options.lint_test_files: return runner.lint() - runner.parse_expectations(port.test_platform_name(), - options.configuration == 'Debug') + + printer.print_update("Parsing expectations ...") + runner.parse_expectations() printer.print_update("Checking build ...") if not port.check_build(runner.needs_http()): @@ -128,9 +131,12 @@ def _set_up_derived_options(port_obj, options): # We return a list of warnings to print after the printer is initialized. warnings = [] - if options.worker_model == 'old-inline': + if options.worker_model is None: + options.worker_model = port_obj.default_worker_model() + + if options.worker_model in ('inline', 'old-inline'): if options.child_processes and int(options.child_processes) > 1: - warnings.append("--worker-model=old-inline overrides --child-processes") + warnings.append("--worker-model=%s overrides --child-processes" % options.worker_model) options.child_processes = "1" if not options.child_processes: options.child_processes = os.environ.get("WEBKIT_TEST_CHILD_PROCESSES", @@ -226,9 +232,6 @@ def parse_args(args=None): optparse.make_option("--nocheck-sys-deps", action="store_true", default=False, help="Don't check the system dependencies (themes)"), - optparse.make_option("--use-test-shell", action="store_true", - default=False, - help="Use test_shell instead of DRT"), optparse.make_option("--accelerated-compositing", action="store_true", help="Use hardware-accelated compositing for rendering"), @@ -368,8 +371,8 @@ def parse_args(args=None): help="Number of DumpRenderTrees to run in parallel."), # FIXME: Display default number of child processes that will run. optparse.make_option("--worker-model", action="store", - default="old-threads", help=("controls worker model. Valid values " - "are 'old-inline', 'old-threads'.")), + default=None, help=("controls worker model. Valid values are 'old-inline', " + "'old-threads', 'inline', 'threads', and 'processes'.")), optparse.make_option("--experimental-fully-parallel", action="store_true", default=False, help="run all tests in parallel"), @@ -415,10 +418,6 @@ def parse_args(args=None): optparse.make_option("--test-results-server", default="", help=("If specified, upload results json files to this appengine " "server.")), - optparse.make_option("--upload-full-results", - action="store_true", - default=False, - help="If true, upload full json results to server."), ] option_list = (configuration_options + print_options + diff --git a/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py b/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py index 677becd..84f5718 100644 --- a/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py @@ -45,7 +45,7 @@ import unittest from webkitpy.common import array_stream from webkitpy.common.system import outputcapture from webkitpy.common.system import filesystem_mock -from webkitpy.common.system import user +from webkitpy.tool import mocktool from webkitpy.layout_tests import port from webkitpy.layout_tests import run_webkit_tests from webkitpy.layout_tests.layout_package import dump_render_tree_thread @@ -56,14 +56,6 @@ from webkitpy.test.skip import skip_if from webkitpy.thirdparty.mock import Mock -class MockUser(): - def __init__(self): - self.url = None - - def open_url(self, url): - self.url = url - - def parse_args(extra_args=None, record_results=False, tests_included=False, print_nothing=True): extra_args = extra_args or [] @@ -93,7 +85,7 @@ def passing_run(extra_args=None, port_obj=None, record_results=False, tests_included) if not port_obj: port_obj = port.get(port_name=options.platform, options=options, - user=MockUser(), filesystem=filesystem) + user=mocktool.MockUser(), filesystem=filesystem) res = run_webkit_tests.run(port_obj, options, parsed_args) return res == 0 @@ -103,7 +95,7 @@ def logging_run(extra_args=None, port_obj=None, record_results=False, tests_incl record_results=record_results, tests_included=tests_included, print_nothing=False) - user = MockUser() + user = mocktool.MockUser() if not port_obj: port_obj = port.get(port_name=options.platform, options=options, user=user, filesystem=filesystem) @@ -135,7 +127,7 @@ def get_tests_run(extra_args=None, tests_included=False, flatten_batches=False, extra_args = ['passes', 'failures'] + extra_args options, parsed_args = parse_args(extra_args, tests_included=True) - user = MockUser() + user = mocktool.MockUser() test_batches = [] @@ -216,7 +208,8 @@ class MainTest(unittest.TestCase): def test_full_results_html(self): # FIXME: verify html? - self.assertTrue(passing_run(['--full-results-html'])) + res, out, err, user = logging_run(['--full-results-html']) + self.assertEqual(res, 0) def test_help_printing(self): res, out, err, user = logging_run(['--help-printing']) @@ -256,7 +249,7 @@ class MainTest(unittest.TestCase): def test_lint_test_files__errors(self): options, parsed_args = parse_args(['--lint-test-files']) - user = MockUser() + user = mocktool.MockUser() port_obj = port.get(options.platform, options=options, user=user) port_obj.test_expectations = lambda: "# syntax error" res, out, err = run_and_capture(port_obj, options, parsed_args) @@ -352,7 +345,7 @@ class MainTest(unittest.TestCase): self.assertEqual(res, 3) self.assertFalse(out.empty()) self.assertFalse(err.empty()) - self.assertEqual(user.url, '/tmp/layout-test-results/results.html') + self.assertEqual(user.opened_urls, ['/tmp/layout-test-results/results.html']) def test_exit_after_n_failures(self): # Unexpected failures should result in tests stopping. @@ -414,7 +407,7 @@ class MainTest(unittest.TestCase): with fs.mkdtemp() as tmpdir: res, out, err, user = logging_run(['--results-directory=' + str(tmpdir)], tests_included=True, filesystem=fs) - self.assertEqual(user.url, fs.join(tmpdir, 'results.html')) + self.assertEqual(user.opened_urls, [fs.join(tmpdir, 'results.html')]) def test_results_directory_default(self): # We run a configuration that should fail, to generate output, then @@ -422,7 +415,7 @@ class MainTest(unittest.TestCase): # This is the default location. res, out, err, user = logging_run(tests_included=True) - self.assertEqual(user.url, '/tmp/layout-test-results/results.html') + self.assertEqual(user.opened_urls, ['/tmp/layout-test-results/results.html']) def test_results_directory_relative(self): # We run a configuration that should fail, to generate output, then @@ -430,7 +423,7 @@ class MainTest(unittest.TestCase): res, out, err, user = logging_run(['--results-directory=foo'], tests_included=True) - self.assertEqual(user.url, '/tmp/foo/results.html') + self.assertEqual(user.opened_urls, ['/tmp/foo/results.html']) def test_tolerance(self): class ImageDiffTestPort(TestPort): @@ -441,7 +434,7 @@ class MainTest(unittest.TestCase): def get_port_for_run(args): options, parsed_args = run_webkit_tests.parse_args(args) - test_port = ImageDiffTestPort(options=options, user=MockUser()) + test_port = ImageDiffTestPort(options=options, user=mocktool.MockUser()) passing_run(args, port_obj=test_port, tests_included=True) return test_port @@ -459,11 +452,27 @@ class MainTest(unittest.TestCase): self.assertEqual(None, test_port.tolerance_used_for_diff_image) def test_worker_model__inline(self): + self.assertTrue(passing_run(['--worker-model', 'inline'])) + + def test_worker_model__old_inline_with_child_processes(self): + res, out, err, user = logging_run(['--worker-model', 'old-inline', + '--child-processes', '2']) + self.assertEqual(res, 0) + self.assertTrue('--worker-model=old-inline overrides --child-processes\n' in err.get()) + + def test_worker_model__old_inline(self): self.assertTrue(passing_run(['--worker-model', 'old-inline'])) - def test_worker_model__threads(self): + def test_worker_model__old_threads(self): self.assertTrue(passing_run(['--worker-model', 'old-threads'])) + def test_worker_model__processes(self): + if compare_version(sys, '2.6')[0] >= 0: + self.assertTrue(passing_run(['--worker-model', 'processes'])) + + def test_worker_model__threads(self): + self.assertTrue(passing_run(['--worker-model', 'threads'])) + def test_worker_model__unknown(self): self.assertRaises(ValueError, logging_run, ['--worker-model', 'unknown']) @@ -491,7 +500,7 @@ class RebaselineTest(unittest.TestCase): 'failures/expected/missing_image.html'], tests_included=True, filesystem=fs) file_list = fs.written_files.keys() - file_list.remove('/tmp/layout-test-results/tests_run.txt') + file_list.remove('/tmp/layout-test-results/tests_run0.txt') self.assertEqual(len(file_list), 6) self.assertBaselines(file_list, "/passes/image") @@ -508,12 +517,12 @@ class RebaselineTest(unittest.TestCase): 'failures/expected/missing_image.html'], tests_included=True, filesystem=fs) file_list = fs.written_files.keys() - file_list.remove('/tmp/layout-test-results/tests_run.txt') + file_list.remove('/tmp/layout-test-results/tests_run0.txt') self.assertEqual(len(file_list), 6) self.assertBaselines(file_list, - "/platform/test/passes/image") + "/platform/test-mac/passes/image") self.assertBaselines(file_list, - "/platform/test/failures/expected/missing_image") + "/platform/test-mac/failures/expected/missing_image") class DryrunTest(unittest.TestCase): diff --git a/Tools/Scripts/webkitpy/layout_tests/test_types/image_diff.py b/Tools/Scripts/webkitpy/layout_tests/test_types/image_diff.py index 44605d2..1d7e107 100644 --- a/Tools/Scripts/webkitpy/layout_tests/test_types/image_diff.py +++ b/Tools/Scripts/webkitpy/layout_tests/test_types/image_diff.py @@ -49,23 +49,6 @@ _log = logging.getLogger("webkitpy.layout_tests.test_types.image_diff") class ImageDiff(test_type_base.TestTypeBase): - def _save_baseline_files(self, filename, image, image_hash, - generate_new_baseline): - """Saves new baselines for the PNG and checksum. - - Args: - filename: test filename - image: a image output - image_hash: a checksum of the image - generate_new_baseline: whether to generate a new, platform-specific - baseline, or update the existing one - """ - self._save_baseline_data(filename, image, ".png", encoding=None, - generate_new_baseline=generate_new_baseline) - self._save_baseline_data(filename, image_hash, ".checksum", - encoding="ascii", - generate_new_baseline=generate_new_baseline) - def _copy_image(self, filename, actual_image, expected_image): self.write_output_files(filename, '.png', output=actual_image, expected=expected_image, @@ -85,54 +68,47 @@ class ImageDiff(test_type_base.TestTypeBase): self.FILENAME_SUFFIX_COMPARE) return port.diff_image(actual_image, expected_image, diff_filename) - def compare_output(self, port, filename, test_args, actual_test_output, - expected_test_output): + def compare_output(self, port, filename, options, actual_driver_output, + expected_driver_output): """Implementation of CompareOutput that checks the output image and checksum against the expected files from the LayoutTest directory. """ failures = [] # If we didn't produce a hash file, this test must be text-only. - if actual_test_output.image_hash is None: - return failures - - # If we're generating a new baseline, we pass. - if test_args.new_baseline or test_args.reset_results: - self._save_baseline_files(filename, actual_test_output.image, - actual_test_output.image_hash, - test_args.new_baseline) + if actual_driver_output.image_hash is None: return failures - if not expected_test_output.image: + if not expected_driver_output.image: # Report a missing expected PNG file. - self._copy_image(filename, actual_test_output.image, expected_image=None) - self._copy_image_hash(filename, actual_test_output.image_hash, - expected_test_output.image_hash) + self._copy_image(filename, actual_driver_output.image, expected_image=None) + self._copy_image_hash(filename, actual_driver_output.image_hash, + expected_driver_output.image_hash) failures.append(test_failures.FailureMissingImage()) return failures - if not expected_test_output.image_hash: + if not expected_driver_output.image_hash: # Report a missing expected checksum file. - self._copy_image(filename, actual_test_output.image, - expected_test_output.image) - self._copy_image_hash(filename, actual_test_output.image_hash, + self._copy_image(filename, actual_driver_output.image, + expected_driver_output.image) + self._copy_image_hash(filename, actual_driver_output.image_hash, expected_image_hash=None) failures.append(test_failures.FailureMissingImageHash()) return failures - if actual_test_output.image_hash == expected_test_output.image_hash: + if actual_driver_output.image_hash == expected_driver_output.image_hash: # Hash matched (no diff needed, okay to return). return failures - self._copy_image(filename, actual_test_output.image, - expected_test_output.image) - self._copy_image_hash(filename, actual_test_output.image_hash, - expected_test_output.image_hash) + self._copy_image(filename, actual_driver_output.image, + expected_driver_output.image) + self._copy_image_hash(filename, actual_driver_output.image_hash, + expected_driver_output.image_hash) # Even though we only use the result in one codepath below but we # still need to call CreateImageDiff for other codepaths. images_are_different = self._create_diff_image(port, filename, - actual_test_output.image, - expected_test_output.image) + actual_driver_output.image, + expected_driver_output.image) if not images_are_different: failures.append(test_failures.FailureImageHashIncorrect()) else: diff --git a/Tools/Scripts/webkitpy/layout_tests/test_types/test_type_base.py b/Tools/Scripts/webkitpy/layout_tests/test_types/test_type_base.py index ad65016..09bfc31 100644 --- a/Tools/Scripts/webkitpy/layout_tests/test_types/test_type_base.py +++ b/Tools/Scripts/webkitpy/layout_tests/test_types/test_type_base.py @@ -28,8 +28,6 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Defines the interface TestTypeBase which other test types inherit from. - -Also defines the TestArguments "struct" to pass them additional arguments. """ import cgi @@ -39,21 +37,6 @@ import logging _log = logging.getLogger("webkitpy.layout_tests.test_types.test_type_base") -class TestArguments(object): - """Struct-like wrapper for additional arguments needed by - specific tests.""" - # Whether to save new baseline results. - new_baseline = False - - # Path to the actual PNG file generated by pixel tests - png_path = None - - # Value of checksum generated by pixel tests. - hash = None - - # Whether to use wdiff to generate by-word diffs. - wdiff = False - # Python bug workaround. See the wdiff code in WriteOutputFiles for an # explanation. _wdiff_available = True @@ -87,39 +70,6 @@ class TestTypeBase(object): self._port.relative_test_filename(filename)) fs.maybe_make_directory(fs.dirname(output_filename)) - def _save_baseline_data(self, filename, data, modifier, encoding, - generate_new_baseline=True): - """Saves a new baseline file into the port's baseline directory. - - The file will be named simply "-expected", suitable for - use as the expected results in a later run. - - Args: - filename: path to the test file - data: result to be saved as the new baseline - modifier: type of the result file, e.g. ".txt" or ".png" - encoding: file encoding (none, "utf-8", etc.) - generate_new_baseline: whether to enerate a new, platform-specific - baseline, or update the existing one - """ - - port = self._port - fs = self._port._filesystem - if generate_new_baseline: - relative_dir = fs.dirname(port.relative_test_filename(filename)) - baseline_path = port.baseline_path() - output_dir = fs.join(baseline_path, relative_dir) - output_file = fs.basename(fs.splitext(filename)[0] + - self.FILENAME_SUFFIX_EXPECTED + modifier) - fs.maybe_make_directory(output_dir) - output_path = fs.join(output_dir, output_file) - _log.debug('writing new baseline result "%s"' % (output_path)) - else: - output_path = port.expected_filename(filename, modifier) - _log.debug('resetting baseline result "%s"' % output_path) - - port.update_baseline(output_path, data, encoding) - def output_filename(self, filename, modifier): """Returns a filename inside the output dir that contains modifier. @@ -139,8 +89,8 @@ class TestTypeBase(object): self._port.relative_test_filename(filename)) return fs.splitext(output_filename)[0] + modifier - def compare_output(self, port, filename, test_args, actual_test_output, - expected_test_output): + def compare_output(self, port, filename, options, actual_driver_output, + expected_driver_output): """Method that compares the output from the test with the expected value. @@ -149,12 +99,11 @@ class TestTypeBase(object): Args: port: object implementing port-specific information and methods filename: absolute filename to test file - test_args: a TestArguments object holding optional additional - arguments - actual_test_output: a TestOutput object which represents actual test + options: command line argument object from optparse + actual_driver_output: a DriverOutput object which represents actual test output - expected_test_output: a TestOutput object which represents a expected - test output + expected_driver_output: a ExpectedDriverOutput object which represents a + expected test output Return: a list of TestFailure objects, empty if the test passes diff --git a/Tools/Scripts/webkitpy/layout_tests/test_types/test_type_base_unittest.py b/Tools/Scripts/webkitpy/layout_tests/test_types/test_type_base_unittest.py index 5dbfcb6..7af4d9c 100644 --- a/Tools/Scripts/webkitpy/layout_tests/test_types/test_type_base_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/test_types/test_type_base_unittest.py @@ -40,7 +40,7 @@ class Test(unittest.TestCase): test_type = test_type_base.TestTypeBase(None, None) self.assertRaises(NotImplementedError, test_type.compare_output, None, "foo.txt", '', - test_type_base.TestArguments(), 'Debug') + {}, 'Debug') if __name__ == '__main__': diff --git a/Tools/Scripts/webkitpy/layout_tests/test_types/text_diff.py b/Tools/Scripts/webkitpy/layout_tests/test_types/text_diff.py index 7b7febe..07c3d03 100644 --- a/Tools/Scripts/webkitpy/layout_tests/test_types/text_diff.py +++ b/Tools/Scripts/webkitpy/layout_tests/test_types/text_diff.py @@ -53,26 +53,16 @@ class TestTextDiff(test_type_base.TestTypeBase): # the normalized text expectation files. return output.replace("\r\r\n", "\r\n").replace("\r\n", "\n") - def compare_output(self, port, filename, test_args, actual_test_output, - expected_test_output): + def compare_output(self, port, filename, options, actual_driver_output, + expected_driver_output): """Implementation of CompareOutput that checks the output text against the expected text from the LayoutTest directory.""" failures = [] - # If we're generating a new baseline, we pass. - if test_args.new_baseline or test_args.reset_results: - # Although all test_shell/DumpRenderTree output should be utf-8, - # we do not ever decode it inside run-webkit-tests. For some tests - # DumpRenderTree may not output utf-8 text (e.g. webarchives). - self._save_baseline_data(filename, actual_test_output.text, - ".txt", encoding=None, - generate_new_baseline=test_args.new_baseline) - return failures - # Normalize text to diff - actual_text = self._get_normalized_output_text(actual_test_output.text) + actual_text = self._get_normalized_output_text(actual_driver_output.text) # Assuming expected_text is already normalized. - expected_text = expected_test_output.text + expected_text = expected_driver_output.text # Write output files for new tests, too. if port.compare_text(actual_text, expected_text): diff --git a/Tools/Scripts/webkitpy/layout_tests/update_webgl_conformance_tests.py b/Tools/Scripts/webkitpy/layout_tests/update_webgl_conformance_tests.py index f4c8098..7267aa6 100755 --- a/Tools/Scripts/webkitpy/layout_tests/update_webgl_conformance_tests.py +++ b/Tools/Scripts/webkitpy/layout_tests/update_webgl_conformance_tests.py @@ -31,7 +31,7 @@ import optparse import os import re import sys -import webkitpy.common.checkout.scm as scm +from webkitpy.common.checkout import scm _log = logging.getLogger("webkitpy.layout_tests." "update-webgl-conformance-tests") diff --git a/Tools/Scripts/webkitpy/style/checker.py b/Tools/Scripts/webkitpy/style/checker.py index ebcf326..23b9ab3 100644 --- a/Tools/Scripts/webkitpy/style/checker.py +++ b/Tools/Scripts/webkitpy/style/checker.py @@ -129,17 +129,20 @@ _PATH_RULES_SPECIFIER = [ ([# The EFL APIs use EFL naming style, which includes # both lower-cased and camel-cased, underscore-sparated # values. - "WebKit/efl/ewk/", + "Source/WebKit/efl/ewk/", # There is no clean way to avoid "yy_*" names used by flex. "Source/WebCore/css/CSSParser.cpp", # Qt code uses '_' in some places (such as private slots # and on test xxx_data methos on tests) "Source/JavaScriptCore/qt/", - "WebKit/qt/Api/", - "WebKit/qt/tests/", - "WebKit/qt/declarative/", - "WebKit/qt/examples/"], + "Source/WebKit/qt/Api/", + "Source/WebKit/qt/tests/", + "Source/WebKit/qt/declarative/", + "Source/WebKit/qt/examples/"], ["-readability/naming"]), + ([# Qt's MiniBrowser has no config.h + "Tools/MiniBrowser/qt"], + ["-build/include"]), ([# The GTK+ APIs use GTK+ naming style, which includes # lower-cased, underscore-separated values. # Also, GTK+ allows the use of NULL. @@ -162,24 +165,18 @@ _PATH_RULES_SPECIFIER = [ "-whitespace/parens"]), # WebKit2 rules: - # WebKit2 doesn't use config.h, and certain directories have other - # idiosyncracies. + # WebKit2 and certain directories have idiosyncracies. ([# NPAPI has function names with underscores. "Source/WebKit2/WebProcess/Plugins/Netscape"], - ["-build/include_order", - "-readability/naming"]), + ["-readability/naming"]), ([# The WebKit2 C API has names with underscores and whitespace-aligned # struct members. Also, we allow unnecessary parameter names in # WebKit2 APIs because we're matching CF's header style. "Source/WebKit2/UIProcess/API/C/", "Source/WebKit2/WebProcess/InjectedBundle/API/c/"], - ["-build/include_order", - "-readability/naming", + ["-readability/naming", "-readability/parameter_name", "-whitespace/declaration"]), - ([# Nothing in WebKit2 uses config.h. - "Source/WebKit2/"], - ["-build/include_order"]), # For third-party Python code, keep only the following checks-- # @@ -243,7 +240,7 @@ _XML_FILE_EXTENSIONS = [ _SKIPPED_FILES_WITH_WARNING = [ "gtk2drawing.c", # WebCore/platform/gtk/gtk2drawing.c "gtkdrawing.h", # WebCore/platform/gtk/gtkdrawing.h - "WebKit/gtk/tests/", + "Source/WebKit/gtk/tests/", # Soup API that is still being cooked, will be removed from WebKit # in a few months when it is merged into soup proper. The style # follows the libsoup style completely. diff --git a/Tools/Scripts/webkitpy/style/checker_unittest.py b/Tools/Scripts/webkitpy/style/checker_unittest.py index a4649d2..a796e0b 100755 --- a/Tools/Scripts/webkitpy/style/checker_unittest.py +++ b/Tools/Scripts/webkitpy/style/checker_unittest.py @@ -216,11 +216,11 @@ class GlobalVariablesTest(unittest.TestCase): "build/include") assertCheck("random_path.cpp", "readability/naming") - assertNoCheck("WebKit/gtk/webkit/webkit.h", + assertNoCheck("Source/WebKit/gtk/webkit/webkit.h", "readability/naming") assertNoCheck("Tools/DumpRenderTree/gtk/DumpRenderTree.cpp", "readability/null") - assertNoCheck("WebKit/efl/ewk/ewk_view.h", + assertNoCheck("Source/WebKit/efl/ewk/ewk_view.h", "readability/naming") assertNoCheck("Source/WebCore/css/CSSParser.cpp", "readability/naming") @@ -228,28 +228,31 @@ class GlobalVariablesTest(unittest.TestCase): # Test if Qt exceptions are indeed working assertCheck("Source/JavaScriptCore/qt/api/qscriptengine.cpp", "readability/braces") - assertCheck("WebKit/qt/Api/qwebpage.cpp", + assertCheck("Source/WebKit/qt/Api/qwebpage.cpp", "readability/braces") - assertCheck("WebKit/qt/tests/qwebelement/tst_qwebelement.cpp", + assertCheck("Source/WebKit/qt/tests/qwebelement/tst_qwebelement.cpp", "readability/braces") - assertCheck("WebKit/qt/declarative/platformplugin/WebPlugin.cpp", + assertCheck("Source/WebKit/qt/declarative/platformplugin/WebPlugin.cpp", "readability/braces") - assertCheck("WebKit/qt/examples/platformplugin/WebPlugin.cpp", + assertCheck("Source/WebKit/qt/examples/platformplugin/WebPlugin.cpp", "readability/braces") assertNoCheck("Source/JavaScriptCore/qt/api/qscriptengine.cpp", "readability/naming") assertNoCheck("Source/JavaScriptCore/qt/benchmarks" "/qscriptengine/tst_qscriptengine.cpp", "readability/naming") - assertNoCheck("WebKit/qt/Api/qwebpage.cpp", + assertNoCheck("Source/WebKit/qt/Api/qwebpage.cpp", "readability/naming") - assertNoCheck("WebKit/qt/tests/qwebelement/tst_qwebelement.cpp", + assertNoCheck("Source/WebKit/qt/tests/qwebelement/tst_qwebelement.cpp", "readability/naming") - assertNoCheck("WebKit/qt/declarative/platformplugin/WebPlugin.cpp", + assertNoCheck("Source/WebKit/qt/declarative/platformplugin/WebPlugin.cpp", "readability/naming") - assertNoCheck("WebKit/qt/examples/platformplugin/WebPlugin.cpp", + assertNoCheck("Source/WebKit/qt/examples/platformplugin/WebPlugin.cpp", "readability/naming") + assertNoCheck("Tools/MiniBrowser/qt/UrlLoader.cpp", + "build/include") + assertNoCheck("Source/WebCore/ForwardingHeaders/debugger/Debugger.h", "build/header_guard") @@ -301,7 +304,7 @@ class CheckerDispatcherSkipTest(unittest.TestCase): "gtkdrawing.h", "Source/WebCore/platform/gtk/gtk2drawing.c", "Source/WebCore/platform/gtk/gtkdrawing.h", - "WebKit/gtk/tests/testatk.c", + "Source/WebKit/gtk/tests/testatk.c", ] for path in paths_to_skip: diff --git a/Tools/Scripts/webkitpy/style/checkers/cpp.py b/Tools/Scripts/webkitpy/style/checkers/cpp.py index 250b9ee..671fd56 100644 --- a/Tools/Scripts/webkitpy/style/checkers/cpp.py +++ b/Tools/Scripts/webkitpy/style/checkers/cpp.py @@ -194,6 +194,31 @@ def iteratively_replace_matches_with_char(pattern, char_replacement, s): s = s[:start_match_index] + char_replacement * match_length + s[end_match_index:] +def _rfind_in_lines(regex, lines, start_position, not_found_position): + """Does a reverse find starting at start position and going backwards until + a match is found. + + Returns the position where the regex ended. + """ + # Put the regex in a group and proceed it with a greedy expression that + # matches anything to ensure that we get the last possible match in a line. + last_in_line_regex = r'.*(' + regex + ')' + current_row = start_position.row + + # Start with the given row and trim off everything past what may be matched. + current_line = lines[start_position.row][:start_position.column] + while True: + found_match = match(last_in_line_regex, current_line) + if found_match: + return Position(current_row, found_match.end(1)) + + # A match was not found so continue backward. + current_row -= 1 + if current_row < 0: + return not_found_position + current_line = lines[current_row] + + def _convert_to_lower_with_underscores(text): """Converts all text strings in camelCase or PascalCase to lowers with underscores.""" @@ -526,6 +551,15 @@ class _FunctionState(object): self._clean_lines = clean_lines self._parameter_list = None + def modifiers_and_return_type(self): + """Returns the modifiers and the return type.""" + # Go backwards from where the function name is until we encounter one of several things: + # ';' or '{' or '}' or 'private:', etc. or '#' or return Position(0, 0) + elided = self._clean_lines.elided + start_modifiers = _rfind_in_lines(r';|\{|\}|((private|public|protected):)|(#.*)', + elided, self.parameter_start_position, Position(0, 0)) + return SingleLineView(elided, start_modifiers, self.function_name_start_position).single_line.strip() + def parameter_list(self): if not self._parameter_list: # Store the final result as a tuple since that is immutable. @@ -2315,7 +2349,7 @@ def check_for_null(clean_lines, line_number, file_state, error): # matches, then do the check with strings collapsed to avoid giving errors for # NULLs occurring in strings. if search(r'\bNULL\b', line) and search(r'\bNULL\b', CleansedLines.collapse_strings(line)): - error(line_number, 'readability/null', 4, 'Use 0 instead of NULL.') + error(line_number, 'readability/null', 4, 'Use 0 or null instead of NULL (even in *comments*).') def get_line_width(line): """Determines the width of the line in column positions. diff --git a/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py b/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py index 868d3f6..53670d7 100644 --- a/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py +++ b/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py @@ -352,16 +352,17 @@ class CppStyleTestBase(unittest.TestCase): class FunctionDetectionTest(CppStyleTestBase): - def perform_function_detection(self, lines, function_information): + def perform_function_detection(self, lines, function_information, detection_line=0): clean_lines = cpp_style.CleansedLines(lines) function_state = cpp_style._FunctionState(5) error_collector = ErrorCollector(self.assert_) - cpp_style.detect_functions(clean_lines, 0, function_state, error_collector) + cpp_style.detect_functions(clean_lines, detection_line, function_state, error_collector) if not function_information: self.assertEquals(function_state.in_a_function, False) return self.assertEquals(function_state.in_a_function, True) self.assertEquals(function_state.current_function, function_information['name'] + '()') + self.assertEquals(function_state.modifiers_and_return_type(), function_information['modifiers_and_return_type']) self.assertEquals(function_state.is_pure, function_information['is_pure']) self.assertEquals(function_state.is_declaration, function_information['is_declaration']) self.assert_positions_equal(function_state.function_name_start_position, function_information['function_name_start_position']) @@ -385,6 +386,7 @@ class FunctionDetectionTest(CppStyleTestBase): ['void theTestFunctionName(int) {', '}'], {'name': 'theTestFunctionName', + 'modifiers_and_return_type': 'void', 'function_name_start_position': (0, 5), 'parameter_start_position': (0, 24), 'parameter_end_position': (0, 29), @@ -397,6 +399,7 @@ class FunctionDetectionTest(CppStyleTestBase): self.perform_function_detection( ['void aFunctionName(int);'], {'name': 'aFunctionName', + 'modifiers_and_return_type': 'void', 'function_name_start_position': (0, 5), 'parameter_start_position': (0, 18), 'parameter_end_position': (0, 23), @@ -408,6 +411,7 @@ class FunctionDetectionTest(CppStyleTestBase): self.perform_function_detection( ['CheckedInt operator /(const CheckedInt &lhs, const CheckedInt &rhs);'], {'name': 'operator /', + 'modifiers_and_return_type': 'CheckedInt', 'function_name_start_position': (0, 14), 'parameter_start_position': (0, 24), 'parameter_end_position': (0, 76), @@ -419,6 +423,7 @@ class FunctionDetectionTest(CppStyleTestBase): self.perform_function_detection( ['CheckedInt operator -(const CheckedInt &lhs, const CheckedInt &rhs);'], {'name': 'operator -', + 'modifiers_and_return_type': 'CheckedInt', 'function_name_start_position': (0, 14), 'parameter_start_position': (0, 24), 'parameter_end_position': (0, 76), @@ -430,6 +435,7 @@ class FunctionDetectionTest(CppStyleTestBase): self.perform_function_detection( ['CheckedInt operator !=(const CheckedInt &lhs, const CheckedInt &rhs);'], {'name': 'operator !=', + 'modifiers_and_return_type': 'CheckedInt', 'function_name_start_position': (0, 14), 'parameter_start_position': (0, 25), 'parameter_end_position': (0, 77), @@ -441,6 +447,7 @@ class FunctionDetectionTest(CppStyleTestBase): self.perform_function_detection( ['CheckedInt operator +(const CheckedInt &lhs, const CheckedInt &rhs);'], {'name': 'operator +', + 'modifiers_and_return_type': 'CheckedInt', 'function_name_start_position': (0, 14), 'parameter_start_position': (0, 24), 'parameter_end_position': (0, 76), @@ -453,6 +460,7 @@ class FunctionDetectionTest(CppStyleTestBase): self.perform_function_detection( ['virtual void theTestFunctionName(int = 0);'], {'name': 'theTestFunctionName', + 'modifiers_and_return_type': 'virtual void', 'function_name_start_position': (0, 13), 'parameter_start_position': (0, 32), 'parameter_end_position': (0, 41), @@ -464,6 +472,7 @@ class FunctionDetectionTest(CppStyleTestBase): self.perform_function_detection( ['virtual void theTestFunctionName(int) = 0;'], {'name': 'theTestFunctionName', + 'modifiers_and_return_type': 'virtual void', 'function_name_start_position': (0, 13), 'parameter_start_position': (0, 32), 'parameter_end_position': (0, 37), @@ -478,6 +487,7 @@ class FunctionDetectionTest(CppStyleTestBase): ' = ', ' 0 ;'], {'name': 'theTestFunctionName', + 'modifiers_and_return_type': 'virtual void', 'function_name_start_position': (0, 13), 'parameter_start_position': (0, 32), 'parameter_end_position': (0, 37), @@ -498,6 +508,7 @@ class FunctionDetectionTest(CppStyleTestBase): # This isn't a function but it looks like one to our simple # algorithm and that is ok. {'name': 'asm', + 'modifiers_and_return_type': '', 'function_name_start_position': (0, 0), 'parameter_start_position': (0, 3), 'parameter_end_position': (2, 1), @@ -514,6 +525,7 @@ class FunctionDetectionTest(CppStyleTestBase): function_state = self.perform_function_detection( ['void functionName();'], {'name': 'functionName', + 'modifiers_and_return_type': 'void', 'function_name_start_position': (0, 5), 'parameter_start_position': (0, 17), 'parameter_end_position': (0, 19), @@ -527,6 +539,7 @@ class FunctionDetectionTest(CppStyleTestBase): function_state = self.perform_function_detection( ['void functionName(int);'], {'name': 'functionName', + 'modifiers_and_return_type': 'void', 'function_name_start_position': (0, 5), 'parameter_start_position': (0, 17), 'parameter_end_position': (0, 22), @@ -541,6 +554,7 @@ class FunctionDetectionTest(CppStyleTestBase): function_state = self.perform_function_detection( ['void functionName(unsigned a, short b, long c, long long short unsigned int);'], {'name': 'functionName', + 'modifiers_and_return_type': 'void', 'function_name_start_position': (0, 5), 'parameter_start_position': (0, 17), 'parameter_end_position': (0, 76), @@ -558,6 +572,7 @@ class FunctionDetectionTest(CppStyleTestBase): function_state = self.perform_function_detection( ['virtual void determineARIADropEffects(Vector*&, const unsigned long int*&, const MediaPlayer::Preload, Other >, int);'], {'name': 'determineARIADropEffects', + 'modifiers_and_return_type': 'virtual void', 'parameter_start_position': (0, 37), 'function_name_start_position': (0, 13), 'parameter_end_position': (0, 147), @@ -574,23 +589,27 @@ class FunctionDetectionTest(CppStyleTestBase): # Try parsing a function with a very complex definition. function_state = self.perform_function_detection( - ['AnotherTemplate aFunctionName(PassRefPtr paramName,', + ['#define MyMacro(a) a', + 'virtual', + 'AnotherTemplate aFunctionName(PassRefPtr paramName,', 'const Other1Class& foo,', 'const ComplexTemplate >* const * param = new ComplexTemplate >(34, 42),', 'int* myCount = 0);'], {'name': 'aFunctionName', - 'function_name_start_position': (0, 32), - 'parameter_start_position': (0, 45), - 'parameter_end_position': (3, 17), - 'body_start_position': (3, 17), - 'end_position': (3, 18), + 'modifiers_and_return_type': 'virtual AnotherTemplate', + 'function_name_start_position': (2, 32), + 'parameter_start_position': (2, 45), + 'parameter_end_position': (5, 17), + 'body_start_position': (5, 17), + 'end_position': (5, 18), 'is_pure': False, 'is_declaration': True, 'parameter_list': - ({'type': 'PassRefPtr', 'name': 'paramName', 'row': 0}, - {'type': 'const Other1Class&', 'name': 'foo', 'row': 1}, - {'type': 'const ComplexTemplate >* const *', 'name': 'param', 'row': 2}, - {'type': 'int*', 'name': 'myCount', 'row': 3})}) + ({'type': 'PassRefPtr', 'name': 'paramName', 'row': 2}, + {'type': 'const Other1Class&', 'name': 'foo', 'row': 3}, + {'type': 'const ComplexTemplate >* const *', 'name': 'param', 'row': 4}, + {'type': 'int*', 'name': 'myCount', 'row': 5})}, + detection_line=2) class CppStyleTest(CppStyleTestBase): @@ -630,6 +649,14 @@ class CppStyleTest(CppStyleTestBase): self.assertTrue(position < cpp_style.Position(position.row + 1, position.column - 1)) self.assertEquals(position.__str__(), '(3, 4)') + def test_rfind_in_lines(self): + not_found_position = cpp_style.Position(10, 11) + start_position = cpp_style.Position(2, 2) + lines = ['ab', 'ace', 'test'] + self.assertEquals(not_found_position, cpp_style._rfind_in_lines('st', lines, start_position, not_found_position)) + self.assertTrue(cpp_style.Position(1, 1) == cpp_style._rfind_in_lines('a', lines, start_position, not_found_position)) + self.assertEquals(cpp_style.Position(2, 2), cpp_style._rfind_in_lines('(te|a)', lines, start_position, not_found_position)) + def test_close_expression(self): self.assertEquals(cpp_style.Position(1, -1), cpp_style.close_expression([')('], cpp_style.Position(0, 1))) self.assertEquals(cpp_style.Position(1, -1), cpp_style.close_expression([') ()'], cpp_style.Position(0, 1))) @@ -3901,12 +3928,12 @@ class WebKitStyleTest(CppStyleTestBase): 'foo.cpp') self.assert_lint( "// Don't use NULL in comments since it isn't in code.", - 'Use 0 instead of NULL.' + 'Use 0 or null instead of NULL (even in *comments*).' ' [readability/null] [4]', 'foo.cpp') self.assert_lint( '"A string with NULL" // and a comment with NULL is tricky to flag correctly in cpp_style.', - 'Use 0 instead of NULL.' + 'Use 0 or null instead of NULL (even in *comments*).' ' [readability/null] [4]', 'foo.cpp') self.assert_lint( diff --git a/Tools/Scripts/webkitpy/style/checkers/test_expectations.py b/Tools/Scripts/webkitpy/style/checkers/test_expectations.py index c86b32c..e37f908 100644 --- a/Tools/Scripts/webkitpy/style/checkers/test_expectations.py +++ b/Tools/Scripts/webkitpy/style/checkers/test_expectations.py @@ -75,7 +75,6 @@ class TestExpectationsChecker(object): "Using 'test' port, but platform-specific expectations " "will fail the check." % self._file_path) self._port_obj = port.get('test') - self._port_to_check = self._port_obj.test_platform_name() # Suppress error messages of test_expectations module since they will be # reported later. log = logging.getLogger("webkitpy.layout_tests.layout_package." @@ -91,7 +90,7 @@ class TestExpectationsChecker(object): try: expectations = test_expectations.TestExpectationsFile( port=self._port_obj, expectations=expectations_str, full_test_list=tests, - test_platform_name=self._port_to_check, is_debug_mode=False, + test_config=self._port_obj.test_configuration(), is_lint_mode=True, overrides=overrides) except test_expectations.ParseError, error: err = error diff --git a/Tools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py b/Tools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py index 9817c5d..f0813e1 100644 --- a/Tools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py +++ b/Tools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py @@ -84,15 +84,6 @@ class TestExpectationsTestCase(unittest.TestCase): def test_valid_expectations(self): self.assert_lines_lint( - ["passes/text.html = PASS"], - "") - self.assert_lines_lint( - ["passes/text.html = FAIL PASS"], - "") - self.assert_lines_lint( - ["passes/text.html = CRASH TIMEOUT FAIL PASS"], - "") - self.assert_lines_lint( ["BUGCR1234 MAC : passes/text.html = PASS FAIL"], "") self.assert_lines_lint( @@ -120,12 +111,12 @@ class TestExpectationsTestCase(unittest.TestCase): def test_modifier_errors(self): self.assert_lines_lint( ["BUG1234 : passes/text.html = FAIL"], - 'Bug must be either BUGCR, BUGWK, or BUGV8_ for test: bug1234 passes/text.html [test/expectations] [5]') + "BUG\\d+ is not allowed, must be one of BUGCR\\d+, BUGWK\\d+, BUGV8_\\d+, or a non-numeric bug identifier. passes/text.html [test/expectations] [5]") def test_valid_modifiers(self): self.assert_lines_lint( ["INVALID-MODIFIER : passes/text.html = PASS"], - "Invalid modifier for test: invalid-modifier " + "Unrecognized option 'invalid-modifier' " "passes/text.html [test/expectations] [5]") self.assert_lines_lint( ["SKIP : passes/text.html = PASS"], @@ -135,38 +126,38 @@ class TestExpectationsTestCase(unittest.TestCase): def test_expectation_errors(self): self.assert_lines_lint( ["missing expectations"], - "Missing expectations. ['missing expectations'] [test/expectations] [5]") + "Missing a ':' missing expectations [test/expectations] [5]") self.assert_lines_lint( ["SLOW : passes/text.html = TIMEOUT"], - "A test can not be both slow and timeout. " - "If it times out indefinitely, then it should be just timeout. " + "A test can not be both SLOW and TIMEOUT. " + "If it times out indefinitely, then it should be just TIMEOUT. " "passes/text.html [test/expectations] [5]") self.assert_lines_lint( - ["does/not/exist.html = FAIL"], + ["BUGWK1 : does/not/exist.html = FAIL"], "Path does not exist. does/not/exist.html [test/expectations] [2]") def test_parse_expectations(self): self.assert_lines_lint( - ["passes/text.html = PASS"], + ["BUGWK1 : passes/text.html = PASS"], "") self.assert_lines_lint( - ["passes/text.html = UNSUPPORTED"], + ["BUGWK1 : passes/text.html = UNSUPPORTED"], "Unsupported expectation: unsupported " "passes/text.html [test/expectations] [5]") self.assert_lines_lint( - ["passes/text.html = PASS UNSUPPORTED"], + ["BUGWK1 : passes/text.html = PASS UNSUPPORTED"], "Unsupported expectation: unsupported " "passes/text.html [test/expectations] [5]") def test_already_seen_test(self): self.assert_lines_lint( - ["passes/text.html = PASS", - "passes/text.html = TIMEOUT"], - "Duplicate expectation. %s [test/expectations] [5]" % self._test_file) + ["BUGWK1 : passes/text.html = PASS", + "BUGWK1 : passes/text.html = TIMEOUT"], + "Duplicate or ambiguous expectation. %s [test/expectations] [5]" % self._test_file) def test_tab(self): self.assert_lines_lint( - ["\tpasses/text.html = PASS"], + ["\tBUGWK1 : passes/text.html = PASS"], "Line contains tab character. [whitespace/tab] [5]") if __name__ == '__main__': diff --git a/Tools/Scripts/webkitpy/tool/bot/irc_command.py b/Tools/Scripts/webkitpy/tool/bot/irc_command.py index 265974e..67a1c44 100644 --- a/Tools/Scripts/webkitpy/tool/bot/irc_command.py +++ b/Tools/Scripts/webkitpy/tool/bot/irc_command.py @@ -27,7 +27,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import random -import webkitpy.common.config.irc as config_irc +from webkitpy.common.config import irc as config_irc from webkitpy.common.config import urls from webkitpy.common.net.bugzilla import parse_bug_id diff --git a/Tools/Scripts/webkitpy/tool/bot/sheriffircbot.py b/Tools/Scripts/webkitpy/tool/bot/sheriffircbot.py index de77222..29e89a8 100644 --- a/Tools/Scripts/webkitpy/tool/bot/sheriffircbot.py +++ b/Tools/Scripts/webkitpy/tool/bot/sheriffircbot.py @@ -26,7 +26,7 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import webkitpy.tool.bot.irc_command as irc_command +from webkitpy.tool.bot import irc_command from webkitpy.common.net.irc.ircbot import IRCBotDelegate from webkitpy.common.thread.threadedmessagequeue import ThreadedMessageQueue diff --git a/Tools/Scripts/webkitpy/tool/commands/__init__.py b/Tools/Scripts/webkitpy/tool/commands/__init__.py index a974b67..26bd195 100644 --- a/Tools/Scripts/webkitpy/tool/commands/__init__.py +++ b/Tools/Scripts/webkitpy/tool/commands/__init__.py @@ -10,5 +10,6 @@ from webkitpy.tool.commands.queries import * from webkitpy.tool.commands.queues import * from webkitpy.tool.commands.rebaseline import Rebaseline from webkitpy.tool.commands.rebaselineserver import RebaselineServer +from webkitpy.tool.commands.roll import * from webkitpy.tool.commands.sheriffbot import * from webkitpy.tool.commands.upload import * diff --git a/Tools/Scripts/webkitpy/tool/commands/commandtest.py b/Tools/Scripts/webkitpy/tool/commands/commandtest.py index c0efa50..e335710 100644 --- a/Tools/Scripts/webkitpy/tool/commands/commandtest.py +++ b/Tools/Scripts/webkitpy/tool/commands/commandtest.py @@ -32,7 +32,7 @@ from webkitpy.common.system.outputcapture import OutputCapture from webkitpy.tool.mocktool import MockOptions, MockTool class CommandsTest(unittest.TestCase): - def assert_execute_outputs(self, command, args, expected_stdout="", expected_stderr="", options=MockOptions(), tool=MockTool()): + def assert_execute_outputs(self, command, args, expected_stdout="", expected_stderr="", expected_exception=None, options=MockOptions(), tool=MockTool()): options.blocks = None options.cc = 'MOCK cc' options.component = 'MOCK component' @@ -45,4 +45,4 @@ class CommandsTest(unittest.TestCase): options.quiet = True options.reviewer = 'MOCK reviewer' command.bind_to_tool(tool) - OutputCapture().assert_outputs(self, command.execute, [options, args, tool], expected_stdout=expected_stdout, expected_stderr=expected_stderr) + OutputCapture().assert_outputs(self, command.execute, [options, args, tool], expected_stdout=expected_stdout, expected_stderr=expected_stderr, expected_exception=expected_exception) diff --git a/Tools/Scripts/webkitpy/tool/commands/download.py b/Tools/Scripts/webkitpy/tool/commands/download.py index 1b478bf..35484cd 100644 --- a/Tools/Scripts/webkitpy/tool/commands/download.py +++ b/Tools/Scripts/webkitpy/tool/commands/download.py @@ -29,7 +29,7 @@ import os -import webkitpy.tool.steps as steps +from webkitpy.tool import steps from webkitpy.common.checkout.changelog import ChangeLog from webkitpy.common.config import urls diff --git a/Tools/Scripts/webkitpy/tool/commands/download_unittest.py b/Tools/Scripts/webkitpy/tool/commands/download_unittest.py index ba23ab9..ced5b2f 100644 --- a/Tools/Scripts/webkitpy/tool/commands/download_unittest.py +++ b/Tools/Scripts/webkitpy/tool/commands/download_unittest.py @@ -200,7 +200,13 @@ where ATTACHMENT_ID is the ID of this attachment. self.assert_execute_outputs(CreateRollout(), ["855 852 854", "Reason"], options=self._default_options(), expected_stderr=expected_stderr) def test_rollout(self): - expected_stderr = "Preparing rollout for bug 42.\nUpdating working directory\nRunning prepare-ChangeLog\nMOCK: user.open_url: file://...\nBuilding WebKit\nCommitted r49824: \n" - expected_stdout = "Was that diff correct?\n" - self.assert_execute_outputs(Rollout(), [852, "Reason"], options=self._default_options(), expected_stdout=expected_stdout, expected_stderr=expected_stderr) + expected_stderr = """Preparing rollout for bug 42. +Updating working directory +Running prepare-ChangeLog +MOCK: user.open_url: file://... +Was that diff correct? +Building WebKit +Committed r49824: +""" + self.assert_execute_outputs(Rollout(), [852, "Reason"], options=self._default_options(), expected_stderr=expected_stderr) diff --git a/Tools/Scripts/webkitpy/tool/commands/prettydiff.py b/Tools/Scripts/webkitpy/tool/commands/prettydiff.py index e3fc00c..67866f0 100644 --- a/Tools/Scripts/webkitpy/tool/commands/prettydiff.py +++ b/Tools/Scripts/webkitpy/tool/commands/prettydiff.py @@ -27,7 +27,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from webkitpy.tool.commands.abstractsequencedcommand import AbstractSequencedCommand -import webkitpy.tool.steps as steps +from webkitpy.tool import steps class PrettyDiff(AbstractSequencedCommand): diff --git a/Tools/Scripts/webkitpy/tool/commands/queries.py b/Tools/Scripts/webkitpy/tool/commands/queries.py index 733751e..57f90ae 100644 --- a/Tools/Scripts/webkitpy/tool/commands/queries.py +++ b/Tools/Scripts/webkitpy/tool/commands/queries.py @@ -30,7 +30,7 @@ from optparse import make_option -import webkitpy.tool.steps as steps +from webkitpy.tool import steps from webkitpy.common.checkout.commitinfo import CommitInfo from webkitpy.common.config.committers import CommitterList diff --git a/Tools/Scripts/webkitpy/tool/commands/roll.py b/Tools/Scripts/webkitpy/tool/commands/roll.py new file mode 100644 index 0000000..0cf95e9 --- /dev/null +++ b/Tools/Scripts/webkitpy/tool/commands/roll.py @@ -0,0 +1,48 @@ +# Copyright (c) 2011 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from webkitpy.tool.commands.abstractsequencedcommand import AbstractSequencedCommand + +from webkitpy.tool import steps + + +class RollChromiumDEPS(AbstractSequencedCommand): + name = "roll-chromium-deps" + help_text = "Updates Chromium DEPS (defaults to the last-known good revision of Chromium)" + argument_names = "[CHROMIUM_REVISION]" + steps = [ + steps.UpdateChromiumDEPS, + steps.PrepareChangeLogForDEPSRoll, + steps.ConfirmDiff, + steps.Commit, + ] + + def _prepare_state(self, options, args, tool): + return { + "chromium_revision": (args and args[0]), + } diff --git a/Tools/Scripts/webkitpy/tool/commands/roll_unittest.py b/Tools/Scripts/webkitpy/tool/commands/roll_unittest.py new file mode 100644 index 0000000..b6f69ea --- /dev/null +++ b/Tools/Scripts/webkitpy/tool/commands/roll_unittest.py @@ -0,0 +1,50 @@ +# Copyright (C) 2011 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from webkitpy.thirdparty.mock import Mock +from webkitpy.tool.commands.commandtest import CommandsTest +from webkitpy.tool.commands.roll import * +from webkitpy.tool.mocktool import MockOptions, MockTool + + +class RollCommandsTest(CommandsTest): + def test_update_chromium_deps(self): + expected_stderr = """Updating Chromium DEPS to 6764 +MOCK: MockDEPS.write_variable(chromium_rev, 6764) +Running prepare-ChangeLog +MOCK: user.open_url: file://... +Was that diff correct? +Committed r49824: +""" + self.assert_execute_outputs(RollChromiumDEPS(), [6764], expected_stderr=expected_stderr) + + def test_update_chromium_deps_older_revision(self): + expected_stderr = """Current Chromium DEPS revision 6564 is newer than 5764. +ERROR: Unable to update Chromium DEPS +""" + self.assert_execute_outputs(RollChromiumDEPS(), [5764], expected_stderr=expected_stderr, expected_exception=SystemExit) diff --git a/Tools/Scripts/webkitpy/tool/commands/stepsequence.py b/Tools/Scripts/webkitpy/tool/commands/stepsequence.py index be2ed4c..b666554 100644 --- a/Tools/Scripts/webkitpy/tool/commands/stepsequence.py +++ b/Tools/Scripts/webkitpy/tool/commands/stepsequence.py @@ -26,12 +26,12 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import webkitpy.tool.steps as steps +from webkitpy.tool import steps -from webkitpy.common.system.executive import ScriptError from webkitpy.common.checkout.scm import CheckoutNeedsUpdate -from webkitpy.tool.bot.queueengine import QueueEngine from webkitpy.common.system.deprecated_logging import log +from webkitpy.common.system.executive import ScriptError +from webkitpy.tool.bot.queueengine import QueueEngine class StepSequenceErrorHandler(): diff --git a/Tools/Scripts/webkitpy/tool/commands/upload.py b/Tools/Scripts/webkitpy/tool/commands/upload.py index 6617b4f..e455b18 100644 --- a/Tools/Scripts/webkitpy/tool/commands/upload.py +++ b/Tools/Scripts/webkitpy/tool/commands/upload.py @@ -34,17 +34,17 @@ import sys from optparse import make_option -import webkitpy.tool.steps as steps +from webkitpy.tool import steps from webkitpy.common.config.committers import CommitterList from webkitpy.common.net.bugzilla import parse_bug_id +from webkitpy.common.system.deprecated_logging import error, log from webkitpy.common.system.user import User from webkitpy.thirdparty.mock import Mock from webkitpy.tool.commands.abstractsequencedcommand import AbstractSequencedCommand -from webkitpy.tool.grammar import pluralize, join_with_separators from webkitpy.tool.comments import bug_comment_from_svn_revision +from webkitpy.tool.grammar import pluralize, join_with_separators from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand -from webkitpy.common.system.deprecated_logging import error, log class CommitMessageForCurrentDiff(AbstractDeclarativeCommand): diff --git a/Tools/Scripts/webkitpy/tool/commands/upload_unittest.py b/Tools/Scripts/webkitpy/tool/commands/upload_unittest.py index a347b00..b5f5ae9 100644 --- a/Tools/Scripts/webkitpy/tool/commands/upload_unittest.py +++ b/Tools/Scripts/webkitpy/tool/commands/upload_unittest.py @@ -61,12 +61,12 @@ class UploadCommandsTest(CommandsTest): options.suggest_reviewers = False expected_stderr = """Running check-webkit-style MOCK: user.open_url: file://... +Was that diff correct? Obsoleting 2 old patches on bug 42 MOCK add_patch_to_bug: bug_id=42, description=MOCK description, mark_for_review=True, mark_for_commit_queue=False, mark_for_landing=False MOCK: user.open_url: http://example.com/42 """ - expected_stdout = "Was that diff correct?\n" - self.assert_execute_outputs(Post(), [42], options=options, expected_stdout=expected_stdout, expected_stderr=expected_stderr) + self.assert_execute_outputs(Post(), [42], options=options, expected_stderr=expected_stderr) def test_land_safely(self): expected_stderr = "Obsoleting 2 old patches on bug 42\nMOCK add_patch_to_bug: bug_id=42, description=Patch for landing, mark_for_review=False, mark_for_commit_queue=False, mark_for_landing=True\n" @@ -90,12 +90,12 @@ MOCK: user.open_url: http://example.com/42 options.suggest_reviewers = False expected_stderr = """Running check-webkit-style MOCK: user.open_url: file://... +Was that diff correct? Obsoleting 2 old patches on bug 42 MOCK add_patch_to_bug: bug_id=42, description=MOCK description, mark_for_review=True, mark_for_commit_queue=False, mark_for_landing=False MOCK: user.open_url: http://example.com/42 """ - expected_stdout = "Was that diff correct?\n" - self.assert_execute_outputs(Upload(), [42], options=options, expected_stdout=expected_stdout, expected_stderr=expected_stderr) + self.assert_execute_outputs(Upload(), [42], options=options, expected_stderr=expected_stderr) def test_mark_bug_fixed(self): tool = MockTool() @@ -106,6 +106,7 @@ MOCK: user.open_url: http://example.com/42 expected_stderr = """Bug: Bug with two r+'d and cq+'d patches, one of which has an invalid commit-queue setter. Revision: 9876 MOCK: user.open_url: http://example.com/42 +Is this correct? Adding comment to Bug 42. MOCK bug comment: bug_id=42, cc=None --- Begin comment --- @@ -115,8 +116,7 @@ Committed r9876: --- End comment --- """ - expected_stdout = "Is this correct?\n" - self.assert_execute_outputs(MarkBugFixed(), [], expected_stdout=expected_stdout, expected_stderr=expected_stderr, tool=tool, options=options) + self.assert_execute_outputs(MarkBugFixed(), [], expected_stderr=expected_stderr, tool=tool, options=options) def test_edit_changelog(self): self.assert_execute_outputs(EditChangeLogs(), []) diff --git a/Tools/Scripts/webkitpy/tool/main.py b/Tools/Scripts/webkitpy/tool/main.py index 6b07615..25cb9f9 100755 --- a/Tools/Scripts/webkitpy/tool/main.py +++ b/Tools/Scripts/webkitpy/tool/main.py @@ -44,7 +44,7 @@ from webkitpy.common.net.statusserver import StatusServer from webkitpy.common.system import executive, filesystem, platforminfo, user, workspace from webkitpy.layout_tests import port from webkitpy.tool.multicommandtool import MultiCommandTool -import webkitpy.tool.commands as commands +from webkitpy.tool import commands class WebKitPatch(MultiCommandTool, Host): diff --git a/Tools/Scripts/webkitpy/tool/mocktool.py b/Tools/Scripts/webkitpy/tool/mocktool.py index 7db2996..73f55a7 100644 --- a/Tools/Scripts/webkitpy/tool/mocktool.py +++ b/Tools/Scripts/webkitpy/tool/mocktool.py @@ -464,6 +464,12 @@ class MockSCM(Mock): # os.getcwd() can't work here because other parts of the code assume that "checkout_root" # will actually be the root. Since getcwd() is wrong, use a globally fake root for now. self.checkout_root = self.fake_checkout_root + self.added_paths = set() + + def add(self, destination_path, return_exit_code=False): + self.added_paths.add(destination_path) + if return_exit_code: + return 0 def changed_files(self, git_commit=None): return ["MockFile1"] @@ -483,16 +489,26 @@ class MockSCM(Mock): "https://bugs.example.org/show_bug.cgi?id=75\n") raise Exception("Bogus commit_id in commit_message_for_local_commit.") + def diff_for_file(self, path, log=None): + return path + '-diff' + def diff_for_revision(self, revision): return "DiffForRevision%s\n" \ "http://bugs.webkit.org/show_bug.cgi?id=12345" % revision + def show_head(self, path): + return path + def svn_revision_from_commit_text(self, commit_text): return "49824" - def add(self, destination_path, return_exit_code=False): - if return_exit_code: - return 0 + +class MockDEPS(object): + def read_variable(self, name): + return 6564 + + def write_variable(self, name, value): + log("MOCK: MockDEPS.write_variable(%s, %s)" % (name, value)) class MockCheckout(object): @@ -528,6 +544,9 @@ class MockCheckout(object): commit_message.message = lambda:"This is a fake commit message that is at least 50 characters." return commit_message + def chromium_deps(self): + return MockDEPS() + def apply_patch(self, patch, force=False): pass @@ -548,6 +567,9 @@ class MockUser(object): def prompt_with_list(cls, list_title, list_items, can_choose_multiple=False, raw_input=raw_input): pass + def __init__(self): + self.opened_urls = [] + def edit(self, files): pass @@ -558,13 +580,14 @@ class MockUser(object): pass def confirm(self, message=None, default='y'): - print message + log(message) return default == 'y' def can_open_url(self): return True def open_url(self, url): + self.opened_urls.append(url) if url.startswith("file://"): log("MOCK: user.open_url: file://...") return diff --git a/Tools/Scripts/webkitpy/tool/steps/__init__.py b/Tools/Scripts/webkitpy/tool/steps/__init__.py index f426f17..d5d7bb4 100644 --- a/Tools/Scripts/webkitpy/tool/steps/__init__.py +++ b/Tools/Scripts/webkitpy/tool/steps/__init__.py @@ -47,6 +47,7 @@ from webkitpy.tool.steps.options import Options from webkitpy.tool.steps.postdiff import PostDiff from webkitpy.tool.steps.postdiffforcommit import PostDiffForCommit from webkitpy.tool.steps.postdiffforrevert import PostDiffForRevert +from webkitpy.tool.steps.preparechangelogfordepsroll import PrepareChangeLogForDEPSRoll from webkitpy.tool.steps.preparechangelogforrevert import PrepareChangeLogForRevert from webkitpy.tool.steps.preparechangelog import PrepareChangeLog from webkitpy.tool.steps.promptforbugortitle import PromptForBugOrTitle @@ -55,6 +56,7 @@ from webkitpy.tool.steps.revertrevision import RevertRevision from webkitpy.tool.steps.runtests import RunTests from webkitpy.tool.steps.suggestreviewers import SuggestReviewers from webkitpy.tool.steps.updatechangelogswithreviewer import UpdateChangeLogsWithReviewer +from webkitpy.tool.steps.updatechromiumdeps import UpdateChromiumDEPS from webkitpy.tool.steps.update import Update from webkitpy.tool.steps.validatechangelogs import ValidateChangeLogs from webkitpy.tool.steps.validatereviewer import ValidateReviewer diff --git a/Tools/Scripts/webkitpy/tool/steps/preparechangelogfordepsroll.py b/Tools/Scripts/webkitpy/tool/steps/preparechangelogfordepsroll.py new file mode 100644 index 0000000..39c9a9a --- /dev/null +++ b/Tools/Scripts/webkitpy/tool/steps/preparechangelogfordepsroll.py @@ -0,0 +1,40 @@ +# Copyright (C) 2011 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import os + +from webkitpy.common.checkout.changelog import ChangeLog +from webkitpy.tool.steps.abstractstep import AbstractStep + + +class PrepareChangeLogForDEPSRoll(AbstractStep): + def run(self, state): + self._run_script("prepare-ChangeLog") + changelog_paths = self._tool.checkout().modified_changelogs(git_commit=None) + for changelog_path in changelog_paths: + ChangeLog(changelog_path).update_with_unreviewed_message("Rolled DEPS.\n\n") diff --git a/Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert.py b/Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert.py index 1e47a6a..dcd4b93 100644 --- a/Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert.py +++ b/Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert.py @@ -29,16 +29,32 @@ import os from webkitpy.common.checkout.changelog import ChangeLog +from webkitpy.common.config import urls +from webkitpy.tool.grammar import join_with_separators from webkitpy.tool.steps.abstractstep import AbstractStep class PrepareChangeLogForRevert(AbstractStep): + @classmethod + def _message_for_revert(cls, revision_list, reason, bug_url=None): + message = "Unreviewed, rolling out %s.\n" % join_with_separators(['r' + str(revision) for revision in revision_list]) + for revision in revision_list: + message += "%s\n" % urls.view_revision_url(revision) + if bug_url: + message += "%s\n" % bug_url + # Add an extra new line after the rollout links, before any reason. + message += "\n" + if reason: + message += "%s\n\n" % reason + return message + def run(self, state): # This could move to prepare-ChangeLog by adding a --revert= option. self._run_script("prepare-ChangeLog") changelog_paths = self._tool.checkout().modified_changelogs(git_commit=None) bug_url = self._tool.bugs.bug_url_for_bug_id(state["bug_id"]) if state["bug_id"] else None + message = self._message_for_revert(state["revision_list"], state["reason"], bug_url) for changelog_path in changelog_paths: # FIXME: Seems we should prepare the message outside of changelogs.py and then just pass in # text that we want to use to replace the reviewed by line. - ChangeLog(changelog_path).update_for_revert(state["revision_list"], state["reason"], bug_url) + ChangeLog(changelog_path).update_with_unreviewed_message(message) diff --git a/Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert_unittest.py b/Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert_unittest.py new file mode 100644 index 0000000..aa9d5e9 --- /dev/null +++ b/Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert_unittest.py @@ -0,0 +1,130 @@ +# Copyright (C) 2011 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from __future__ import with_statement + +import codecs +import os +import tempfile +import unittest + +from webkitpy.common.checkout.changelog import ChangeLog +from webkitpy.common.checkout.changelog_unittest import ChangeLogTest +from webkitpy.tool.steps.preparechangelogforrevert import * + + +class UpdateChangeLogsForRevertTest(unittest.TestCase): + @staticmethod + def _write_tmp_file_with_contents(byte_array): + assert(isinstance(byte_array, str)) + (file_descriptor, file_path) = tempfile.mkstemp() # NamedTemporaryFile always deletes the file on close in python < 2.6 + with os.fdopen(file_descriptor, "w") as file: + file.write(byte_array) + return file_path + + _revert_entry_with_bug_url = '''2009-08-19 Eric Seidel + + Unreviewed, rolling out r12345. + http://trac.webkit.org/changeset/12345 + http://example.com/123 + + Reason + + * Scripts/bugzilla-tool: +''' + + _revert_entry_without_bug_url = '''2009-08-19 Eric Seidel + + Unreviewed, rolling out r12345. + http://trac.webkit.org/changeset/12345 + + Reason + + * Scripts/bugzilla-tool: +''' + + _multiple_revert_entry_with_bug_url = '''2009-08-19 Eric Seidel + + Unreviewed, rolling out r12345, r12346, and r12347. + http://trac.webkit.org/changeset/12345 + http://trac.webkit.org/changeset/12346 + http://trac.webkit.org/changeset/12347 + http://example.com/123 + + Reason + + * Scripts/bugzilla-tool: +''' + + _multiple_revert_entry_without_bug_url = '''2009-08-19 Eric Seidel + + Unreviewed, rolling out r12345, r12346, and r12347. + http://trac.webkit.org/changeset/12345 + http://trac.webkit.org/changeset/12346 + http://trac.webkit.org/changeset/12347 + + Reason + + * Scripts/bugzilla-tool: +''' + + _revert_with_log_reason = """2009-08-19 Eric Seidel + + Unreviewed, rolling out r12345. + http://trac.webkit.org/changeset/12345 + http://example.com/123 + + This is a very long reason which should be long enough so that + _message_for_revert will need to wrap it. We'll also include + a + https://veryveryveryveryverylongbugurl.com/reallylongbugthingy.cgi?bug_id=12354 + link so that we can make sure we wrap that right too. + + * Scripts/bugzilla-tool: +""" + + def _assert_message_for_revert_output(self, args, expected_entry): + changelog_contents = u"%s\n%s" % (ChangeLogTest._new_entry_boilerplate, ChangeLogTest._example_changelog) + changelog_path = self._write_tmp_file_with_contents(changelog_contents.encode("utf-8")) + changelog = ChangeLog(changelog_path) + changelog.update_with_unreviewed_message(PrepareChangeLogForRevert._message_for_revert(*args)) + actual_entry = changelog.latest_entry() + os.remove(changelog_path) + self.assertEquals(actual_entry.contents(), expected_entry) + self.assertEquals(actual_entry.reviewer_text(), None) + # These checks could be removed to allow this to work on other entries: + self.assertEquals(actual_entry.author_name(), "Eric Seidel") + self.assertEquals(actual_entry.author_email(), "eric@webkit.org") + + def test_message_for_revert(self): + self._assert_message_for_revert_output([[12345], "Reason"], self._revert_entry_without_bug_url) + self._assert_message_for_revert_output([[12345], "Reason", "http://example.com/123"], self._revert_entry_with_bug_url) + self._assert_message_for_revert_output([[12345, 12346, 12347], "Reason"], self._multiple_revert_entry_without_bug_url) + self._assert_message_for_revert_output([[12345, 12346, 12347], "Reason", "http://example.com/123"], self._multiple_revert_entry_with_bug_url) + long_reason = "This is a very long reason which should be long enough so that _message_for_revert will need to wrap it. We'll also include a https://veryveryveryveryverylongbugurl.com/reallylongbugthingy.cgi?bug_id=12354 link so that we can make sure we wrap that right too." + self._assert_message_for_revert_output([[12345], long_reason, "http://example.com/123"], self._revert_with_log_reason) diff --git a/Tools/Scripts/webkitpy/tool/steps/suggestreviewers_unittest.py b/Tools/Scripts/webkitpy/tool/steps/suggestreviewers_unittest.py index 0c86535..e995663 100644 --- a/Tools/Scripts/webkitpy/tool/steps/suggestreviewers_unittest.py +++ b/Tools/Scripts/webkitpy/tool/steps/suggestreviewers_unittest.py @@ -41,5 +41,6 @@ class SuggestReviewersTest(unittest.TestCase): def test_basic(self): capture = OutputCapture() step = SuggestReviewers(MockTool(), MockOptions(suggest_reviewers=True, git_commit=None)) - expected_stdout = "The following reviewers have recently modified files in your patch:\nFoo Bar\nWould you like to CC them?\n" - capture.assert_outputs(self, step.run, [{"bug_id": "123"}], expected_stdout=expected_stdout) + expected_stdout = "The following reviewers have recently modified files in your patch:\nFoo Bar\n" + expected_stderr = "Would you like to CC them?\n" + capture.assert_outputs(self, step.run, [{"bug_id": "123"}], expected_stdout=expected_stdout, expected_stderr=expected_stderr) diff --git a/Tools/Scripts/webkitpy/tool/steps/updatechromiumdeps.py b/Tools/Scripts/webkitpy/tool/steps/updatechromiumdeps.py new file mode 100644 index 0000000..315bbac --- /dev/null +++ b/Tools/Scripts/webkitpy/tool/steps/updatechromiumdeps.py @@ -0,0 +1,64 @@ +# Copyright (C) 2011 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import urllib2 + +from webkitpy.tool.steps.abstractstep import AbstractStep +from webkitpy.tool.steps.options import Options +from webkitpy.common.config import urls +from webkitpy.common.system.deprecated_logging import log, error + + +class UpdateChromiumDEPS(AbstractStep): + # Notice that this method throws lots of exciting exceptions! + def _fetch_last_known_good_revision(self): + return int(urllib2.urlopen(urls.chromium_lkgr_url).read()) + + def _validate_revisions(self, current_chromium_revision, new_chromium_revision): + if new_chromium_revision < current_chromium_revision: + log("Current Chromium DEPS revision %s is newer than %s." % (current_chromium_revision, new_chromium_revision)) + new_chromium_revision = self._tool.user.prompt("Enter new chromium revision (enter nothing to cancel):\n") + try: + new_chromium_revision = int(new_chromium_revision) + except ValueError, TypeError: + new_chromium_revision = None + if not new_chromium_revision: + error("Unable to update Chromium DEPS") + + + def run(self, state): + # Note that state["chromium_revision"] must be defined, but can be None. + new_chromium_revision = state["chromium_revision"] + if not new_chromium_revision: + new_chromium_revision = self._fetch_last_known_good_revision() + + deps = self._tool.checkout().chromium_deps() + current_chromium_revision = deps.read_variable("chromium_rev") + self._validate_revisions(current_chromium_revision, new_chromium_revision) + log("Updating Chromium DEPS to %s" % new_chromium_revision) + deps.write_variable("chromium_rev", new_chromium_revision) diff --git a/Tools/Scripts/webkitpy/tool/steps/validatechangelogs_unittest.py b/Tools/Scripts/webkitpy/tool/steps/validatechangelogs_unittest.py index db35a58..96bae9f 100644 --- a/Tools/Scripts/webkitpy/tool/steps/validatechangelogs_unittest.py +++ b/Tools/Scripts/webkitpy/tool/steps/validatechangelogs_unittest.py @@ -45,9 +45,8 @@ class ValidateChangeLogsTest(unittest.TestCase): diff_file.lines = [(start_line, start_line, "foo")] expected_stdout = expected_stderr = "" if should_fail and not non_interactive: - expected_stdout = "OK to continue?\n" - expected_stderr = "The diff to mock/ChangeLog looks wrong. Are you sure your ChangeLog entry is at the top of the file?\n" - result = OutputCapture().assert_outputs(self, step._check_changelog_diff, [diff_file], expected_stdout=expected_stdout, expected_stderr=expected_stderr) + expected_stderr = "The diff to mock/ChangeLog looks wrong. Are you sure your ChangeLog entry is at the top of the file?\nOK to continue?\n" + result = OutputCapture().assert_outputs(self, step._check_changelog_diff, [diff_file], expected_stderr=expected_stderr) self.assertEqual(not result, should_fail) def test_check_changelog_diff(self): diff --git a/Tools/TestWebKitAPI/JavaScriptTest.cpp b/Tools/TestWebKitAPI/JavaScriptTest.cpp new file mode 100644 index 0000000..08418c2 --- /dev/null +++ b/Tools/TestWebKitAPI/JavaScriptTest.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "JavaScriptTest.h" + +#include "PlatformUtilities.h" +#include "Test.h" +#include + +namespace TestWebKitAPI { + +struct JavaScriptCallbackContext { + JavaScriptCallbackContext(const char* expectedString) : didFinish(false), expectedString(expectedString), didMatchExpectedString(false) { } + + bool didFinish; + const char* expectedString; + bool didMatchExpectedString; +}; + +static void javaScriptCallback(WKStringRef string, WKErrorRef error, void* ctx) +{ + JavaScriptCallbackContext* context = static_cast(ctx); + + context->didFinish = true; + context->didMatchExpectedString = WKStringIsEqualToUTF8CString(string, context->expectedString); + + TEST_ASSERT(!error); +} + +static WKRetainPtr wk(const char* utf8String) +{ + return WKRetainPtr(AdoptWK, WKStringCreateWithUTF8CString(utf8String)); +} + +bool runJSTest(WKPageRef page, const char* script, const char* expectedResult) +{ + JavaScriptCallbackContext context(expectedResult); + WKPageRunJavaScriptInMainFrame(page, wk(script).get(), &context, javaScriptCallback); + Util::run(&context.didFinish); + return context.didMatchExpectedString; +} + +} // namespace TestWebKitAPI diff --git a/Tools/TestWebKitAPI/JavaScriptTest.h b/Tools/TestWebKitAPI/JavaScriptTest.h new file mode 100644 index 0000000..e01fcd5 --- /dev/null +++ b/Tools/TestWebKitAPI/JavaScriptTest.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +namespace TestWebKitAPI { + +// Executes |script| in the page and waits until it has run. Returns true if the script's output +// matches |expectedResult|, false otherwise. Asserts if an error occurs. +bool runJSTest(WKPageRef, const char* script, const char* expectedResult); + +} // namespace TestWebKitAPI diff --git a/Tools/TestWebKitAPI/PlatformWebView.h b/Tools/TestWebKitAPI/PlatformWebView.h index 43e329b..312168d 100644 --- a/Tools/TestWebKitAPI/PlatformWebView.h +++ b/Tools/TestWebKitAPI/PlatformWebView.h @@ -54,7 +54,7 @@ public: PlatformWebView(WKContextRef, WKPageGroupRef = 0); ~PlatformWebView(); - WKPageRef page(); + WKPageRef page() const; PlatformWKView platformView() const { return m_view; } void resizeTo(unsigned width, unsigned height); void focus(); diff --git a/Tools/TestWebKitAPI/Test.h b/Tools/TestWebKitAPI/Test.h index 93bfd8b..ed772d5 100644 --- a/Tools/TestWebKitAPI/Test.h +++ b/Tools/TestWebKitAPI/Test.h @@ -79,7 +79,9 @@ protected: \ void TEST_CLASS_NAME(testSuite, testCaseName)::run() -#define TEST_ASSERT(expression) do { if (!(expression)) { TestsController::shared().testFailed(__FILE__, __LINE__, #expression); return; } } while (0) +#define _TEST_ASSERT_HELPER(expression, returnStatement) do { if (!(expression)) { TestsController::shared().testFailed(__FILE__, __LINE__, #expression); returnStatement; } } while (0) +#define TEST_ASSERT(expression) _TEST_ASSERT_HELPER(expression, return) +#define TEST_ASSERT_RETURN(expression, returnValue) _TEST_ASSERT_HELPER(expression, return (returnValue)) } // namespace TestWebKitAPI diff --git a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj index 0aca686..99bb29f 100644 --- a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj +++ b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj @@ -45,6 +45,9 @@ C01A23F21266156700C9ED55 /* spacebar-scrolling.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = C02B7882126615410026BF0F /* spacebar-scrolling.html */; }; C02B77F2126612140026BF0F /* SpacebarScrolling.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C02B77F1126612140026BF0F /* SpacebarScrolling.cpp */; }; C02B7854126613AE0026BF0F /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C02B7853126613AE0026BF0F /* Carbon.framework */; }; + C0ADBE7C12FCA4D000D2C129 /* JavaScriptTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C0ADBE7A12FCA4D000D2C129 /* JavaScriptTest.cpp */; }; + C0ADBE8312FCA6AA00D2C129 /* RestoreSessionStateContainingFormData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C0ADBE8212FCA6AA00D2C129 /* RestoreSessionStateContainingFormData.cpp */; }; + C0ADBE9612FCA79B00D2C129 /* simple-form.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = C0ADBE8412FCA6B600D2C129 /* simple-form.html */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -76,6 +79,7 @@ BCBD3737125ABBEB00D2C29F /* icon.png in Copy Resources */, 1A02C870125D4CFD00E3F4BD /* find.html in Copy Resources */, BC909784125571CF00083756 /* simple.html in Copy Resources */, + C0ADBE9612FCA79B00D2C129 /* simple-form.html in Copy Resources */, C01A23F21266156700C9ED55 /* spacebar-scrolling.html in Copy Resources */, BC2D006412AA04CE00E732A3 /* file-with-anchor.html in Copy Resources */, ); @@ -132,6 +136,10 @@ C02B77F1126612140026BF0F /* SpacebarScrolling.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SpacebarScrolling.cpp; sourceTree = ""; }; C02B7853126613AE0026BF0F /* Carbon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; name = Carbon.framework; sourceTree = SDKROOT; }; C02B7882126615410026BF0F /* spacebar-scrolling.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "spacebar-scrolling.html"; sourceTree = ""; }; + C0ADBE7A12FCA4D000D2C129 /* JavaScriptTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JavaScriptTest.cpp; sourceTree = ""; }; + C0ADBE7B12FCA4D000D2C129 /* JavaScriptTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JavaScriptTest.h; sourceTree = ""; }; + C0ADBE8212FCA6AA00D2C129 /* RestoreSessionStateContainingFormData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RestoreSessionStateContainingFormData.cpp; sourceTree = ""; }; + C0ADBE8412FCA6B600D2C129 /* simple-form.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "simple-form.html"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -177,6 +185,8 @@ children = ( BCA61C3A11700B9400460D1E /* mac */, BC575944126E733C006F0F12 /* InjectedBundle */, + C0ADBE7A12FCA4D000D2C129 /* JavaScriptTest.cpp */, + C0ADBE7B12FCA4D000D2C129 /* JavaScriptTest.h */, BC131A9E1171317C00B69727 /* TestWebKitAPIPrefix.h */, BC575BBF126F5752006F0F12 /* PlatformUtilities.cpp */, BC131883117114A800B69727 /* PlatformUtilities.h */, @@ -246,6 +256,7 @@ BC909779125571AB00083756 /* PageLoadBasic.cpp */, BC2D004812A9FDFA00E732A3 /* PageLoadDidChangeLocationWithinPageForFrame.cpp */, 333B9CE11277F23100FEFCE3 /* PreventEmptyUserAgent.cpp */, + C0ADBE8212FCA6AA00D2C129 /* RestoreSessionStateContainingFormData.cpp */, C02B77F1126612140026BF0F /* SpacebarScrolling.cpp */, BC7B619A1299FE9E00D174A4 /* WKPreferences.cpp */, BC90995D12567BC100083756 /* WKString.cpp */, @@ -269,6 +280,7 @@ 1A02C84B125D4A5E00E3F4BD /* find.html */, BCBD372E125ABBE600D2C29F /* icon.png */, BC909778125571AB00083756 /* simple.html */, + C0ADBE8412FCA6B600D2C129 /* simple-form.html */, C02B7882126615410026BF0F /* spacebar-scrolling.html */, ); name = Resources; @@ -393,6 +405,8 @@ 333B9CE21277F23100FEFCE3 /* PreventEmptyUserAgent.cpp in Sources */, BC7B61AA129A038700D174A4 /* WKPreferences.cpp in Sources */, BC2D004912A9FDFA00E732A3 /* PageLoadDidChangeLocationWithinPageForFrame.cpp in Sources */, + C0ADBE7C12FCA4D000D2C129 /* JavaScriptTest.cpp in Sources */, + C0ADBE8312FCA6AA00D2C129 /* RestoreSessionStateContainingFormData.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Tools/TestWebKitAPI/Tests/WebKit2/PageLoadBasic.cpp b/Tools/TestWebKitAPI/Tests/WebKit2/PageLoadBasic.cpp index 98a636c..6cd281e 100644 --- a/Tools/TestWebKitAPI/Tests/WebKit2/PageLoadBasic.cpp +++ b/Tools/TestWebKitAPI/Tests/WebKit2/PageLoadBasic.cpp @@ -87,7 +87,7 @@ static void didFinishLoadForFrame(WKPageRef page, WKFrameRef frame, WKTypeRef us test1Done = true; } -static void decidePolicyForNavigationAction(WKPageRef page, WKFrameNavigationType navigationType, WKEventModifiers modifiers, WKEventMouseButton mouseButton, WKURLRef url, WKFrameRef frame, WKFramePolicyListenerRef listener, const void* clientInfo) +static void decidePolicyForNavigationAction(WKPageRef page, WKFrameRef frame, WKFrameNavigationType navigationType, WKEventModifiers modifiers, WKEventMouseButton mouseButton, WKURLRequestRef request, WKFramePolicyListenerRef listener, WKTypeRef userData, const void* clientInfo) { State* state = reinterpret_cast(const_cast(clientInfo)); TEST_ASSERT(!state->didStartProvisionalLoadForFrame); @@ -98,12 +98,12 @@ static void decidePolicyForNavigationAction(WKPageRef page, WKFrameNavigationTyp WKFramePolicyListenerUse(listener); } -static void decidePolicyForNewWindowAction(WKPageRef page, WKFrameNavigationType navigationType, WKEventModifiers modifiers, WKEventMouseButton mouseButton, WKURLRef url, WKFrameRef frame, WKFramePolicyListenerRef listener, const void* clientInfo) +static void decidePolicyForNewWindowAction(WKPageRef page, WKFrameRef frame, WKFrameNavigationType navigationType, WKEventModifiers modifiers, WKEventMouseButton mouseButton, WKURLRequestRef request, WKStringRef frameName, WKFramePolicyListenerRef listener, WKTypeRef userData, const void* clientInfo) { WKFramePolicyListenerUse(listener); } -static void decidePolicyForMIMEType(WKPageRef page, WKStringRef MIMEType, WKURLRef url, WKFrameRef frame, WKFramePolicyListenerRef listener, const void* clientInfo) +static void decidePolicyForMIMEType(WKPageRef page, WKFrameRef frame, WKStringRef MIMEType, WKURLRequestRef request, WKFramePolicyListenerRef listener, WKTypeRef userData, const void* clientInfo) { WKFramePolicyListenerUse(listener); } diff --git a/Tools/TestWebKitAPI/Tests/WebKit2/RestoreSessionStateContainingFormData.cpp b/Tools/TestWebKitAPI/Tests/WebKit2/RestoreSessionStateContainingFormData.cpp new file mode 100644 index 0000000..7c08735 --- /dev/null +++ b/Tools/TestWebKitAPI/Tests/WebKit2/RestoreSessionStateContainingFormData.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "Test.h" + +#include "JavaScriptTest.h" +#include "PlatformUtilities.h" +#include "PlatformWebView.h" + +namespace TestWebKitAPI { + +static bool didFinishLoad; + +static void didFinishLoadForFrame(WKPageRef, WKFrameRef, WKTypeRef, const void*) +{ + didFinishLoad = true; +} + +static void setPageLoaderClient(WKPageRef page) +{ + WKPageLoaderClient loaderClient; + memset(&loaderClient, 0, sizeof(loaderClient)); + loaderClient.version = 0; + loaderClient.didFinishLoadForFrame = didFinishLoadForFrame; + + WKPageSetPageLoaderClient(page, &loaderClient); +} + +static WKRetainPtr createSessionStateContainingFormData(WKContextRef context) +{ + PlatformWebView webView(context); + setPageLoaderClient(webView.page()); + + WKPageLoadURL(webView.page(), Util::adoptWK(Util::createURLForResource("simple-form", "html")).get()); + Util::run(&didFinishLoad); + didFinishLoad = false; + + TEST_ASSERT_RETURN(runJSTest(webView.page(), "submitForm()", "undefined"), 0); + Util::run(&didFinishLoad); + didFinishLoad = false; + + return Util::adoptWK(WKPageCopySessionState(webView.page(), 0, 0)); +} + +TEST(WebKit2, RestoreSessionStateContainingFormData) +{ + WKRetainPtr context(AdoptWK, WKContextCreate()); + + // FIXME: Once is fixed, we can move the creation of this + // PlatformWebView after the call to createSessionStaetContainingFormData. Until then, it must + // remain here to avoid a race condition between the UI and web processes. + PlatformWebView webView(context.get()); + setPageLoaderClient(webView.page()); + + WKRetainPtr data = createSessionStateContainingFormData(context.get()); + TEST_ASSERT(data); + + WKPageRestoreFromSessionState(webView.page(), data.get()); + Util::run(&didFinishLoad); + + TEST_ASSERT(WKPageCanGoBack(webView.page())); +} + +} // namespace TestWebKitAPI diff --git a/Tools/TestWebKitAPI/Tests/WebKit2/SpacebarScrolling.cpp b/Tools/TestWebKitAPI/Tests/WebKit2/SpacebarScrolling.cpp index 6d4783c..e3c6b20 100644 --- a/Tools/TestWebKitAPI/Tests/WebKit2/SpacebarScrolling.cpp +++ b/Tools/TestWebKitAPI/Tests/WebKit2/SpacebarScrolling.cpp @@ -25,20 +25,13 @@ #include "Test.h" +#include "JavaScriptTest.h" #include "PlatformUtilities.h" #include "PlatformWebView.h" #include namespace TestWebKitAPI { -struct JavaScriptCallbackContext { - JavaScriptCallbackContext(const char* expectedString) : didFinish(false), expectedString(expectedString), didMatchExpectedString(false) { } - - bool didFinish; - const char* expectedString; - bool didMatchExpectedString; -}; - static bool didFinishLoad; static bool didNotHandleKeyDownEvent; @@ -53,29 +46,6 @@ static void didNotHandleKeyEventCallback(WKPageRef, WKNativeEventPtr event, cons didNotHandleKeyDownEvent = true; } -static void javaScriptCallback(WKStringRef string, WKErrorRef error, void* ctx) -{ - JavaScriptCallbackContext* context = static_cast(ctx); - - context->didFinish = true; - context->didMatchExpectedString = WKStringIsEqualToUTF8CString(string, context->expectedString); - - TEST_ASSERT(!error); -} - -static WKRetainPtr wk(const char* utf8String) -{ - return WKRetainPtr(AdoptWK, WKStringCreateWithUTF8CString(utf8String)); -} - -static bool runJSTest(WKPageRef page, const char* script, const char* expectedResult) -{ - JavaScriptCallbackContext context(expectedResult); - WKPageRunJavaScriptInMainFrame(page, wk(script).get(), &context, javaScriptCallback); - Util::run(&context.didFinish); - return context.didMatchExpectedString; -} - TEST(WebKit2, SpacebarScrolling) { WKRetainPtr context(AdoptWK, WKContextCreate()); diff --git a/Tools/TestWebKitAPI/Tests/WebKit2/simple-form.html b/Tools/TestWebKitAPI/Tests/WebKit2/simple-form.html new file mode 100644 index 0000000..3bf1852 --- /dev/null +++ b/Tools/TestWebKitAPI/Tests/WebKit2/simple-form.html @@ -0,0 +1,11 @@ + + +
+ + +
diff --git a/Tools/TestWebKitAPI/Tests/WebKit2/win/HideFindIndicator.cpp b/Tools/TestWebKitAPI/Tests/WebKit2/win/HideFindIndicator.cpp new file mode 100644 index 0000000..40d4f41 --- /dev/null +++ b/Tools/TestWebKitAPI/Tests/WebKit2/win/HideFindIndicator.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "Test.h" + +#include "PlatformUtilities.h" +#include "PlatformWebView.h" + +namespace TestWebKitAPI { + +static bool didFinishLoad; +static bool findIndicatorCallbackWasCalled; +static HBITMAP bitmap; + +static void didFinishLoadForFrame(WKPageRef page, WKFrameRef frame, WKTypeRef userData, const void* clientInfo) +{ + didFinishLoad = true; +} + +static void findIndicatorCallback(WKViewRef, HBITMAP selectionBitmap, RECT, bool, void*) +{ + findIndicatorCallbackWasCalled = true; + bitmap = selectionBitmap; +} + +static void initialize(const PlatformWebView& webView) +{ + WKPageLoaderClient loaderClient; + memset(&loaderClient, 0, sizeof(loaderClient)); + + loaderClient.version = 0; + loaderClient.didFinishLoadForFrame = didFinishLoadForFrame; + WKPageSetPageLoaderClient(webView.page(), &loaderClient); + + WKViewSetFindIndicatorCallback(webView.platformView(), findIndicatorCallback, 0); +} + +TEST(WebKit2, HideFindIndicator) +{ + WKRetainPtr context(AdoptWK, WKContextCreate()); + PlatformWebView webView(context.get()); + initialize(webView); + + WKRetainPtr url = Util::adoptWK(Util::createURLForResource("find", "html")); + WKPageLoadURL(webView.page(), url.get()); + Util::run(&didFinishLoad); + didFinishLoad = false; + + WKPageFindString(webView.page(), Util::toWK("Hello").get(), kWKFindOptionsShowFindIndicator, 100); + Util::run(&findIndicatorCallbackWasCalled); + findIndicatorCallbackWasCalled = false; + + TEST_ASSERT(bitmap); + ::DeleteObject(bitmap); + bitmap = 0; + + WKPageHideFindUI(webView.page()); + Util::run(&findIndicatorCallbackWasCalled); + + TEST_ASSERT(!bitmap); +} + +} // namespace TestWebKitAPI diff --git a/Tools/TestWebKitAPI/Tests/WebKit2/win/ResizeViewWhileHidden.cpp b/Tools/TestWebKitAPI/Tests/WebKit2/win/ResizeViewWhileHidden.cpp new file mode 100644 index 0000000..7310e6c --- /dev/null +++ b/Tools/TestWebKitAPI/Tests/WebKit2/win/ResizeViewWhileHidden.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "Test.h" + +#include "PlatformUtilities.h" +#include "PlatformWebView.h" +#include + +namespace TestWebKitAPI { + +static bool didFinishLoad; + +static void didFinishLoadForFrame(WKPageRef, WKFrameRef, WKTypeRef, const void*) +{ + didFinishLoad = true; +} + +static void setPageLoaderClient(WKPageRef page) +{ + WKPageLoaderClient loaderClient; + memset(&loaderClient, 0, sizeof(loaderClient)); + loaderClient.version = 0; + loaderClient.didFinishLoadForFrame = didFinishLoadForFrame; + + WKPageSetPageLoaderClient(page, &loaderClient); +} + +static void flushMessages(WKPageRef page) +{ + // In order to ensure all pending messages have been handled by the UI and web processes, we + // load a URL and wait for the load to finish. + + setPageLoaderClient(page); + + WKPageLoadURL(page, Util::adoptWK(Util::createURLForResource("simple", "html")).get()); + Util::run(&didFinishLoad); + didFinishLoad = false; + + WKPageSetPageLoaderClient(page, 0); +} + +static bool timerFired; +static void CALLBACK timerCallback(HWND hwnd, UINT, UINT_PTR timerID, DWORD) +{ + ::KillTimer(hwnd, timerID); + timerFired = true; +} + +static void runForDuration(double seconds) +{ + ::SetTimer(0, 0, seconds * 1000, timerCallback); + Util::run(&timerFired); + timerFired = false; +} + +static void waitForBackingStoreUpdate(WKPageRef page) +{ + // Wait for the web process to handle the changes we just made, to perform a display (which + // happens on a timer), and to tell the UI process about the display (which updates the backing + // store). + // FIXME: It would be much less fragile (and maybe faster) to have an explicit way to wait + // until the backing store is updated. + runForDuration(0.5); + flushMessages(page); +} + +TEST(WebKit2, ResizeViewWhileHidden) +{ + WKRetainPtr context(AdoptWK, WKContextCreate()); + PlatformWebView webView(context.get()); + + HWND window = WKViewGetWindow(webView.platformView()); + + RECT originalRect; + ::GetClientRect(window, &originalRect); + RECT newRect = originalRect; + ::InflateRect(&newRect, 1, 1); + + // Show the WKView and resize it so that the WKView's backing store will be created. Ideally + // we'd have some more explicit way of forcing the backing store to be created. + ::ShowWindow(window, SW_SHOW); + webView.resizeTo(newRect.right - newRect.left, newRect.bottom - newRect.top); + + waitForBackingStoreUpdate(webView.page()); + + // Resize the window while hidden and show it again so that it will update its backing store at + // the new size. + ::ShowWindow(window, SW_HIDE); + webView.resizeTo(originalRect.right - originalRect.left, originalRect.bottom - originalRect.top); + ::ShowWindow(window, SW_SHOW); + + // Force the WKView to paint to try to trigger . + ::SendMessage(window, WM_PAINT, 0, 0); + + // In Debug builds without the fix for , the web process will assert + // at this point. + // FIXME: It would be good to have a way to check that our behavior is correct in Release + // builds, too! + waitForBackingStoreUpdate(webView.page()); +} + +} // namespace TestWebKitAPI diff --git a/Tools/TestWebKitAPI/Tests/WebKit2/win/WMPrint.cpp b/Tools/TestWebKitAPI/Tests/WebKit2/win/WMPrint.cpp new file mode 100644 index 0000000..390f161 --- /dev/null +++ b/Tools/TestWebKitAPI/Tests/WebKit2/win/WMPrint.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "Test.h" + +#include "PlatformUtilities.h" +#include "PlatformWebView.h" +#include + +namespace TestWebKitAPI { + +TEST(WebKit2, WMPrint) +{ + WKRetainPtr context(AdoptWK, WKContextCreate()); + PlatformWebView webView(context.get()); + + HWND window = WKViewGetWindow(webView.platformView()); + HDC dc = ::CreateCompatibleDC(0); + // FIXME: It would be nice to test that this actually paints the view into dc. + ::SendMessage(window, WM_PRINT, reinterpret_cast(dc), PRF_CLIENT | PRF_CHILDREN); + ::DeleteDC(dc); +} + +} // namespace TestWebKitAPI diff --git a/Tools/TestWebKitAPI/mac/PlatformWebViewMac.mm b/Tools/TestWebKitAPI/mac/PlatformWebViewMac.mm index c4f2d72..ad901d3 100644 --- a/Tools/TestWebKitAPI/mac/PlatformWebViewMac.mm +++ b/Tools/TestWebKitAPI/mac/PlatformWebViewMac.mm @@ -55,7 +55,7 @@ PlatformWebView::~PlatformWebView() [m_view release]; } -WKPageRef PlatformWebView::page() +WKPageRef PlatformWebView::page() const { return [m_view pageRef]; } diff --git a/Tools/TestWebKitAPI/win/PlatformWebViewWin.cpp b/Tools/TestWebKitAPI/win/PlatformWebViewWin.cpp index dede4b2..01a76eb 100644 --- a/Tools/TestWebKitAPI/win/PlatformWebViewWin.cpp +++ b/Tools/TestWebKitAPI/win/PlatformWebViewWin.cpp @@ -72,11 +72,16 @@ PlatformWebView::~PlatformWebView() WKRelease(m_view); } -WKPageRef PlatformWebView::page() +WKPageRef PlatformWebView::page() const { return WKViewGetPage(m_view); } +void PlatformWebView::resizeTo(unsigned width, unsigned height) +{ + ::SetWindowPos(WKViewGetWindow(m_view), 0, 0, 0, width, height, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOCOPYBITS); +} + void PlatformWebView::simulateSpacebarKeyPress() { HWND window = WKViewGetWindow(m_view); diff --git a/Tools/TestWebKitAPI/win/TestWebKitAPI.vcproj b/Tools/TestWebKitAPI/win/TestWebKitAPI.vcproj index a2412ef..0c806a8 100644 --- a/Tools/TestWebKitAPI/win/TestWebKitAPI.vcproj +++ b/Tools/TestWebKitAPI/win/TestWebKitAPI.vcproj @@ -456,6 +456,14 @@ > + + + + @@ -487,6 +495,14 @@ > + + + + @@ -502,6 +518,14 @@ + + + + diff --git a/Tools/TestWebKitAPI/win/copy-resources.cmd b/Tools/TestWebKitAPI/win/copy-resources.cmd index dc56479..b0ef3df 100755 --- a/Tools/TestWebKitAPI/win/copy-resources.cmd +++ b/Tools/TestWebKitAPI/win/copy-resources.cmd @@ -12,6 +12,7 @@ for %%f in ( ..\Tests\WebKit2\find.html ..\Tests\WebKit2\icon.png ..\Tests\WebKit2\simple.html + ..\Tests\WebKit2\simple-form.html ..\Tests\WebKit2\spacebar-scrolling.html ) do ( xcopy /y /d %%f "%ResourcesDirectory%" diff --git a/Tools/Tools.pro b/Tools/Tools.pro new file mode 100644 index 0000000..ca4ba3d --- /dev/null +++ b/Tools/Tools.pro @@ -0,0 +1,15 @@ +TEMPLATE = subdirs +CONFIG += ordered + +exists($$PWD/QtTestBrowser/QtTestBrowser.pro): SUBDIRS += QtTestBrowser/QtTestBrowser.pro +exists($$PWD/DumpRenderTree/qt/DumpRenderTree.pro): SUBDIRS += DumpRenderTree/qt/DumpRenderTree.pro +exists($$PWD/DumpRenderTree/qt/ImageDiff.pro): SUBDIRS += DumpRenderTree/qt/ImageDiff.pro + +webkit2 { + exists($$PWD/MiniBrowser/qt/MiniBrowser.pro): SUBDIRS += MiniBrowser/qt/MiniBrowser.pro + exists($$PWD/WebKitTestRunner/WebKitTestRunner.pro): SUBDIRS += WebKitTestRunner/WebKitTestRunner.pro +} + +!win32:!symbian { + exists($$PWD/DumpRenderTree/qt/TestNetscapePlugin/TestNetscapePlugin.pro): SUBDIRS += DumpRenderTree/qt/TestNetscapePlugin/TestNetscapePlugin.pro +} diff --git a/Tools/WebKitTestRunner/Configurations/Base.xcconfig b/Tools/WebKitTestRunner/Configurations/Base.xcconfig index feb7c5e..ac2ff94 100644 --- a/Tools/WebKitTestRunner/Configurations/Base.xcconfig +++ b/Tools/WebKitTestRunner/Configurations/Base.xcconfig @@ -21,7 +21,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -HEADER_SEARCH_PATHS = $(WEBCORE_PRIVATE_HEADERS_DIR)/ForwardingHeaders; +HEADER_SEARCH_PATHS = $(WEBCORE_PRIVATE_HEADERS_DIR)/ForwardingHeaders ${SRCROOT}/../../Source/JavaScriptCore/icu; FRAMEWORK_SEARCH_PATHS = $(SYSTEM_LIBRARY_DIR)/Frameworks/Quartz.framework/Frameworks $(SYSTEM_LIBRARY_DIR)/Frameworks/ApplicationServices.framework/Frameworks $(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework/Frameworks; GCC_PREPROCESSOR_DEFINITIONS = ENABLE_DASHBOARD_SUPPORT WEBKIT_VERSION_MIN_REQUIRED=WEBKIT_VERSION_LATEST; DEBUG_INFORMATION_FORMAT = dwarf diff --git a/Tools/WebKitTestRunner/DerivedSources.pro b/Tools/WebKitTestRunner/DerivedSources.pro index 1e4e461..ce7ac63 100644 --- a/Tools/WebKitTestRunner/DerivedSources.pro +++ b/Tools/WebKitTestRunner/DerivedSources.pro @@ -34,7 +34,7 @@ defineTest(addExtraCompiler) { return(true) } -SRC_ROOT_DIR = $$replace(PWD, /Tools/WebKitTestRunner, /) +SRC_ROOT_DIR = $$replace(PWD, /Tools/WebKitTestRunner, "") # Make sure forwarded headers needed by this project are present fwheader_generator.commands = perl $${SRC_ROOT_DIR}/Source/WebKit2/Scripts/generate-forwarding-headers.pl $${SRC_ROOT_DIR}/Tools/WebKitTestRunner $${OUTPUT_DIR}/include qt diff --git a/Tools/WebKitTestRunner/InjectedBundle/Bindings/LayoutTestController.idl b/Tools/WebKitTestRunner/InjectedBundle/Bindings/LayoutTestController.idl index a88a838..a331c64 100644 --- a/Tools/WebKitTestRunner/InjectedBundle/Bindings/LayoutTestController.idl +++ b/Tools/WebKitTestRunner/InjectedBundle/Bindings/LayoutTestController.idl @@ -77,8 +77,18 @@ module WTR { // Text search testing. boolean findString(in DOMString target, in object optionsArray); - // Evaluating script in a special context - [PassContext] void evaluateScriptInIsolatedWorld(in unsigned long worldID, in DOMString script) + // Evaluating script in a special context. + [PassContext] void evaluateScriptInIsolatedWorld(in unsigned long worldID, in DOMString script); + + // For Web Inspector tests + void showWebInspector(); + void closeWebInspector(); + void evaluateInWebInspector(in long callID, in DOMString script); + void setTimelineProfilingEnabled(in boolean enabled); + + void setPOSIXLocale(in DOMString locale); + + void setWillSendRequestReturnsNull(in boolean flag); }; } diff --git a/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.cpp b/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.cpp index 972a606..d042431 100644 --- a/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.cpp +++ b/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.cpp @@ -118,6 +118,11 @@ InjectedBundlePage* InjectedBundle::page() const return m_pages[0].get(); } +void InjectedBundle::resetLocalSettings() +{ + setlocale(LC_ALL, ""); +} + void InjectedBundle::didReceiveMessage(WKStringRef messageName, WKTypeRef messageBody) { if (WKStringIsEqualToUTF8CString(messageName, "BeginTest")) { @@ -131,6 +136,9 @@ void InjectedBundle::didReceiveMessage(WKStringRef messageName, WKTypeRef messag return; } else if (WKStringIsEqualToUTF8CString(messageName, "Reset")) { m_state = Idle; + + resetLocalSettings(); + return; } diff --git a/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.h b/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.h index 9778441..741ade4 100644 --- a/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.h +++ b/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.h @@ -82,6 +82,8 @@ private: void didInitializePageGroup(WKBundlePageGroupRef); void didReceiveMessage(WKStringRef messageName, WKTypeRef messageBody); + void resetLocalSettings(); + void beginTesting(); WKBundleRef m_bundle; diff --git a/Tools/WebKitTestRunner/InjectedBundle/InjectedBundlePage.cpp b/Tools/WebKitTestRunner/InjectedBundle/InjectedBundlePage.cpp index c5f4909..739da3b 100644 --- a/Tools/WebKitTestRunner/InjectedBundle/InjectedBundlePage.cpp +++ b/Tools/WebKitTestRunner/InjectedBundle/InjectedBundlePage.cpp @@ -29,6 +29,7 @@ #include "StringFunctions.h" #include #include +#include #include #include #include @@ -36,6 +37,7 @@ #include #include #include +#include using namespace std; @@ -190,9 +192,20 @@ InjectedBundlePage::InjectedBundlePage(WKBundlePageRef page) didCancelClientRedirectForFrame, willPerformClientRedirectForFrame, didHandleOnloadEventsForFrame, - shouldLoadResourceForFrame }; - WKBundlePageSetLoaderClient(m_page, &loaderClient); + WKBundlePageSetPageLoaderClient(m_page, &loaderClient); + + WKBundlePageResourceLoadClient resourceLoadClient = { + 0, + this, + didInitiateLoadForResource, + willSendRequestForFrame, + didReceiveResponseForResource, + didReceiveContentLengthForResource, + didFinishLoadForResource, + didFailLoadForResource + }; + WKBundlePageSetResourceLoadClient(m_page, &resourceLoadClient); WKBundlePageUIClient uiClient = { 0, @@ -204,6 +217,7 @@ InjectedBundlePage::InjectedBundlePage(WKBundlePageRef page) willRunJavaScriptPrompt, 0, /*mouseDidMoveOverElement*/ 0, /*pageDidScroll*/ + 0, /*paintCustomOverhangArea*/ }; WKBundlePageSetUIClient(m_page, &uiClient); @@ -321,11 +335,35 @@ void InjectedBundlePage::didRunInsecureContentForFrame(WKBundlePageRef page, WKB static_cast(const_cast(clientInfo))->didRunInsecureContentForFrame(frame); } -bool InjectedBundlePage::shouldLoadResourceForFrame(WKBundlePageRef page, WKBundleFrameRef frame, WKStringRef, const void* clientInfo) +void InjectedBundlePage::didInitiateLoadForResource(WKBundlePageRef page, WKBundleFrameRef frame, uint64_t identifier, WKURLRequestRef request, bool pageLoadIsProvisional, const void* clientInfo) +{ + static_cast(const_cast(clientInfo))->didInitiateLoadForResource(page, frame, identifier, request, pageLoadIsProvisional); +} + +WKURLRequestRef InjectedBundlePage::willSendRequestForFrame(WKBundlePageRef page, WKBundleFrameRef frame, uint64_t identifier, WKURLRequestRef request, WKURLResponseRef redirectResponse, const void* clientInfo) +{ + return static_cast(const_cast(clientInfo))->willSendRequestForFrame(page, frame, identifier, request, redirectResponse); +} + +void InjectedBundlePage::didReceiveResponseForResource(WKBundlePageRef page, WKBundleFrameRef frame, uint64_t identifier, WKURLResponseRef response, const void* clientInfo) { - return static_cast(const_cast(clientInfo))->shouldLoadResourceForFrame(frame); + static_cast(const_cast(clientInfo))->didReceiveResponseForResource(page, frame, identifier, response); } +void InjectedBundlePage::didReceiveContentLengthForResource(WKBundlePageRef page, WKBundleFrameRef frame, uint64_t identifier, uint64_t length, const void* clientInfo) +{ + static_cast(const_cast(clientInfo))->didReceiveContentLengthForResource(page, frame, identifier, length); +} + +void InjectedBundlePage::didFinishLoadForResource(WKBundlePageRef page, WKBundleFrameRef frame, uint64_t identifier, const void* clientInfo) +{ + static_cast(const_cast(clientInfo))->didFinishLoadForResource(page, frame, identifier); +} + +void InjectedBundlePage::didFailLoadForResource(WKBundlePageRef page, WKBundleFrameRef frame, uint64_t identifier, WKErrorRef error, const void* clientInfo) +{ + static_cast(const_cast(clientInfo))->didFinishLoadForResource(page, frame, identifier, error); +} void InjectedBundlePage::didStartProvisionalLoadForFrame(WKBundleFrameRef frame) { @@ -574,9 +612,46 @@ void InjectedBundlePage::didRunInsecureContentForFrame(WKBundleFrameRef frame) { } -bool InjectedBundlePage::shouldLoadResourceForFrame(WKBundleFrameRef frame) +void InjectedBundlePage::didInitiateLoadForResource(WKBundlePageRef, WKBundleFrameRef, uint64_t identifier, WKURLRequestRef, bool) +{ +} + +// Resource Load Client Callbacks + +WKURLRequestRef InjectedBundlePage::willSendRequestForFrame(WKBundlePageRef, WKBundleFrameRef, uint64_t, WKURLRequestRef request, WKURLResponseRef) +{ + if (InjectedBundle::shared().isTestRunning() && InjectedBundle::shared().layoutTestController()->willSendRequestReturnsNull()) + return 0; + + string urlString = toSTD(adoptWK(WKURLCopyString(adoptWK(WKURLRequestCopyURL(request)).get()))); + WebCore::KURL url(WebCore::ParsedURLString, urlString.c_str()); + + if (!url.host().isEmpty() + && (equalIgnoringCase(url.protocol(), "http") || (equalIgnoringCase(url.protocol(), "https"))) + && (url.host() != "127.0.0.1") + && (url.host() != "255.255.255.255") // used in some tests that expect to get back an error + && (!equalIgnoringCase(url.host(), "localhost"))) { + InjectedBundle::shared().os() << "Blocked access to external URL " << urlString << "\n"; + return 0; + } + + return request; +} + +void InjectedBundlePage::didReceiveResponseForResource(WKBundlePageRef, WKBundleFrameRef, uint64_t, WKURLResponseRef) +{ +} + +void InjectedBundlePage::didReceiveContentLengthForResource(WKBundlePageRef, WKBundleFrameRef, uint64_t, uint64_t) +{ +} + +void InjectedBundlePage::didFinishLoadForResource(WKBundlePageRef, WKBundleFrameRef, uint64_t) +{ +} + +void InjectedBundlePage::didFailLoadForResource(WKBundlePageRef, WKBundleFrameRef, uint64_t, WKErrorRef) { - return true; } // UI Client Callbacks diff --git a/Tools/WebKitTestRunner/InjectedBundle/InjectedBundlePage.h b/Tools/WebKitTestRunner/InjectedBundle/InjectedBundlePage.h index 3b99109..2c3e9df 100644 --- a/Tools/WebKitTestRunner/InjectedBundle/InjectedBundlePage.h +++ b/Tools/WebKitTestRunner/InjectedBundle/InjectedBundlePage.h @@ -63,7 +63,13 @@ private: static void didHandleOnloadEventsForFrame(WKBundlePageRef, WKBundleFrameRef, const void*); static void didDisplayInsecureContentForFrame(WKBundlePageRef, WKBundleFrameRef, WKTypeRef*, const void*); static void didRunInsecureContentForFrame(WKBundlePageRef, WKBundleFrameRef, WKTypeRef*, const void*); - static bool shouldLoadResourceForFrame(WKBundlePageRef, WKBundleFrameRef, WKStringRef, const void*); + static void didInitiateLoadForResource(WKBundlePageRef, WKBundleFrameRef, uint64_t identifier, WKURLRequestRef, bool pageLoadIsProvisional, const void*); + static WKURLRequestRef willSendRequestForFrame(WKBundlePageRef, WKBundleFrameRef, uint64_t identifier, WKURLRequestRef, WKURLResponseRef, const void*); + static void didReceiveResponseForResource(WKBundlePageRef, WKBundleFrameRef, uint64_t identifier, WKURLResponseRef, const void*); + static void didReceiveContentLengthForResource(WKBundlePageRef, WKBundleFrameRef, uint64_t identifier, uint64_t length, const void*); + static void didFinishLoadForResource(WKBundlePageRef, WKBundleFrameRef, uint64_t identifier, const void*); + static void didFailLoadForResource(WKBundlePageRef, WKBundleFrameRef, uint64_t identifier, WKErrorRef, const void*); + void didStartProvisionalLoadForFrame(WKBundleFrameRef); void didReceiveServerRedirectForProvisionalLoadForFrame(WKBundleFrameRef); void didFailProvisionalLoadWithErrorForFrame(WKBundleFrameRef, WKErrorRef); @@ -79,7 +85,14 @@ private: void didHandleOnloadEventsForFrame(WKBundleFrameRef); void didDisplayInsecureContentForFrame(WKBundleFrameRef); void didRunInsecureContentForFrame(WKBundleFrameRef); - bool shouldLoadResourceForFrame(WKBundleFrameRef); + + // Resource Load Client + void didInitiateLoadForResource(WKBundlePageRef, WKBundleFrameRef, uint64_t identifier, WKURLRequestRef, bool pageLoadIsProvisional); + WKURLRequestRef willSendRequestForFrame(WKBundlePageRef, WKBundleFrameRef, uint64_t identifier, WKURLRequestRef, WKURLResponseRef); + void didReceiveResponseForResource(WKBundlePageRef, WKBundleFrameRef, uint64_t identifier, WKURLResponseRef); + void didReceiveContentLengthForResource(WKBundlePageRef, WKBundleFrameRef, uint64_t identifier, uint64_t length); + void didFinishLoadForResource(WKBundlePageRef, WKBundleFrameRef, uint64_t identifier); + void didFailLoadForResource(WKBundlePageRef, WKBundleFrameRef, uint64_t identifier, WKErrorRef); // UI Client static void willAddMessageToConsole(WKBundlePageRef, WKStringRef message, uint32_t lineNumber, const void* clientInfo); diff --git a/Tools/WebKitTestRunner/InjectedBundle/LayoutTestController.cpp b/Tools/WebKitTestRunner/InjectedBundle/LayoutTestController.cpp index 13c7b10..4707ed8 100644 --- a/Tools/WebKitTestRunner/InjectedBundle/LayoutTestController.cpp +++ b/Tools/WebKitTestRunner/InjectedBundle/LayoutTestController.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -96,6 +97,7 @@ LayoutTestController::LayoutTestController() , m_waitToDump(false) , m_testRepaint(false) , m_testRepaintSweepHorizontally(false) + , m_willSendRequestReturnsNull(false) { platformInitialize(); } @@ -309,6 +311,27 @@ void LayoutTestController::makeWindowObject(JSContextRef context, JSObjectRef wi setProperty(context, windowObject, "layoutTestController", this, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete, exception); } +void LayoutTestController::showWebInspector() +{ + WKBundleInspectorShow(WKBundlePageGetInspector(InjectedBundle::shared().page()->page())); +} + +void LayoutTestController::closeWebInspector() +{ + WKBundleInspectorClose(WKBundlePageGetInspector(InjectedBundle::shared().page()->page())); +} + +void LayoutTestController::evaluateInWebInspector(long callID, JSStringRef script) +{ + WKRetainPtr scriptWK = toWK(script); + WKBundleInspectorEvaluateScriptForTest(WKBundlePageGetInspector(InjectedBundle::shared().page()->page()), callID, scriptWK.get()); +} + +void LayoutTestController::setTimelineProfilingEnabled(bool enabled) +{ + WKBundleInspectorSetPageProfilingEnabled(WKBundlePageGetInspector(InjectedBundle::shared().page()->page()), enabled); +} + typedef WTF::HashMap > WorldMap; static WorldMap& worldMap() { @@ -349,4 +372,11 @@ void LayoutTestController::evaluateScriptInIsolatedWorld(JSContextRef context, u JSEvaluateScript(jsContext, script, 0, 0, 0, 0); } +void LayoutTestController::setPOSIXLocale(JSStringRef locale) +{ + char localeBuf[32]; + JSStringGetUTF8CString(locale, localeBuf, sizeof(localeBuf)); + setlocale(LC_ALL, localeBuf); +} + } // namespace WTR diff --git a/Tools/WebKitTestRunner/InjectedBundle/LayoutTestController.h b/Tools/WebKitTestRunner/InjectedBundle/LayoutTestController.h index 1f81970..9271124 100644 --- a/Tools/WebKitTestRunner/InjectedBundle/LayoutTestController.h +++ b/Tools/WebKitTestRunner/InjectedBundle/LayoutTestController.h @@ -126,6 +126,16 @@ public: void evaluateScriptInIsolatedWorld(JSContextRef, unsigned worldID, JSStringRef script); static unsigned worldIDForWorld(WKBundleScriptWorldRef); + void showWebInspector(); + void closeWebInspector(); + void evaluateInWebInspector(long callId, JSStringRef script); + void setTimelineProfilingEnabled(bool); + + void setPOSIXLocale(JSStringRef); + + bool willSendRequestReturnsNull() { return m_willSendRequestReturnsNull; } + void setWillSendRequestReturnsNull(bool f) { m_willSendRequestReturnsNull = f; } + private: static const double waitToDumpWatchdogTimerInterval; @@ -148,6 +158,8 @@ private: bool m_testRepaint; bool m_testRepaintSweepHorizontally; + bool m_willSendRequestReturnsNull; + PlatformTimerRef m_waitToDumpWatchdogTimer; }; diff --git a/Tools/WebKitTestRunner/InjectedBundle/qt/ActivateFontsQt.cpp b/Tools/WebKitTestRunner/InjectedBundle/qt/ActivateFontsQt.cpp index d8166d0..9c4bc68 100644 --- a/Tools/WebKitTestRunner/InjectedBundle/qt/ActivateFontsQt.cpp +++ b/Tools/WebKitTestRunner/InjectedBundle/qt/ActivateFontsQt.cpp @@ -29,10 +29,13 @@ #include "ActivateFonts.h" +#include #include #include +#include #ifdef Q_WS_X11 +#include #include #endif @@ -43,6 +46,8 @@ namespace WTR { void activateFonts() { #if defined(Q_WS_X11) + FcInit(); + static int numFonts = -1; // Some test cases may add or remove application fonts (via @font-face). @@ -78,6 +83,18 @@ void activateFonts() appFontSet = FcConfigGetFonts(config, FcSetApplication); numFonts = appFontSet->nfont; #endif + + QApplication::setGraphicsSystem("raster"); + QApplication::setStyle(new QWindowsStyle); + + QFont f("Sans Serif"); + f.setPointSize(9); + f.setWeight(QFont::Normal); + f.setStyle(QFont::StyleNormal); + QApplication::setFont(f); + + QX11Info::setAppDpiX(0, 96); + QX11Info::setAppDpiY(0, 96); } } diff --git a/Tools/WebKitTestRunner/InjectedBundle/qt/InjectedBundle.pro b/Tools/WebKitTestRunner/InjectedBundle/qt/InjectedBundle.pro index 92d31b8..24db4b8 100644 --- a/Tools/WebKitTestRunner/InjectedBundle/qt/InjectedBundle.pro +++ b/Tools/WebKitTestRunner/InjectedBundle/qt/InjectedBundle.pro @@ -43,11 +43,11 @@ HEADERS += \ OBJECTS_DIR = obj/release } -include(../../../../WebKit.pri) +include(../../../../Source/WebKit.pri) include(../../../../Source/JavaScriptCore/JavaScriptCore.pri) -addJavaScriptCoreLib(../../../../Source/JavaScriptCore) +addJavaScriptCoreLib(../../../JavaScriptCore) include(../../../../Source/WebKit2/WebKit2.pri) -addWebKit2Lib(../../../../Source/WebKit2) +addWebKit2Lib(../../../WebKit2) INCLUDEPATH += \ $$PWD \ @@ -56,6 +56,8 @@ INCLUDEPATH += \ $$PWD/../Bindings \ $$PWD/../../../../Source/JavaScriptCore \ $$PWD/../../../../Source/JavaScriptCore/wtf \ + $$PWD/../../../../Source/WebCore \ + $$PWD/../../../../Source/WebCore/platform/text \ $$PWD/../../../../Source/WebKit2 \ $$PWD/../../../../Source/WebKit2/Shared \ $$GENERATED_SOURCES_DIR diff --git a/Tools/WebKitTestRunner/TestController.cpp b/Tools/WebKitTestRunner/TestController.cpp index e856176..6f5b9a7 100644 --- a/Tools/WebKitTestRunner/TestController.cpp +++ b/Tools/WebKitTestRunner/TestController.cpp @@ -29,9 +29,10 @@ #include "StringFunctions.h" #include "TestInvocation.h" #include -#include #include +#include #include +#include #include namespace WTR { @@ -103,6 +104,13 @@ static bool runBeforeUnloadConfirmPanel(WKPageRef page, WKStringRef message, WKF return true; } +static unsigned long long exceededDatabaseQuota(WKPageRef, WKFrameRef, WKSecurityOriginRef, WKStringRef, WKStringRef, unsigned long long, unsigned long long, unsigned long long, const void*) +{ + static const unsigned long long defaultQuota = 5 * 1024 * 1024; + return defaultQuota; +} + + void TestController::runModal(WKPageRef page, const void* clientInfo) { runModal(static_cast(const_cast(clientInfo))); @@ -148,7 +156,7 @@ WKPageRef TestController::createOtherPage(WKPageRef oldPage, WKDictionaryRef, WK runBeforeUnloadConfirmPanel, 0, // didDraw 0, // pageDidScroll - 0, // exceededDatabaseQuota + exceededDatabaseQuota, 0, // runOpenPanel 0, // decidePolicyForGeolocationPermissionRequest 0, // headerHeight @@ -157,6 +165,7 @@ WKPageRef TestController::createOtherPage(WKPageRef oldPage, WKDictionaryRef, WK 0, // drawFooter 0, // printFrame runModal, + 0, // didCompleteRubberBandForMainFrame }; WKPageSetPageUIClient(newPage, &otherPageUIClient); @@ -164,6 +173,18 @@ WKPageRef TestController::createOtherPage(WKPageRef oldPage, WKDictionaryRef, WK return newPage; } +const char* TestController::libraryPathForTesting() +{ + // FIXME: This may not be sufficient to prevent interactions/crashes + // when running more than one copy of DumpRenderTree. + // See https://bugs.webkit.org/show_bug.cgi?id=10906 + char* dumpRenderTreeTemp = getenv("DUMPRENDERTREE_TEMP"); + if (dumpRenderTreeTemp) + return dumpRenderTreeTemp; + return platformLibraryPathForTesting(); +} + + void TestController::initialize(int argc, const char* argv[]) { platformInitialize(); @@ -219,6 +240,15 @@ void TestController::initialize(int argc, const char* argv[]) m_pageGroup.adopt(WKPageGroupCreateWithIdentifier(pageGroupIdentifier.get())); m_context.adopt(WKContextCreateWithInjectedBundlePath(injectedBundlePath())); + + const char* path = libraryPathForTesting(); + if (path) { + Vector databaseDirectory(strlen(path) + strlen("/Databases") + 1); + sprintf(databaseDirectory.data(), "%s%s", path, "/Databases"); + WKRetainPtr databaseDirectoryWK(AdoptWK, WKStringCreateWithUTF8CString(databaseDirectory.data())); + WKContextSetDatabaseDirectory(m_context.get(), databaseDirectoryWK.get()); + } + platformInitializeContext(); WKContextInjectedBundleClient injectedBundleClient = { @@ -259,7 +289,7 @@ void TestController::initialize(int argc, const char* argv[]) runBeforeUnloadConfirmPanel, 0, // didDraw 0, // pageDidScroll - 0, // exceededDatabaseQuota + exceededDatabaseQuota, 0, // runOpenPanel 0, // decidePolicyForGeolocationPermissionRequest 0, // headerHeight @@ -268,6 +298,7 @@ void TestController::initialize(int argc, const char* argv[]) 0, // drawFooter 0, // printFrame 0, // runModal + 0, // didCompleteRubberBandForMainFrame }; WKPageSetPageUIClient(m_mainWebView->page(), &pageUIClient); @@ -317,6 +348,8 @@ bool TestController::resetStateToConsistentValues() WKPreferencesSetXSSAuditorEnabled(preferences, false); WKPreferencesSetDeveloperExtrasEnabled(preferences, true); WKPreferencesSetJavaScriptCanOpenWindowsAutomatically(preferences, true); + WKPreferencesSetJavaScriptCanAccessClipboard(preferences, true); + WKPreferencesSetDOMPasteAllowed(preferences, true); static WKStringRef standardFontFamily = WKStringCreateWithUTF8CString("Times"); static WKStringRef cursiveFontFamily = WKStringCreateWithUTF8CString("Apple Chancery"); diff --git a/Tools/WebKitTestRunner/TestController.h b/Tools/WebKitTestRunner/TestController.h index 65305a2..fe37952 100644 --- a/Tools/WebKitTestRunner/TestController.h +++ b/Tools/WebKitTestRunner/TestController.h @@ -90,6 +90,9 @@ private: static void runModal(WKPageRef, const void* clientInfo); static void runModal(PlatformWebView*); + static const char* libraryPathForTesting(); + static const char* platformLibraryPathForTesting(); + OwnPtr m_currentInvocation; bool m_dumpPixels; diff --git a/Tools/WebKitTestRunner/TestInvocation.cpp b/Tools/WebKitTestRunner/TestInvocation.cpp index 26ecfc0..3af184a 100644 --- a/Tools/WebKitTestRunner/TestInvocation.cpp +++ b/Tools/WebKitTestRunner/TestInvocation.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -113,6 +114,11 @@ static void sizeWebViewForCurrentTest(char* pathOrURL) TestController::shared().mainWebView()->resizeTo(normalWidth, normalHeight); } +static bool shouldOpenWebInspector(const char* pathOrURL) +{ + return strstr(pathOrURL, "inspector/"); +} + void TestInvocation::invoke() { sizeWebViewForCurrentTest(m_pathOrURL); @@ -130,17 +136,18 @@ void TestInvocation::invoke() return; } + if (shouldOpenWebInspector(m_pathOrURL)) + WKInspectorShow(WKPageGetInspector(TestController::shared().mainWebView()->page())); + WKPageLoadURL(TestController::shared().mainWebView()->page(), m_url.get()); TestController::shared().runUntil(m_gotFinalMessage, TestController::LongTimeout); - if (!m_gotFinalMessage) { + if (!m_gotFinalMessage) dump("Timed out waiting for final message from web process\n"); - return; - } - if (m_error) { + else if (m_error) dump("FAIL\n"); - return; - } + + WKInspectorClose(WKPageGetInspector(TestController::shared().mainWebView()->page())); } void TestInvocation::dump(const char* stringToDump) diff --git a/Tools/WebKitTestRunner/WebKitTestRunner.xcodeproj/project.pbxproj b/Tools/WebKitTestRunner/WebKitTestRunner.xcodeproj/project.pbxproj index a15fe41..8428403 100644 --- a/Tools/WebKitTestRunner/WebKitTestRunner.xcodeproj/project.pbxproj +++ b/Tools/WebKitTestRunner/WebKitTestRunner.xcodeproj/project.pbxproj @@ -32,6 +32,8 @@ 6510A78A11EC643800410867 /* WebKitWeightWatcher700.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6510A77F11EC643800410867 /* WebKitWeightWatcher700.ttf */; }; 6510A78B11EC643800410867 /* WebKitWeightWatcher800.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6510A78011EC643800410867 /* WebKitWeightWatcher800.ttf */; }; 6510A78C11EC643800410867 /* WebKitWeightWatcher900.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6510A78111EC643800410867 /* WebKitWeightWatcher900.ttf */; }; + 65DD1D9612FFE798004B5285 /* WebCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65DD1D9512FFE798004B5285 /* WebCore.framework */; }; + 65DE432512FFE84000171BCA /* WebCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65DD1D9512FFE798004B5285 /* WebCore.framework */; }; 65EB85A011EC67CC0034D300 /* ActivateFonts.mm in Sources */ = {isa = PBXBuildFile; fileRef = 65EB859F11EC67CC0034D300 /* ActivateFonts.mm */; }; BC14E4DB120E02D000826C0C /* GCController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BC14E4D9120E02D000826C0C /* GCController.cpp */; }; BC14E4EA120E03D800826C0C /* JSGCController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BC14E4E8120E03D800826C0C /* JSGCController.cpp */; }; @@ -86,6 +88,7 @@ 6510A77F11EC643800410867 /* WebKitWeightWatcher700.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = WebKitWeightWatcher700.ttf; path = fonts/WebKitWeightWatcher700.ttf; sourceTree = ""; }; 6510A78011EC643800410867 /* WebKitWeightWatcher800.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = WebKitWeightWatcher800.ttf; path = fonts/WebKitWeightWatcher800.ttf; sourceTree = ""; }; 6510A78111EC643800410867 /* WebKitWeightWatcher900.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = WebKitWeightWatcher900.ttf; path = fonts/WebKitWeightWatcher900.ttf; sourceTree = ""; }; + 65DD1D9512FFE798004B5285 /* WebCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = WebCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 65EB859D11EC67CC0034D300 /* ActivateFonts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ActivateFonts.h; sourceTree = ""; }; 65EB859F11EC67CC0034D300 /* ActivateFonts.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ActivateFonts.mm; sourceTree = ""; }; 8DD76FA10486AA7600D96B5E /* WebKitTestRunner */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = WebKitTestRunner; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -144,6 +147,7 @@ BC7934A511906584005EA8E2 /* Cocoa.framework in Frameworks */, BC7934AC1190658C005EA8E2 /* WebKit2.framework in Frameworks */, BCDA2B9A1191051F00C3BC47 /* JavaScriptCore.framework in Frameworks */, + 65DD1D9612FFE798004B5285 /* WebCore.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -154,6 +158,7 @@ BC25193E11D15D8B002EBC01 /* Cocoa.framework in Frameworks */, BC25193F11D15D8B002EBC01 /* WebKit2.framework in Frameworks */, BC25194011D15D8B002EBC01 /* JavaScriptCore.framework in Frameworks */, + 65DE432512FFE84000171BCA /* WebCore.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -195,6 +200,7 @@ BC7934A411906584005EA8E2 /* Cocoa.framework */, BC7934AB1190658C005EA8E2 /* WebKit2.framework */, BCDA2B991191051F00C3BC47 /* JavaScriptCore.framework */, + 65DD1D9512FFE798004B5285 /* WebCore.framework */, ); name = "External Frameworks and Libraries"; sourceTree = ""; diff --git a/Tools/WebKitTestRunner/WebKitTestRunnerPrefix.h b/Tools/WebKitTestRunner/WebKitTestRunnerPrefix.h index 9d508ed..1e540cc 100644 --- a/Tools/WebKitTestRunner/WebKitTestRunnerPrefix.h +++ b/Tools/WebKitTestRunner/WebKitTestRunnerPrefix.h @@ -34,4 +34,5 @@ #define min min #endif +#include #include diff --git a/Tools/WebKitTestRunner/mac/TestControllerMac.mm b/Tools/WebKitTestRunner/mac/TestControllerMac.mm index 8107890..8ceabc1 100644 --- a/Tools/WebKitTestRunner/mac/TestControllerMac.mm +++ b/Tools/WebKitTestRunner/mac/TestControllerMac.mm @@ -71,4 +71,9 @@ void TestController::runModal(PlatformWebView* view) [NSApp runModalForWindow:window]; } +const char* TestController::platformLibraryPathForTesting() +{ + return [[@"~/Library/Application Support/DumpRenderTree" stringByExpandingTildeInPath] UTF8String]; +} + } // namespace WTR diff --git a/Tools/WebKitTestRunner/qt/TestControllerQt.cpp b/Tools/WebKitTestRunner/qt/TestControllerQt.cpp index df977fb..30a1b5c 100644 --- a/Tools/WebKitTestRunner/qt/TestControllerQt.cpp +++ b/Tools/WebKitTestRunner/qt/TestControllerQt.cpp @@ -133,6 +133,11 @@ void TestController::runModal(PlatformWebView*) // FIXME: Need to implement this to test showModalDialog. } +const char* TestController::platformLibraryPathForTesting() +{ + return 0; +} + #include "TestControllerQt.moc" } // namespace WTR diff --git a/Tools/WebKitTestRunner/qt/WebKitTestRunner.pro b/Tools/WebKitTestRunner/qt/WebKitTestRunner.pro index 73ab4b0..de648ea 100644 --- a/Tools/WebKitTestRunner/qt/WebKitTestRunner.pro +++ b/Tools/WebKitTestRunner/qt/WebKitTestRunner.pro @@ -6,7 +6,7 @@ isEmpty(OUTPUT_DIR): OUTPUT_DIR = ../../.. GENERATED_SOURCES_DIR = ../generated -include(../../../WebKit.pri) +include(../../../Source/WebKit.pri) !CONFIG(release, debug|release) { OBJECTS_DIR = obj/debug @@ -68,4 +68,4 @@ linux-* { } include(../../../Source/JavaScriptCore/JavaScriptCore.pri) -addJavaScriptCoreLib(../../../Source/JavaScriptCore) +addJavaScriptCoreLib(../../JavaScriptCore) diff --git a/Tools/WebKitTestRunner/win/TestControllerWin.cpp b/Tools/WebKitTestRunner/win/TestControllerWin.cpp index 08f188f..5af5503 100644 --- a/Tools/WebKitTestRunner/win/TestControllerWin.cpp +++ b/Tools/WebKitTestRunner/win/TestControllerWin.cpp @@ -157,4 +157,9 @@ void TestController::runModal(PlatformWebView*) // FIXME: Need to implement this to test showModalDialog. } +const char* TestController::platformLibraryPathForTesting() +{ + return 0; +} + } // namespace WTR diff --git a/Tools/wx/browser/wscript b/Tools/wx/browser/wscript index 4fb00b4..7c5aeee 100644 --- a/Tools/wx/browser/wscript +++ b/Tools/wx/browser/wscript @@ -30,7 +30,7 @@ import sys from settings import * include_paths = [os.path.join(wk_root, 'Source', 'WebCore', 'bindings', 'wx'), - os.path.join(wk_root, 'WebKit', 'wx')] + os.path.join(wk_root, 'Source', 'WebKit', 'wx')] def set_options(opt): common_set_options(opt) diff --git a/Tools/wx/build/build_utils.py b/Tools/wx/build/build_utils.py index bf440a6..569703d 100644 --- a/Tools/wx/build/build_utils.py +++ b/Tools/wx/build/build_utils.py @@ -120,7 +120,7 @@ def update_wx_deps(conf, wk_root, msvc_version='msvc2008'): sys.exit(1) # since this module is still experimental - wxpy_dir = os.path.join(wk_root, 'WebKit', 'wx', 'bindings', 'python') + wxpy_dir = os.path.join(wk_root, 'Source', 'WebKit', 'wx', 'bindings', 'python') swig_module = download_if_newer('http://wxwebkit.wxcommunity.com/downloads/deps/swig.py.txt', wxpy_dir) if swig_module: shutil.copy(os.path.join(wxpy_dir, 'swig.py.txt'), os.path.join(wxpy_dir, 'swig.py')) diff --git a/Tools/wx/build/settings.py b/Tools/wx/build/settings.py index 64c2693..2b757c4 100644 --- a/Tools/wx/build/settings.py +++ b/Tools/wx/build/settings.py @@ -86,7 +86,6 @@ jscore_dirs = [ 'interpreter', 'jit', 'parser', - 'pcre', 'profiler', 'runtime', 'wtf', @@ -102,7 +101,6 @@ webcore_dirs = [ 'Source/WebCore/bindings/cpp', 'Source/WebCore/bindings/generic', 'Source/WebCore/bindings/js', - 'Source/WebCore/bindings/js/specialization', 'Source/WebCore/bridge', 'Source/WebCore/bridge/c', 'Source/WebCore/bridge/jsc', @@ -116,6 +114,7 @@ webcore_dirs = [ 'Source/WebCore/html', 'Source/WebCore/html/canvas', 'Source/WebCore/html/parser', + 'Source/WebCore/html/shadow', 'Source/WebCore/inspector', 'Source/WebCore/loader', 'Source/WebCore/loader/appcache', -- cgit v1.1