summaryrefslogtreecommitdiff
path: root/Tools/Scripts/webkitpy
diff options
context:
space:
mode:
authorSimon Hausmann <simon.hausmann@digia.com>2012-10-17 16:21:14 +0200
committerSimon Hausmann <simon.hausmann@digia.com>2012-10-17 16:21:14 +0200
commit8995b83bcbfbb68245f779b64e5517627c6cc6ea (patch)
tree17985605dab9263cc2444bd4d45f189e142cca7c /Tools/Scripts/webkitpy
parentb9c9652036d5e9f1e29c574f40bc73a35c81ace6 (diff)
downloadqtwebkit-8995b83bcbfbb68245f779b64e5517627c6cc6ea.tar.gz
Imported WebKit commit cf4f8fc6f19b0629f51860cb2d4b25e139d07e00 (http://svn.webkit.org/repository/webkit/trunk@131592)
New snapshot that includes the build fixes for Mac OS X 10.6 and earlier as well as the previously cherry-picked changes
Diffstat (limited to 'Tools/Scripts/webkitpy')
-rw-r--r--Tools/Scripts/webkitpy/common/config/committers.py23
-rwxr-xr-xTools/Scripts/webkitpy/common/config/watchlist54
-rw-r--r--Tools/Scripts/webkitpy/common/host.py2
-rw-r--r--Tools/Scripts/webkitpy/common/host_mock.py4
-rw-r--r--Tools/Scripts/webkitpy/common/system/executive_unittest.py4
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/controllers/layout_test_finder.py8
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/controllers/manager.py3
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py11
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py6
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/models/test_expectations_unittest.py23
-rwxr-xr-xTools/Scripts/webkitpy/layout_tests/port/base.py29
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py6
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/builders.py22
-rwxr-xr-xTools/Scripts/webkitpy/layout_tests/port/chromium.py22
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium_android.py123
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium_android_unittest.py4
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py17
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py8
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium_mac_unittest.py3
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium_port_testcase.py9
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/driver.py17
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/efl.py9
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/factory_unittest.py2
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/gtk.py15
-rwxr-xr-xTools/Scripts/webkitpy/layout_tests/port/port_testcase.py27
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/qt.py18
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/qt_unittest.py22
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/server_process.py24
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/server_process_unittest.py14
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/test.py5
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/xvfbdriver.py42
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/xvfbdriver_unittest.py52
-rwxr-xr-xTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py11
-rwxr-xr-xTools/Scripts/webkitpy/layout_tests/run_webkit_tests_integrationtest.py19
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/servers/apache_http_server.py2
-rw-r--r--Tools/Scripts/webkitpy/performance_tests/perftest.py71
-rwxr-xr-xTools/Scripts/webkitpy/performance_tests/perftest_unittest.py73
-rwxr-xr-xTools/Scripts/webkitpy/performance_tests/perftestsrunner.py26
-rwxr-xr-xTools/Scripts/webkitpy/performance_tests/perftestsrunner_unittest.py8
-rw-r--r--Tools/Scripts/webkitpy/style/checker.py1
-rw-r--r--Tools/Scripts/webkitpy/style/checkers/cpp.py61
-rw-r--r--Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py341
-rw-r--r--Tools/Scripts/webkitpy/style/checkers/test_expectations.py6
-rw-r--r--Tools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py4
-rw-r--r--Tools/Scripts/webkitpy/style/main.py2
-rw-r--r--Tools/Scripts/webkitpy/test/main.py11
-rw-r--r--Tools/Scripts/webkitpy/test/main_unittest.py9
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/__init__.py41
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hixie75.py3
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hybi.py686
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/common.py5
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/dispatch.py16
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/extensions.py437
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/__init__.py10
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/_base.py15
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/draft75.py190
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/hybi.py64
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/headerparserhandler.py5
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/msgutil.py16
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/mux.py1636
-rwxr-xr-xTools/Scripts/webkitpy/thirdparty/mod_pywebsocket/standalone.py206
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/stream.py1
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/util.py31
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/download_unittest.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py120
-rwxr-xr-xTools/Scripts/webkitpy/tool/main.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/servers/gardeningserver_unittest.py4
-rw-r--r--Tools/Scripts/webkitpy/tool/servers/rebaselineserver.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/createbug.py2
69 files changed, 3593 insertions, 1174 deletions
diff --git a/Tools/Scripts/webkitpy/common/config/committers.py b/Tools/Scripts/webkitpy/common/config/committers.py
index e98b25936..1c231d37a 100644
--- a/Tools/Scripts/webkitpy/common/config/committers.py
+++ b/Tools/Scripts/webkitpy/common/config/committers.py
@@ -108,18 +108,17 @@ watchers_who_are_not_contributors = [
contributors_who_are_not_committers = [
Contributor("Aharon Lanin", "aharon@google.com"),
- Contributor("Alan Stearns", "stearns@adobe.com", 'astearns'),
+ Contributor("Alan Stearns", "stearns@adobe.com", "astearns"),
Contributor("Alexey Marinichev", ["amarinichev@chromium.org", "amarinichev@google.com"], "amarinichev"),
Contributor("Andras Piroska", "pandras@inf.u-szeged.hu", "andris88"),
Contributor("Andrei Bucur", "abucur@adobe.com", "abucur"),
Contributor("Anne van Kesteren", "annevankesteren+webkit@gmail.com", "annevk"),
Contributor("Annie Sullivan", "sullivan@chromium.org", "annie"),
Contributor("Aryeh Gregor", "ayg@aryeh.name", "AryehGregor"),
- Contributor("Balazs Ankes", "bank@inf.u-szeged.hu", 'abalazs'),
+ Contributor("Balazs Ankes", "bank@inf.u-szeged.hu", "abalazs"),
Contributor("Brian Salomon", "bsalomon@google.com"),
Contributor("Commit Queue", "commit-queue@webkit.org"),
Contributor("Daniel Sievers", "sievers@chromium.org"),
- Contributor("David Barr", "davidbarr@chromium.org", "barrbrain"),
Contributor("David Dorwin", "ddorwin@chromium.org", "ddorwin"),
Contributor("David Reveman", "reveman@chromium.org", "reveman"),
Contributor("Dongsung Huang", "luxtella@company100.net", "Huang"),
@@ -136,8 +135,8 @@ contributors_who_are_not_committers = [
Contributor("Gregg Tavares", ["gman@google.com", "gman@chromium.org"], "gman"),
Contributor("Hao Zheng", "zhenghao@chromium.org"),
Contributor("Ian Hickson", "ian@hixie.ch", "hixie"),
- Contributor("Janos Badics", "jbadics@inf.u-szeged.hu", 'dicska'),
- Contributor("Jonathan Backer", "backer@chromium.org", 'backer'),
+ Contributor("Janos Badics", "jbadics@inf.u-szeged.hu", "dicska"),
+ Contributor("Jonathan Backer", "backer@chromium.org", "backer"),
Contributor("Jeff Timanus", ["twiz@chromium.org", "twiz@google.com"], "twiz"),
Contributor("Jing Zhao", "jingzhao@chromium.org"),
Contributor("John Bates", ["jbates@google.com", "jbates@chromium.org"], "jbates"),
@@ -151,7 +150,7 @@ contributors_who_are_not_committers = [
Contributor("Oliver Varga", ["voliver@inf.u-szeged.hu", "Varga.Oliver@stud.u-szeged.hu"], "TwistO"),
Contributor("Peter Gal", "galpeter@inf.u-szeged.hu", "elecro"),
Contributor("Peter Linss", "peter.linss@hp.com", "plinss"),
- Contributor("Pravin D", "pravind.2k4@gmail.com", 'pravind'),
+ Contributor("Pravin D", "pravind.2k4@gmail.com", "pravind"),
Contributor("Radar WebKit Bug Importer", "webkit-bug-importer@group.apple.com"),
Contributor("Raul Hudea", "rhudea@adobe.com", "rhudea"),
Contributor("Roland Takacs", "rtakacs@inf.u-szeged.hu", "rtakacs"),
@@ -179,7 +178,7 @@ contributors_who_are_not_committers = [
committers_unable_to_review = [
Committer("Aaron Boodman", "aa@chromium.org", "aboodman"),
Committer("Adam Bergkvist", "adam.bergkvist@ericsson.com", "adambe"),
- Committer("Adam Kallai", "kadam@inf.u-szeged.hu", 'kadam'),
+ Committer("Adam Kallai", "kadam@inf.u-szeged.hu", "kadam"),
Committer("Adam Klein", "adamk@chromium.org", "aklein"),
Committer("Adam Langley", "agl@chromium.org", "agl"),
Committer("Ademar de Souza Reis Jr", ["ademar.reis@gmail.com", "ademar@webkit.org"], "ademar"),
@@ -216,7 +215,6 @@ committers_unable_to_review = [
Committer("Benjamin Otte", ["otte@gnome.org", "otte@webkit.org"], "otte"),
Committer("Bill Budge", ["bbudge@chromium.org", "bbudge@gmail.com"], "bbudge"),
Committer("Brett Wilson", "brettw@chromium.org", "brettx"),
- Committer("Caio Marcelo de Oliveira Filho", ["cmarcelo@webkit.org", "caio.oliveira@openbossa.org"], "cmarcelo"),
Committer("Cameron McCormack", ["cam@mcc.id.au", "cam@webkit.org"], "heycam"),
Committer("Carol Szabo", ["carol@webkit.org", "carol.szabo@nokia.com"], "cszabo1"),
Committer("Cary Clark", ["caryclark@google.com", "caryclark@chromium.org"], "caryclark"),
@@ -233,9 +231,11 @@ committers_unable_to_review = [
Committer("Daniel Cheng", "dcheng@chromium.org", "dcheng"),
Committer("Dave Barton", "dbarton@mathscribe.com", "dbarton"),
Committer("Dave Tharp", "dtharp@codeaurora.org", "dtharp"),
+ Committer("David Michael Barr", ["davidbarr@chromium.org", "davidbarr@google.com", "b@rr-dav.id.au"], "barrbrain"),
Committer("David Grogan", ["dgrogan@chromium.org", "dgrogan@google.com"], "dgrogan"),
Committer("David Smith", ["catfish.man@gmail.com", "dsmith@webkit.org"], "catfishman"),
Committer("Diego Gonzalez", ["diegohcg@webkit.org", "diego.gonzalez@openbossa.org"], "diegohcg"),
+ Committer("Dinu Jacob", "dinu.s.jacob@intel.com", "dsjacob"),
Committer("Dmitry Lomov", ["dslomov@google.com", "dslomov@chromium.org"], "dslomov"),
Committer("Dominic Cooney", ["dominicc@chromium.org", "dominicc@google.com"], "dominicc"),
Committer("Dominic Mazzoni", ["dmazzoni@google.com", "dmazzoni@chromium.org"], "dmazzoni"),
@@ -243,7 +243,6 @@ committers_unable_to_review = [
Committer("Drew Wilson", "atwilson@chromium.org", "atwilson"),
Committer("Eli Fidler", ["eli@staikos.net", "efidler@rim.com"], "efidler"),
Committer("Elliot Poger", "epoger@chromium.org", "epoger"),
- Committer("Emil A Eklund", "eae@chromium.org", "eae"),
Committer("Erik Arvidsson", "arv@chromium.org", "arv"),
Committer("Eric Roman", "eroman@chromium.org", "eroman"),
Committer("Eric Uhrhane", "ericu@chromium.org", "ericu"),
@@ -299,6 +298,7 @@ committers_unable_to_review = [
Committer("Joshua Bell", ["jsbell@chromium.org", "jsbell@google.com"], "jsbell"),
Committer("Julie Parent", ["jparent@google.com", "jparent@chromium.org"], "jparent"),
Committer("Jungshik Shin", "jshin@chromium.org"),
+ Committer("Justin Novosad", ["junov@google.com", "junov@chromium.org"], "junov"),
Committer("Justin Schuh", "jschuh@chromium.org", "jschuh"),
Committer("Kaustubh Atrawalkar", ["kaustubh@motorola.com"], "silverroots"),
Committer("Keishi Hattori", "keishi@webkit.org", "keishi"),
@@ -378,6 +378,7 @@ committers_unable_to_review = [
Committer("Siddharth Mathur", "siddharth.mathur@nokia.com", "simathur"),
Committer("Stephen Chenney", "schenney@chromium.org", "schenney"),
Committer("Steve Lacey", "sjl@chromium.org", "stevela"),
+ Committer("Taiju Tsuiki", "tzik@chromium.org", "tzik"),
Committer("Takashi Toyoshima", "toyoshim@chromium.org", "toyoshim"),
Committer("Thomas Sepez", "tsepez@chromium.org", "tsepez"),
Committer("Tom Hudson", ["tomhudson@google.com", "tomhudson@chromium.org"], "tomhudson"),
@@ -441,6 +442,7 @@ reviewers_list = [
Reviewer("Brady Eidson", "beidson@apple.com", "bradee-oh"),
Reviewer("Brent Fulgham", "bfulgham@webkit.org", "bfulgham"),
Reviewer("Brian Weinstein", "bweinstein@apple.com", "bweinstein"),
+ Reviewer("Caio Marcelo de Oliveira Filho", ["cmarcelo@webkit.org", "caio.oliveira@openbossa.org"], "cmarcelo"),
Reviewer("Cameron Zwarich", ["zwarich@apple.com", "cwzwarich@apple.com", "cwzwarich@webkit.org"]),
Reviewer("Carlos Garcia Campos", ["cgarcia@igalia.com", "carlosgc@gnome.org", "carlosgc@webkit.org"], "KaL"),
Reviewer("Chang Shu", ["cshu@webkit.org", "c.shu@sisa.samsung.com"], "cshu"),
@@ -465,6 +467,7 @@ reviewers_list = [
Reviewer("Dmitry Titov", "dimich@chromium.org", "dimich"),
Reviewer("Don Melton", "gramps@apple.com", "gramps"),
Reviewer("Dumitru Daniliuc", "dumi@chromium.org", "dumi"),
+ Reviewer("Emil A Eklund", "eae@chromium.org", "eae"),
Reviewer("Enrica Casucci", "enrica@apple.com", "enrica"),
Reviewer("Eric Carlson", "eric.carlson@apple.com", "eric_carlson"),
Reviewer("Eric Seidel", "eric@webkit.org", "eseidel"),
@@ -497,7 +500,7 @@ reviewers_list = [
Reviewer("Kevin McCullough", "kmccullough@apple.com", "maculloch"),
Reviewer("Kevin Ollivier", ["kevino@theolliviers.com", "kevino@webkit.org"], "kollivier"),
Reviewer("Lars Knoll", ["lars@trolltech.com", "lars@kde.org", "lars.knoll@nokia.com"], "lars"),
- Reviewer("Laszlo Gombos", "laszlo.1.gombos@nokia.com", "lgombos"),
+ Reviewer("Laszlo Gombos", ["laszlo.gombos@webkit.org", "l.gombos@samsung.com", "laszlo.1.gombos@nokia.com"], "lgombos"),
Reviewer("Levi Weintraub", ["leviw@chromium.org", "leviw@google.com", "lweintraub@apple.com"], "leviw"),
Reviewer("Luiz Agostini", ["luiz@webkit.org", "luiz.agostini@openbossa.org"], "lca"),
Reviewer("Maciej Stachowiak", "mjs@apple.com", "othermaciej"),
diff --git a/Tools/Scripts/webkitpy/common/config/watchlist b/Tools/Scripts/webkitpy/common/config/watchlist
index 2ab16b0c2..0f3dd65a1 100755
--- a/Tools/Scripts/webkitpy/common/config/watchlist
+++ b/Tools/Scripts/webkitpy/common/config/watchlist
@@ -56,6 +56,15 @@
"webkitpy": {
"filename": r"Tools/Scripts/webkitpy/",
},
+ "webkitperl": {
+ "filename": r"Tools/Scripts/webkitperl/"
+ r"|Tools/Scripts/webkitdirs.pm"
+ r"|Tools/Scripts/VCSUtils.pm"
+ r"|Tools/Scripts/test-webkitperl",
+ },
+ "SVNScripts": {
+ "filename": r"Tools/Scripts/svn-.*",
+ },
"TestFailures": {
"filename": r"Tools/BuildSlaveSupport/build.webkit.org-config/public_html/TestFailures/",
},
@@ -64,6 +73,9 @@
"less": r"[Ss]ecurityOrigin(?!\.(h|cpp))",
"filename": r"XSS|[Ss]ecurity",
},
+ "XSS": {
+ "filename": r".*XSS",
+ },
"SkiaGraphics": {
"filename": r"Source/WebCore/platform/graphics/skia/"
r"|Source/WebCore/platform/graphics/filters/skia/",
@@ -103,8 +115,8 @@
},
"QtGraphics": {
"filename": r"Source/WebCore/platform/graphics/qt/"
- r"|Source/WebKit2/WebProcess/WebPage/LayerTreeCoordinator/"
- r"|Source/WebKit2/UIProcess/WebLayerTreeRenderer(?!\.(h|cpp))",
+ r"|Source/WebKit2/WebProcess/WebPage/CoordinatedGraphics/"
+ r"|Source/WebKit2/UIProcess/CoordinatedGraphics",
},
"TextureMapper": {
"filename": r"Source/WebCore/platform/graphics/texmap/",
@@ -232,10 +244,27 @@
r"|Source/WebCore/svg/graphics/filters"
r"|Source/WebCore/svg/graphics/.*Filter.*",
},
+ "TouchAdjustment": {
+ "filename": r"Source/WebCore/page/TouchAdjustment.*"
+ r"|LayoutTests/touchadjustment"
+ r"|Source/WebKit/blackberry/WebKitSupport/FatFingers.*",
+ },
"SVG": {
"filename": r"Source/WebCore/svg"
r"|Source/WebCore/rendering/svg",
},
+ "WebInspectorProtocol": {
+ "filename": r"Source/WebCore/inspector/Inspector.json",
+ },
+ "WebSocket": {
+ "filename": r"Source/WebCore/Modules/websockets"
+ r"|Source/WebCore/platform/network/(|.+/)SocketStream.*",
+ },
+ "MediaStream": {
+ "filename": r"Source/WebCore/Modules/mediastream"
+ r"|Source/WebCore/platform/mediastream"
+ r"|LayoutTests/fast/mediastream",
+ },
},
"CC_RULES": {
# Note: All email addresses listed must be registered with bugzilla.
@@ -243,9 +272,9 @@
# two different accounts as far as bugzilla is concerned.
"AppleMacPublicApi": [ "timothy@apple.com" ],
"Battery": [ "gyuyoung.kim@samsung.com" ],
- "BlackBerry": [ "mifenton@rim.com", "rwlbuis@gmail.com" ],
+ "BlackBerry": [ "mifenton@rim.com", "rwlbuis@gmail.com", "tonikitoo@webkit.org" ],
"CMake": [ "rakuco@webkit.org", "gyuyoung.kim@samsung.com" ],
- "CSS": [ "alexis@webkit.org", "macpherson@chromium.org", "cmarcelo@webkit.org" ],
+ "CSS": [ "alexis@webkit.org", "macpherson@chromium.org", "cmarcelo@webkit.org", "allan.jensen@digia.com" ],
"ChromiumGraphics": [ "jamesr@chromium.org", "cc-bugs@chromium.org" ],
"ChromiumPublicApi": [ "abarth@webkit.org", "dglazkov@chromium.org", "fishd@chromium.org", "jamesr@chromium.org", "tkent+wkapi@chromium.org" ],
"DOMAttributes": [ "cmarcelo@webkit.org", ],
@@ -261,29 +290,36 @@
"Loader": [ "japhet@chromium.org" ],
"MathML": [ "dbarton@mathscribe.com" ],
"Media": [ "feature-media-reviews@chromium.org", "eric.carlson@apple.com" ],
+ "MediaStream": [ "tommyw@google.com" ],
"NetworkInfo": [ "gyuyoung.kim@samsung.com" ],
"OpenGL" : [ "noam.rosenthal@nokia.com", "dino@apple.com" ],
- "SkiaGraphics": [ "senorblanco@chromium.org" ],
"QtBuildSystem" : [ "vestbo@webkit.org", "abecsi@webkit.org" ],
"QtGraphics" : [ "noam.rosenthal@nokia.com" ],
- "QtWebKit2PlatformSpecific": [ "alexis@webkit.org", "zoltan@webkit.org", "cmarcelo@webkit.org", "abecsi@webkit.org" ],
- "QtWebKit2PublicAPI": [ "alexis@webkit.org", "zoltan@webkit.org", "cmarcelo@webkit.org", "abecsi@webkit.org" ],
+ "QtWebKit2PlatformSpecific": [ "alexis@webkit.org", "cmarcelo@webkit.org", "abecsi@webkit.org" ],
+ "QtWebKit2PublicAPI": [ "alexis@webkit.org", "cmarcelo@webkit.org", "abecsi@webkit.org" ],
"Rendering": [ "eric@webkit.org" ],
+ "SVG": ["schenney@chromium.org", "pdr@google.com", "fmalita@chromium.org" ],
+ "SVNScripts": [ "dbates@webkit.org" ],
"ScrollingCoordinator": [ "andersca@apple.com", "jamesr@chromium.org", "tonikitoo@webkit.org" ],
"SecurityCritical": [ "abarth@webkit.org" ],
+ "SkiaGraphics": [ "senorblanco@chromium.org" ],
"SoupNetwork": [ "rakuco@webkit.org", "gns@gnome.org", "mrobinson@webkit.org", "danw@gnome.org" ],
"StyleChecker": [ "levin@chromium.org", ],
- "SVG": ["schenney@chromium.org", "pdr@google.com", "fmalita@chromium.org" ],
"TestFailures": [ "abarth@webkit.org", "dglazkov@chromium.org" ],
"TextureMapper" : [ "noam.rosenthal@nokia.com" ],
"ThreadingFiles|ThreadingUsage": [ "levin+threading@chromium.org", ],
+ "TouchAdjustment" : [ "allan.jensen@digia.com" ],
"V8Bindings|BindingsScripts": [ "abarth@webkit.org", "japhet@chromium.org", "haraken@chromium.org" ],
+ "WTF": [ "benjamin@webkit.org",],
"WatchListScript": [ "levin+watchlist@chromium.org", ],
"WebGL": [ "dino@apple.com" ],
"WebIDL": [ "abarth@webkit.org", "ojan@chromium.org" ],
+ "WebInspectorProtocol": [ "timothy@apple.com", ],
"WebKitGTKTranslations": [ "gns@gnome.org", "mrobinson@webkit.org" ],
+ "WebSocket": [ "yutak@chromium.org" ],
+ "XSS": [ "dbates@webkit.org" ],
+ "webkitperl": [ "dbates@webkit.org" ],
"webkitpy": [ "abarth@webkit.org", "ojan@chromium.org", "dpranke@chromium.org" ],
- "WTF": [ "benjamin@webkit.org",],
},
"MESSAGE_RULES": {
"ChromiumPublicApi": [ "Please wait for approval from abarth@webkit.org, dglazkov@chromium.org, "
diff --git a/Tools/Scripts/webkitpy/common/host.py b/Tools/Scripts/webkitpy/common/host.py
index 53889657b..7dd5ad024 100644
--- a/Tools/Scripts/webkitpy/common/host.py
+++ b/Tools/Scripts/webkitpy/common/host.py
@@ -125,7 +125,7 @@ class Host(SystemHost):
except OSError, e:
_log.debug('Failed to engage git.bat Windows hack.')
- def _initialize_scm(self, patch_directories=None):
+ def initialize_scm(self, patch_directories=None):
if sys.platform == "win32":
self._engage_awesome_windows_hacks()
detector = SCMDetector(self.filesystem, self.executive)
diff --git a/Tools/Scripts/webkitpy/common/host_mock.py b/Tools/Scripts/webkitpy/common/host_mock.py
index ca4f78eb3..8b508bf8f 100644
--- a/Tools/Scripts/webkitpy/common/host_mock.py
+++ b/Tools/Scripts/webkitpy/common/host_mock.py
@@ -50,7 +50,7 @@ class MockHost(MockSystemHost):
# FIXME: we should never initialize the SCM by default, since the real
# object doesn't either. This has caused at least one bug (see bug 89498).
if initialize_scm_by_default:
- self._initialize_scm()
+ self.initialize_scm()
self.bugs = MockBugzilla()
self.buildbot = MockBuildBot()
self._chromium_buildbot = MockBuildBot()
@@ -61,7 +61,7 @@ class MockHost(MockSystemHost):
self._watch_list = MockWatchList()
- def _initialize_scm(self, patch_directories=None):
+ def initialize_scm(self, patch_directories=None):
self._scm = MockSCM(filesystem=self.filesystem, executive=self.executive)
# Various pieces of code (wrongly) call filesystem.chdir(checkout_root).
# Making the checkout_root exist in the mock filesystem makes that chdir not raise.
diff --git a/Tools/Scripts/webkitpy/common/system/executive_unittest.py b/Tools/Scripts/webkitpy/common/system/executive_unittest.py
index 8a322e480..57c557369 100644
--- a/Tools/Scripts/webkitpy/common/system/executive_unittest.py
+++ b/Tools/Scripts/webkitpy/common/system/executive_unittest.py
@@ -162,6 +162,10 @@ class ExecutiveTest(unittest.TestCase):
# FIXME: https://bugs.webkit.org/show_bug.cgi?id=54790
# We seem to get either 0 or 1 here for some reason.
self.assertTrue(process.wait() in (0, 1))
+ elif sys.platform == "cygwin":
+ # FIXME: https://bugs.webkit.org/show_bug.cgi?id=98196
+ # cygwin seems to give us either SIGABRT or SIGKILL
+ self.assertTrue(process.wait() in (-signal.SIGABRT, -signal.SIGKILL))
else:
expected_exit_code = -signal.SIGKILL
self.assertEqual(process.wait(), expected_exit_code)
diff --git a/Tools/Scripts/webkitpy/layout_tests/controllers/layout_test_finder.py b/Tools/Scripts/webkitpy/layout_tests/controllers/layout_test_finder.py
index a7b49831c..6447c8fb4 100644
--- a/Tools/Scripts/webkitpy/layout_tests/controllers/layout_test_finder.py
+++ b/Tools/Scripts/webkitpy/layout_tests/controllers/layout_test_finder.py
@@ -106,11 +106,9 @@ class LayoutTestFinder(object):
tests_to_skip = all_tests - tests_to_skip
elif self._options.skipped == 'ignore':
tests_to_skip = set()
- elif self._options.skipped == 'default':
- pass # listed for completeness
-
- # make sure we're explicitly running any tests passed on the command line.
- tests_to_skip -= paths
+ elif self._options.skipped != 'always':
+ # make sure we're explicitly running any tests passed on the command line; equivalent to 'default'.
+ tests_to_skip -= paths
# unless of course we don't want to run the HTTP tests :)
if not self._options.http:
diff --git a/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py b/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py
index d06ed7153..c0a70e615 100644
--- a/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py
+++ b/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py
@@ -239,8 +239,9 @@ def summarize_results(port_obj, expectations, result_summary, retry_summary, tes
try:
# We only use the svn revision for using trac links in the results.html file,
# Don't do this by default since it takes >100ms.
+ # FIXME: Do we really need to populate this both here and in the json_results_generator?
if use_trac_links_in_results_html(port_obj):
- port_obj.host._initialize_scm()
+ port_obj.host.initialize_scm()
results['revision'] = port_obj.host.scm().head_svn_revision()
except Exception, e:
_log.warn("Failed to determine svn revision for checkout (cwd: %s, webkit_base: %s), leaving 'revision' key blank in full_results.json.\n%s" % (port_obj._filesystem.getcwd(), port_obj.path_from_webkit_base(), e))
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 c7d7c3eb7..73834f0ad 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
@@ -281,7 +281,8 @@ class JSONResultsGeneratorBase(object):
results_for_builder = results_json[builder_name]
- self._insert_generic_metadata(results_for_builder)
+ if builder_name:
+ self._insert_generic_metadata(results_for_builder)
self._insert_failure_summaries(results_for_builder)
@@ -377,15 +378,17 @@ class JSONResultsGeneratorBase(object):
return self.__class__.PASS_RESULT
- # FIXME: Callers should use scm.py instead.
- # FIXME: Identify and fix the run-time errors that were observed on Windows
- # chromium buildbot when we had updated this code to use scm.py once before.
def _get_svn_revision(self, in_directory):
"""Returns the svn revision for the given directory.
Args:
in_directory: The directory where svn is to be run.
"""
+
+ # FIXME: We initialize this here in order to engage the stupid windows hacks :).
+ # We can't reuse an existing scm object because the specific directories may
+ # be part of other checkouts.
+ self._port.host.initialize_scm()
scm = SCMDetector(self._filesystem, self._executive).detect_scm_system(in_directory)
if scm:
return scm.svn_revision(in_directory)
diff --git a/Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py b/Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py
index 42b518f7f..b48c5b933 100644
--- a/Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py
+++ b/Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py
@@ -155,7 +155,7 @@ class TestExpectationParser(object):
else:
parsed_specifiers.add(modifier)
- if not expectation_line.parsed_bug_modifiers and not has_wontfix and not has_bugid:
+ if not expectation_line.parsed_bug_modifiers and not has_wontfix and not has_bugid and self._port.warn_if_bug_missing_in_test_expectations():
expectation_line.warnings.append(self.MISSING_BUG_WARNING)
if self._allow_rebaseline_modifier and self.REBASELINE_MODIFIER in modifiers:
@@ -869,7 +869,7 @@ class TestExpectations(object):
return self._model
def get_rebaselining_failures(self):
- return self._model.get_test_set(REBASELINE, IMAGE) | self._model.get_test_set(REBASELINE, FAIL)
+ return self._model.get_test_set(REBASELINE)
# FIXME: Change the callsites to use TestExpectationsModel and remove.
def get_expectations(self, test):
@@ -970,7 +970,7 @@ class TestExpectations(object):
expectation.name in except_these_tests and
'rebaseline' in expectation.parsed_modifiers))
- return self.list_to_string(filter(without_rebaseline_modifier, self._expectations))
+ return self.list_to_string(filter(without_rebaseline_modifier, self._expectations), reconstitute_only_these=[])
def _add_expectations(self, expectation_list):
for expectation_line in expectation_list:
diff --git a/Tools/Scripts/webkitpy/layout_tests/models/test_expectations_unittest.py b/Tools/Scripts/webkitpy/layout_tests/models/test_expectations_unittest.py
index c5606071d..c3fc02658 100644
--- a/Tools/Scripts/webkitpy/layout_tests/models/test_expectations_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/models/test_expectations_unittest.py
@@ -179,8 +179,7 @@ class MiscTests(Base):
"Bug(rniwa) disabled-test.html-disabled [ ImageOnlyFailure ]", is_lint_mode=True)
self.assertFalse(True, "ParseError wasn't raised")
except ParseError, e:
- warnings = ("expectations:1 Test lacks BUG modifier. failures/expected/text.html\n"
- "expectations:1 Unrecognized modifier 'foo' failures/expected/text.html\n"
+ warnings = ("expectations:1 Unrecognized modifier 'foo' failures/expected/text.html\n"
"expectations:2 Path does not exist. non-existent-test.html")
self.assertEqual(str(e), warnings)
@@ -372,7 +371,9 @@ class SemanticTests(Base):
def test_missing_bugid(self):
self.parse_exp('failures/expected/text.html [ Failure ]')
- self.assertTrue(self._exp.has_warnings())
+ self.assertFalse(self._exp.has_warnings())
+
+ self._port.warn_if_bug_missing_in_test_expectations = lambda: True
self.parse_exp('failures/expected/text.html [ Failure ]')
line = self._exp._model.get_expectation_line('failures/expected/text.html')
@@ -512,8 +513,22 @@ class RebaseliningTest(Base):
'Bug(z) failures/expected/crash.html [ Crash ]\n',
'Bug(x0) failures/expected/image.html [ Crash ]\n')
+ # Ensure that we don't modify unrelated lines, even if we could rewrite them.
+ # i.e., the second line doesn't get rewritten to "Bug(y) failures/expected/skip.html"
+ self.assertRemove('Bug(x) failures/expected/text.html [ Failure Rebaseline ]\n'
+ 'Bug(Y) failures/expected/image.html [ Skip ]\n'
+ 'Bug(z) failures/expected/crash.html\n',
+ '',
+ ['failures/expected/text.html'],
+ 'Bug(Y) failures/expected/image.html [ Skip ]\n'
+ 'Bug(z) failures/expected/crash.html\n',
+ '')
+
+ def test_get_rebaselining_failures(self):
+ # Make sure we find a test as needing a rebaseline even if it is not marked as a failure.
+ self.parse_exp('Bug(x) failures/expected/text.html [ Rebaseline ]\n')
+ self.assertEqual(len(self._exp.get_rebaselining_failures()), 1)
- def test_no_get_rebaselining_failures(self):
self.parse_exp(self.get_basic_expectations())
self.assertEqual(len(self._exp.get_rebaselining_failures()), 0)
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/base.py b/Tools/Scripts/webkitpy/layout_tests/port/base.py
index cd57032e9..ae55c684d 100755
--- a/Tools/Scripts/webkitpy/layout_tests/port/base.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/base.py
@@ -182,6 +182,10 @@ class Port(object):
"""Return the number of DumpRenderTree instances to use for this port."""
return self._executive.cpu_count()
+ def default_max_locked_shards(self):
+ """Return the number of "locked" shards to run in parallel (like the http tests)."""
+ return 1
+
def worker_startup_delay_secs(self):
# FIXME: If we start workers up too quickly, DumpRenderTree appears
# to thrash on something and time out its first few tests. Until
@@ -870,6 +874,7 @@ class Port(object):
# Most ports (?):
'WEBKIT_TESTFONTS',
+ 'WEBKITOUTPUTDIR',
]
for variable in variables_to_copy:
self._copy_value_from_environ_if_set(clean_env, variable)
@@ -997,6 +1002,9 @@ class Port(object):
# some ports have Skipped files which are returned as part of test_expectations().
return self._filesystem.exists(self.path_to_test_expectations_file())
+ def warn_if_bug_missing_in_test_expectations(self):
+ return False
+
def expectations_dict(self):
"""Returns an OrderedDict of name -> expectations strings.
The names are expected to be (but not required to be) paths in the filesystem.
@@ -1162,9 +1170,18 @@ class Port(object):
return "apache2-httpd.conf"
def _path_to_apache_config_file(self):
- """Returns the full path to the apache binary.
+ """Returns the full path to the apache configuration file.
+
+ If the WEBKIT_HTTP_SERVER_CONF_PATH environment variable is set, its
+ contents will be used instead.
This is needed only by ports that use the apache_http_server module."""
+ config_file_from_env = os.environ.get('WEBKIT_HTTP_SERVER_CONF_PATH')
+ if config_file_from_env:
+ if not self._filesystem.exists(config_file_from_env):
+ raise IOError('%s was not found on the system' % config_file_from_env)
+ return config_file_from_env
+
config_file_name = self._apache_config_file_name_for_platform(sys.platform)
return self._filesystem.join(self.layout_tests_dir(), 'http', 'conf', config_file_name)
@@ -1223,11 +1240,15 @@ class Port(object):
This is needed only by ports that use the http_server.py module."""
raise NotImplementedError('Port._path_to_lighttpd_php')
+ @memoized
def _path_to_wdiff(self):
"""Returns the full path to the wdiff binary, or None if it is not available.
This is likely used only by wdiff_text()"""
- return 'wdiff'
+ for path in ("/usr/bin/wdiff", "/usr/bin/dwdiff"):
+ if self._filesystem.exists(path):
+ return path
+ return None
def _webkit_baseline_path(self, platform):
"""Return the full path to the top of the baseline tree for a
@@ -1275,7 +1296,7 @@ class Port(object):
base_tests = self._real_tests([suite.base])
suite.tests = {}
for test in base_tests:
- suite.tests[test.replace(suite.base, suite.name)] = test
+ suite.tests[test.replace(suite.base, suite.name, 1)] = test
return suites
def _virtual_tests(self, paths, suites):
@@ -1292,7 +1313,7 @@ class Port(object):
def lookup_virtual_test_base(self, test_name):
for suite in self.populated_virtual_test_suites():
if test_name.startswith(suite.name):
- return test_name.replace(suite.name, suite.base)
+ return test_name.replace(suite.name, suite.base, 1)
return None
def lookup_virtual_test_args(self, test_name):
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py
index f6a6eb01f..e9b2f060d 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py
@@ -437,11 +437,15 @@ class PortTest(unittest.TestCase):
tests = port.tests(['passes'])
self.assertTrue('passes/text.html' in tests)
+ self.assertTrue('passes/passes/test-virtual-passes.html' in tests)
self.assertFalse('virtual/passes/text.html' in tests)
tests = port.tests(['virtual/passes'])
self.assertFalse('passes/text.html' in tests)
- self.assertTrue('virtual/passes/text.html' in tests)
+ self.assertTrue('virtual/passes/test-virtual-passes.html' in tests)
+ self.assertTrue('virtual/passes/passes/test-virtual-passes.html' in tests)
+ self.assertFalse('virtual/passes/test-virtual-virtual/passes.html' in tests)
+ self.assertFalse('virtual/passes/virtual/passes/test-virtual-passes.html' in tests)
def test_build_path(self):
port = self.make_port(options=optparse.Values({'build_directory': '/my-build-directory/'}))
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/builders.py b/Tools/Scripts/webkitpy/layout_tests/port/builders.py
index 3d03fc7af..605b8cccc 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/builders.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/builders.py
@@ -37,16 +37,16 @@ from webkitpy.common.memoized import memoized
# * specifiers -- a set of specifiers, representing configurations covered by this builder.
_exact_matches = {
# These builders are on build.chromium.org.
- "Webkit Win": {"port_name": "chromium-win-xp", "specifiers": set(["xp", "release"])},
- "Webkit Win7": {"port_name": "chromium-win-win7", "specifiers": set(["win7"])},
- "Webkit Win (dbg)(1)": {"port_name": "chromium-win-xp", "specifiers": set(["win", "debug"])},
- "Webkit Win (dbg)(2)": {"port_name": "chromium-win-xp", "specifiers": set(["win", "debug"])},
- "Webkit Linux": {"port_name": "chromium-linux-x86_64", "specifiers": set(["linux", "x86_64", "release"])},
- "Webkit Linux 32": {"port_name": "chromium-linux-x86", "specifiers": set(["linux", "x86"])},
- "Webkit Linux (dbg)": {"port_name": "chromium-linux-x86_64", "specifiers": set(["linux", "debug"])},
- "Webkit Mac10.6": {"port_name": "chromium-mac-snowleopard", "specifiers": set(["snowleopard"])},
- "Webkit Mac10.6 (dbg)": {"port_name": "chromium-mac-snowleopard", "specifiers": set(["snowleopard", "debug"])},
- "Webkit Mac10.7": {"port_name": "chromium-mac-lion", "specifiers": set(["lion"])},
+ "WebKit XP": {"port_name": "chromium-win-xp", "specifiers": set(["xp", "release"])},
+ "WebKit Win7": {"port_name": "chromium-win-win7", "specifiers": set(["win7"])},
+ "WebKit Win7 (dbg)(1)": {"port_name": "chromium-win-xp", "specifiers": set(["win", "debug"])},
+ "WebKit Win7 (dbg)(2)": {"port_name": "chromium-win-xp", "specifiers": set(["win", "debug"])},
+ "WebKit Linux": {"port_name": "chromium-linux-x86_64", "specifiers": set(["linux", "x86_64", "release"])},
+ "WebKit Linux 32": {"port_name": "chromium-linux-x86", "specifiers": set(["linux", "x86"])},
+ "WebKit Linux (dbg)": {"port_name": "chromium-linux-x86_64", "specifiers": set(["linux", "debug"])},
+ "WebKit Mac10.6": {"port_name": "chromium-mac-snowleopard", "specifiers": set(["snowleopard"])},
+ "WebKit Mac10.6 (dbg)": {"port_name": "chromium-mac-snowleopard", "specifiers": set(["snowleopard", "debug"])},
+ "WebKit Mac10.7": {"port_name": "chromium-mac-lion", "specifiers": set(["lion"])},
# These builders are on build.webkit.org.
"Apple MountainLion Release WK1 (Tests)": {"port_name": "mac-mountainlion", "specifiers": set(["mountainlion"]), "rebaseline_override_dir": "mac"},
@@ -92,6 +92,8 @@ _ports_without_builders = [
"qt-mac",
"qt-win",
"qt-wk2",
+ # FIXME: Move to _extact_matches.
+ "chromium-android",
]
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium.py
index 44c98a383..c310eb15b 100755
--- a/Tools/Scripts/webkitpy/layout_tests/port/chromium.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium.py
@@ -53,6 +53,7 @@ class ChromiumPort(Port):
ALL_SYSTEMS = (
('snowleopard', 'x86'),
('lion', 'x86'),
+ ('mountainlion', 'x86'),
('xp', 'x86'),
('win7', 'x86'),
('lucid', 'x86'),
@@ -62,13 +63,13 @@ class ChromiumPort(Port):
('icecreamsandwich', 'x86'))
ALL_BASELINE_VARIANTS = [
- 'chromium-mac-lion', 'chromium-mac-snowleopard', 'chromium-mac-leopard',
+ 'chromium-mac-mountainlion', 'chromium-mac-lion', 'chromium-mac-snowleopard',
'chromium-win-win7', 'chromium-win-xp',
'chromium-linux-x86_64', 'chromium-linux-x86',
]
CONFIGURATION_SPECIFIER_MACROS = {
- 'mac': ['snowleopard', 'lion'],
+ 'mac': ['snowleopard', 'lion', 'mountainlion'],
'win': ['xp', 'win7'],
'linux': ['lucid'],
'android': ['icecreamsandwich'],
@@ -111,6 +112,13 @@ class ChromiumPort(Port):
def is_chromium(self):
return True
+ def default_max_locked_shards(self):
+ """Return the number of "locked" shards to run in parallel (like the http tests)."""
+ max_locked_shards = int(self.default_child_processes()) / 4
+ if not max_locked_shards:
+ return 1
+ return max_locked_shards
+
def default_pixel_tests(self):
return True
@@ -332,13 +340,16 @@ class ChromiumPort(Port):
'win_layout_rel',
])
+ def warn_if_bug_missing_in_test_expectations(self):
+ return True
+
def expectations_files(self):
paths = [self.path_to_test_expectations_file()]
skia_expectations_path = self.path_from_chromium_base('skia', 'skia_test_expectations.txt')
+ # FIXME: we should probably warn if this file is missing in some situations.
+ # See the discussion in webkit.org/b/97699.
if self._filesystem.exists(skia_expectations_path):
paths.append(skia_expectations_path)
- else:
- _log.warning("Using the chromium port without having the downstream skia_test_expectations.txt file checked out. Expectations related things might be wonky.")
builder_name = self.get_option('builder_name', 'DUMMY_BUILDER_NAME')
if builder_name == 'DUMMY_BUILDER_NAME' or '(deps)' in builder_name or builder_name in self.try_builder_names:
@@ -376,6 +387,9 @@ class ChromiumPort(Port):
VirtualTestSuite('platform/chromium/virtual/gpu/fast/hidpi',
'fast/hidpi',
['--force-compositing-mode']),
+ VirtualTestSuite('platform/chromium/virtual/softwarecompositing',
+ 'compositing',
+ ['--enable-software-compositing']),
]
#
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_android.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_android.py
index 3dfeab7a3..e246b8870 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_android.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_android.py
@@ -77,45 +77,51 @@ TEST_PATH_PREFIX = '/all-tests'
FORWARD_PORTS = '8000 8080 8443 8880 9323'
MS_TRUETYPE_FONTS_DIR = '/usr/share/fonts/truetype/msttcorefonts/'
+MS_TRUETYPE_FONTS_PACKAGE = 'ttf-mscorefonts-installer'
# Timeout in seconds to wait for start/stop of DumpRenderTree.
DRT_START_STOP_TIMEOUT_SECS = 10
-# List of fonts that layout tests expect, copied from DumpRenderTree/gtk/TestShellX11.cpp.
+# List of fonts that layout tests expect, copied from DumpRenderTree/chromium/TestShellX11.cpp.
HOST_FONT_FILES = [
- [MS_TRUETYPE_FONTS_DIR, 'Arial.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Arial_Bold.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Arial_Bold_Italic.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Arial_Italic.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Comic_Sans_MS.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Comic_Sans_MS_Bold.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Courier_New.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Courier_New_Bold.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Courier_New_Bold_Italic.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Courier_New_Italic.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Georgia.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Georgia_Bold.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Georgia_Bold_Italic.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Georgia_Italic.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Impact.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Trebuchet_MS.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Trebuchet_MS_Bold.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Trebuchet_MS_Bold_Italic.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Trebuchet_MS_Italic.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Times_New_Roman.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Times_New_Roman_Bold.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Times_New_Roman_Bold_Italic.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Times_New_Roman_Italic.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Verdana.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Verdana_Bold.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Verdana_Bold_Italic.ttf'],
- [MS_TRUETYPE_FONTS_DIR, 'Verdana_Italic.ttf'],
+ [[MS_TRUETYPE_FONTS_DIR], 'Arial.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Arial_Bold.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Arial_Bold_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Arial_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Comic_Sans_MS.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Comic_Sans_MS_Bold.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Courier_New.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Courier_New_Bold.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Courier_New_Bold_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Courier_New_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Georgia.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Georgia_Bold.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Georgia_Bold_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Georgia_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Impact.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Trebuchet_MS.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Trebuchet_MS_Bold.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Trebuchet_MS_Bold_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Trebuchet_MS_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Times_New_Roman.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Times_New_Roman_Bold.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Times_New_Roman_Bold_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Times_New_Roman_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Verdana.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Verdana_Bold.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Verdana_Bold_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
+ [[MS_TRUETYPE_FONTS_DIR], 'Verdana_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
# The Microsoft font EULA
- ['/usr/share/doc/ttf-mscorefonts-installer/', 'READ_ME!.gz'],
- ['/usr/share/fonts/truetype/ttf-dejavu/', 'DejaVuSans.ttf'],
+ [['/usr/share/doc/ttf-mscorefonts-installer/'], 'READ_ME!.gz', MS_TRUETYPE_FONTS_PACKAGE],
+ # Other fonts: Arabic, CJK, Indic, Thai, etc.
+ [['/usr/share/fonts/truetype/ttf-dejavu/'], 'DejaVuSans.ttf', 'ttf-dejavu'],
+ [['/usr/share/fonts/truetype/kochi/'], 'kochi-mincho.ttf', 'ttf-kochi-mincho'],
+ [['/usr/share/fonts/truetype/ttf-indic-fonts-core/'], 'lohit_hi.ttf', 'ttf-indic-fonts-core'],
+ [['/usr/share/fonts/truetype/ttf-indic-fonts-core/'], 'lohit_ta.ttf', 'ttf-indic-fonts-core'],
+ [['/usr/share/fonts/truetype/ttf-indic-fonts-core/'], 'MuktiNarrow.ttf', 'ttf-indic-fonts-core'],
+ [['/usr/share/fonts/truetype/thai/', '/usr/share/fonts/truetype/tlwg/'], 'Garuda.ttf', 'fonts-tlwg-garuda'],
+ [['/usr/share/fonts/truetype/ttf-indic-fonts-core/', '/usr/share/fonts/truetype/ttf-punjabi-fonts/'], 'lohit_pa.ttf', 'ttf-indic-fonts-core'],
]
-# Should increase this version after changing HOST_FONT_FILES.
-FONT_FILES_VERSION = 2
DEVICE_FONTS_DIR = DEVICE_DRT_DIR + 'fonts/'
@@ -198,7 +204,8 @@ class ChromiumAndroidPort(chromium.ChromiumPort):
def check_build(self, needs_http):
result = super(ChromiumAndroidPort, self).check_build(needs_http)
- result = self.check_wdiff() and result
+ result = self._check_file_exists(self._path_to_md5sum(), 'md5sum utility') and result
+ result = self._check_file_exists(self._path_to_forwarder(), 'forwarder utility') and result
if not result:
_log.error('For complete Android build requirements, please see:')
_log.error('')
@@ -207,11 +214,15 @@ class ChromiumAndroidPort(chromium.ChromiumPort):
return result
def check_sys_deps(self, needs_http):
- for (font_dir, font_file) in HOST_FONT_FILES:
- font_path = font_dir + font_file
- if not self._check_file_exists(font_path, 'font file'):
- _log.error('You are missing %s. Try installing msttcorefonts. '
- 'See build instructions.' % font_path)
+ for (font_dirs, font_file, package) in HOST_FONT_FILES:
+ exists = False
+ for font_dir in font_dirs:
+ font_path = font_dir + font_file
+ if self._check_file_exists(font_path, '', logging=False):
+ exists = True
+ break
+ if not exists:
+ _log.error('You are missing %s under %s. Try installing %s. See build instructions.' % (font_file, font_dirs, package))
return False
return True
@@ -271,6 +282,9 @@ class ChromiumAndroidPort(chromium.ChromiumPort):
def _path_to_forwarder(self):
return self._build_path('forwarder')
+ def _path_to_md5sum(self):
+ return self._build_path(MD5SUM_DEVICE_FILE_NAME)
+
def _path_to_image_diff(self):
return self._host_port._path_to_image_diff()
@@ -330,12 +344,10 @@ class ChromiumAndroidDriver(driver.Driver):
super(ChromiumAndroidDriver, self).__del__()
def _setup_md5sum_and_push_data_if_needed(self):
- self._md5sum_path = self._port._build_path_with_configuration(self._port.get_option('configuration'), MD5SUM_DEVICE_FILE_NAME)
- assert os.path.exists(self._md5sum_path)
-
+ self._md5sum_path = self._port._path_to_md5sum()
if not self._file_exists_on_device(MD5SUM_DEVICE_PATH):
if not self._push_to_device(self._md5sum_path, MD5SUM_DEVICE_PATH):
- _log.error('Could not push md5sum to device')
+ raise AssertionError('Could not push md5sum to device')
self._push_executable()
self._push_fonts()
@@ -401,31 +413,20 @@ class ChromiumAndroidDriver(driver.Driver):
self._abort('Failed to install %s onto device: %s' % (drt_host_path, install_result))
def _push_fonts(self):
- if not self._check_version(DEVICE_FONTS_DIR, FONT_FILES_VERSION):
- self._log_debug('Pushing fonts')
- path_to_ahem_font = self._port._build_path('AHEM____.TTF')
- self._push_to_device(path_to_ahem_font, DEVICE_FONTS_DIR + 'AHEM____.TTF')
- for (host_dir, font_file) in HOST_FONT_FILES:
- self._push_to_device(host_dir + font_file, DEVICE_FONTS_DIR + font_file)
- self._link_device_file('/system/fonts/DroidSansFallback.ttf', DEVICE_FONTS_DIR + 'DroidSansFallback.ttf')
- self._update_version(DEVICE_FONTS_DIR, FONT_FILES_VERSION)
+ self._log_debug('Pushing fonts')
+ path_to_ahem_font = self._port._build_path('AHEM____.TTF')
+ self._push_file_if_needed(path_to_ahem_font, DEVICE_FONTS_DIR + 'AHEM____.TTF')
+ for (host_dirs, font_file, package) in HOST_FONT_FILES:
+ for host_dir in host_dirs:
+ host_font_path = host_dir + font_file
+ if self._port._check_file_exists(host_font_path, '', logging=False):
+ self._push_file_if_needed(host_font_path, DEVICE_FONTS_DIR + font_file)
def _push_test_resources(self):
self._log_debug('Pushing test resources')
for resource in TEST_RESOURCES_TO_PUSH:
self._push_file_if_needed(self._port.layout_tests_dir() + '/' + resource, DEVICE_LAYOUT_TESTS_DIR + resource)
- def _check_version(self, dir, version):
- assert(dir.endswith('/'))
- try:
- device_version = int(self._run_adb_command(['shell', 'cat %sVERSION || echo 0' % dir]))
- return device_version == version
- except:
- return False
-
- def _update_version(self, dir, version):
- self._run_adb_command(['shell', 'echo %d > %sVERSION' % (version, dir)])
-
def _run_adb_command(self, cmd, ignore_error=False):
self._log_debug('Run adb command: ' + str(cmd))
if ignore_error:
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_android_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_android_unittest.py
index 2ffb77979..bb4229e65 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_android_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_android_unittest.py
@@ -30,6 +30,7 @@ import optparse
import StringIO
import time
import unittest
+import sys
from webkitpy.common.system import executive_mock
from webkitpy.common.system.executive_mock import MockExecutive2
@@ -244,7 +245,8 @@ class ChromiumAndroidDriverTest(unittest.TestCase):
def test_command_from_driver_input(self):
driver_input = driver.DriverInput('foo/bar/test.html', 10, 'checksum', True)
expected_command = "/data/local/tmp/third_party/WebKit/LayoutTests/foo/bar/test.html'--pixel-test'checksum\n"
- self.assertEquals(self.driver._command_from_driver_input(driver_input), expected_command)
+ if (sys.platform != "cygwin"):
+ self.assertEquals(self.driver._command_from_driver_input(driver_input), expected_command)
driver_input = driver.DriverInput('http/tests/foo/bar/test.html', 10, 'checksum', True)
expected_command = "http://127.0.0.1:8000/foo/bar/test.html'--pixel-test'checksum\n"
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py
index fa8c274ea..a2252c1b3 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py
@@ -155,14 +155,6 @@ class ChromiumLinuxPort(chromium.ChromiumPort):
else:
return '/usr/sbin/apache2'
- def _path_to_apache_config_file(self):
- if self._is_redhat_based():
- config_name = 'fedora-httpd.conf'
- else:
- config_name = 'apache2-debian-httpd.conf'
-
- return self._filesystem.join(self.layout_tests_dir(), 'http', 'conf', config_name)
-
def _path_to_lighttpd(self):
return "/usr/sbin/lighttpd"
@@ -178,12 +170,3 @@ class ChromiumLinuxPort(chromium.ChromiumPort):
def _path_to_helper(self):
return None
-
- def _path_to_wdiff(self):
- if self._is_redhat_based():
- return '/usr/bin/dwdiff'
- else:
- return '/usr/bin/wdiff'
-
- def _is_redhat_based(self):
- return self._filesystem.exists(self._filesystem.join('/etc', 'redhat-release'))
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py
index db82d5c79..08c1ede0f 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py
@@ -94,6 +94,14 @@ class ChromiumMacPort(chromium.ChromiumPort):
def operating_system(self):
return 'mac'
+ def expectations_files(self):
+ # FIXME: This is a temporary hack while getting the 10.8 baselines up to date.
+ # See https://bugs.webkit.org/show_bug.cgi?id=99505
+ files = super(ChromiumMacPort, self).expectations_files()
+ if self.name() == 'chromium-mac-mountainlion':
+ files.append(self._filesystem.join(self._webkit_baseline_path(self.name()), 'TestExpectations'))
+ return files
+
#
# PROTECTED METHODS
#
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac_unittest.py
index edf92ea20..14e5a2b84 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac_unittest.py
@@ -95,6 +95,9 @@ class ChromiumMacPortTest(chromium_port_testcase.ChromiumPortTestCase):
def test_path_to_image_diff(self):
self.assertEquals(self.make_port()._path_to_image_diff(), '/mock-checkout/out/Release/ImageDiff')
+ def test_ml_expectations(self):
+ self.assertTrue(self.make_port(port_name='chromium-mac-mountainlion').expectations_files()[-1].endswith('-mountainlion/TestExpectations'))
+ self.assertFalse(self.make_port(port_name='chromium-mac-lion').expectations_files()[-1].endswith('-mountainlion/TestExpectations'))
if __name__ == '__main__':
port_testcase.main()
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_port_testcase.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_port_testcase.py
index fb90d1b9b..a082a131c 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_port_testcase.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_port_testcase.py
@@ -48,6 +48,13 @@ class ChromiumPortTestCase(port_testcase.PortTestCase):
port = self.make_port()
port.check_build(needs_http=True)
+ def test_default_max_locked_shards(self):
+ port = self.make_port()
+ port.default_child_processes = lambda: 16
+ self.assertEquals(port.default_max_locked_shards(), 4)
+ port.default_child_processes = lambda: 2
+ self.assertEquals(port.default_max_locked_shards(), 1)
+
def test_default_timeout_ms(self):
self.assertEquals(self.make_port(options=MockOptions(configuration='Release')).default_timeout_ms(), 6000)
self.assertEquals(self.make_port(options=MockOptions(configuration='Debug')).default_timeout_ms(), 12000)
@@ -72,6 +79,8 @@ class ChromiumPortTestCase(port_testcase.PortTestCase):
TestConfiguration('snowleopard', 'x86', 'release'),
TestConfiguration('lion', 'x86', 'debug'),
TestConfiguration('lion', 'x86', 'release'),
+ TestConfiguration('mountainlion', 'x86', 'debug'),
+ TestConfiguration('mountainlion', 'x86', 'release'),
TestConfiguration('xp', 'x86', 'debug'),
TestConfiguration('xp', 'x86', 'release'),
TestConfiguration('win7', 'x86', 'debug'),
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/driver.py b/Tools/Scripts/webkitpy/layout_tests/port/driver.py
index 4bf53d46a..7993d0577 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/driver.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/driver.py
@@ -76,7 +76,7 @@ class DriverOutput(object):
strip_patterns.append((re.compile('scrolled to [0-9]+,[0-9]+'), 'scrolled'))
def __init__(self, text, image, image_hash, audio, crash=False,
- test_time=0, timeout=False, error='', crashed_process_name='??',
+ test_time=0, measurements=None, timeout=False, error='', crashed_process_name='??',
crashed_pid=None, crash_log=None):
# FIXME: Args could be renamed to better clarify what they do.
self.text = text
@@ -89,6 +89,7 @@ class DriverOutput(object):
self.crashed_pid = crashed_pid
self.crash_log = crash_log
self.test_time = test_time
+ self.measurements = measurements
self.timeout = timeout
self.error = error # stderr output
@@ -138,6 +139,8 @@ class Driver(object):
self.err_seen_eof = False
self._server_process = None
+ self._measurements = {}
+
def __del__(self):
self.stop()
@@ -193,7 +196,7 @@ class Driver(object):
crash_log += '\nstdout:\n%s\nstderr:\n%s\n' % (text, self.error_from_test)
return DriverOutput(text, image, actual_image_hash, audio,
- crash=crashed, test_time=time.time() - test_begin_time,
+ crash=crashed, test_time=time.time() - test_begin_time, measurements=self._measurements,
timeout=timed_out, error=self.error_from_test,
crashed_process_name=self._crashed_process_name,
crashed_pid=self._crashed_pid, crash_log=crash_log)
@@ -360,6 +363,10 @@ class Driver(object):
def _read_first_block(self, deadline):
# returns (text_content, audio_content)
block = self._read_block(deadline)
+ if block.malloc:
+ self._measurements['Malloc'] = float(block.malloc)
+ if block.js_heap:
+ self._measurements['JSHeap'] = float(block.js_heap)
if block.content_type == 'audio/wav':
return (None, block.decoded_content)
return (block.decoded_content, None)
@@ -384,7 +391,9 @@ class Driver(object):
if (self._read_header(block, line, 'Content-Type: ', 'content_type')
or self._read_header(block, line, 'Content-Transfer-Encoding: ', 'encoding')
or self._read_header(block, line, 'Content-Length: ', '_content_length', int)
- or self._read_header(block, line, 'ActualHash: ', 'content_hash')):
+ or self._read_header(block, line, 'ActualHash: ', 'content_hash')
+ or self._read_header(block, line, 'DumpMalloc: ', 'malloc')
+ or self._read_header(block, line, 'DumpJSHeap: ', 'js_heap')):
return
# Note, we're not reading ExpectedHash: here, but we could.
# If the line wasn't a header, we just append it to the content.
@@ -450,6 +459,8 @@ class ContentBlock(object):
# Content is treated as binary data even though the text output is usually UTF-8.
self.content = str() # FIXME: Should be bytearray() once we require Python 2.6.
self.decoded_content = None
+ self.malloc = None
+ self.js_heap = None
def decode_content(self):
if self.encoding == 'base64' and self.content is not None:
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/efl.py b/Tools/Scripts/webkitpy/layout_tests/port/efl.py
index 269146aed..1022cd7b7 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/efl.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/efl.py
@@ -32,7 +32,7 @@ import os
from webkitpy.layout_tests.models.test_configuration import TestConfiguration
from webkitpy.layout_tests.port.base import Port
from webkitpy.layout_tests.port.pulseaudio_sanitizer import PulseAudioSanitizer
-
+from webkitpy.layout_tests.port.xvfbdriver import XvfbDriver
class EflPort(Port, PulseAudioSanitizer):
port_name = 'efl'
@@ -80,6 +80,9 @@ class EflPort(Port, PulseAudioSanitizer):
def _generate_all_test_configurations(self):
return [TestConfiguration(version=self._version, architecture='x86', build_type=build_type) for build_type in self.ALL_BUILD_TYPES]
+ def _driver_class(self):
+ return XvfbDriver
+
def _path_to_driver(self):
return self._build_path('bin', self.driver_name())
@@ -98,18 +101,22 @@ class EflPort(Port, PulseAudioSanitizer):
search_paths = []
if self.get_option('webkit_test_runner'):
search_paths.append(self.port_name + '-wk2')
+ search_paths.append('wk2')
else:
search_paths.append(self.port_name + '-wk1')
search_paths.append(self.port_name)
return search_paths
def expectations_files(self):
+ # FIXME: We should be able to use the default algorithm here.
return list(reversed([self._filesystem.join(self._webkit_baseline_path(p), 'TestExpectations') for p in self._search_paths()]))
def show_results_html_file(self, results_filename):
# FIXME: We should find a way to share this implmentation with Gtk,
# or teach run-launcher how to call run-safari and move this down to WebKitPort.
run_launcher_args = ["file://%s" % results_filename]
+ if self.get_option('webkit_test_runner'):
+ run_launcher_args.append('-2')
# FIXME: old-run-webkit-tests also added ["-graphicssystem", "raster", "-style", "windows"]
# FIXME: old-run-webkit-tests converted results_filename path for cygwin.
self._run_script("run-launcher", run_launcher_args)
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/factory_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/factory_unittest.py
index bb077c40a..2980c2d23 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/factory_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/factory_unittest.py
@@ -105,7 +105,7 @@ class FactoryTest(unittest.TestCase):
self.assertRaises(NotImplementedError, factory.PortFactory(MockSystemHost(os_name='vms')).get)
def test_get_from_builder_name(self):
- self.assertEquals(factory.PortFactory(MockSystemHost()).get_from_builder_name('Webkit Mac10.7').name(),
+ self.assertEquals(factory.PortFactory(MockSystemHost()).get_from_builder_name('WebKit Mac10.7').name(),
'chromium-mac-lion')
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/gtk.py b/Tools/Scripts/webkitpy/layout_tests/port/gtk.py
index f02d14819..6c57b5363 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/gtk.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/gtk.py
@@ -41,6 +41,9 @@ class GtkPort(Port, PulseAudioSanitizer):
def expectations_files(self):
return [self._filesystem.join(self._webkit_baseline_path(d), 'TestExpectations') for d in self._skipped_file_search_paths()]
+ def warn_if_bug_missing_in_test_expectations(self):
+ return True
+
def _port_flag_for_scripts(self):
return "--gtk"
@@ -89,18 +92,6 @@ class GtkPort(Port, PulseAudioSanitizer):
def _path_to_image_diff(self):
return self._build_path('Programs', 'ImageDiff')
- def _path_to_apache(self):
- if self._is_redhat_based():
- return '/usr/sbin/httpd'
- else:
- return '/usr/sbin/apache2'
-
- def _path_to_wdiff(self):
- if self._is_redhat_based():
- return '/usr/bin/dwdiff'
- else:
- return '/usr/bin/wdiff'
-
def _path_to_webcore_library(self):
gtk_library_names = [
"libwebkitgtk-1.0.so",
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py b/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py
index 985241ede..f704a7a13 100755
--- a/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py
@@ -30,6 +30,7 @@
import errno
import logging
+import os
import socket
import sys
import time
@@ -89,6 +90,13 @@ class PortTestCase(unittest.TestCase):
port_name = self.port_maker.determine_full_port_name(host, options, port_name)
return self.port_maker(host, port_name, options=options, config=config, **kwargs)
+ def test_default_max_locked_shards(self):
+ port = self.make_port()
+ port.default_child_processes = lambda: 16
+ self.assertEquals(port.default_max_locked_shards(), 1)
+ port.default_child_processes = lambda: 2
+ self.assertEquals(port.default_max_locked_shards(), 1)
+
def test_default_timeout_ms(self):
self.assertEquals(self.make_port(options=MockOptions(configuration='Release')).default_timeout_ms(), 35000)
self.assertEquals(self.make_port(options=MockOptions(configuration='Debug')).default_timeout_ms(), 35000)
@@ -582,10 +590,29 @@ class PortTestCase(unittest.TestCase):
def test_path_to_apache_config_file(self):
port = TestWebKitPort()
+
+ saved_environ = os.environ.copy()
+ try:
+ os.environ['WEBKIT_HTTP_SERVER_CONF_PATH'] = '/path/to/httpd.conf'
+ self.assertRaises(IOError, port._path_to_apache_config_file)
+ port._filesystem.write_text_file('/existing/httpd.conf', 'Hello, world!')
+ os.environ['WEBKIT_HTTP_SERVER_CONF_PATH'] = '/existing/httpd.conf'
+ self.assertEquals(port._path_to_apache_config_file(), '/existing/httpd.conf')
+ finally:
+ os.environ = saved_environ.copy()
+
# Mock out _apache_config_file_name_for_platform to ignore the passed sys.platform value.
port._apache_config_file_name_for_platform = lambda platform: 'httpd.conf'
self.assertEquals(port._path_to_apache_config_file(), '/mock-checkout/LayoutTests/http/conf/httpd.conf')
+ # Check that even if we mock out _apache_config_file_name, the environment variable takes precedence.
+ saved_environ = os.environ.copy()
+ try:
+ os.environ['WEBKIT_HTTP_SERVER_CONF_PATH'] = '/existing/httpd.conf'
+ self.assertEquals(port._path_to_apache_config_file(), '/existing/httpd.conf')
+ finally:
+ os.environ = saved_environ.copy()
+
def test_check_build(self):
port = self.make_port(options=MockOptions(build=True))
self.build_called = False
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/qt.py b/Tools/Scripts/webkitpy/layout_tests/port/qt.py
index 828a80d64..5b8342d9c 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/qt.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/qt.py
@@ -88,9 +88,9 @@ class QtPort(Port):
def _path_to_webcore_library(self):
if self.operating_system() == 'mac':
- return self._build_path('lib/QtWebKit.framework/QtWebKit')
+ return self._build_path('lib/QtWebKitWidgets.framework/QtWebKitWidgets')
else:
- return self._build_path('lib/libQtWebKit.so')
+ return self._build_path('lib/libQtWebKitWidgets.so')
def _modules_to_search_for_symbols(self):
# We search in every library to be reliable in the case of building with CONFIG+=force_static_libs_as_shared.
@@ -135,23 +135,21 @@ class QtPort(Port):
search_paths.append('qt-4.8')
elif version:
search_paths.append('qt-5.0')
- search_paths.append(self.port_name + '-' + self.host.platform.os_name)
+ search_paths.append(self.port_name + '-' + self.operating_system())
search_paths.append(self.port_name)
return search_paths
def default_baseline_search_path(self):
return map(self._webkit_baseline_path, self._search_paths())
- def _skipped_file_search_paths(self):
- skipped_path = self._search_paths()
- if self.get_option('webkit_test_runner') and '5.0' in self.qt_version():
- skipped_path.append('wk2')
- return skipped_path
-
def expectations_files(self):
+ paths = self._search_paths()
+ if self.get_option('webkit_test_runner'):
+ paths.append('wk2')
+
# expectations_files() uses the directories listed in _search_paths reversed.
# e.g. qt -> qt-linux -> qt-4.8
- return list(reversed([self._filesystem.join(self._webkit_baseline_path(p), 'TestExpectations') for p in self._search_paths()]))
+ return list(reversed([self._filesystem.join(self._webkit_baseline_path(p), 'TestExpectations') for p in paths]))
def setup_environ_for_server(self, server_name=None):
clean_env = super(QtPort, self).setup_environ_for_server(server_name)
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/qt_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/qt_unittest.py
index 374b10270..cf09bd8e0 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/qt_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/qt_unittest.py
@@ -71,14 +71,6 @@ class QtPortTest(port_testcase.PortTestCase):
absolute_search_paths = map(port._webkit_baseline_path, search_paths)
self.assertEquals(port.baseline_search_path(), absolute_search_paths)
- def _assert_skipped_path(self, search_paths, os_name, use_webkit2=False, qt_version='4.8'):
- host = MockSystemHost(os_name=os_name)
- host.executive = MockExecutive2(self._qt_version(qt_version))
- port_name = 'qt-' + os_name
- port = self.make_port(host=host, qt_version=qt_version, port_name=port_name,
- options=MockOptions(webkit_test_runner=use_webkit2, platform='qt'))
- self.assertEquals(port._skipped_file_search_paths(), search_paths)
-
def _assert_expectations_files(self, search_paths, os_name, use_webkit2=False, qt_version='4.8'):
# FIXME: Port constructors should not "parse" the port name, but
# rather be passed components (directly or via setters). Once
@@ -100,19 +92,13 @@ class QtPortTest(port_testcase.PortTestCase):
for case in self.search_paths_cases:
self._assert_search_path(**case)
- def test_skipped_file_search_path(self):
- caselist = self.search_paths_cases[:]
- for case in caselist:
- if case['use_webkit2'] and case['qt_version'] == '5.0':
- case['search_paths'].append("wk2")
- self._assert_skipped_path(**case)
-
def test_expectations_files(self):
for case in self.search_paths_cases:
expectations_case = deepcopy(case)
- expectations_case['search_paths'] = []
- for path in reversed(case['search_paths']):
- expectations_case['search_paths'].append('/mock-checkout/LayoutTests/platform/%s/TestExpectations' % (path))
+ if expectations_case['use_webkit2']:
+ expectations_case['search_paths'].append("wk2")
+ expectations_case['search_paths'].reverse()
+ expectations_case['search_paths'] = map(lambda path: '/mock-checkout/LayoutTests/platform/%s/TestExpectations' % (path), expectations_case['search_paths'])
self._assert_expectations_files(**expectations_case)
def test_show_results_html_file(self):
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/server_process.py b/Tools/Scripts/webkitpy/layout_tests/port/server_process.py
index bfa6aab80..bfdf8301b 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/server_process.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/server_process.py
@@ -84,6 +84,17 @@ class ServerProcess(object):
return self._pid
def _reset(self):
+ if getattr(self, '_proc', None):
+ if self._proc.stdin:
+ self._proc.stdin.close()
+ self._proc.stdin = None
+ if self._proc.stdout:
+ self._proc.stdout.close()
+ self._proc.stdout = None
+ if self._proc.stderr:
+ self._proc.stderr.close()
+ self._proc.stderr = None
+
self._proc = None
self._output = str() # bytesarray() once we require Python 2.6
self._error = str() # bytesarray() once we require Python 2.6
@@ -321,8 +332,11 @@ class ServerProcess(object):
now = time.time()
self._proc.stdin.close()
+ self._proc.stdin = None
+ killed = False
if not timeout_secs:
self._kill()
+ killed = True
elif not self._host.platform.is_win():
# FIXME: Why aren't we calling this on win?
deadline = now + timeout_secs
@@ -331,13 +345,15 @@ class ServerProcess(object):
if self._proc.poll() is None:
_log.warning('stopping %s timed out, killing it' % self._name)
self._kill()
+ killed = True
_log.warning('killed')
# read any remaining data on the pipes and return it.
- if self._use_win32_apis:
- self._wait_for_data_and_update_buffers_using_win32_apis(now)
- else:
- self._wait_for_data_and_update_buffers_using_select(now, stopping=True)
+ if not killed:
+ if self._use_win32_apis:
+ self._wait_for_data_and_update_buffers_using_win32_apis(now)
+ else:
+ self._wait_for_data_and_update_buffers_using_select(now, stopping=True)
out, err = self._output, self._error
self._reset()
return (out, err)
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/server_process_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/server_process_unittest.py
index 48c41e6f2..7a5ac45d4 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/server_process_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/server_process_unittest.py
@@ -57,6 +57,7 @@ class TrivialMockPort(object):
class MockFile(object):
def __init__(self, server_process):
self._server_process = server_process
+ self.closed = False
def fileno(self):
return 1
@@ -66,7 +67,7 @@ class MockFile(object):
raise IOError
def close(self):
- pass
+ self.closed = True
class MockProc(object):
@@ -87,6 +88,8 @@ class FakeServerProcess(server_process.ServerProcess):
def _start(self):
self._proc = MockProc(self)
self.stdin = self._proc.stdin
+ self.stdout = self._proc.stdout
+ self.stderr = self._proc.stderr
self._pid = self._proc.pid
self.broken_pipes = []
@@ -121,6 +124,15 @@ class TestServerProcess(unittest.TestCase):
proc.stop(0)
+ def test_cleanup(self):
+ port_obj = TrivialMockPort()
+ server_process = FakeServerProcess(port_obj=port_obj, name="test", cmd=["test"])
+ server_process._start()
+ server_process.stop()
+ self.assertTrue(server_process.stdin.closed)
+ self.assertTrue(server_process.stdout.closed)
+ self.assertTrue(server_process.stderr.closed)
+
def test_broken_pipe(self):
port_obj = TrivialMockPort()
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/test.py b/Tools/Scripts/webkitpy/layout_tests/port/test.py
index d008d995d..726575614 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/test.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/test.py
@@ -239,6 +239,11 @@ layer at (0,0) size 800x34
actual_image='image_not_in_pixeldir-pngtEXtchecksum\x00checksum_fail',
expected_image='image_not_in_pixeldir-pngtEXtchecksum\x00checksum-png')
+ # For testing that virtual test suites don't expand names containing themselves
+ # See webkit.org/b/97925 and base_unittest.PortTest.test_tests().
+ tests.add('passes/test-virtual-passes.html')
+ tests.add('passes/passes/test-virtual-passes.html')
+
return tests
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/xvfbdriver.py b/Tools/Scripts/webkitpy/layout_tests/port/xvfbdriver.py
index 7e386a108..b927720db 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/xvfbdriver.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/xvfbdriver.py
@@ -28,41 +28,60 @@
import logging
import os
+import re
+import time
from webkitpy.layout_tests.port.server_process import ServerProcess
from webkitpy.layout_tests.port.driver import Driver
+from webkitpy.common.system.file_lock import FileLock
_log = logging.getLogger(__name__)
class XvfbDriver(Driver):
- def _start(self, pixel_tests, per_test_args):
-
- # Collect the number of X servers running already and make
- # sure our Xvfb process doesn't clash with any of them.
- def x_filter(process_name):
- return process_name.find("Xorg") > -1
+ def __init__(self, *args, **kwargs):
+ Driver.__init__(self, *args, **kwargs)
+ self._guard_lock = None
+ self._startup_delay_secs = 1.0
- running_displays = len(self._port.host.executive.running_pids(x_filter))
+ def _next_free_display(self):
+ running_pids = self._port.host.executive.run_command(['ps', '-eo', 'comm,command'])
+ reserved_screens = set()
+ for pid in running_pids.split('\n'):
+ match = re.match('(X|Xvfb|Xorg)\s+.*\s:(?P<screen_number>\d+)', pid)
+ if match:
+ reserved_screens.add(int(match.group('screen_number')))
+ for i in range(99):
+ if i not in reserved_screens:
+ _guard_lock_file = self._port.host.filesystem.join('/tmp', 'WebKitXvfb.lock.%i' % i)
+ self._guard_lock = FileLock(_guard_lock_file)
+ if self._guard_lock.acquire_lock():
+ return i
+ def _start(self, pixel_tests, per_test_args):
# Use even displays for pixel tests and odd ones otherwise. When pixel tests are disabled,
# DriverProxy creates two drivers, one for normal and the other for ref tests. Both have
# the same worker number, so this prevents them from using the same Xvfb instance.
- display_id = self._worker_number * 2 + running_displays
- if pixel_tests:
- display_id += 1
+ display_id = self._next_free_display()
self._lock_file = "/tmp/.X%d-lock" % display_id
run_xvfb = ["Xvfb", ":%d" % display_id, "-screen", "0", "800x600x24", "-nolisten", "tcp"]
with open(os.devnull, 'w') as devnull:
self._xvfb_process = self._port.host.executive.popen(run_xvfb, stderr=devnull)
+ # Crashes intend to occur occasionally in the first few tests that are run through each
+ # worker because the Xvfb display isn't ready yet. Halting execution a bit should avoid that.
+ time.sleep(self._startup_delay_secs)
+
server_name = self._port.driver_name()
environment = self._port.setup_environ_for_server(server_name)
# We must do this here because the DISPLAY number depends on _worker_number
environment['DISPLAY'] = ":%d" % display_id
# Drivers should use separate application cache locations
environment['XDG_CACHE_HOME'] = self._port.host.filesystem.join(self._port.results_directory(), '%s-appcache-%d' % (server_name, self._worker_number))
+ self._driver_tempdir = self._port._filesystem.mkdtemp(prefix='%s-' % self._port.driver_name())
+ environment['DUMPRENDERTREE_TEMP'] = str(self._driver_tempdir)
+ environment['LOCAL_RESOURCE_ROOT'] = self._port.layout_tests_dir()
self._crashed_process_name = None
self._crashed_pid = None
@@ -71,6 +90,9 @@ class XvfbDriver(Driver):
def stop(self):
super(XvfbDriver, self).stop()
+ if self._guard_lock:
+ self._guard_lock.release_lock()
+ self._guard_lock = None
if getattr(self, '_xvfb_process', None):
self._port.host.executive.kill_process(self._xvfb_process.pid)
self._xvfb_process = None
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/xvfbdriver_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/xvfbdriver_unittest.py
index 37a2fbd4d..220dd3517 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/xvfbdriver_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/xvfbdriver_unittest.py
@@ -30,6 +30,7 @@ import unittest
from webkitpy.common.system.deprecated_logging import log
from webkitpy.common.system.filesystem_mock import MockFileSystem
+from webkitpy.common.system.executive_mock import MockExecutive2
from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.common.system.systemhost_mock import MockSystemHost
from webkitpy.layout_tests.port import Port
@@ -39,13 +40,14 @@ from webkitpy.layout_tests.port.xvfbdriver import XvfbDriver
class XvfbDriverTest(unittest.TestCase):
- def make_driver(self, worker_number=0, xorg_running=False):
- port = Port(host=MockSystemHost(log_executive=True), config=MockConfig())
+ def make_driver(self, worker_number=0, xorg_running=False, executive=None):
+ port = Port(host=MockSystemHost(log_executive=True, executive=executive), config=MockConfig())
port._server_process_constructor = MockServerProcess
if xorg_running:
port._executive._running_pids['Xorg'] = 108
driver = XvfbDriver(port, worker_number=worker_number, pixel_tests=True)
+ driver._startup_delay_secs = 0
return driver
def cleanup_driver(self, driver):
@@ -61,26 +63,54 @@ class XvfbDriverTest(unittest.TestCase):
def test_start_no_pixel_tests(self):
driver = self.make_driver()
- expected_stderr = "MOCK running_pids: []\nMOCK popen: ['Xvfb', ':0', '-screen', '0', '800x600x24', '-nolisten', 'tcp']\n"
+ expected_stderr = "MOCK run_command: ['ps', '-eo', 'comm,command'], cwd=None\nMOCK popen: ['Xvfb', ':0', '-screen', '0', '800x600x24', '-nolisten', 'tcp']\n"
self.assertDriverStartSuccessful(driver, expected_stderr=expected_stderr, expected_display=":0")
self.cleanup_driver(driver)
def test_start_pixel_tests(self):
driver = self.make_driver()
- expected_stderr = "MOCK running_pids: []\nMOCK popen: ['Xvfb', ':1', '-screen', '0', '800x600x24', '-nolisten', 'tcp']\n"
- self.assertDriverStartSuccessful(driver, expected_stderr=expected_stderr, expected_display=":1", pixel_tests=True)
+ expected_stderr = "MOCK run_command: ['ps', '-eo', 'comm,command'], cwd=None\nMOCK popen: ['Xvfb', ':0', '-screen', '0', '800x600x24', '-nolisten', 'tcp']\n"
+ self.assertDriverStartSuccessful(driver, expected_stderr=expected_stderr, expected_display=":0", pixel_tests=True)
self.cleanup_driver(driver)
def test_start_arbitrary_worker_number(self):
driver = self.make_driver(worker_number=17)
- expected_stderr = "MOCK running_pids: []\nMOCK popen: ['Xvfb', ':35', '-screen', '0', '800x600x24', '-nolisten', 'tcp']\n"
- self.assertDriverStartSuccessful(driver, expected_stderr=expected_stderr, expected_display=":35", pixel_tests=True)
+ expected_stderr = "MOCK run_command: ['ps', '-eo', 'comm,command'], cwd=None\nMOCK popen: ['Xvfb', ':0', '-screen', '0', '800x600x24', '-nolisten', 'tcp']\n"
+ self.assertDriverStartSuccessful(driver, expected_stderr=expected_stderr, expected_display=":0", pixel_tests=True)
self.cleanup_driver(driver)
- def test_start_existing_xorg_process(self):
- driver = self.make_driver(xorg_running=True)
- expected_stderr = "MOCK running_pids: [108]\nMOCK popen: ['Xvfb', ':1', '-screen', '0', '800x600x24', '-nolisten', 'tcp']\n"
- self.assertDriverStartSuccessful(driver, expected_stderr=expected_stderr, expected_display=":1")
+ def disabled_test_next_free_display(self):
+ output = "Xorg /usr/bin/X :0 -auth /var/run/lightdm/root/:0 -nolisten tcp vt7 -novtswitch -background none\nXvfb Xvfb :1 -screen 0 800x600x24 -nolisten tcp"
+ executive = MockExecutive2(output)
+ driver = self.make_driver(executive=executive)
+ self.assertEqual(driver._next_free_display(), 2)
+ self.cleanup_driver(driver)
+ output = "X /usr/bin/X :0 vt7 -nolisten tcp -auth /var/run/xauth/A:0-8p7Ybb"
+ executive = MockExecutive2(output)
+ driver = self.make_driver(executive=executive)
+ self.assertEqual(driver._next_free_display(), 1)
+ self.cleanup_driver(driver)
+ output = "Xvfb Xvfb :0 -screen 0 800x600x24 -nolisten tcp"
+ executive = MockExecutive2(output)
+ driver = self.make_driver(executive=executive)
+ self.assertEqual(driver._next_free_display(), 1)
+ self.cleanup_driver(driver)
+ output = "Xvfb Xvfb :1 -screen 0 800x600x24 -nolisten tcp\nXvfb Xvfb :0 -screen 0 800x600x24 -nolisten tcp\nXvfb Xvfb :3 -screen 0 800x600x24 -nolisten tcp"
+ executive = MockExecutive2(output)
+ driver = self.make_driver(executive=executive)
+ self.assertEqual(driver._next_free_display(), 2)
+ self.cleanup_driver(driver)
+
+ def test_start_next_worker(self):
+ driver = self.make_driver()
+ driver._next_free_display = lambda: 0
+ expected_stderr = "MOCK popen: ['Xvfb', ':0', '-screen', '0', '800x600x24', '-nolisten', 'tcp']\n"
+ self.assertDriverStartSuccessful(driver, expected_stderr=expected_stderr, expected_display=":0", pixel_tests=True)
+ self.cleanup_driver(driver)
+ driver = self.make_driver()
+ driver._next_free_display = lambda: 3
+ expected_stderr = "MOCK popen: ['Xvfb', ':3', '-screen', '0', '800x600x24', '-nolisten', 'tcp']\n"
+ self.assertDriverStartSuccessful(driver, expected_stderr=expected_stderr, expected_display=":3", pixel_tests=True)
self.cleanup_driver(driver)
def test_stop(self):
diff --git a/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py b/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py
index e784cb61d..89522079c 100755
--- a/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py
+++ b/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py
@@ -129,6 +129,9 @@ def _set_up_derived_options(port, options):
if not options.child_processes:
options.child_processes = os.environ.get("WEBKIT_TEST_CHILD_PROCESSES",
str(port.default_child_processes()))
+ if not options.max_locked_shards:
+ options.max_locked_shards = int(os.environ.get("WEBKIT_TEST_MAX_LOCKED_SHARDS",
+ str(port.default_max_locked_shards())))
if not options.configuration:
options.configuration = port.default_configuration()
@@ -375,7 +378,11 @@ def parse_args(args=None):
optparse.make_option("--test-list", action="append",
help="read list of tests to run from file", metavar="FILE"),
optparse.make_option("--skipped", action="store", default="default",
- help="control how tests marked SKIP are run. 'default' == Skip, 'ignore' == Run them anyway, 'only' == only run the SKIP tests."),
+ help=("control how tests marked SKIP are run. "
+ "'default' == Skip tests unless explicitly listed on the command line, "
+ "'ignore' == Run them anyway, "
+ "'only' == only run the SKIP tests, "
+ "'always' == always skip, even if listed on the command line.")),
optparse.make_option("--force", dest="skipped", action="store_const", const='ignore',
help="Run all tests, even those marked SKIP in the test list (same as --skipped=ignore)"),
optparse.make_option("--time-out-ms",
@@ -412,7 +419,7 @@ def parse_args(args=None):
optparse.make_option("--no-retry-failures", action="store_false",
dest="retry_failures",
help="Don't re-try any tests that produce unexpected results."),
- optparse.make_option("--max-locked-shards", type="int", default=1,
+ optparse.make_option("--max-locked-shards", type="int", default=0,
help="Set the maximum number of locked shards"),
optparse.make_option("--additional-env-var", type="string", action="append", default=[],
help="Passes that environment variable to the tests (--additional-env-var=NAME=VALUE)"),
diff --git a/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_integrationtest.py b/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_integrationtest.py
index 4afcc1466..85437449b 100755
--- a/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_integrationtest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_integrationtest.py
@@ -301,6 +301,12 @@ class MainTest(unittest.TestCase, StreamTestingMixin):
for batch in batch_tests_run:
self.assertTrue(len(batch) <= 2, '%s had too many tests' % ', '.join(batch))
+ def test_max_locked_shards(self):
+ if not self.should_test_processes:
+ return
+ _, _, regular_output, _ = logging_run(['--debug-rwt-logging', '--child-processes', '2'], shared_port=False)
+ self.assertTrue(any(['(1 locked)' in line for line in regular_output.buflist]))
+
def test_child_processes_2(self):
if self.should_test_processes:
_, _, regular_output, _ = logging_run(
@@ -310,7 +316,7 @@ class MainTest(unittest.TestCase, StreamTestingMixin):
def test_child_processes_min(self):
if self.should_test_processes:
_, _, regular_output, _ = logging_run(
- ['--debug-rwt-logging', '--child-processes', '2', 'passes'],
+ ['--debug-rwt-logging', '--child-processes', '2', '-i', 'passes/passes', 'passes'],
tests_included=True, shared_port=False)
self.assertTrue(any(['Running 1 ' in line for line in regular_output.buflist]))
@@ -418,6 +424,10 @@ class MainTest(unittest.TestCase, StreamTestingMixin):
self.assertEquals(get_tests_run(['--skipped=only', 'passes'], tests_included=True, flatten_batches=True),
['passes/skipped/skip.html'])
+ # Now check that we don't run anything.
+ self.assertEquals(get_tests_run(['--skipped=always', 'passes/skipped/skip.html'], tests_included=True, flatten_batches=True),
+ [])
+
def test_iterations(self):
tests_to_run = ['passes/image.html', 'passes/text.html']
tests_run = get_tests_run(['--iterations', '2'] + tests_to_run, tests_included=True, flatten_batches=True)
@@ -775,7 +785,7 @@ class MainTest(unittest.TestCase, StreamTestingMixin):
# These next tests test that we run the tests in ascending alphabetical
# order per directory. HTTP tests are sharded separately from other tests,
# so we have to test both.
- tests_run = get_tests_run(['passes'], tests_included=True, flatten_batches=True)
+ tests_run = get_tests_run(['-i', 'passes/passes', 'passes'], tests_included=True, flatten_batches=True)
self.assertEquals(tests_run, sorted(tests_run))
tests_run = get_tests_run(['http/tests/passes'], tests_included=True, flatten_batches=True)
@@ -922,6 +932,11 @@ class MainTest(unittest.TestCase, StreamTestingMixin):
# child process (e.g., on win32) and we need to make sure that works and we still
# see the verbose log output. However, we can't use logging_run() because using
# outputcapture to capture stdout and stderr latter results in a nonpicklable host.
+
+ # Test is flaky on Windows: https://bugs.webkit.org/show_bug.cgi?id=98559
+ if not self.should_test_processes:
+ return
+
options, parsed_args = parse_args(['--verbose', '--fully-parallel', '--child-processes', '2', 'passes/text.html', 'passes/image.html'], tests_included=True, print_nothing=False)
host = MockHost()
port_obj = host.port_factory.get(port_name=options.platform, options=options)
diff --git a/Tools/Scripts/webkitpy/layout_tests/servers/apache_http_server.py b/Tools/Scripts/webkitpy/layout_tests/servers/apache_http_server.py
index 7cd4af538..a616fab5b 100644
--- a/Tools/Scripts/webkitpy/layout_tests/servers/apache_http_server.py
+++ b/Tools/Scripts/webkitpy/layout_tests/servers/apache_http_server.py
@@ -54,7 +54,6 @@ class LayoutTestApacheHttpd(http_server_base.HttpServerBase):
self._name = 'httpd'
self._mappings = [{'port': 8000},
{'port': 8080},
- {'port': 8081},
{'port': 8443, 'sslcert': True}]
self._output_dir = output_dir
self._filesystem.maybe_make_directory(output_dir)
@@ -79,7 +78,6 @@ class LayoutTestApacheHttpd(http_server_base.HttpServerBase):
'-c', "\'Alias /js-test-resources \"%s\"'" % js_test_resources_dir,
'-c', "\'Alias /media-resources \"%s\"'" % media_resources_dir,
'-C', "\'Listen %s\'" % "127.0.0.1:8000",
- '-C', "\'Listen %s\'" % "127.0.0.1:8081",
'-c', "\'TypesConfig \"%s\"\'" % mime_types_path,
'-c', "\'CustomLog \"%s\" common\'" % access_log,
'-c', "\'ErrorLog \"%s\"\'" % error_log,
diff --git a/Tools/Scripts/webkitpy/performance_tests/perftest.py b/Tools/Scripts/webkitpy/performance_tests/perftest.py
index fdac35b11..32b9d8bc6 100644
--- a/Tools/Scripts/webkitpy/performance_tests/perftest.py
+++ b/Tools/Scripts/webkitpy/performance_tests/perftest.py
@@ -1,5 +1,6 @@
#!/usr/bin/env python
# Copyright (C) 2012 Google Inc. All rights reserved.
+# Copyright (C) 2012 Zoltan Horvath, Adobe Systems Incorporated. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
@@ -202,11 +203,40 @@ class ChromiumStylePerfTest(PerfTest):
class PageLoadingPerfTest(PerfTest):
+ _FORCE_GC_FILE = 'resources/force-gc.html'
+
def __init__(self, port, test_name, path_or_url):
super(PageLoadingPerfTest, self).__init__(port, test_name, path_or_url)
+ self.force_gc_test = self._port.host.filesystem.join(self._port.perf_tests_dir(), self._FORCE_GC_FILE)
+
+ def run_single(self, driver, path_or_url, time_out_ms, should_run_pixel_test=False):
+ # Force GC to prevent pageload noise. See https://bugs.webkit.org/show_bug.cgi?id=98203
+ super(PageLoadingPerfTest, self).run_single(driver, self.force_gc_test, time_out_ms, False)
+ return super(PageLoadingPerfTest, self).run_single(driver, path_or_url, time_out_ms, should_run_pixel_test)
+
+ def calculate_statistics(self, values):
+ sorted_values = sorted(values)
+
+ # Compute the mean and variance using Knuth's online algorithm (has good numerical stability).
+ squareSum = 0
+ mean = 0
+ for i, time in enumerate(sorted_values):
+ delta = time - mean
+ sweep = i + 1.0
+ mean += delta / sweep
+ squareSum += delta * (time - mean)
+
+ middle = int(len(sorted_values) / 2)
+ result = {'avg': mean,
+ 'min': sorted_values[0],
+ 'max': sorted_values[-1],
+ 'median': sorted_values[middle] if len(sorted_values) % 2 else (sorted_values[middle - 1] + sorted_values[middle]) / 2,
+ 'stdev': math.sqrt(squareSum / (len(sorted_values) - 1))}
+ return result
def run(self, driver, time_out_ms):
- test_times = []
+ results = {}
+ results.setdefault(self.test_name(), {'unit': 'ms', 'values': []})
for i in range(0, 20):
output = self.run_single(driver, self.path_or_url(), time_out_ms)
@@ -214,30 +244,25 @@ class PageLoadingPerfTest(PerfTest):
return None
if i == 0:
continue
- test_times.append(output.test_time * 1000)
- sorted_test_times = sorted(test_times)
+ results[self.test_name()]['values'].append(output.test_time * 1000)
- # Compute the mean and variance using a numerically stable algorithm.
- squareSum = 0
- mean = 0
- valueSum = sum(sorted_test_times)
- for i, time in enumerate(sorted_test_times):
- delta = time - mean
- sweep = i + 1.0
- mean += delta / sweep
- squareSum += delta * delta * (i / sweep)
-
- middle = int(len(test_times) / 2)
- results = {'values': test_times,
- 'avg': mean,
- 'min': sorted_test_times[0],
- 'max': sorted_test_times[-1],
- 'median': sorted_test_times[middle] if len(sorted_test_times) % 2 else (sorted_test_times[middle - 1] + sorted_test_times[middle]) / 2,
- 'stdev': math.sqrt(squareSum),
- 'unit': 'ms'}
- self.output_statistics(self.test_name(), results, '')
- return {self.test_name(): results}
+ if not output.measurements:
+ continue
+
+ for result_class, result in output.measurements.items():
+ name = self.test_name() + ':' + result_class
+ if not name in results:
+ results.setdefault(name, {'values': []})
+ results[name]['values'].append(result)
+ if result_class == 'Malloc' or result_class == 'JSHeap':
+ results[name]['unit'] = 'bytes'
+
+ for result_class in results.keys():
+ results[result_class].update(self.calculate_statistics(results[result_class]['values']))
+ self.output_statistics(result_class, results[result_class], '')
+
+ return results
class ReplayServer(object):
diff --git a/Tools/Scripts/webkitpy/performance_tests/perftest_unittest.py b/Tools/Scripts/webkitpy/performance_tests/perftest_unittest.py
index 27a4bb385..4410903e9 100755
--- a/Tools/Scripts/webkitpy/performance_tests/perftest_unittest.py
+++ b/Tools/Scripts/webkitpy/performance_tests/perftest_unittest.py
@@ -43,6 +43,10 @@ from webkitpy.performance_tests.perftest import PerfTestFactory
from webkitpy.performance_tests.perftest import ReplayPerfTest
+class MockPort(TestPort):
+ def __init__(self, custom_run_test=None):
+ super(MockPort, self).__init__(host=MockHost(), custom_run_test=custom_run_test)
+
class MainTest(unittest.TestCase):
def test_parse_output(self):
output = DriverOutput('\n'.join([
@@ -98,39 +102,69 @@ class MainTest(unittest.TestCase):
class TestPageLoadingPerfTest(unittest.TestCase):
class MockDriver(object):
- def __init__(self, values):
+ def __init__(self, values, test, measurements=None):
self._values = values
self._index = 0
+ self._test = test
+ self._measurements = measurements
def run_test(self, input, stop_when_done):
+ if input.test_name == self._test.force_gc_test:
+ return
value = self._values[self._index]
self._index += 1
if isinstance(value, str):
return DriverOutput('some output', image=None, image_hash=None, audio=None, error=value)
else:
- return DriverOutput('some output', image=None, image_hash=None, audio=None, test_time=self._values[self._index - 1])
+ return DriverOutput('some output', image=None, image_hash=None, audio=None, test_time=self._values[self._index - 1], measurements=self._measurements)
def test_run(self):
- test = PageLoadingPerfTest(None, 'some-test', '/path/some-dir/some-test')
- driver = TestPageLoadingPerfTest.MockDriver(range(1, 21))
+ port = MockPort()
+ test = PageLoadingPerfTest(port, 'some-test', '/path/some-dir/some-test')
+ driver = TestPageLoadingPerfTest.MockDriver(range(1, 21), test)
output_capture = OutputCapture()
output_capture.capture_output()
try:
self.assertEqual(test.run(driver, None),
- {'some-test': {'max': 20000, 'avg': 11000.0, 'median': 11000, 'stdev': math.sqrt(570 * 1000 * 1000), 'min': 2000, 'unit': 'ms',
+ {'some-test': {'max': 20000, 'avg': 11000.0, 'median': 11000, 'stdev': 5627.314338711378, 'min': 2000, 'unit': 'ms',
'values': [i * 1000 for i in range(2, 21)]}})
finally:
actual_stdout, actual_stderr, actual_logs = output_capture.restore_output()
self.assertEqual(actual_stdout, '')
self.assertEqual(actual_stderr, '')
- self.assertEqual(actual_logs, 'RESULT some-test= 11000.0 ms\nmedian= 11000 ms, stdev= 23874.6727726 ms, min= 2000 ms, max= 20000 ms\n')
+ self.assertEqual(actual_logs, 'RESULT some-test= 11000.0 ms\nmedian= 11000 ms, stdev= 5627.31433871 ms, min= 2000 ms, max= 20000 ms\n')
+
+ def test_run_with_memory_output(self):
+ port = MockPort()
+ test = PageLoadingPerfTest(port, 'some-test', '/path/some-dir/some-test')
+ memory_results = {'Malloc': 10, 'JSHeap': 5}
+ self.maxDiff = None
+ driver = TestPageLoadingPerfTest.MockDriver(range(1, 21), test, memory_results)
+ output_capture = OutputCapture()
+ output_capture.capture_output()
+ try:
+ self.assertEqual(test.run(driver, None),
+ {'some-test': {'max': 20000, 'avg': 11000.0, 'median': 11000, 'stdev': 5627.314338711378, 'min': 2000, 'unit': 'ms',
+ 'values': [i * 1000 for i in range(2, 21)]},
+ 'some-test:Malloc': {'max': 10, 'avg': 10.0, 'median': 10, 'min': 10, 'stdev': 0.0, 'unit': 'bytes',
+ 'values': [10] * 19},
+ 'some-test:JSHeap': {'max': 5, 'avg': 5.0, 'median': 5, 'min': 5, 'stdev': 0.0, 'unit': 'bytes',
+ 'values': [5] * 19}})
+ finally:
+ actual_stdout, actual_stderr, actual_logs = output_capture.restore_output()
+ self.assertEqual(actual_stdout, '')
+ self.assertEqual(actual_stderr, '')
+ self.assertEqual(actual_logs, 'RESULT some-test= 11000.0 ms\nmedian= 11000 ms, stdev= 5627.31433871 ms, min= 2000 ms, max= 20000 ms\n'
+ + 'RESULT some-test: Malloc= 10.0 bytes\nmedian= 10 bytes, stdev= 0.0 bytes, min= 10 bytes, max= 10 bytes\n'
+ + 'RESULT some-test: JSHeap= 5.0 bytes\nmedian= 5 bytes, stdev= 0.0 bytes, min= 5 bytes, max= 5 bytes\n')
def test_run_with_bad_output(self):
output_capture = OutputCapture()
output_capture.capture_output()
try:
- test = PageLoadingPerfTest(None, 'some-test', '/path/some-dir/some-test')
- driver = TestPageLoadingPerfTest.MockDriver([1, 2, 3, 4, 5, 6, 7, 'some error', 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20])
+ port = MockPort()
+ test = PageLoadingPerfTest(port, 'some-test', '/path/some-dir/some-test')
+ driver = TestPageLoadingPerfTest.MockDriver([1, 2, 3, 4, 5, 6, 7, 'some error', 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], test)
self.assertEqual(test.run(driver, None), None)
finally:
actual_stdout, actual_stderr, actual_logs = output_capture.restore_output()
@@ -141,7 +175,7 @@ class TestPageLoadingPerfTest(unittest.TestCase):
class TestReplayPerfTest(unittest.TestCase):
- class ReplayTestPort(TestPort):
+ class ReplayTestPort(MockPort):
def __init__(self, custom_run_test=None):
class ReplayTestDriver(TestDriver):
@@ -149,7 +183,7 @@ class TestReplayPerfTest(unittest.TestCase):
return custom_run_test(text_input, stop_when_done) if custom_run_test else None
self._custom_driver_class = ReplayTestDriver
- super(self.__class__, self).__init__(host=MockHost())
+ super(self.__class__, self).__init__()
def _driver_class(self):
return self._custom_driver_class
@@ -179,6 +213,9 @@ class TestReplayPerfTest(unittest.TestCase):
loaded_pages = []
def run_test(test_input, stop_when_done):
+ if test_input.test_name == test.force_gc_test:
+ loaded_pages.append(test_input)
+ return
if test_input.test_name != "about:blank":
self.assertEqual(test_input.test_name, 'http://some-test/')
loaded_pages.append(test_input)
@@ -196,8 +233,9 @@ class TestReplayPerfTest(unittest.TestCase):
finally:
actual_stdout, actual_stderr, actual_logs = output_capture.restore_output()
- self.assertEqual(len(loaded_pages), 1)
- self.assertEqual(loaded_pages[0].test_name, 'http://some-test/')
+ self.assertEqual(len(loaded_pages), 2)
+ self.assertEqual(loaded_pages[0].test_name, test.force_gc_test)
+ self.assertEqual(loaded_pages[1].test_name, 'http://some-test/')
self.assertEqual(actual_stdout, '')
self.assertEqual(actual_stderr, '')
self.assertEqual(actual_logs, '')
@@ -262,8 +300,9 @@ class TestReplayPerfTest(unittest.TestCase):
finally:
actual_stdout, actual_stderr, actual_logs = output_capture.restore_output()
- self.assertEqual(len(loaded_pages), 1)
- self.assertEqual(loaded_pages[0].test_name, 'http://some-test/')
+ self.assertEqual(len(loaded_pages), 2)
+ self.assertEqual(loaded_pages[0].test_name, test.force_gc_test)
+ self.assertEqual(loaded_pages[1].test_name, 'http://some-test/')
self.assertEqual(actual_stdout, '')
self.assertEqual(actual_stderr, '')
self.assertEqual(actual_logs, 'error: some-test.replay\nsome error\n')
@@ -316,15 +355,15 @@ class TestReplayPerfTest(unittest.TestCase):
class TestPerfTestFactory(unittest.TestCase):
def test_regular_test(self):
- test = PerfTestFactory.create_perf_test(None, 'some-dir/some-test', '/path/some-dir/some-test')
+ test = PerfTestFactory.create_perf_test(MockPort(), 'some-dir/some-test', '/path/some-dir/some-test')
self.assertEqual(test.__class__, PerfTest)
def test_inspector_test(self):
- test = PerfTestFactory.create_perf_test(None, 'inspector/some-test', '/path/inspector/some-test')
+ test = PerfTestFactory.create_perf_test(MockPort(), 'inspector/some-test', '/path/inspector/some-test')
self.assertEqual(test.__class__, ChromiumStylePerfTest)
def test_page_loading_test(self):
- test = PerfTestFactory.create_perf_test(None, 'PageLoad/some-test', '/path/PageLoad/some-test')
+ test = PerfTestFactory.create_perf_test(MockPort(), 'PageLoad/some-test', '/path/PageLoad/some-test')
self.assertEqual(test.__class__, PageLoadingPerfTest)
diff --git a/Tools/Scripts/webkitpy/performance_tests/perftestsrunner.py b/Tools/Scripts/webkitpy/performance_tests/perftestsrunner.py
index c34d0b3e4..42e0d96e1 100755
--- a/Tools/Scripts/webkitpy/performance_tests/perftestsrunner.py
+++ b/Tools/Scripts/webkitpy/performance_tests/perftestsrunner.py
@@ -29,19 +29,17 @@
"""Run Inspector's perf tests in perf mode."""
+import os
import json
import logging
import optparse
-import re
-import sys
import time
from webkitpy.common import find_files
+from webkitpy.common.checkout.scm.detection import SCMDetector
from webkitpy.common.host import Host
from webkitpy.common.net.file_uploader import FileUploader
-from webkitpy.layout_tests.views import printing
from webkitpy.performance_tests.perftest import PerfTestFactory
-from webkitpy.performance_tests.perftest import ReplayPerfTest
_log = logging.getLogger(__name__)
@@ -65,7 +63,7 @@ class PerfTestsRunner(object):
else:
self._host = Host()
self._port = self._host.port_factory.get(self._options.platform, self._options)
- self._host._initialize_scm()
+ self._host.initialize_scm()
self._webkit_base_dir_len = len(self._port.webkit_base())
self._base_path = self._port.perf_tests_dir()
self._results = {}
@@ -73,6 +71,9 @@ class PerfTestsRunner(object):
@staticmethod
def _parse_args(args=None):
+ def _expand_path(option, opt_str, value, parser):
+ path = os.path.expandvars(os.path.expanduser(value))
+ setattr(parser.values, option.dest, path)
perf_option_list = [
optparse.make_option('--debug', action='store_const', const='Debug', dest="configuration",
help='Set the configuration to Debug'),
@@ -98,15 +99,12 @@ class PerfTestsRunner(object):
help="Pause before running the tests to let user attach a performance monitor."),
optparse.make_option("--no-results", action="store_false", dest="generate_results", default=True,
help="Do no generate results JSON and results page."),
- optparse.make_option("--output-json-path",
+ optparse.make_option("--output-json-path", action='callback', callback=_expand_path, type="str",
help="Path to generate a JSON file at; may contain previous results if it already exists."),
optparse.make_option("--reset-results", action="store_true",
help="Clears the content in the generated JSON file before adding the results."),
- optparse.make_option("--slave-config-json-path",
+ optparse.make_option("--slave-config-json-path", action='callback', callback=_expand_path, type="str",
help="Only used on bots. Path to a slave configuration file."),
- optparse.make_option("--source-json-path", dest="slave_config_json_path",
- # FIXME: Remove this option once build.webkit.org is updated to use --slave-config-json-path.
- help="Deprecated. Overrides --slave-config-json-path."),
optparse.make_option("--description",
help="Add a description to the output JSON file if one is generated"),
optparse.make_option("--no-show-results", action="store_false", default=True, dest="show_results",
@@ -181,11 +179,6 @@ class PerfTestsRunner(object):
def _generate_and_show_results(self):
options = self._options
- if options.test_results_server:
- # Remove this code once build.webkit.org started using --no-show-results and --reset-results
- options.reset_results = True
- options.show_results = False
-
output_json_path = self._output_json_path()
output = self._generate_results_dict(self._timestamp, options.description, options.platform, options.builder_name, options.build_number)
@@ -213,7 +206,8 @@ class PerfTestsRunner(object):
if description:
contents['description'] = description
for (name, path) in self._port.repository_paths():
- contents[name + '-revision'] = self._host.scm().svn_revision(path)
+ scm = SCMDetector(self._host.filesystem, self._host.executive).detect_scm_system(path) or self._host.scm()
+ contents[name + '-revision'] = scm.svn_revision(path)
# FIXME: Add --branch or auto-detect the branch we're in
for key, value in {'timestamp': int(timestamp), 'branch': self._default_branch, 'platform': platform,
diff --git a/Tools/Scripts/webkitpy/performance_tests/perftestsrunner_unittest.py b/Tools/Scripts/webkitpy/performance_tests/perftestsrunner_unittest.py
index d3de7b3df..6119c61d3 100755
--- a/Tools/Scripts/webkitpy/performance_tests/perftestsrunner_unittest.py
+++ b/Tools/Scripts/webkitpy/performance_tests/perftestsrunner_unittest.py
@@ -473,7 +473,7 @@ max 548000 bytes
def test_run_with_slave_config_json(self):
runner, port = self.create_runner_and_setup_results_template(args=['--output-json-path=/mock-checkout/output.json',
- '--source-json-path=/mock-checkout/slave-config.json', '--test-results-server=some.host'])
+ '--slave-config-json-path=/mock-checkout/slave-config.json', '--test-results-server=some.host'])
port.host.filesystem.write_text_file('/mock-checkout/slave-config.json', '{"key": "value"}')
self._test_run_with_json_output(runner, port.host.filesystem, upload_suceeds=True)
self.assertEqual(runner.load_output_json(), [{
@@ -625,8 +625,10 @@ max 548000 bytes
'--builder-name', 'webkit-mac-1',
'--build-number=56',
'--time-out-ms=42',
+ '--no-show-results',
+ '--reset-results',
'--output-json-path=a/output.json',
- '--source-json-path=a/source.json',
+ '--slave-config-json-path=a/source.json',
'--test-results-server=somehost',
'--debug'])
self.assertEqual(options.build, True)
@@ -636,6 +638,8 @@ max 548000 bytes
self.assertEqual(options.build_number, '56')
self.assertEqual(options.time_out_ms, '42')
self.assertEqual(options.configuration, 'Debug')
+ self.assertEqual(options.show_results, False)
+ self.assertEqual(options.reset_results, True)
self.assertEqual(options.output_json_path, 'a/output.json')
self.assertEqual(options.slave_config_json_path, 'a/source.json')
self.assertEqual(options.test_results_server, 'somehost')
diff --git a/Tools/Scripts/webkitpy/style/checker.py b/Tools/Scripts/webkitpy/style/checker.py
index 5b11af483..9f27c36da 100644
--- a/Tools/Scripts/webkitpy/style/checker.py
+++ b/Tools/Scripts/webkitpy/style/checker.py
@@ -96,7 +96,6 @@ _BASE_FILTER_RULES = [
'-runtime/rtti',
'-whitespace/blank_line',
'-whitespace/end_of_line',
- '-whitespace/labels',
# List Python pep8 categories last.
#
# Because much of WebKit's Python code base does not abide by the
diff --git a/Tools/Scripts/webkitpy/style/checkers/cpp.py b/Tools/Scripts/webkitpy/style/checkers/cpp.py
index a0051c979..ebbd1ad2f 100644
--- a/Tools/Scripts/webkitpy/style/checkers/cpp.py
+++ b/Tools/Scripts/webkitpy/style/checkers/cpp.py
@@ -2060,6 +2060,31 @@ def check_directive_indentation(clean_lines, line_number, file_state, error):
error(line_number, 'whitespace/indent', 4, 'preprocessor directives (e.g., #ifdef, #define, #import) should never be indented.')
+def get_initial_spaces_for_line(clean_line):
+ initial_spaces = 0
+ while initial_spaces < len(clean_line) and clean_line[initial_spaces] == ' ':
+ initial_spaces += 1
+ return initial_spaces
+
+
+def check_indentation_amount(clean_lines, line_number, error):
+ line = clean_lines.elided[line_number]
+ initial_spaces = get_initial_spaces_for_line(line)
+
+ if initial_spaces % 4:
+ error(line_number, 'whitespace/indent', 3,
+ 'Weird number of spaces at line-start. Are you using a 4-space indent?')
+ return
+
+ previous_line = get_previous_non_blank_line(clean_lines, line_number)[0]
+ if not previous_line.strip() or match(r'\s*\w+\s*:\s*$', previous_line) or previous_line[0] == '#':
+ return
+
+ previous_line_initial_spaces = get_initial_spaces_for_line(previous_line)
+ if initial_spaces > previous_line_initial_spaces + 4:
+ error(line_number, 'whitespace/indent', 3, 'When wrapping a line, only indent 4 spaces.')
+
+
def check_using_std(clean_lines, line_number, file_state, error):
"""Looks for 'using std::foo;' statements which should be replaced with 'using namespace std;'.
@@ -2535,44 +2560,10 @@ def check_style(clean_lines, line_number, file_extension, class_state, file_stat
error(line_number, 'whitespace/tab', 1,
'Tab found; better to use spaces')
- # One or three blank spaces at the beginning of the line is weird; it's
- # hard to reconcile that with 4-space indents.
- # NOTE: here are the conditions rob pike used for his tests. Mine aren't
- # as sophisticated, but it may be worth becoming so: RLENGTH==initial_spaces
- # if(RLENGTH > 20) complain = 0;
- # if(match($0, " +(error|private|public|protected):")) complain = 0;
- # if(match(prev, "&& *$")) complain = 0;
- # if(match(prev, "\\|\\| *$")) complain = 0;
- # if(match(prev, "[\",=><] *$")) complain = 0;
- # if(match($0, " <<")) complain = 0;
- # if(match(prev, " +for \\(")) complain = 0;
- # if(prevodd && match(prevprev, " +for \\(")) complain = 0;
- initial_spaces = 0
cleansed_line = clean_lines.elided[line_number]
- while initial_spaces < len(line) and line[initial_spaces] == ' ':
- initial_spaces += 1
if line and line[-1].isspace():
error(line_number, 'whitespace/end_of_line', 4,
'Line ends in whitespace. Consider deleting these extra spaces.')
- # There are certain situations we allow one space, notably for labels
- elif ((initial_spaces >= 1 and initial_spaces <= 3)
- and not match(r'\s*\w+\s*:\s*$', cleansed_line)):
- error(line_number, 'whitespace/indent', 3,
- 'Weird number of spaces at line-start. '
- 'Are you using a 4-space indent?')
- # Labels should always be indented at least one space.
- elif not initial_spaces and line[:2] != '//':
- label_match = match(r'(?P<label>[^:]+):\s*$', line)
-
- if label_match:
- label = label_match.group('label')
- # Only throw errors for stuff that is definitely not a goto label,
- # because goto labels can in fact occur at the start of the line.
- if label in ['public', 'private', 'protected'] or label.find(' ') != -1:
- error(line_number, 'whitespace/labels', 4,
- 'Labels should always be indented at least one space. '
- 'If this is a member-initializer list in a constructor, '
- 'the colon should be on the line after the definition header.')
if (cleansed_line.count(';') > 1
# for loops are allowed two ;'s (and may run over two lines).
@@ -2612,6 +2603,7 @@ def check_style(clean_lines, line_number, file_extension, class_state, file_stat
check_check(clean_lines, line_number, error)
check_for_comparisons_to_zero(clean_lines, line_number, error)
check_for_null(clean_lines, line_number, file_state, error)
+ check_indentation_amount(clean_lines, line_number, error)
_RE_PATTERN_INCLUDE_NEW_STYLE = re.compile(r'#include +"[^/]+\.h"')
@@ -3634,7 +3626,6 @@ class CppChecker(object):
'whitespace/end_of_line',
'whitespace/ending_newline',
'whitespace/indent',
- 'whitespace/labels',
'whitespace/line_length',
'whitespace/newline',
'whitespace/operators',
diff --git a/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py b/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py
index a1b91f292..6f001e0cb 100644
--- a/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py
+++ b/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py
@@ -1086,7 +1086,8 @@ class CppStyleTest(CppStyleTestBase):
*/ ''',
'')
self.assert_multi_line_lint(
- r'''/* int a = 0; multi-liner
+ '''\
+ /* int a = 0; multi-liner
static const int b = 0;''',
['Could not find end of multi-line comment'
' [readability/multiline_comment] [5]',
@@ -1125,97 +1126,111 @@ class CppStyleTest(CppStyleTestBase):
def test_explicit_single_argument_constructors(self):
# missing explicit is bad
self.assert_multi_line_lint(
- '''class Foo {
- Foo(int f);
- };''',
+ '''\
+ class Foo {
+ Foo(int f);
+ };''',
'Single-argument constructors should be marked explicit.'
' [runtime/explicit] [5]')
# missing explicit is bad, even with whitespace
self.assert_multi_line_lint(
- '''class Foo {
- Foo (int f);
- };''',
+ '''\
+ class Foo {
+ Foo (int f);
+ };''',
['Extra space before ( in function call [whitespace/parens] [4]',
'Single-argument constructors should be marked explicit.'
' [runtime/explicit] [5]'])
# missing explicit, with distracting comment, is still bad
self.assert_multi_line_lint(
- '''class Foo {
- Foo(int f); // simpler than Foo(blargh, blarg)
- };''',
+ '''\
+ class Foo {
+ Foo(int f); // simpler than Foo(blargh, blarg)
+ };''',
'Single-argument constructors should be marked explicit.'
' [runtime/explicit] [5]')
# missing explicit, with qualified classname
self.assert_multi_line_lint(
- '''class Qualifier::AnotherOne::Foo {
- Foo(int f);
- };''',
+ '''\
+ class Qualifier::AnotherOne::Foo {
+ Foo(int f);
+ };''',
'Single-argument constructors should be marked explicit.'
' [runtime/explicit] [5]')
# structs are caught as well.
self.assert_multi_line_lint(
- '''struct Foo {
- Foo(int f);
- };''',
+ '''\
+ struct Foo {
+ Foo(int f);
+ };''',
'Single-argument constructors should be marked explicit.'
' [runtime/explicit] [5]')
# Templatized classes are caught as well.
self.assert_multi_line_lint(
- '''template<typename T> class Foo {
- Foo(int f);
- };''',
+ '''\
+ template<typename T> class Foo {
+ Foo(int f);
+ };''',
'Single-argument constructors should be marked explicit.'
' [runtime/explicit] [5]')
# proper style is okay
self.assert_multi_line_lint(
- '''class Foo {
- explicit Foo(int f);
- };''',
+ '''\
+ class Foo {
+ explicit Foo(int f);
+ };''',
'')
# two argument constructor is okay
self.assert_multi_line_lint(
- '''class Foo {
- Foo(int f, int b);
- };''',
+ '''\
+ class Foo {
+ Foo(int f, int b);
+ };''',
'')
# two argument constructor, across two lines, is okay
self.assert_multi_line_lint(
- '''class Foo {
- Foo(int f,
- int b);
- };''',
+ '''\
+ class Foo {
+ Foo(int f,
+ int b);
+ };''',
'')
# non-constructor (but similar name), is okay
self.assert_multi_line_lint(
- '''class Foo {
- aFoo(int f);
- };''',
+ '''\
+ class Foo {
+ aFoo(int f);
+ };''',
'')
# constructor with void argument is okay
self.assert_multi_line_lint(
- '''class Foo {
- Foo(void);
- };''',
+ '''\
+ class Foo {
+ Foo(void);
+ };''',
'')
# single argument method is okay
self.assert_multi_line_lint(
- '''class Foo {
- Bar(int b);
- };''',
+ '''\
+ class Foo {
+ Bar(int b);
+ };''',
'')
# comments should be ignored
self.assert_multi_line_lint(
- '''class Foo {
- // Foo(int f);
- };''',
+ '''\
+ class Foo {
+ // Foo(int f);
+ };''',
'')
# single argument function following class definition is okay
# (okay, it's not actually valid, but we don't want a false positive)
self.assert_multi_line_lint(
- '''class Foo {
- Foo(int f, int b);
- };
- Foo(int f);''',
+ '''\
+ class Foo {
+ Foo(int f, int b);
+ };
+ Foo(int f);''',
'')
# single argument function is okay
self.assert_multi_line_lint(
@@ -1223,14 +1238,16 @@ class CppStyleTest(CppStyleTestBase):
'')
# single argument copy constructor is okay.
self.assert_multi_line_lint(
- '''class Foo {
- Foo(const Foo&);
- };''',
+ '''\
+ class Foo {
+ Foo(const Foo&);
+ };''',
'')
self.assert_multi_line_lint(
- '''class Foo {
- Foo(Foo&);
- };''',
+ '''\
+ class Foo {
+ Foo(Foo&);
+ };''',
'')
def test_slash_star_comment_on_single_line(self):
@@ -1248,9 +1265,6 @@ class CppStyleTest(CppStyleTestBase):
''' /*/ static Foo(int f);''',
'Could not find end of multi-line comment'
' [readability/multiline_comment] [5]')
- self.assert_multi_line_lint(
- ''' /**/ static Foo(int f);''',
- '')
# Test suspicious usage of "if" like this:
# if (a == b) {
@@ -1383,23 +1397,27 @@ class CppStyleTest(CppStyleTestBase):
# or initializing an array
self.assert_lint('int a[3] = { 1, 2, 3 };', '')
self.assert_lint(
- '''const int foo[] =
- {1, 2, 3 };''',
+ '''\
+ const int foo[] =
+ {1, 2, 3 };''',
'')
# For single line, unmatched '}' with a ';' is ignored (not enough context)
self.assert_multi_line_lint(
- '''int a[3] = { 1,
- 2,
- 3 };''',
+ '''\
+ int a[3] = { 1,
+ 2,
+ 3 };''',
'')
self.assert_multi_line_lint(
- '''int a[2][3] = { { 1, 2 },
- { 3, 4 } };''',
+ '''\
+ int a[2][3] = { { 1, 2 },
+ { 3, 4 } };''',
'')
self.assert_multi_line_lint(
- '''int a[2][3] =
- { { 1, 2 },
- { 3, 4 } };''',
+ '''\
+ int a[2][3] =
+ { { 1, 2 },
+ { 3, 4 } };''',
'')
# CHECK/EXPECT_TRUE/EXPECT_FALSE replacements
@@ -1753,7 +1771,7 @@ class CppStyleTest(CppStyleTestBase):
self.assert_lint('default:;',
'Semicolon defining empty statement. Use { } instead.'
' [whitespace/semicolon] [5]')
- self.assert_lint(' ;',
+ self.assert_lint(' ;',
'Line contains only semicolon. If this should be an empty '
'statement, use { } instead.'
' [whitespace/semicolon] [5]')
@@ -1795,10 +1813,10 @@ class CppStyleTest(CppStyleTestBase):
' VeryLongNameType veryLongNameVariable) { }', '')
self.assert_lint('template<>\n'
'string FunctionTemplateSpecialization<SomeType>(\n'
- ' int x) { return ""; }', '')
+ ' int x) { return ""; }', '')
self.assert_lint('template<>\n'
'string FunctionTemplateSpecialization<vector<A::B>* >(\n'
- ' int x) { return ""; }', '')
+ ' int x) { return ""; }', '')
# should not catch methods of template classes.
self.assert_lint('string Class<Type>::Method() const\n'
@@ -2028,21 +2046,27 @@ class CppStyleTest(CppStyleTestBase):
self.assert_lint(' char* oneSpaceIndent = "public:";',
'Weird number of spaces at line-start. '
'Are you using a 4-space indent? [whitespace/indent] [3]')
- self.assert_lint(' public:', '')
- self.assert_lint(' public:', '')
- self.assert_lint(' public:', '')
-
- def test_label(self):
- self.assert_lint('public:',
- 'Labels should always be indented at least one space. '
- 'If this is a member-initializer list in a constructor, '
- 'the colon should be on the line after the definition '
- 'header. [whitespace/labels] [4]')
- self.assert_lint(' public:', '')
- self.assert_lint(' public:', '')
- self.assert_lint(' public:', '')
- self.assert_lint(' public:', '')
- self.assert_lint(' public:', '')
+ self.assert_lint(' public:',
+ 'Weird number of spaces at line-start. '
+ 'Are you using a 4-space indent? [whitespace/indent] [3]')
+ self.assert_lint(' public:',
+ 'Weird number of spaces at line-start. '
+ 'Are you using a 4-space indent? [whitespace/indent] [3]')
+ self.assert_lint(' public:',
+ 'Weird number of spaces at line-start. '
+ 'Are you using a 4-space indent? [whitespace/indent] [3]')
+ self.assert_multi_line_lint(
+ 'class Foo {\n'
+ 'public:\n'
+ ' enum Bar {\n'
+ ' Alpha,\n'
+ ' Beta,\n'
+ '#if ENABLED_BETZ\n'
+ ' Charlie,\n'
+ '#endif\n'
+ ' };\n'
+ '};',
+ '')
def test_not_alabel(self):
self.assert_lint('MyVeryLongNamespace::MyVeryLongClassName::', '')
@@ -2080,8 +2104,9 @@ class CppStyleTest(CppStyleTestBase):
'class Foo;',
'')
self.assert_multi_line_lint(
- '''struct Foo*
- foo = NewFoo();''',
+ '''\
+ struct Foo*
+ foo = NewFoo();''',
'')
# Here is an example where the linter gets confused, even though
# the code doesn't violate the style guide.
@@ -3171,31 +3196,35 @@ class NoNonVirtualDestructorsTest(CppStyleTestBase):
def test_no_error(self):
self.assert_multi_line_lint(
- '''class Foo {
- virtual ~Foo();
- virtual void foo();
- };''',
+ '''\
+ class Foo {
+ virtual ~Foo();
+ virtual void foo();
+ };''',
'')
self.assert_multi_line_lint(
- '''class Foo {
- virtual inline ~Foo();
- virtual void foo();
- };''',
+ '''\
+ class Foo {
+ virtual inline ~Foo();
+ virtual void foo();
+ };''',
'')
self.assert_multi_line_lint(
- '''class Foo {
- inline virtual ~Foo();
- virtual void foo();
- };''',
+ '''\
+ class Foo {
+ inline virtual ~Foo();
+ virtual void foo();
+ };''',
'')
self.assert_multi_line_lint(
- '''class Foo::Goo {
- virtual ~Goo();
- virtual void goo();
- };''',
+ '''\
+ class Foo::Goo {
+ virtual ~Goo();
+ virtual void goo();
+ };''',
'')
self.assert_multi_line_lint(
'class Foo { void foo(); };',
@@ -3215,92 +3244,92 @@ class NoNonVirtualDestructorsTest(CppStyleTestBase):
'More than one command on the same line [whitespace/newline] [4]')
self.assert_multi_line_lint(
- '''class Qualified::Goo : public Foo {
- virtual void goo();
- };''',
- '')
-
- self.assert_multi_line_lint(
- # Line-ending :
- '''class Goo :
- public Foo {
+ '''\
+ class Qualified::Goo : public Foo {
virtual void goo();
- };''',
- 'Labels should always be indented at least one space. If this is a '
- 'member-initializer list in a constructor, the colon should be on the '
- 'line after the definition header. [whitespace/labels] [4]')
+ };''',
+ '')
def test_no_destructor_when_virtual_needed(self):
self.assert_multi_line_lint_re(
- '''class Foo {
- virtual void foo();
- };''',
+ '''\
+ class Foo {
+ virtual void foo();
+ };''',
'The class Foo probably needs a virtual destructor')
def test_destructor_non_virtual_when_virtual_needed(self):
self.assert_multi_line_lint_re(
- '''class Foo {
- ~Foo();
- virtual void foo();
- };''',
+ '''\
+ class Foo {
+ ~Foo();
+ virtual void foo();
+ };''',
'The class Foo probably needs a virtual destructor')
def test_no_warn_when_derived(self):
self.assert_multi_line_lint(
- '''class Foo : public Goo {
- virtual void foo();
- };''',
+ '''\
+ class Foo : public Goo {
+ virtual void foo();
+ };''',
'')
def test_internal_braces(self):
self.assert_multi_line_lint_re(
- '''class Foo {
- enum Goo {
- GOO
- };
- virtual void foo();
- };''',
+ '''\
+ class Foo {
+ enum Goo {
+ GOO
+ };
+ virtual void foo();
+ };''',
'The class Foo probably needs a virtual destructor')
def test_inner_class_needs_virtual_destructor(self):
self.assert_multi_line_lint_re(
- '''class Foo {
- class Goo {
- virtual void goo();
- };
- };''',
+ '''\
+ class Foo {
+ class Goo {
+ virtual void goo();
+ };
+ };''',
'The class Goo probably needs a virtual destructor')
def test_outer_class_needs_virtual_destructor(self):
self.assert_multi_line_lint_re(
- '''class Foo {
- class Goo {
- };
- virtual void foo();
- };''',
+ '''\
+ class Foo {
+ class Goo {
+ };
+ virtual void foo();
+ };''',
'The class Foo probably needs a virtual destructor')
def test_qualified_class_needs_virtual_destructor(self):
self.assert_multi_line_lint_re(
- '''class Qualified::Foo {
- virtual void foo();
- };''',
+ '''\
+ class Qualified::Foo {
+ virtual void foo();
+ };''',
'The class Qualified::Foo probably needs a virtual destructor')
def test_multi_line_declaration_no_error(self):
self.assert_multi_line_lint_re(
- '''class Foo
- : public Goo {
- virtual void foo();
- };''',
+ '''\
+ class Foo
+ : public Goo {
+ virtual void foo();
+ };''',
'')
def test_multi_line_declaration_with_error(self):
self.assert_multi_line_lint(
- '''class Foo
- {
- virtual void foo();
- };''',
+ '''\
+ class Foo
+ {
+ virtual void foo();
+ };''',
['This { should be at the end of the previous line '
'[whitespace/braces] [4]',
'The class Foo probably needs a virtual destructor due to having '
@@ -3495,7 +3524,6 @@ class WebKitStyleTest(CppStyleTestBase):
' int goo;\n'
'};',
'Weird number of spaces at line-start. Are you using a 4-space indent? [whitespace/indent] [3]')
- # FIXME: No tests for 8-spaces.
# 3. In a header, code inside a namespace should not be indented.
self.assert_multi_line_lint(
@@ -4180,7 +4208,14 @@ class WebKitStyleTest(CppStyleTestBase):
' myFunction(reallyLongParam1, reallyLongParam2,\n'
' reallyLongParam3);\n'
'}\n',
- '')
+ 'Weird number of spaces at line-start. Are you using a 4-space indent? [whitespace/indent] [3]')
+
+ self.assert_multi_line_lint(
+ 'if (true) {\n'
+ ' myFunction(reallyLongParam1, reallyLongParam2,\n'
+ ' reallyLongParam3);\n'
+ '}\n',
+ 'When wrapping a line, only indent 4 spaces. [whitespace/indent] [3]')
# 4. Control clauses without a body should use empty braces.
self.assert_multi_line_lint(
@@ -4189,7 +4224,7 @@ class WebKitStyleTest(CppStyleTestBase):
self.assert_multi_line_lint(
'for ( ; current;\n'
' current = current->next) { }\n',
- '')
+ 'Weird number of spaces at line-start. Are you using a 4-space indent? [whitespace/indent] [3]')
self.assert_multi_line_lint(
'for ( ; current; current = current->next);\n',
'Semicolon defining empty statement for this loop. Use { } instead. [whitespace/semicolon] [5]')
diff --git a/Tools/Scripts/webkitpy/style/checkers/test_expectations.py b/Tools/Scripts/webkitpy/style/checkers/test_expectations.py
index 46403b7db..1ce40cd39 100644
--- a/Tools/Scripts/webkitpy/style/checkers/test_expectations.py
+++ b/Tools/Scripts/webkitpy/style/checkers/test_expectations.py
@@ -67,7 +67,7 @@ class TestExpectationsChecker(object):
# FIXME: host should be a required parameter, not an optional one.
host = host or Host()
- host._initialize_scm()
+ host.initialize_scm()
self._port_obj = self._determine_port_from_expectations_path(host, file_path)
@@ -94,8 +94,6 @@ class TestExpectationsChecker(object):
expectations = '\n'.join(lines)
if self._port_obj:
self.check_test_expectations(expectations_str=expectations, tests=None)
- else:
- self._handle_style_error(1, 'test/expectations', 5,
- 'No port uses path %s for test_expectations' % self._file_path)
+
# Warn tabs in lines as well
self.check_tabs(lines)
diff --git a/Tools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py b/Tools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py
index f12397787..1516de797 100644
--- a/Tools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py
+++ b/Tools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py
@@ -82,6 +82,10 @@ class TestExpectationsTestCase(unittest.TestCase):
self._expect_port_for_expectations_path('efl', 'LayoutTests/platform/efl/TestExpectations')
self._expect_port_for_expectations_path('efl', 'LayoutTests/platform/efl-wk1/TestExpectations')
self._expect_port_for_expectations_path('efl', 'LayoutTests/platform/efl-wk2/TestExpectations')
+ self._expect_port_for_expectations_path('qt', 'LayoutTests/platform/qt-win/TestExpectations')
+ # FIXME: check-webkit-style doesn't know how to create port objects for all Qt version (4.8, 5.0) and
+ # will only check files based on the installed version of Qt.
+ #self._expect_port_for_expectations_path('qt', 'LayoutTests/platform/qt-5.0-wk2/TestExpectations')
def assert_lines_lint(self, lines, should_pass, expected_output=None):
self._error_collector.reset_errors()
diff --git a/Tools/Scripts/webkitpy/style/main.py b/Tools/Scripts/webkitpy/style/main.py
index e90d98a42..574368a3e 100644
--- a/Tools/Scripts/webkitpy/style/main.py
+++ b/Tools/Scripts/webkitpy/style/main.py
@@ -124,7 +124,7 @@ class CheckWebKitStyle(object):
args = sys.argv[1:]
host = Host()
- host._initialize_scm()
+ host.initialize_scm()
stderr = self._engage_awesome_stderr_hacks()
diff --git a/Tools/Scripts/webkitpy/test/main.py b/Tools/Scripts/webkitpy/test/main.py
index 28de8a6e0..852413299 100644
--- a/Tools/Scripts/webkitpy/test/main.py
+++ b/Tools/Scripts/webkitpy/test/main.py
@@ -81,7 +81,7 @@ class Tester(object):
def skip(self, names, reason, bugid):
self.finder.skip(names, reason, bugid)
- def _parse_args(self):
+ def _parse_args(self, argv=None):
parser = optparse.OptionParser(usage='usage: %prog [options] [args...]')
parser.add_option('-a', '--all', action='store_true', default=False,
help='run all the tests')
@@ -103,7 +103,7 @@ class Tester(object):
parser.epilog = ('[args...] is an optional list of modules, test_classes, or individual tests. '
'If no args are given, all the tests will be run.')
- return parser.parse_args()
+ return parser.parse_args(argv)
def run(self):
self._options, args = self._parse_args()
@@ -188,10 +188,15 @@ class Tester(object):
loader.test_method_prefixes = []
serial_tests = []
- loader.test_method_prefixes.extend(['serial_test_', 'serial_integration_test_'])
+ loader.test_method_prefixes = ['serial_test_', 'serial_integration_test_']
for name in names:
serial_tests.extend(self._all_test_names(loader.loadTestsFromName(name, None)))
+ # loader.loadTestsFromName() will not verify that names begin with one of the test_method_prefixes
+ # if the names were explicitly provided (e.g., MainTest.test_basic), so this means that any individual
+ # tests will be included in both parallel_tests and serial_tests, and we need to de-dup them.
+ serial_tests = list(set(serial_tests).difference(set(parallel_tests)))
+
return (parallel_tests, serial_tests)
def _all_test_names(self, suite):
diff --git a/Tools/Scripts/webkitpy/test/main_unittest.py b/Tools/Scripts/webkitpy/test/main_unittest.py
index ca7ebba0e..2020f5b60 100644
--- a/Tools/Scripts/webkitpy/test/main_unittest.py
+++ b/Tools/Scripts/webkitpy/test/main_unittest.py
@@ -25,7 +25,7 @@ import unittest
import StringIO
from webkitpy.common.system.outputcapture import OutputCapture
-from webkitpy.test.main import Tester
+from webkitpy.test.main import Tester, _Loader
class TesterTest(unittest.TestCase):
@@ -52,3 +52,10 @@ class TesterTest(unittest.TestCase):
self.assertTrue('No tests to run' in errors.getvalue())
self.assertTrue('No tests to run' in logs)
+
+ def test_individual_names_are_not_run_twice(self):
+ tester = Tester()
+ tester._options, args = tester._parse_args(["webkitpy.test.main_unittest.TesterTest.test_no_tests_found"])
+ parallel_tests, serial_tests = tester._test_names(_Loader(), args)
+ self.assertEquals(parallel_tests, args)
+ self.assertEquals(serial_tests, [])
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/__init__.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/__init__.py
index c154da4a1..454ae0c45 100644
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/__init__.py
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/__init__.py
@@ -34,7 +34,8 @@ mod_pywebsocket is a WebSocket extension for Apache HTTP Server
intended for testing or experimental purposes. mod_python is required.
-Installation:
+Installation
+============
0. Prepare an Apache HTTP Server for which mod_python is enabled.
@@ -60,11 +61,6 @@ Installation:
<scan_dir> is useful in saving scan time when <websock_handlers>
contains many non-WebSocket handler files.
- If you want to support old handshake based on
- draft-hixie-thewebsocketprotocol-75:
-
- PythonOption mod_pywebsocket.allow_draft75 On
-
If you want to allow handlers whose canonical path is not under the root
directory (i.e. symbolic link is in root directory but its target is not),
configure as follows:
@@ -89,7 +85,8 @@ Installation:
3. Verify installation. You can use example/console.html to poke the server.
-Writing WebSocket handlers:
+Writing WebSocket handlers
+==========================
When a WebSocket request comes in, the resource name
specified in the handshake is considered as if it is a file path under
@@ -118,28 +115,36 @@ extra handshake (web_socket_do_extra_handshake):
- ws_resource
- ws_origin
- ws_version
-- ws_location (Hixie 75 and HyBi 00 only)
-- ws_extensions (Hybi 06 and later)
+- ws_location (HyBi 00 only)
+- ws_extensions (HyBi 06 and later)
- ws_deflate (HyBi 06 and later)
- ws_protocol
- ws_requested_protocols (HyBi 06 and later)
-The last two are a bit tricky.
+The last two are a bit tricky. See the next subsection.
+
+
+Subprotocol Negotiation
+-----------------------
For HyBi 06 and later, ws_protocol is always set to None when
web_socket_do_extra_handshake is called. If ws_requested_protocols is not
None, you must choose one subprotocol from this list and set it to
ws_protocol.
-For Hixie 75 and HyBi 00, when web_socket_do_extra_handshake is called,
+For HyBi 00, when web_socket_do_extra_handshake is called,
ws_protocol is set to the value given by the client in
-Sec-WebSocket-Protocol (WebSocket-Protocol for Hixie 75) header or None if
+Sec-WebSocket-Protocol header or None if
such header was not found in the opening handshake request. Finish extra
handshake with ws_protocol untouched to accept the request subprotocol.
-Then, Sec-WebSocket-Protocol (or WebSocket-Protocol) header will be sent to
+Then, Sec-WebSocket-Protocol header will be sent to
the client in response with the same value as requested. Raise an exception
in web_socket_do_extra_handshake to reject the requested subprotocol.
+
+Data Transfer
+-------------
+
web_socket_transfer_data is called after the handshake completed
successfully. A handler can receive/send messages from/to the client
using request. mod_pywebsocket.msgutil module provides utilities
@@ -159,12 +164,16 @@ You can send a message by the following statement.
request.ws_stream.send_message(message)
+
+Closing Connection
+------------------
+
Executing the following statement or just return-ing from
web_socket_transfer_data cause connection close.
request.ws_stream.close_connection()
-When you're using IETF HyBi 00 or later protocol, close_connection will wait
+close_connection will wait
for closing handshake acknowledgement coming from the client. When it
couldn't receive a valid acknowledgement, raises an exception.
@@ -176,6 +185,10 @@ use in web_socket_passive_closing_handshake.
- ws_close_code
- ws_close_reason
+
+Threading
+---------
+
A WebSocket handler must be thread-safe if the server (Apache or
standalone.py) is configured to use threads.
"""
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hixie75.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hixie75.py
index c84ca6e07..94cf5b31b 100644
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hixie75.py
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hixie75.py
@@ -32,7 +32,8 @@
protocol version HyBi 00 and Hixie 75.
Specification:
-http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
+- HyBi 00 http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
+- Hixie 75 http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
"""
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hybi.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hybi.py
index 34fa7a60e..bd158fa6b 100644
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hybi.py
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hybi.py
@@ -37,6 +37,7 @@ http://tools.ietf.org/html/rfc6455
from collections import deque
+import logging
import os
import struct
import time
@@ -162,14 +163,145 @@ def create_text_frame(
frame_filters)
+def parse_frame(receive_bytes, logger=None,
+ ws_version=common.VERSION_HYBI_LATEST,
+ unmask_receive=True):
+ """Parses a frame. Returns a tuple containing each header field and
+ payload.
+
+ Args:
+ receive_bytes: a function that reads frame data from a stream or
+ something similar. The function takes length of the bytes to be
+ read. The function must raise ConnectionTerminatedException if
+ there is not enough data to be read.
+ logger: a logging object.
+ ws_version: the version of WebSocket protocol.
+ unmask_receive: unmask received frames. When received unmasked
+ frame, raises InvalidFrameException.
+
+ Raises:
+ ConnectionTerminatedException: when receive_bytes raises it.
+ InvalidFrameException: when the frame contains invalid data.
+ """
+
+ if not logger:
+ logger = logging.getLogger()
+
+ logger.log(common.LOGLEVEL_FINE, 'Receive the first 2 octets of a frame')
+
+ received = receive_bytes(2)
+
+ first_byte = ord(received[0])
+ fin = (first_byte >> 7) & 1
+ rsv1 = (first_byte >> 6) & 1
+ rsv2 = (first_byte >> 5) & 1
+ rsv3 = (first_byte >> 4) & 1
+ opcode = first_byte & 0xf
+
+ second_byte = ord(received[1])
+ mask = (second_byte >> 7) & 1
+ payload_length = second_byte & 0x7f
+
+ logger.log(common.LOGLEVEL_FINE,
+ 'FIN=%s, RSV1=%s, RSV2=%s, RSV3=%s, opcode=%s, '
+ 'Mask=%s, Payload_length=%s',
+ fin, rsv1, rsv2, rsv3, opcode, mask, payload_length)
+
+ if (mask == 1) != unmask_receive:
+ raise InvalidFrameException(
+ 'Mask bit on the received frame did\'nt match masking '
+ 'configuration for received frames')
+
+ # The HyBi and later specs disallow putting a value in 0x0-0xFFFF
+ # into the 8-octet extended payload length field (or 0x0-0xFD in
+ # 2-octet field).
+ valid_length_encoding = True
+ length_encoding_bytes = 1
+ if payload_length == 127:
+ logger.log(common.LOGLEVEL_FINE,
+ 'Receive 8-octet extended payload length')
+
+ extended_payload_length = receive_bytes(8)
+ payload_length = struct.unpack(
+ '!Q', extended_payload_length)[0]
+ if payload_length > 0x7FFFFFFFFFFFFFFF:
+ raise InvalidFrameException(
+ 'Extended payload length >= 2^63')
+ if ws_version >= 13 and payload_length < 0x10000:
+ valid_length_encoding = False
+ length_encoding_bytes = 8
+
+ logger.log(common.LOGLEVEL_FINE,
+ 'Decoded_payload_length=%s', payload_length)
+ elif payload_length == 126:
+ logger.log(common.LOGLEVEL_FINE,
+ 'Receive 2-octet extended payload length')
+
+ extended_payload_length = receive_bytes(2)
+ payload_length = struct.unpack(
+ '!H', extended_payload_length)[0]
+ if ws_version >= 13 and payload_length < 126:
+ valid_length_encoding = False
+ length_encoding_bytes = 2
+
+ logger.log(common.LOGLEVEL_FINE,
+ 'Decoded_payload_length=%s', payload_length)
+
+ if not valid_length_encoding:
+ logger.warning(
+ 'Payload length is not encoded using the minimal number of '
+ 'bytes (%d is encoded using %d bytes)',
+ payload_length,
+ length_encoding_bytes)
+
+ if mask == 1:
+ logger.log(common.LOGLEVEL_FINE, 'Receive mask')
+
+ masking_nonce = receive_bytes(4)
+ masker = util.RepeatedXorMasker(masking_nonce)
+
+ logger.log(common.LOGLEVEL_FINE, 'Mask=%r', masking_nonce)
+ else:
+ masker = _NOOP_MASKER
+
+ logger.log(common.LOGLEVEL_FINE, 'Receive payload data')
+ if logger.isEnabledFor(common.LOGLEVEL_FINE):
+ receive_start = time.time()
+
+ raw_payload_bytes = receive_bytes(payload_length)
+
+ if logger.isEnabledFor(common.LOGLEVEL_FINE):
+ logger.log(
+ common.LOGLEVEL_FINE,
+ 'Done receiving payload data at %s MB/s',
+ payload_length / (time.time() - receive_start) / 1000 / 1000)
+ logger.log(common.LOGLEVEL_FINE, 'Unmask payload data')
+
+ if logger.isEnabledFor(common.LOGLEVEL_FINE):
+ unmask_start = time.time()
+
+ bytes = masker.mask(raw_payload_bytes)
+
+ if logger.isEnabledFor(common.LOGLEVEL_FINE):
+ logger.log(
+ common.LOGLEVEL_FINE,
+ 'Done unmasking payload data at %s MB/s',
+ payload_length / (time.time() - unmask_start) / 1000 / 1000)
+
+ return opcode, bytes, fin, rsv1, rsv2, rsv3
+
+
class FragmentedFrameBuilder(object):
"""A stateful class to send a message as fragments."""
- def __init__(self, mask, frame_filters=[]):
+ def __init__(self, mask, frame_filters=[], encode_utf8=True):
"""Constructs an instance."""
self._mask = mask
self._frame_filters = frame_filters
+ # This is for skipping UTF-8 encoding when building text type frames
+ # from compressed data.
+ self._encode_utf8 = encode_utf8
self._started = False
@@ -177,7 +309,7 @@ class FragmentedFrameBuilder(object):
# frames in the message are all the same.
self._opcode = common.OPCODE_TEXT
- def build(self, message, end, binary):
+ def build(self, payload_data, end, binary):
if binary:
frame_type = common.OPCODE_BINARY
else:
@@ -198,12 +330,12 @@ class FragmentedFrameBuilder(object):
self._started = True
fin = 0
- if binary:
+ if binary or not self._encode_utf8:
return create_binary_frame(
- message, opcode, fin, self._mask, self._frame_filters)
+ payload_data, opcode, fin, self._mask, self._frame_filters)
else:
return create_text_frame(
- message, opcode, fin, self._mask, self._frame_filters)
+ payload_data, opcode, fin, self._mask, self._frame_filters)
def _create_control_frame(opcode, body, mask, frame_filters):
@@ -235,6 +367,22 @@ def create_close_frame(body, mask=False, frame_filters=[]):
common.OPCODE_CLOSE, body, mask, frame_filters)
+def create_closing_handshake_body(code, reason):
+ body = ''
+ if code is not None:
+ if (code > common.STATUS_USER_PRIVATE_MAX or
+ code < common.STATUS_NORMAL_CLOSURE):
+ raise BadOperationException('Status code is out of range')
+ if (code == common.STATUS_NO_STATUS_RECEIVED or
+ code == common.STATUS_ABNORMAL_CLOSURE or
+ code == common.STATUS_TLS_HANDSHAKE):
+ raise BadOperationException('Status code is reserved pseudo '
+ 'code')
+ encoded_reason = reason.encode('utf-8')
+ body = struct.pack('!H', code) + encoded_reason
+ return body
+
+
class StreamOptions(object):
"""Holds option values to configure Stream objects."""
@@ -248,8 +396,16 @@ class StreamOptions(object):
self.outgoing_frame_filters = []
self.incoming_frame_filters = []
+ # Filters applied to messages. Control frames are not affected by them.
+ self.outgoing_message_filters = []
+ self.incoming_message_filters = []
+
+ self.encode_text_message_to_utf8 = True
self.mask_send = False
self.unmask_receive = True
+ # RFC6455 disallows fragmented control frames, but mux extension
+ # relaxes the restriction.
+ self.allow_fragmented_control_frame = False
class Stream(StreamBase):
@@ -283,7 +439,8 @@ class Stream(StreamBase):
self._original_opcode = None
self._writer = FragmentedFrameBuilder(
- self._options.mask_send, self._options.outgoing_frame_filters)
+ self._options.mask_send, self._options.outgoing_frame_filters,
+ self._options.encode_text_message_to_utf8)
self._ping_queue = deque()
@@ -297,109 +454,13 @@ class Stream(StreamBase):
InvalidFrameException: when the frame contains invalid data.
"""
- self._logger.log(common.LOGLEVEL_FINE,
- 'Receive the first 2 octets of a frame')
-
- received = self.receive_bytes(2)
-
- first_byte = ord(received[0])
- fin = (first_byte >> 7) & 1
- rsv1 = (first_byte >> 6) & 1
- rsv2 = (first_byte >> 5) & 1
- rsv3 = (first_byte >> 4) & 1
- opcode = first_byte & 0xf
-
- second_byte = ord(received[1])
- mask = (second_byte >> 7) & 1
- payload_length = second_byte & 0x7f
-
- self._logger.log(common.LOGLEVEL_FINE,
- 'FIN=%s, RSV1=%s, RSV2=%s, RSV3=%s, opcode=%s, '
- 'Mask=%s, Payload_length=%s',
- fin, rsv1, rsv2, rsv3, opcode, mask, payload_length)
-
- if (mask == 1) != self._options.unmask_receive:
- raise InvalidFrameException(
- 'Mask bit on the received frame did\'nt match masking '
- 'configuration for received frames')
-
- # The Hybi-13 and later specs disallow putting a value in 0x0-0xFFFF
- # into the 8-octet extended payload length field (or 0x0-0xFD in
- # 2-octet field).
- valid_length_encoding = True
- length_encoding_bytes = 1
- if payload_length == 127:
- self._logger.log(common.LOGLEVEL_FINE,
- 'Receive 8-octet extended payload length')
-
- extended_payload_length = self.receive_bytes(8)
- payload_length = struct.unpack(
- '!Q', extended_payload_length)[0]
- if payload_length > 0x7FFFFFFFFFFFFFFF:
- raise InvalidFrameException(
- 'Extended payload length >= 2^63')
- if self._request.ws_version >= 13 and payload_length < 0x10000:
- valid_length_encoding = False
- length_encoding_bytes = 8
-
- self._logger.log(common.LOGLEVEL_FINE,
- 'Decoded_payload_length=%s', payload_length)
- elif payload_length == 126:
- self._logger.log(common.LOGLEVEL_FINE,
- 'Receive 2-octet extended payload length')
-
- extended_payload_length = self.receive_bytes(2)
- payload_length = struct.unpack(
- '!H', extended_payload_length)[0]
- if self._request.ws_version >= 13 and payload_length < 126:
- valid_length_encoding = False
- length_encoding_bytes = 2
-
- self._logger.log(common.LOGLEVEL_FINE,
- 'Decoded_payload_length=%s', payload_length)
-
- if not valid_length_encoding:
- self._logger.warning(
- 'Payload length is not encoded using the minimal number of '
- 'bytes (%d is encoded using %d bytes)',
- payload_length,
- length_encoding_bytes)
-
- if mask == 1:
- self._logger.log(common.LOGLEVEL_FINE, 'Receive mask')
-
- masking_nonce = self.receive_bytes(4)
- masker = util.RepeatedXorMasker(masking_nonce)
-
- self._logger.log(common.LOGLEVEL_FINE, 'Mask=%r', masking_nonce)
- else:
- masker = _NOOP_MASKER
-
- self._logger.log(common.LOGLEVEL_FINE, 'Receive payload data')
- if self._logger.isEnabledFor(common.LOGLEVEL_FINE):
- receive_start = time.time()
-
- raw_payload_bytes = self.receive_bytes(payload_length)
-
- if self._logger.isEnabledFor(common.LOGLEVEL_FINE):
- self._logger.log(
- common.LOGLEVEL_FINE,
- 'Done receiving payload data at %s MB/s',
- payload_length / (time.time() - receive_start) / 1000 / 1000)
- self._logger.log(common.LOGLEVEL_FINE, 'Unmask payload data')
-
- if self._logger.isEnabledFor(common.LOGLEVEL_FINE):
- unmask_start = time.time()
-
- bytes = masker.mask(raw_payload_bytes)
+ def _receive_bytes(length):
+ return self.receive_bytes(length)
- if self._logger.isEnabledFor(common.LOGLEVEL_FINE):
- self._logger.log(
- common.LOGLEVEL_FINE,
- 'Done unmasking payload data at %s MB/s',
- payload_length / (time.time() - unmask_start) / 1000 / 1000)
-
- return opcode, bytes, fin, rsv1, rsv2, rsv3
+ return parse_frame(receive_bytes=_receive_bytes,
+ logger=self._logger,
+ ws_version=self._request.ws_version,
+ unmask_receive=self._options.unmask_receive)
def _receive_frame_as_frame_object(self):
opcode, bytes, fin, rsv1, rsv2, rsv3 = self._receive_frame()
@@ -407,6 +468,32 @@ class Stream(StreamBase):
return Frame(fin=fin, rsv1=rsv1, rsv2=rsv2, rsv3=rsv3,
opcode=opcode, payload=bytes)
+ def receive_filtered_frame(self):
+ """Receives a frame and applies frame filters and message filters.
+ The frame to be received must satisfy following conditions:
+ - The frame is not fragmented.
+ - The opcode of the frame is TEXT or BINARY.
+
+ DO NOT USE this method except for testing purpose.
+ """
+
+ frame = self._receive_frame_as_frame_object()
+ if not frame.fin:
+ raise InvalidFrameException(
+ 'Segmented frames must not be received via '
+ 'receive_filtered_frame()')
+ if (frame.opcode != common.OPCODE_TEXT and
+ frame.opcode != common.OPCODE_BINARY):
+ raise InvalidFrameException(
+ 'Control frames must not be received via '
+ 'receive_filtered_frame()')
+
+ for frame_filter in self._options.incoming_frame_filters:
+ frame_filter.filter(frame)
+ for message_filter in self._options.incoming_message_filters:
+ frame.payload = message_filter.filter(frame.payload)
+ return frame
+
def send_message(self, message, end=True, binary=False):
"""Send message.
@@ -428,11 +515,219 @@ class Stream(StreamBase):
raise BadOperationException(
'Message for binary frame must be instance of str')
+ for message_filter in self._options.outgoing_message_filters:
+ message = message_filter.filter(message, end, binary)
+
try:
- self._write(self._writer.build(message, end, binary))
+ # Set this to any positive integer to limit maximum size of data in
+ # payload data of each frame.
+ MAX_PAYLOAD_DATA_SIZE = -1
+
+ if MAX_PAYLOAD_DATA_SIZE <= 0:
+ self._write(self._writer.build(message, end, binary))
+ return
+
+ bytes_written = 0
+ while True:
+ end_for_this_frame = end
+ bytes_to_write = len(message) - bytes_written
+ if (MAX_PAYLOAD_DATA_SIZE > 0 and
+ bytes_to_write > MAX_PAYLOAD_DATA_SIZE):
+ end_for_this_frame = False
+ bytes_to_write = MAX_PAYLOAD_DATA_SIZE
+
+ frame = self._writer.build(
+ message[bytes_written:bytes_written + bytes_to_write],
+ end_for_this_frame,
+ binary)
+ self._write(frame)
+
+ bytes_written += bytes_to_write
+
+ # This if must be placed here (the end of while block) so that
+ # at least one frame is sent.
+ if len(message) <= bytes_written:
+ break
except ValueError, e:
raise BadOperationException(e)
+ def _get_message_from_frame(self, frame):
+ """Gets a message from frame. If the message is composed of fragmented
+ frames and the frame is not the last fragmented frame, this method
+ returns None. The whole message will be returned when the last
+ fragmented frame is passed to this method.
+
+ Raises:
+ InvalidFrameException: when the frame doesn't match defragmentation
+ context, or the frame contains invalid data.
+ """
+
+ if frame.opcode == common.OPCODE_CONTINUATION:
+ if not self._received_fragments:
+ if frame.fin:
+ raise InvalidFrameException(
+ 'Received a termination frame but fragmentation '
+ 'not started')
+ else:
+ raise InvalidFrameException(
+ 'Received an intermediate frame but '
+ 'fragmentation not started')
+
+ if frame.fin:
+ # End of fragmentation frame
+ self._received_fragments.append(frame.payload)
+ message = ''.join(self._received_fragments)
+ self._received_fragments = []
+ return message
+ else:
+ # Intermediate frame
+ self._received_fragments.append(frame.payload)
+ return None
+ else:
+ if self._received_fragments:
+ if frame.fin:
+ raise InvalidFrameException(
+ 'Received an unfragmented frame without '
+ 'terminating existing fragmentation')
+ else:
+ raise InvalidFrameException(
+ 'New fragmentation started without terminating '
+ 'existing fragmentation')
+
+ if frame.fin:
+ # Unfragmented frame
+
+ self._original_opcode = frame.opcode
+ return frame.payload
+ else:
+ # Start of fragmentation frame
+
+ if (not self._options.allow_fragmented_control_frame and
+ common.is_control_opcode(frame.opcode)):
+ raise InvalidFrameException(
+ 'Control frames must not be fragmented')
+
+ self._original_opcode = frame.opcode
+ self._received_fragments.append(frame.payload)
+ return None
+
+ def _process_close_message(self, message):
+ """Processes close message.
+
+ Args:
+ message: close message.
+
+ Raises:
+ InvalidFrameException: when the message is invalid.
+ """
+
+ self._request.client_terminated = True
+
+ # Status code is optional. We can have status reason only if we
+ # have status code. Status reason can be empty string. So,
+ # allowed cases are
+ # - no application data: no code no reason
+ # - 2 octet of application data: has code but no reason
+ # - 3 or more octet of application data: both code and reason
+ if len(message) == 0:
+ self._logger.debug('Received close frame (empty body)')
+ self._request.ws_close_code = (
+ common.STATUS_NO_STATUS_RECEIVED)
+ elif len(message) == 1:
+ raise InvalidFrameException(
+ 'If a close frame has status code, the length of '
+ 'status code must be 2 octet')
+ elif len(message) >= 2:
+ self._request.ws_close_code = struct.unpack(
+ '!H', message[0:2])[0]
+ self._request.ws_close_reason = message[2:].decode(
+ 'utf-8', 'replace')
+ self._logger.debug(
+ 'Received close frame (code=%d, reason=%r)',
+ self._request.ws_close_code,
+ self._request.ws_close_reason)
+
+ # Drain junk data after the close frame if necessary.
+ self._drain_received_data()
+
+ if self._request.server_terminated:
+ self._logger.debug(
+ 'Received ack for server-initiated closing handshake')
+ return
+
+ self._logger.debug(
+ 'Received client-initiated closing handshake')
+
+ code = common.STATUS_NORMAL_CLOSURE
+ reason = ''
+ if hasattr(self._request, '_dispatcher'):
+ dispatcher = self._request._dispatcher
+ code, reason = dispatcher.passive_closing_handshake(
+ self._request)
+ if code is None and reason is not None and len(reason) > 0:
+ self._logger.warning(
+ 'Handler specified reason despite code being None')
+ reason = ''
+ if reason is None:
+ reason = ''
+ self._send_closing_handshake(code, reason)
+ self._logger.debug(
+ 'Sent ack for client-initiated closing handshake '
+ '(code=%r, reason=%r)', code, reason)
+
+ def _process_ping_message(self, message):
+ """Processes ping message.
+
+ Args:
+ message: ping message.
+ """
+
+ try:
+ handler = self._request.on_ping_handler
+ if handler:
+ handler(self._request, message)
+ return
+ except AttributeError, e:
+ pass
+ self._send_pong(message)
+
+ def _process_pong_message(self, message):
+ """Processes pong message.
+
+ Args:
+ message: pong message.
+ """
+
+ # TODO(tyoshino): Add ping timeout handling.
+
+ inflight_pings = deque()
+
+ while True:
+ try:
+ expected_body = self._ping_queue.popleft()
+ if expected_body == message:
+ # inflight_pings contains pings ignored by the
+ # other peer. Just forget them.
+ self._logger.debug(
+ 'Ping %r is acked (%d pings were ignored)',
+ expected_body, len(inflight_pings))
+ break
+ else:
+ inflight_pings.append(expected_body)
+ except IndexError, e:
+ # The received pong was unsolicited pong. Keep the
+ # ping queue as is.
+ self._ping_queue = inflight_pings
+ self._logger.debug('Received a unsolicited pong')
+ break
+
+ try:
+ handler = self._request.on_pong_handler
+ if handler:
+ handler(self._request, message)
+ except AttributeError, e:
+ pass
+
def receive_message(self):
"""Receive a WebSocket frame and return its payload as a text in
unicode or a binary in str.
@@ -482,52 +777,12 @@ class Stream(StreamBase):
'Unsupported flag is set (rsv = %d%d%d)' %
(frame.rsv1, frame.rsv2, frame.rsv3))
- if frame.opcode == common.OPCODE_CONTINUATION:
- if not self._received_fragments:
- if frame.fin:
- raise InvalidFrameException(
- 'Received a termination frame but fragmentation '
- 'not started')
- else:
- raise InvalidFrameException(
- 'Received an intermediate frame but '
- 'fragmentation not started')
-
- if frame.fin:
- # End of fragmentation frame
- self._received_fragments.append(frame.payload)
- message = ''.join(self._received_fragments)
- self._received_fragments = []
- else:
- # Intermediate frame
- self._received_fragments.append(frame.payload)
- continue
- else:
- if self._received_fragments:
- if frame.fin:
- raise InvalidFrameException(
- 'Received an unfragmented frame without '
- 'terminating existing fragmentation')
- else:
- raise InvalidFrameException(
- 'New fragmentation started without terminating '
- 'existing fragmentation')
-
- if frame.fin:
- # Unfragmented frame
-
- self._original_opcode = frame.opcode
- message = frame.payload
- else:
- # Start of fragmentation frame
-
- if common.is_control_opcode(frame.opcode):
- raise InvalidFrameException(
- 'Control frames must not be fragmented')
+ message = self._get_message_from_frame(frame)
+ if message is None:
+ continue
- self._original_opcode = frame.opcode
- self._received_fragments.append(frame.payload)
- continue
+ for message_filter in self._options.incoming_message_filters:
+ message = message_filter.filter(message)
if self._original_opcode == common.OPCODE_TEXT:
# The WebSocket protocol section 4.4 specifies that invalid
@@ -540,124 +795,21 @@ class Stream(StreamBase):
elif self._original_opcode == common.OPCODE_BINARY:
return message
elif self._original_opcode == common.OPCODE_CLOSE:
- self._request.client_terminated = True
-
- # Status code is optional. We can have status reason only if we
- # have status code. Status reason can be empty string. So,
- # allowed cases are
- # - no application data: no code no reason
- # - 2 octet of application data: has code but no reason
- # - 3 or more octet of application data: both code and reason
- if len(message) == 0:
- self._logger.debug('Received close frame (empty body)')
- self._request.ws_close_code = (
- common.STATUS_NO_STATUS_RECEIVED)
- elif len(message) == 1:
- raise InvalidFrameException(
- 'If a close frame has status code, the length of '
- 'status code must be 2 octet')
- elif len(message) >= 2:
- self._request.ws_close_code = struct.unpack(
- '!H', message[0:2])[0]
- self._request.ws_close_reason = message[2:].decode(
- 'utf-8', 'replace')
- self._logger.debug(
- 'Received close frame (code=%d, reason=%r)',
- self._request.ws_close_code,
- self._request.ws_close_reason)
-
- # Drain junk data after the close frame if necessary.
- self._drain_received_data()
-
- if self._request.server_terminated:
- self._logger.debug(
- 'Received ack for server-initiated closing handshake')
- return None
-
- self._logger.debug(
- 'Received client-initiated closing handshake')
-
- code = common.STATUS_NORMAL_CLOSURE
- reason = ''
- if hasattr(self._request, '_dispatcher'):
- dispatcher = self._request._dispatcher
- code, reason = dispatcher.passive_closing_handshake(
- self._request)
- if code is None and reason is not None and len(reason) > 0:
- self._logger.warning(
- 'Handler specified reason despite code being None')
- reason = ''
- if reason is None:
- reason = ''
- self._send_closing_handshake(code, reason)
- self._logger.debug(
- 'Sent ack for client-initiated closing handshake '
- '(code=%r, reason=%r)', code, reason)
+ self._process_close_message(message)
return None
elif self._original_opcode == common.OPCODE_PING:
- try:
- handler = self._request.on_ping_handler
- if handler:
- handler(self._request, message)
- continue
- except AttributeError, e:
- pass
- self._send_pong(message)
+ self._process_ping_message(message)
elif self._original_opcode == common.OPCODE_PONG:
- # TODO(tyoshino): Add ping timeout handling.
-
- inflight_pings = deque()
-
- while True:
- try:
- expected_body = self._ping_queue.popleft()
- if expected_body == message:
- # inflight_pings contains pings ignored by the
- # other peer. Just forget them.
- self._logger.debug(
- 'Ping %r is acked (%d pings were ignored)',
- expected_body, len(inflight_pings))
- break
- else:
- inflight_pings.append(expected_body)
- except IndexError, e:
- # The received pong was unsolicited pong. Keep the
- # ping queue as is.
- self._ping_queue = inflight_pings
- self._logger.debug('Received a unsolicited pong')
- break
-
- try:
- handler = self._request.on_pong_handler
- if handler:
- handler(self._request, message)
- continue
- except AttributeError, e:
- pass
-
- continue
+ self._process_pong_message(message)
else:
raise UnsupportedFrameException(
'Opcode %d is not supported' % self._original_opcode)
def _send_closing_handshake(self, code, reason):
- body = ''
- if code is not None:
- if (code > common.STATUS_USER_PRIVATE_MAX or
- code < common.STATUS_NORMAL_CLOSURE):
- raise BadOperationException('Status code is out of range')
- if (code == common.STATUS_NO_STATUS_RECEIVED or
- code == common.STATUS_ABNORMAL_CLOSURE or
- code == common.STATUS_TLS_HANDSHAKE):
- raise BadOperationException('Status code is reserved pseudo '
- 'code')
- encoded_reason = reason.encode('utf-8')
- body = struct.pack('!H', code) + encoded_reason
-
+ body = create_closing_handshake_body(code, reason)
frame = create_close_frame(
- body,
- self._options.mask_send,
- self._options.outgoing_frame_filters)
+ body, mask=self._options.mask_send,
+ frame_filters=self._options.outgoing_frame_filters)
self._request.server_terminated = True
@@ -731,6 +883,14 @@ class Stream(StreamBase):
self._options.outgoing_frame_filters)
self._write(frame)
+ def get_last_received_opcode(self):
+ """Returns the opcode of the WebSocket message which the last received
+ frame belongs to. The return value is valid iff immediately after
+ receive_message call.
+ """
+
+ return self._original_opcode
+
def _drain_received_data(self):
"""Drains unread data in the receive buffer to avoid sending out TCP
RST packet. This is because when deflate-stream is enabled, some
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/common.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/common.py
index 710967c80..2388379c0 100644
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/common.py
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/common.py
@@ -104,7 +104,10 @@ SEC_WEBSOCKET_LOCATION_HEADER = 'Sec-WebSocket-Location'
DEFLATE_STREAM_EXTENSION = 'deflate-stream'
DEFLATE_FRAME_EXTENSION = 'deflate-frame'
PERFRAME_COMPRESSION_EXTENSION = 'perframe-compress'
+PERMESSAGE_COMPRESSION_EXTENSION = 'permessage-compress'
X_WEBKIT_DEFLATE_FRAME_EXTENSION = 'x-webkit-deflate-frame'
+X_WEBKIT_PERMESSAGE_COMPRESSION_EXTENSION = 'x-webkit-permessage-compress'
+MUX_EXTENSION = 'mux_DO_NOT_USE'
# Status codes
# Code STATUS_NO_STATUS_RECEIVED, STATUS_ABNORMAL_CLOSURE, and
@@ -125,7 +128,7 @@ STATUS_INVALID_FRAME_PAYLOAD_DATA = 1007
STATUS_POLICY_VIOLATION = 1008
STATUS_MESSAGE_TOO_BIG = 1009
STATUS_MANDATORY_EXTENSION = 1010
-STATUS_INTERNAL_SERVER_ERROR = 1011
+STATUS_INTERNAL_ENDPOINT_ERROR = 1011
STATUS_TLS_HANDSHAKE = 1015
STATUS_USER_REGISTERED_BASE = 3000
STATUS_USER_REGISTERED_MAX = 3999
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/dispatch.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/dispatch.py
index ab1eb4fb3..25905f180 100644
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/dispatch.py
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/dispatch.py
@@ -39,6 +39,7 @@ import re
from mod_pywebsocket import common
from mod_pywebsocket import handshake
from mod_pywebsocket import msgutil
+from mod_pywebsocket import mux
from mod_pywebsocket import stream
from mod_pywebsocket import util
@@ -277,13 +278,18 @@ class Dispatcher(object):
AbortedByUserException: when user handler abort connection
"""
- handler_suite = self.get_handler_suite(request.ws_resource)
- if handler_suite is None:
- raise DispatchException('No handler for: %r' % request.ws_resource)
- transfer_data_ = handler_suite.transfer_data
# TODO(tyoshino): Terminate underlying TCP connection if possible.
try:
- transfer_data_(request)
+ if mux.use_mux(request):
+ mux.start(request, self)
+ else:
+ handler_suite = self.get_handler_suite(request.ws_resource)
+ if handler_suite is None:
+ raise DispatchException('No handler for: %r' %
+ request.ws_resource)
+ transfer_data_ = handler_suite.transfer_data
+ transfer_data_(request)
+
if not request.server_terminated:
request.ws_stream.close_connection()
# Catch non-critical exceptions the handler didn't handle.
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/extensions.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/extensions.py
index 52b7a4a19..03dbf9ee1 100644
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/extensions.py
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/extensions.py
@@ -38,6 +38,9 @@ _available_processors = {}
class ExtensionProcessorInterface(object):
+ def name(self):
+ return None
+
def get_extension_response(self):
return None
@@ -46,13 +49,21 @@ class ExtensionProcessorInterface(object):
class DeflateStreamExtensionProcessor(ExtensionProcessorInterface):
- """WebSocket DEFLATE stream extension processor."""
+ """WebSocket DEFLATE stream extension processor.
+
+ Specification:
+ Section 9.2.1 in
+ http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
+ """
def __init__(self, request):
self._logger = util.get_class_logger(self)
self._request = request
+ def name(self):
+ return common.DEFLATE_STREAM_EXTENSION
+
def get_extension_response(self):
if len(self._request.get_parameter_names()) != 0:
return None
@@ -70,8 +81,40 @@ _available_processors[common.DEFLATE_STREAM_EXTENSION] = (
DeflateStreamExtensionProcessor)
+def _log_compression_ratio(logger, original_bytes, total_original_bytes,
+ filtered_bytes, total_filtered_bytes):
+ # Print inf when ratio is not available.
+ ratio = float('inf')
+ average_ratio = float('inf')
+ if original_bytes != 0:
+ ratio = float(filtered_bytes) / original_bytes
+ if total_original_bytes != 0:
+ average_ratio = (
+ float(total_filtered_bytes) / total_original_bytes)
+ logger.debug('Outgoing compress ratio: %f (average: %f)' %
+ (ratio, average_ratio))
+
+
+def _log_decompression_ratio(logger, received_bytes, total_received_bytes,
+ filtered_bytes, total_filtered_bytes):
+ # Print inf when ratio is not available.
+ ratio = float('inf')
+ average_ratio = float('inf')
+ if received_bytes != 0:
+ ratio = float(received_bytes) / filtered_bytes
+ if total_filtered_bytes != 0:
+ average_ratio = (
+ float(total_received_bytes) / total_filtered_bytes)
+ logger.debug('Incoming compress ratio: %f (average: %f)' %
+ (ratio, average_ratio))
+
+
class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
- """WebSocket Per-frame DEFLATE extension processor."""
+ """WebSocket Per-frame DEFLATE extension processor.
+
+ Specification:
+ http://tools.ietf.org/html/draft-tyoshino-hybi-websocket-perframe-deflate
+ """
_WINDOW_BITS_PARAM = 'max_window_bits'
_NO_CONTEXT_TAKEOVER_PARAM = 'no_context_takeover'
@@ -83,6 +126,7 @@ class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
self._response_window_bits = None
self._response_no_context_takeover = False
+ self._bfinal = False
# Counters for statistics.
@@ -96,6 +140,9 @@ class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
# Total number of incoming bytes obtained after applying this filter.
self._total_filtered_incoming_payload_bytes = 0
+ def name(self):
+ return common.DEFLATE_FRAME_EXTENSION
+
def get_extension_response(self):
# Any unknown parameter will be just ignored.
@@ -173,6 +220,9 @@ class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
def set_response_no_context_takeover(self, value):
self._response_no_context_takeover = value
+ def set_bfinal(self, value):
+ self._bfinal = value
+
def enable_outgoing_compression(self):
self._compress_outgoing = True
@@ -193,24 +243,17 @@ class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
original_payload_size)
return
- frame.payload = self._deflater.filter(frame.payload)
+ frame.payload = self._deflater.filter(
+ frame.payload, bfinal=self._bfinal)
frame.rsv1 = 1
filtered_payload_size = len(frame.payload)
self._total_filtered_outgoing_payload_bytes += filtered_payload_size
- # Print inf when ratio is not available.
- ratio = float('inf')
- average_ratio = float('inf')
- if original_payload_size != 0:
- ratio = float(filtered_payload_size) / original_payload_size
- if self._total_outgoing_payload_bytes != 0:
- average_ratio = (
- float(self._total_filtered_outgoing_payload_bytes) /
- self._total_outgoing_payload_bytes)
- self._logger.debug(
- 'Outgoing compress ratio: %f (average: %f)' %
- (ratio, average_ratio))
+ _log_compression_ratio(self._logger, original_payload_size,
+ self._total_outgoing_payload_bytes,
+ filtered_payload_size,
+ self._total_filtered_outgoing_payload_bytes)
def _incoming_filter(self, frame):
"""Transform incoming frames. This method is called only by
@@ -231,18 +274,10 @@ class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
filtered_payload_size = len(frame.payload)
self._total_filtered_incoming_payload_bytes += filtered_payload_size
- # Print inf when ratio is not available.
- ratio = float('inf')
- average_ratio = float('inf')
- if received_payload_size != 0:
- ratio = float(received_payload_size) / filtered_payload_size
- if self._total_filtered_incoming_payload_bytes != 0:
- average_ratio = (
- float(self._total_incoming_payload_bytes) /
- self._total_filtered_incoming_payload_bytes)
- self._logger.debug(
- 'Incoming compress ratio: %f (average: %f)' %
- (ratio, average_ratio))
+ _log_decompression_ratio(self._logger, received_payload_size,
+ self._total_incoming_payload_bytes,
+ filtered_payload_size,
+ self._total_filtered_incoming_payload_bytes)
_available_processors[common.DEFLATE_FRAME_EXTENSION] = (
@@ -250,7 +285,7 @@ _available_processors[common.DEFLATE_FRAME_EXTENSION] = (
# Adding vendor-prefixed deflate-frame extension.
-# TODO(bashi): Remove this after WebKit stops using vender prefix.
+# TODO(bashi): Remove this after WebKit stops using vendor prefix.
_available_processors[common.X_WEBKIT_DEFLATE_FRAME_EXTENSION] = (
DeflateFrameExtensionProcessor)
@@ -270,21 +305,22 @@ def _create_accepted_method_desc(method_name, method_params):
return common.format_extension(extension)
-class PerFrameCompressionExtensionProcessor(ExtensionProcessorInterface):
- """WebSocket Per-frame compression extension processor."""
+class CompressionExtensionProcessorBase(ExtensionProcessorInterface):
+ """Base class for Per-frame and Per-message compression extension."""
_METHOD_PARAM = 'method'
- _DEFLATE_METHOD = 'deflate'
def __init__(self, request):
self._logger = util.get_class_logger(self)
self._request = request
self._compression_method_name = None
self._compression_processor = None
+ self._compression_processor_hook = None
+
+ def name(self):
+ return ''
def _lookup_compression_processor(self, method_desc):
- if method_desc.name() == self._DEFLATE_METHOD:
- return DeflateFrameExtensionProcessor(method_desc)
return None
def _get_compression_processor_response(self):
@@ -311,6 +347,10 @@ class PerFrameCompressionExtensionProcessor(ExtensionProcessorInterface):
break
if compression_processor is None:
return None
+
+ if self._compression_processor_hook:
+ self._compression_processor_hook(compression_processor)
+
processor_response = compression_processor.get_extension_response()
if processor_response is None:
return None
@@ -337,14 +377,345 @@ class PerFrameCompressionExtensionProcessor(ExtensionProcessorInterface):
return
self._compression_processor.setup_stream_options(stream_options)
+ def set_compression_processor_hook(self, hook):
+ self._compression_processor_hook = hook
+
def get_compression_processor(self):
return self._compression_processor
+class PerFrameCompressionExtensionProcessor(CompressionExtensionProcessorBase):
+ """WebSocket Per-frame compression extension processor.
+
+ Specification:
+ http://tools.ietf.org/html/draft-ietf-hybi-websocket-perframe-compression
+ """
+
+ _DEFLATE_METHOD = 'deflate'
+
+ def __init__(self, request):
+ CompressionExtensionProcessorBase.__init__(self, request)
+
+ def name(self):
+ return common.PERFRAME_COMPRESSION_EXTENSION
+
+ def _lookup_compression_processor(self, method_desc):
+ if method_desc.name() == self._DEFLATE_METHOD:
+ return DeflateFrameExtensionProcessor(method_desc)
+ return None
+
+
_available_processors[common.PERFRAME_COMPRESSION_EXTENSION] = (
PerFrameCompressionExtensionProcessor)
+class DeflateMessageProcessor(ExtensionProcessorInterface):
+ """Per-message deflate processor."""
+
+ _S2C_MAX_WINDOW_BITS_PARAM = 's2c_max_window_bits'
+ _S2C_NO_CONTEXT_TAKEOVER_PARAM = 's2c_no_context_takeover'
+ _C2S_MAX_WINDOW_BITS_PARAM = 'c2s_max_window_bits'
+ _C2S_NO_CONTEXT_TAKEOVER_PARAM = 'c2s_no_context_takeover'
+
+ def __init__(self, request):
+ self._request = request
+ self._logger = util.get_class_logger(self)
+
+ self._c2s_max_window_bits = None
+ self._c2s_no_context_takeover = False
+ self._bfinal = False
+
+ self._compress_outgoing_enabled = False
+
+ # True if a message is fragmented and compression is ongoing.
+ self._compress_ongoing = False
+
+ # Counters for statistics.
+
+ # Total number of outgoing bytes supplied to this filter.
+ self._total_outgoing_payload_bytes = 0
+ # Total number of bytes sent to the network after applying this filter.
+ self._total_filtered_outgoing_payload_bytes = 0
+
+ # Total number of bytes received from the network.
+ self._total_incoming_payload_bytes = 0
+ # Total number of incoming bytes obtained after applying this filter.
+ self._total_filtered_incoming_payload_bytes = 0
+
+ def name(self):
+ return 'deflate'
+
+ def get_extension_response(self):
+ # Any unknown parameter will be just ignored.
+
+ s2c_max_window_bits = self._request.get_parameter_value(
+ self._S2C_MAX_WINDOW_BITS_PARAM)
+ if s2c_max_window_bits is not None:
+ try:
+ s2c_max_window_bits = int(s2c_max_window_bits)
+ except ValueError, e:
+ return None
+ if s2c_max_window_bits < 8 or s2c_max_window_bits > 15:
+ return None
+
+ s2c_no_context_takeover = self._request.has_parameter(
+ self._S2C_NO_CONTEXT_TAKEOVER_PARAM)
+ if (s2c_no_context_takeover and
+ self._request.get_parameter_value(
+ self._S2C_NO_CONTEXT_TAKEOVER_PARAM) is not None):
+ return None
+
+ self._deflater = util._RFC1979Deflater(
+ s2c_max_window_bits, s2c_no_context_takeover)
+
+ self._inflater = util._RFC1979Inflater()
+
+ self._compress_outgoing_enabled = True
+
+ response = common.ExtensionParameter(self._request.name())
+
+ if s2c_max_window_bits is not None:
+ response.add_parameter(
+ self._S2C_MAX_WINDOW_BITS_PARAM, str(s2c_max_window_bits))
+
+ if s2c_no_context_takeover:
+ response.add_parameter(
+ self._S2C_NO_CONTEXT_TAKEOVER_PARAM, None)
+
+ if self._c2s_max_window_bits is not None:
+ response.add_parameter(
+ self._C2S_MAX_WINDOW_BITS_PARAM,
+ str(self._c2s_max_window_bits))
+ if self._c2s_no_context_takeover:
+ response.add_parameter(
+ self._C2S_NO_CONTEXT_TAKEOVER_PARAM, None)
+
+ self._logger.debug(
+ 'Enable %s extension ('
+ 'request: s2c_max_window_bits=%s; s2c_no_context_takeover=%r, '
+ 'response: c2s_max_window_bits=%s; c2s_no_context_takeover=%r)' %
+ (self._request.name(),
+ s2c_max_window_bits,
+ s2c_no_context_takeover,
+ self._c2s_max_window_bits,
+ self._c2s_no_context_takeover))
+
+ return response
+
+ def setup_stream_options(self, stream_options):
+ class _OutgoingMessageFilter(object):
+
+ def __init__(self, parent):
+ self._parent = parent
+
+ def filter(self, message, end=True, binary=False):
+ return self._parent._process_outgoing_message(
+ message, end, binary)
+
+ class _IncomingMessageFilter(object):
+
+ def __init__(self, parent):
+ self._parent = parent
+ self._decompress_next_message = False
+
+ def decompress_next_message(self):
+ self._decompress_next_message = True
+
+ def filter(self, message):
+ message = self._parent._process_incoming_message(
+ message, self._decompress_next_message)
+ self._decompress_next_message = False
+ return message
+
+ self._outgoing_message_filter = _OutgoingMessageFilter(self)
+ self._incoming_message_filter = _IncomingMessageFilter(self)
+ stream_options.outgoing_message_filters.append(
+ self._outgoing_message_filter)
+ stream_options.incoming_message_filters.append(
+ self._incoming_message_filter)
+
+ class _OutgoingFrameFilter(object):
+
+ def __init__(self, parent):
+ self._parent = parent
+ self._set_compression_bit = False
+
+ def set_compression_bit(self):
+ self._set_compression_bit = True
+
+ def filter(self, frame):
+ self._parent._process_outgoing_frame(
+ frame, self._set_compression_bit)
+ self._set_compression_bit = False
+
+ class _IncomingFrameFilter(object):
+
+ def __init__(self, parent):
+ self._parent = parent
+
+ def filter(self, frame):
+ self._parent._process_incoming_frame(frame)
+
+ self._outgoing_frame_filter = _OutgoingFrameFilter(self)
+ self._incoming_frame_filter = _IncomingFrameFilter(self)
+ stream_options.outgoing_frame_filters.append(
+ self._outgoing_frame_filter)
+ stream_options.incoming_frame_filters.append(
+ self._incoming_frame_filter)
+
+ stream_options.encode_text_message_to_utf8 = False
+
+ def set_c2s_max_window_bits(self, value):
+ self._c2s_max_window_bits = value
+
+ def set_c2s_no_context_takeover(self, value):
+ self._c2s_no_context_takeover = value
+
+ def set_bfinal(self, value):
+ self._bfinal = value
+
+ def enable_outgoing_compression(self):
+ self._compress_outgoing_enabled = True
+
+ def disable_outgoing_compression(self):
+ self._compress_outgoing_enabled = False
+
+ def _process_incoming_message(self, message, decompress):
+ if not decompress:
+ return message
+
+ received_payload_size = len(message)
+ self._total_incoming_payload_bytes += received_payload_size
+
+ message = self._inflater.filter(message)
+
+ filtered_payload_size = len(message)
+ self._total_filtered_incoming_payload_bytes += filtered_payload_size
+
+ _log_decompression_ratio(self._logger, received_payload_size,
+ self._total_incoming_payload_bytes,
+ filtered_payload_size,
+ self._total_filtered_incoming_payload_bytes)
+
+ return message
+
+ def _process_outgoing_message(self, message, end, binary):
+ if not binary:
+ message = message.encode('utf-8')
+
+ if not self._compress_outgoing_enabled:
+ return message
+
+ original_payload_size = len(message)
+ self._total_outgoing_payload_bytes += original_payload_size
+
+ message = self._deflater.filter(
+ message, flush=end, bfinal=self._bfinal)
+
+ filtered_payload_size = len(message)
+ self._total_filtered_outgoing_payload_bytes += filtered_payload_size
+
+ _log_compression_ratio(self._logger, original_payload_size,
+ self._total_outgoing_payload_bytes,
+ filtered_payload_size,
+ self._total_filtered_outgoing_payload_bytes)
+
+ if not self._compress_ongoing:
+ self._outgoing_frame_filter.set_compression_bit()
+ self._compress_ongoing = not end
+ return message
+
+ def _process_incoming_frame(self, frame):
+ if frame.rsv1 == 1 and not common.is_control_opcode(frame.opcode):
+ self._incoming_message_filter.decompress_next_message()
+ frame.rsv1 = 0
+
+ def _process_outgoing_frame(self, frame, compression_bit):
+ if (not compression_bit or
+ common.is_control_opcode(frame.opcode)):
+ return
+
+ frame.rsv1 = 1
+
+
+class PerMessageCompressionExtensionProcessor(
+ CompressionExtensionProcessorBase):
+ """WebSocket Per-message compression extension processor.
+
+ Specification:
+ http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression
+ """
+
+ _DEFLATE_METHOD = 'deflate'
+
+ def __init__(self, request):
+ CompressionExtensionProcessorBase.__init__(self, request)
+
+ def name(self):
+ return common.PERMESSAGE_COMPRESSION_EXTENSION
+
+ def _lookup_compression_processor(self, method_desc):
+ if method_desc.name() == self._DEFLATE_METHOD:
+ return DeflateMessageProcessor(method_desc)
+ return None
+
+
+_available_processors[common.PERMESSAGE_COMPRESSION_EXTENSION] = (
+ PerMessageCompressionExtensionProcessor)
+
+
+# Adding vendor-prefixed permessage-compress extension.
+# TODO(bashi): Remove this after WebKit stops using vendor prefix.
+_available_processors[common.X_WEBKIT_PERMESSAGE_COMPRESSION_EXTENSION] = (
+ PerMessageCompressionExtensionProcessor)
+
+
+class MuxExtensionProcessor(ExtensionProcessorInterface):
+ """WebSocket multiplexing extension processor."""
+
+ _QUOTA_PARAM = 'quota'
+
+ def __init__(self, request):
+ self._request = request
+
+ def name(self):
+ return common.MUX_EXTENSION
+
+ def get_extension_response(self, ws_request,
+ logical_channel_extensions):
+ # Mux extension cannot be used after extensions that depend on
+ # frame boundary, extension data field, or any reserved bits
+ # which are attributed to each frame.
+ for extension in logical_channel_extensions:
+ name = extension.name()
+ if (name == common.PERFRAME_COMPRESSION_EXTENSION or
+ name == common.DEFLATE_FRAME_EXTENSION or
+ name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION):
+ return None
+
+ quota = self._request.get_parameter_value(self._QUOTA_PARAM)
+ if quota is None:
+ ws_request.mux_quota = 0
+ else:
+ try:
+ quota = int(quota)
+ except ValueError, e:
+ return None
+ if quota < 0 or quota >= 2 ** 32:
+ return None
+ ws_request.mux_quota = quota
+
+ ws_request.mux = True
+ ws_request.mux_extensions = logical_channel_extensions
+ return common.ExtensionParameter(common.MUX_EXTENSION)
+
+ def setup_stream_options(self, stream_options):
+ pass
+
+
+_available_processors[common.MUX_EXTENSION] = MuxExtensionProcessor
+
+
def get_extension_processor(extension_request):
global _available_processors
processor_class = _available_processors.get(extension_request.name())
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/__init__.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/__init__.py
index 10a178314..194f6b395 100644
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/__init__.py
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/__init__.py
@@ -37,7 +37,6 @@ successfully established.
import logging
from mod_pywebsocket import common
-from mod_pywebsocket.handshake import draft75
from mod_pywebsocket.handshake import hybi00
from mod_pywebsocket.handshake import hybi
# Export AbortedByUserException, HandshakeException, and VersionException
@@ -56,10 +55,8 @@ def do_handshake(request, dispatcher, allowDraft75=False, strict=False):
Args:
request: mod_python request.
dispatcher: Dispatcher (dispatch.Dispatcher).
- allowDraft75: allow draft 75 handshake protocol.
- strict: Strictly check handshake request in draft 75.
- Default: False. If True, request.connection must provide
- get_memorized_lines method.
+ allowDraft75: obsolete argument. ignored.
+ strict: obsolete argument. ignored.
Handshaker will add attributes such as ws_resource in performing
handshake.
@@ -86,9 +83,6 @@ def do_handshake(request, dispatcher, allowDraft75=False, strict=False):
('RFC 6455', hybi.Handshaker(request, dispatcher)))
handshakers.append(
('HyBi 00', hybi00.Handshaker(request, dispatcher)))
- if allowDraft75:
- handshakers.append(
- ('Hixie 75', draft75.Handshaker(request, dispatcher, strict)))
for name, handshaker in handshakers:
_LOGGER.debug('Trying protocol version %s', name)
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/_base.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/_base.py
index bc095b129..e5c94ca90 100644
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/_base.py
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/_base.py
@@ -85,13 +85,16 @@ def get_default_port(is_secure):
def validate_subprotocol(subprotocol, hixie):
- """Validate a value in subprotocol fields such as WebSocket-Protocol,
- Sec-WebSocket-Protocol.
+ """Validate a value in the Sec-WebSocket-Protocol field.
See
- RFC 6455: Section 4.1., 4.2.2., and 4.3.
- HyBi 00: Section 4.1. Opening handshake
- - Hixie 75: Section 4.1. Handshake
+
+ Args:
+ hixie: if True, checks if characters in subprotocol are in range
+ between U+0020 and U+007E. It's required by HyBi 00 but not by
+ RFC 6455.
"""
if not subprotocol:
@@ -170,7 +173,11 @@ def check_request_line(request):
# 5.1 1. The three character UTF-8 string "GET".
# 5.1 2. A UTF-8-encoded U+0020 SPACE character (0x20 byte).
if request.method != 'GET':
- raise HandshakeException('Method is not GET')
+ raise HandshakeException('Method is not GET: %r' % request.method)
+
+ if request.protocol != 'HTTP/1.1':
+ raise HandshakeException('Version is not HTTP/1.1: %r' %
+ request.protocol)
def check_header_lines(request, mandatory_headers):
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/draft75.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/draft75.py
deleted file mode 100644
index 802a31c9a..000000000
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/draft75.py
+++ /dev/null
@@ -1,190 +0,0 @@
-# Copyright 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.
-
-
-"""WebSocket handshaking defined in draft-hixie-thewebsocketprotocol-75."""
-
-
-# Note: request.connection.write is used in this module, even though mod_python
-# document says that it should be used only in connection handlers.
-# Unfortunately, we have no other options. For example, request.write is not
-# suitable because it doesn't allow direct raw bytes writing.
-
-
-import logging
-import re
-
-from mod_pywebsocket import common
-from mod_pywebsocket.stream import StreamHixie75
-from mod_pywebsocket import util
-from mod_pywebsocket.handshake._base import HandshakeException
-from mod_pywebsocket.handshake._base import build_location
-from mod_pywebsocket.handshake._base import validate_subprotocol
-
-
-_MANDATORY_HEADERS = [
- # key, expected value or None
- ['Upgrade', 'WebSocket'],
- ['Connection', 'Upgrade'],
- ['Host', None],
- ['Origin', None],
-]
-
-_FIRST_FIVE_LINES = map(re.compile, [
- r'^GET /[\S]* HTTP/1.1\r\n$',
- r'^Upgrade: WebSocket\r\n$',
- r'^Connection: Upgrade\r\n$',
- r'^Host: [\S]+\r\n$',
- r'^Origin: [\S]+\r\n$',
-])
-
-_SIXTH_AND_LATER = re.compile(
- r'^'
- r'(WebSocket-Protocol: [\x20-\x7e]+\r\n)?'
- r'(Cookie: [^\r]*\r\n)*'
- r'(Cookie2: [^\r]*\r\n)?'
- r'(Cookie: [^\r]*\r\n)*'
- r'\r\n')
-
-
-class Handshaker(object):
- """This class performs WebSocket handshake."""
-
- def __init__(self, request, dispatcher, strict=False):
- """Construct an instance.
-
- Args:
- request: mod_python request.
- dispatcher: Dispatcher (dispatch.Dispatcher).
- strict: Strictly check handshake request. Default: False.
- If True, request.connection must provide get_memorized_lines
- method.
-
- Handshaker will add attributes such as ws_resource in performing
- handshake.
- """
-
- self._logger = util.get_class_logger(self)
-
- self._request = request
- self._dispatcher = dispatcher
- self._strict = strict
-
- def do_handshake(self):
- """Perform WebSocket Handshake.
-
- On _request, we set
- ws_resource, ws_origin, ws_location, ws_protocol
- ws_challenge_md5: WebSocket handshake information.
- ws_stream: Frame generation/parsing class.
- ws_version: Protocol version.
- """
-
- self._check_header_lines()
- self._set_resource()
- self._set_origin()
- self._set_location()
- self._set_subprotocol()
- self._set_protocol_version()
-
- self._dispatcher.do_extra_handshake(self._request)
-
- self._send_handshake()
-
- self._logger.debug('Sent opening handshake response')
-
- def _set_resource(self):
- self._request.ws_resource = self._request.uri
-
- def _set_origin(self):
- self._request.ws_origin = self._request.headers_in['Origin']
-
- def _set_location(self):
- self._request.ws_location = build_location(self._request)
-
- def _set_subprotocol(self):
- subprotocol = self._request.headers_in.get('WebSocket-Protocol')
- if subprotocol is not None:
- validate_subprotocol(subprotocol, hixie=True)
- self._request.ws_protocol = subprotocol
-
- def _set_protocol_version(self):
- self._logger.debug('IETF Hixie 75 protocol')
- self._request.ws_version = common.VERSION_HIXIE75
- self._request.ws_stream = StreamHixie75(self._request)
-
- def _sendall(self, data):
- self._request.connection.write(data)
-
- def _send_handshake(self):
- self._sendall('HTTP/1.1 101 Web Socket Protocol Handshake\r\n')
- self._sendall('Upgrade: WebSocket\r\n')
- self._sendall('Connection: Upgrade\r\n')
- self._sendall('WebSocket-Origin: %s\r\n' % self._request.ws_origin)
- self._sendall('WebSocket-Location: %s\r\n' % self._request.ws_location)
- if self._request.ws_protocol:
- self._sendall(
- 'WebSocket-Protocol: %s\r\n' % self._request.ws_protocol)
- self._sendall('\r\n')
-
- def _check_header_lines(self):
- for key, expected_value in _MANDATORY_HEADERS:
- actual_value = self._request.headers_in.get(key)
- if not actual_value:
- raise HandshakeException('Header %s is not defined' % key)
- if expected_value:
- if actual_value != expected_value:
- raise HandshakeException(
- 'Expected %r for header %s but found %r' %
- (expected_value, key, actual_value))
- if self._strict:
- try:
- lines = self._request.connection.get_memorized_lines()
- except AttributeError, e:
- raise AttributeError(
- 'Strict handshake is specified but the connection '
- 'doesn\'t provide get_memorized_lines()')
- self._check_first_lines(lines)
-
- def _check_first_lines(self, lines):
- if len(lines) < len(_FIRST_FIVE_LINES):
- raise HandshakeException('Too few header lines: %d' % len(lines))
- for line, regexp in zip(lines, _FIRST_FIVE_LINES):
- if not regexp.search(line):
- raise HandshakeException(
- 'Unexpected header: %r doesn\'t match %r'
- % (line, regexp.pattern))
- sixth_and_later = ''.join(lines[5:])
- if not _SIXTH_AND_LATER.search(sixth_and_later):
- raise HandshakeException(
- 'Unexpected header: %r doesn\'t match %r'
- % (sixth_and_later, _SIXTH_AND_LATER.pattern))
-
-
-# vi:sts=4 sw=4 et
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/hybi.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/hybi.py
index 2883acbf8..fc0e2a096 100644
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/hybi.py
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/hybi.py
@@ -182,34 +182,60 @@ class Handshaker(object):
# Extra handshake handler may modify/remove processors.
self._dispatcher.do_extra_handshake(self._request)
+ processors = filter(lambda processor: processor is not None,
+ self._request.ws_extension_processors)
+
+ accepted_extensions = []
+
+ # We need to take care of mux extension here. Extensions that
+ # are placed before mux should be applied to logical channels.
+ mux_index = -1
+ for i, processor in enumerate(processors):
+ if processor.name() == common.MUX_EXTENSION:
+ mux_index = i
+ break
+ if mux_index >= 0:
+ mux_processor = processors[mux_index]
+ logical_channel_processors = processors[:mux_index]
+ processors = processors[mux_index+1:]
+
+ for processor in logical_channel_processors:
+ extension_response = processor.get_extension_response()
+ if extension_response is None:
+ # Rejected.
+ continue
+ accepted_extensions.append(extension_response)
+ # Pass a shallow copy of accepted_extensions as extensions for
+ # logical channels.
+ mux_response = mux_processor.get_extension_response(
+ self._request, accepted_extensions[:])
+ if mux_response is not None:
+ accepted_extensions.append(mux_response)
stream_options = StreamOptions()
- self._request.ws_extensions = None
- for processor in self._request.ws_extension_processors:
- if processor is None:
- # Some processors may be removed by extra handshake
- # handler.
- continue
+ # When there is mux extension, here, |processors| contain only
+ # prosessors for extensions placed after mux.
+ for processor in processors:
extension_response = processor.get_extension_response()
if extension_response is None:
# Rejected.
continue
- if self._request.ws_extensions is None:
- self._request.ws_extensions = []
- self._request.ws_extensions.append(extension_response)
+ accepted_extensions.append(extension_response)
processor.setup_stream_options(stream_options)
- if self._request.ws_extensions is not None:
+ if len(accepted_extensions) > 0:
+ self._request.ws_extensions = accepted_extensions
self._logger.debug(
'Extensions accepted: %r',
- map(common.ExtensionParameter.name,
- self._request.ws_extensions))
+ map(common.ExtensionParameter.name, accepted_extensions))
+ else:
+ self._request.ws_extensions = None
- self._request.ws_stream = Stream(self._request, stream_options)
+ self._request.ws_stream = self._create_stream(stream_options)
if self._request.ws_requested_protocols is not None:
if self._request.ws_protocol is None:
@@ -268,7 +294,7 @@ class Handshaker(object):
protocol_header = self._request.headers_in.get(
common.SEC_WEBSOCKET_PROTOCOL_HEADER)
- if not protocol_header:
+ if protocol_header is None:
self._request.ws_requested_protocols = None
return
@@ -341,7 +367,10 @@ class Handshaker(object):
return key
- def _send_handshake(self, accept):
+ def _create_stream(self, stream_options):
+ return Stream(self._request, stream_options)
+
+ def _create_handshake_response(self, accept):
response = []
response.append('HTTP/1.1 101 Switching Protocols\r\n')
@@ -363,7 +392,10 @@ class Handshaker(object):
common.format_extensions(self._request.ws_extensions)))
response.append('\r\n')
- raw_response = ''.join(response)
+ return ''.join(response)
+
+ def _send_handshake(self, accept):
+ raw_response = self._create_handshake_response(accept)
self._request.connection.write(raw_response)
self._logger.debug('Sent server\'s opening handshake: %r',
raw_response)
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/headerparserhandler.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/headerparserhandler.py
index b68c240e1..2cc62de04 100644
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/headerparserhandler.py
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/headerparserhandler.py
@@ -63,8 +63,9 @@ _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT = (
_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT_DEFINITION = {
'off': False, 'no': False, 'on': True, 'yes': True}
-# PythonOption to specify to allow draft75 handshake.
-# The default is None (Off)
+# (Obsolete option. Ignored.)
+# PythonOption to specify to allow handshake defined in Hixie 75 version
+# protocol. The default is None (Off)
_PYOPT_ALLOW_DRAFT75 = 'mod_pywebsocket.allow_draft75'
# Map from values to their meanings.
_PYOPT_ALLOW_DRAFT75_DEFINITION = {'off': False, 'on': True}
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/msgutil.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/msgutil.py
index 21ffdacf6..4c1a0114b 100644
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/msgutil.py
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/msgutil.py
@@ -59,20 +59,20 @@ def close_connection(request):
request.ws_stream.close_connection()
-def send_message(request, message, end=True, binary=False):
- """Send message.
+def send_message(request, payload_data, end=True, binary=False):
+ """Send a message (or part of a message).
Args:
request: mod_python request.
- message: unicode text or str binary to send.
- end: False to send message as a fragment. All messages until the
- first call with end=True (inclusive) will be delivered to the
- client in separate frames but as one WebSocket message.
- binary: send message as binary frame.
+ payload_data: unicode text or str binary to send.
+ end: True to terminate a message.
+ False to send payload_data as part of a message that is to be
+ terminated by next or later send_message call with end=True.
+ binary: send payload_data as binary frame(s).
Raises:
BadOperationException: when server already terminated.
"""
- request.ws_stream.send_message(message, end, binary)
+ request.ws_stream.send_message(payload_data, end, binary)
def receive_message(request):
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/mux.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/mux.py
new file mode 100644
index 000000000..f0bdd2461
--- /dev/null
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/mux.py
@@ -0,0 +1,1636 @@
+# Copyright 2012, 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.
+
+
+"""This file provides classes and helper functions for multiplexing extension.
+
+Specification:
+http://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-06
+"""
+
+
+import collections
+import copy
+import email
+import email.parser
+import logging
+import math
+import struct
+import threading
+import traceback
+
+from mod_pywebsocket import common
+from mod_pywebsocket import handshake
+from mod_pywebsocket import util
+from mod_pywebsocket._stream_base import BadOperationException
+from mod_pywebsocket._stream_base import ConnectionTerminatedException
+from mod_pywebsocket._stream_hybi import Frame
+from mod_pywebsocket._stream_hybi import Stream
+from mod_pywebsocket._stream_hybi import StreamOptions
+from mod_pywebsocket._stream_hybi import create_binary_frame
+from mod_pywebsocket._stream_hybi import create_closing_handshake_body
+from mod_pywebsocket._stream_hybi import create_header
+from mod_pywebsocket._stream_hybi import create_length_header
+from mod_pywebsocket._stream_hybi import parse_frame
+from mod_pywebsocket.handshake import hybi
+
+
+_CONTROL_CHANNEL_ID = 0
+_DEFAULT_CHANNEL_ID = 1
+
+_MUX_OPCODE_ADD_CHANNEL_REQUEST = 0
+_MUX_OPCODE_ADD_CHANNEL_RESPONSE = 1
+_MUX_OPCODE_FLOW_CONTROL = 2
+_MUX_OPCODE_DROP_CHANNEL = 3
+_MUX_OPCODE_NEW_CHANNEL_SLOT = 4
+
+_MAX_CHANNEL_ID = 2 ** 29 - 1
+
+_INITIAL_NUMBER_OF_CHANNEL_SLOTS = 64
+_INITIAL_QUOTA_FOR_CLIENT = 8 * 1024
+
+_HANDSHAKE_ENCODING_IDENTITY = 0
+_HANDSHAKE_ENCODING_DELTA = 1
+
+# We need only these status code for now.
+_HTTP_BAD_RESPONSE_MESSAGES = {
+ common.HTTP_STATUS_BAD_REQUEST: 'Bad Request',
+}
+
+# DropChannel reason code
+# TODO(bashi): Define all reason code defined in -05 draft.
+_DROP_CODE_NORMAL_CLOSURE = 1000
+
+_DROP_CODE_INVALID_ENCAPSULATING_MESSAGE = 2001
+_DROP_CODE_CHANNEL_ID_TRUNCATED = 2002
+_DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED = 2003
+_DROP_CODE_UNKNOWN_MUX_OPCODE = 2004
+_DROP_CODE_INVALID_MUX_CONTROL_BLOCK = 2005
+_DROP_CODE_CHANNEL_ALREADY_EXISTS = 2006
+_DROP_CODE_NEW_CHANNEL_SLOT_VIOLATION = 2007
+
+_DROP_CODE_UNKNOWN_REQUEST_ENCODING = 3002
+_DROP_CODE_SEND_QUOTA_VIOLATION = 3005
+_DROP_CODE_ACKNOWLEDGED = 3008
+
+
+class MuxUnexpectedException(Exception):
+ """Exception in handling multiplexing extension."""
+ pass
+
+
+# Temporary
+class MuxNotImplementedException(Exception):
+ """Raised when a flow enters unimplemented code path."""
+ pass
+
+
+class LogicalConnectionClosedException(Exception):
+ """Raised when logical connection is gracefully closed."""
+ pass
+
+
+class PhysicalConnectionError(Exception):
+ """Raised when there is a physical connection error."""
+ def __init__(self, drop_code, message=''):
+ super(PhysicalConnectionError, self).__init__(
+ 'code=%d, message=%r' % (drop_code, message))
+ self.drop_code = drop_code
+ self.message = message
+
+
+class LogicalChannelError(Exception):
+ """Raised when there is a logical channel error."""
+ def __init__(self, channel_id, drop_code, message=''):
+ super(LogicalChannelError, self).__init__(
+ 'channel_id=%d, code=%d, message=%r' % (
+ channel_id, drop_code, message))
+ self.channel_id = channel_id
+ self.drop_code = drop_code
+ self.message = message
+
+
+def _encode_channel_id(channel_id):
+ if channel_id < 0:
+ raise ValueError('Channel id %d must not be negative' % channel_id)
+
+ if channel_id < 2 ** 7:
+ return chr(channel_id)
+ if channel_id < 2 ** 14:
+ return struct.pack('!H', 0x8000 + channel_id)
+ if channel_id < 2 ** 21:
+ first = chr(0xc0 + (channel_id >> 16))
+ return first + struct.pack('!H', channel_id & 0xffff)
+ if channel_id < 2 ** 29:
+ return struct.pack('!L', 0xe0000000 + channel_id)
+
+ raise ValueError('Channel id %d is too large' % channel_id)
+
+
+def _encode_number(number):
+ return create_length_header(number, False)
+
+
+def _create_add_channel_response(channel_id, encoded_handshake,
+ encoding=0, rejected=False,
+ outer_frame_mask=False):
+ if encoding != 0 and encoding != 1:
+ raise ValueError('Invalid encoding %d' % encoding)
+
+ first_byte = ((_MUX_OPCODE_ADD_CHANNEL_RESPONSE << 5) |
+ (rejected << 4) | encoding)
+ block = (chr(first_byte) +
+ _encode_channel_id(channel_id) +
+ _encode_number(len(encoded_handshake)) +
+ encoded_handshake)
+ payload = _encode_channel_id(_CONTROL_CHANNEL_ID) + block
+ return create_binary_frame(payload, mask=outer_frame_mask)
+
+
+def _create_drop_channel(channel_id, code=None, message='',
+ outer_frame_mask=False):
+ if len(message) > 0 and code is None:
+ raise ValueError('Code must be specified if message is specified')
+
+ first_byte = _MUX_OPCODE_DROP_CHANNEL << 5
+ block = chr(first_byte) + _encode_channel_id(channel_id)
+ if code is None:
+ block += _encode_number(0) # Reason size
+ else:
+ reason = struct.pack('!H', code) + message
+ reason_size = _encode_number(len(reason))
+ block += reason_size + reason
+
+ payload = _encode_channel_id(_CONTROL_CHANNEL_ID) + block
+ return create_binary_frame(payload, mask=outer_frame_mask)
+
+
+def _create_flow_control(channel_id, replenished_quota,
+ outer_frame_mask=False):
+ first_byte = _MUX_OPCODE_FLOW_CONTROL << 5
+ block = (chr(first_byte) +
+ _encode_channel_id(channel_id) +
+ _encode_number(replenished_quota))
+ payload = _encode_channel_id(_CONTROL_CHANNEL_ID) + block
+ return create_binary_frame(payload, mask=outer_frame_mask)
+
+
+def _create_new_channel_slot(slots, send_quota, outer_frame_mask=False):
+ if slots < 0 or send_quota < 0:
+ raise ValueError('slots and send_quota must be non-negative.')
+ first_byte = _MUX_OPCODE_NEW_CHANNEL_SLOT << 5
+ block = (chr(first_byte) +
+ _encode_number(slots) +
+ _encode_number(send_quota))
+ payload = _encode_channel_id(_CONTROL_CHANNEL_ID) + block
+ return create_binary_frame(payload, mask=outer_frame_mask)
+
+
+def _create_fallback_new_channel_slot(outer_frame_mask=False):
+ first_byte = (_MUX_OPCODE_NEW_CHANNEL_SLOT << 5) | 1 # Set the F flag
+ block = (chr(first_byte) + _encode_number(0) + _encode_number(0))
+ payload = _encode_channel_id(_CONTROL_CHANNEL_ID) + block
+ return create_binary_frame(payload, mask=outer_frame_mask)
+
+
+def _parse_request_text(request_text):
+ request_line, header_lines = request_text.split('\r\n', 1)
+
+ words = request_line.split(' ')
+ if len(words) != 3:
+ raise ValueError('Bad Request-Line syntax %r' % request_line)
+ [command, path, version] = words
+ if version != 'HTTP/1.1':
+ raise ValueError('Bad request version %r' % version)
+
+ # email.parser.Parser() parses RFC 2822 (RFC 822) style headers.
+ # RFC 6455 refers RFC 2616 for handshake parsing, and RFC 2616 refers
+ # RFC 822.
+ headers = email.parser.Parser().parsestr(header_lines)
+ return command, path, version, headers
+
+
+class _ControlBlock(object):
+ """A structure that holds parsing result of multiplexing control block.
+ Control block specific attributes will be added by _MuxFramePayloadParser.
+ (e.g. encoded_handshake will be added for AddChannelRequest and
+ AddChannelResponse)
+ """
+
+ def __init__(self, opcode):
+ self.opcode = opcode
+
+
+class _MuxFramePayloadParser(object):
+ """A class that parses multiplexed frame payload."""
+
+ def __init__(self, payload):
+ self._data = payload
+ self._read_position = 0
+ self._logger = util.get_class_logger(self)
+
+ def read_channel_id(self):
+ """Reads channel id.
+
+ Raises:
+ ValueError: when the payload doesn't contain
+ valid channel id.
+ """
+
+ remaining_length = len(self._data) - self._read_position
+ pos = self._read_position
+ if remaining_length == 0:
+ raise ValueError('Invalid channel id format')
+
+ channel_id = ord(self._data[pos])
+ channel_id_length = 1
+ if channel_id & 0xe0 == 0xe0:
+ if remaining_length < 4:
+ raise ValueError('Invalid channel id format')
+ channel_id = struct.unpack('!L',
+ self._data[pos:pos+4])[0] & 0x1fffffff
+ channel_id_length = 4
+ elif channel_id & 0xc0 == 0xc0:
+ if remaining_length < 3:
+ raise ValueError('Invalid channel id format')
+ channel_id = (((channel_id & 0x1f) << 16) +
+ struct.unpack('!H', self._data[pos+1:pos+3])[0])
+ channel_id_length = 3
+ elif channel_id & 0x80 == 0x80:
+ if remaining_length < 2:
+ raise ValueError('Invalid channel id format')
+ channel_id = struct.unpack('!H',
+ self._data[pos:pos+2])[0] & 0x3fff
+ channel_id_length = 2
+ self._read_position += channel_id_length
+
+ return channel_id
+
+ def read_inner_frame(self):
+ """Reads an inner frame.
+
+ Raises:
+ PhysicalConnectionError: when the inner frame is invalid.
+ """
+
+ if len(self._data) == self._read_position:
+ raise PhysicalConnectionError(
+ _DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED)
+
+ bits = ord(self._data[self._read_position])
+ self._read_position += 1
+ fin = (bits & 0x80) == 0x80
+ rsv1 = (bits & 0x40) == 0x40
+ rsv2 = (bits & 0x20) == 0x20
+ rsv3 = (bits & 0x10) == 0x10
+ opcode = bits & 0xf
+ payload = self.remaining_data()
+ # Consume rest of the message which is payload data of the original
+ # frame.
+ self._read_position = len(self._data)
+ return fin, rsv1, rsv2, rsv3, opcode, payload
+
+ def _read_number(self):
+ if self._read_position + 1 > len(self._data):
+ raise PhysicalConnectionError(
+ _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+ 'Cannot read the first byte of number field')
+
+ number = ord(self._data[self._read_position])
+ if number & 0x80 == 0x80:
+ raise PhysicalConnectionError(
+ _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+ 'The most significant bit of the first byte of number should '
+ 'be unset')
+ self._read_position += 1
+ pos = self._read_position
+ if number == 127:
+ if pos + 8 > len(self._data):
+ raise PhysicalConnectionError(
+ _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+ 'Invalid number field')
+ self._read_position += 8
+ number = struct.unpack('!Q', self._data[pos:pos+8])[0]
+ if number > 0x7FFFFFFFFFFFFFFF:
+ raise PhysicalConnectionError(
+ _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+ 'Encoded number >= 2^63')
+ if number <= 0xFFFF:
+ raise PhysicalConnectionError(
+ _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+ '%d should not be encoded by 9 bytes encoding' % number)
+ return number
+ if number == 126:
+ if pos + 2 > len(self._data):
+ raise PhysicalConnectionError(
+ _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+ 'Invalid number field')
+ self._read_position += 2
+ number = struct.unpack('!H', self._data[pos:pos+2])[0]
+ if number <= 125:
+ raise PhysicalConnectionError(
+ _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+ '%d should not be encoded by 3 bytes encoding' % number)
+ return number
+
+ def _read_size_and_contents(self):
+ """Reads data that consists of followings:
+ - the size of the contents encoded the same way as payload length
+ of the WebSocket Protocol with 1 bit padding at the head.
+ - the contents.
+ """
+
+ size = self._read_number()
+ pos = self._read_position
+ if pos + size > len(self._data):
+ raise PhysicalConnectionError(
+ _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+ 'Cannot read %d bytes data' % size)
+
+ self._read_position += size
+ return self._data[pos:pos+size]
+
+ def _read_add_channel_request(self, first_byte, control_block):
+ reserved = (first_byte >> 2) & 0x7
+ if reserved != 0:
+ raise PhysicalConnectionError(
+ _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+ 'Reserved bits must be unset')
+
+ # Invalid encoding will be handled by MuxHandler.
+ encoding = first_byte & 0x3
+ try:
+ control_block.channel_id = self.read_channel_id()
+ except ValueError, e:
+ raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK)
+ control_block.encoding = encoding
+ encoded_handshake = self._read_size_and_contents()
+ control_block.encoded_handshake = encoded_handshake
+ return control_block
+
+ def _read_add_channel_response(self, first_byte, control_block):
+ reserved = (first_byte >> 2) & 0x3
+ if reserved != 0:
+ raise PhysicalConnectionError(
+ _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+ 'Reserved bits must be unset')
+
+ control_block.accepted = (first_byte >> 4) & 1
+ control_block.encoding = first_byte & 0x3
+ try:
+ control_block.channel_id = self.read_channel_id()
+ except ValueError, e:
+ raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK)
+ control_block.encoded_handshake = self._read_size_and_contents()
+ return control_block
+
+ def _read_flow_control(self, first_byte, control_block):
+ reserved = first_byte & 0x1f
+ if reserved != 0:
+ raise PhysicalConnectionError(
+ _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+ 'Reserved bits must be unset')
+
+ try:
+ control_block.channel_id = self.read_channel_id()
+ except ValueError, e:
+ raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK)
+ control_block.send_quota = self._read_number()
+ return control_block
+
+ def _read_drop_channel(self, first_byte, control_block):
+ reserved = first_byte & 0x1f
+ if reserved != 0:
+ raise PhysicalConnectionError(
+ _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+ 'Reserved bits must be unset')
+
+ try:
+ control_block.channel_id = self.read_channel_id()
+ except ValueError, e:
+ raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK)
+ reason = self._read_size_and_contents()
+ if len(reason) == 0:
+ control_block.drop_code = None
+ control_block.drop_message = ''
+ elif len(reason) >= 2:
+ control_block.drop_code = struct.unpack('!H', reason[:2])[0]
+ control_block.drop_message = reason[2:]
+ else:
+ raise PhysicalConnectionError(
+ _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+ 'Received DropChannel that conains only 1-byte reason')
+ return control_block
+
+ def _read_new_channel_slot(self, first_byte, control_block):
+ reserved = first_byte & 0x1e
+ if reserved != 0:
+ raise PhysicalConnectionError(
+ _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+ 'Reserved bits must be unset')
+ control_block.fallback = first_byte & 1
+ control_block.slots = self._read_number()
+ control_block.send_quota = self._read_number()
+ return control_block
+
+ def read_control_blocks(self):
+ """Reads control block(s).
+
+ Raises:
+ PhysicalConnectionError: when the payload contains invalid control
+ block(s).
+ StopIteration: when no control blocks left.
+ """
+
+ while self._read_position < len(self._data):
+ first_byte = ord(self._data[self._read_position])
+ self._read_position += 1
+ opcode = (first_byte >> 5) & 0x7
+ control_block = _ControlBlock(opcode=opcode)
+ if opcode == _MUX_OPCODE_ADD_CHANNEL_REQUEST:
+ yield self._read_add_channel_request(first_byte, control_block)
+ elif opcode == _MUX_OPCODE_ADD_CHANNEL_RESPONSE:
+ yield self._read_add_channel_response(
+ first_byte, control_block)
+ elif opcode == _MUX_OPCODE_FLOW_CONTROL:
+ yield self._read_flow_control(first_byte, control_block)
+ elif opcode == _MUX_OPCODE_DROP_CHANNEL:
+ yield self._read_drop_channel(first_byte, control_block)
+ elif opcode == _MUX_OPCODE_NEW_CHANNEL_SLOT:
+ yield self._read_new_channel_slot(first_byte, control_block)
+ else:
+ raise PhysicalConnectionError(
+ _DROP_CODE_UNKNOWN_MUX_OPCODE,
+ 'Invalid opcode %d' % opcode)
+
+ assert self._read_position == len(self._data)
+ raise StopIteration
+
+ def remaining_data(self):
+ """Returns remaining data."""
+
+ return self._data[self._read_position:]
+
+
+class _LogicalRequest(object):
+ """Mimics mod_python request."""
+
+ def __init__(self, channel_id, command, path, protocol, headers,
+ connection):
+ """Constructs an instance.
+
+ Args:
+ channel_id: the channel id of the logical channel.
+ command: HTTP request command.
+ path: HTTP request path.
+ headers: HTTP headers.
+ connection: _LogicalConnection instance.
+ """
+
+ self.channel_id = channel_id
+ self.method = command
+ self.uri = path
+ self.protocol = protocol
+ self.headers_in = headers
+ self.connection = connection
+ self.server_terminated = False
+ self.client_terminated = False
+
+ def is_https(self):
+ """Mimics request.is_https(). Returns False because this method is
+ used only by old protocols (hixie and hybi00).
+ """
+
+ return False
+
+
+class _LogicalConnection(object):
+ """Mimics mod_python mp_conn."""
+
+ # For details, see the comment of set_read_state().
+ STATE_ACTIVE = 1
+ STATE_GRACEFULLY_CLOSED = 2
+ STATE_TERMINATED = 3
+
+ def __init__(self, mux_handler, channel_id):
+ """Constructs an instance.
+
+ Args:
+ mux_handler: _MuxHandler instance.
+ channel_id: channel id of this connection.
+ """
+
+ self._mux_handler = mux_handler
+ self._channel_id = channel_id
+ self._incoming_data = ''
+ self._write_condition = threading.Condition()
+ self._waiting_write_completion = False
+ self._read_condition = threading.Condition()
+ self._read_state = self.STATE_ACTIVE
+
+ def get_local_addr(self):
+ """Getter to mimic mp_conn.local_addr."""
+
+ return self._mux_handler.physical_connection.get_local_addr()
+ local_addr = property(get_local_addr)
+
+ def get_remote_addr(self):
+ """Getter to mimic mp_conn.remote_addr."""
+
+ return self._mux_handler.physical_connection.get_remote_addr()
+ remote_addr = property(get_remote_addr)
+
+ def get_memorized_lines(self):
+ """Gets memorized lines. Not supported."""
+
+ raise MuxUnexpectedException('_LogicalConnection does not support '
+ 'get_memorized_lines')
+
+ def write(self, data):
+ """Writes data. mux_handler sends data asynchronously. The caller will
+ be suspended until write done.
+
+ Args:
+ data: data to be written.
+
+ Raises:
+ MuxUnexpectedException: when called before finishing the previous
+ write.
+ """
+
+ try:
+ self._write_condition.acquire()
+ if self._waiting_write_completion:
+ raise MuxUnexpectedException(
+ 'Logical connection %d is already waiting the completion '
+ 'of write' % self._channel_id)
+
+ self._waiting_write_completion = True
+ self._mux_handler.send_data(self._channel_id, data)
+ self._write_condition.wait()
+ finally:
+ self._write_condition.release()
+
+ def write_control_data(self, data):
+ """Writes data via the control channel. Don't wait finishing write
+ because this method can be called by mux dispatcher.
+
+ Args:
+ data: data to be written.
+ """
+
+ self._mux_handler.send_control_data(data)
+
+ def notify_write_done(self):
+ """Called when sending data is completed."""
+
+ try:
+ self._write_condition.acquire()
+ if not self._waiting_write_completion:
+ raise MuxUnexpectedException(
+ 'Invalid call of notify_write_done for logical connection'
+ ' %d' % self._channel_id)
+ self._waiting_write_completion = False
+ self._write_condition.notify()
+ finally:
+ self._write_condition.release()
+
+ def append_frame_data(self, frame_data):
+ """Appends incoming frame data. Called when mux_handler dispatches
+ frame data to the corresponding application.
+
+ Args:
+ frame_data: incoming frame data.
+ """
+
+ self._read_condition.acquire()
+ self._incoming_data += frame_data
+ self._read_condition.notify()
+ self._read_condition.release()
+
+ def read(self, length):
+ """Reads data. Blocks until enough data has arrived via physical
+ connection.
+
+ Args:
+ length: length of data to be read.
+ Raises:
+ LogicalConnectionClosedException: when closing handshake for this
+ logical channel has been received.
+ ConnectionTerminatedException: when the physical connection has
+ closed, or an error is caused on the reader thread.
+ """
+
+ self._read_condition.acquire()
+ while (self._read_state == self.STATE_ACTIVE and
+ len(self._incoming_data) < length):
+ self._read_condition.wait()
+
+ try:
+ if self._read_state == self.STATE_GRACEFULLY_CLOSED:
+ raise LogicalConnectionClosedException(
+ 'Logical channel %d has closed.' % self._channel_id)
+ elif self._read_state == self.STATE_TERMINATED:
+ raise ConnectionTerminatedException(
+ 'Receiving %d byte failed. Logical channel (%d) closed' %
+ (length, self._channel_id))
+
+ value = self._incoming_data[:length]
+ self._incoming_data = self._incoming_data[length:]
+ finally:
+ self._read_condition.release()
+
+ return value
+
+ def set_read_state(self, new_state):
+ """Sets the state of this connection. Called when an event for this
+ connection has occurred.
+
+ Args:
+ new_state: state to be set. new_state must be one of followings:
+ - STATE_GRACEFULLY_CLOSED: when closing handshake for this
+ connection has been received.
+ - STATE_TERMINATED: when the physical connection has closed or
+ DropChannel of this connection has received.
+ """
+
+ self._read_condition.acquire()
+ self._read_state = new_state
+ self._read_condition.notify()
+ self._read_condition.release()
+
+
+class _LogicalStream(Stream):
+ """Mimics the Stream class. This class interprets multiplexed WebSocket
+ frames.
+ """
+
+ def __init__(self, request, send_quota, receive_quota):
+ """Constructs an instance.
+
+ Args:
+ request: _LogicalRequest instance.
+ send_quota: Initial send quota.
+ receive_quota: Initial receive quota.
+ """
+
+ # TODO(bashi): Support frame filters.
+ stream_options = StreamOptions()
+ # Physical stream is responsible for masking.
+ stream_options.unmask_receive = False
+ # Control frames can be fragmented on logical channel.
+ stream_options.allow_fragmented_control_frame = True
+ Stream.__init__(self, request, stream_options)
+ self._send_quota = send_quota
+ self._send_quota_condition = threading.Condition()
+ self._receive_quota = receive_quota
+ self._write_inner_frame_semaphore = threading.Semaphore()
+
+ def _create_inner_frame(self, opcode, payload, end=True):
+ # TODO(bashi): Support extensions that use reserved bits.
+ first_byte = (end << 7) | opcode
+ return (_encode_channel_id(self._request.channel_id) +
+ chr(first_byte) + payload)
+
+ def _write_inner_frame(self, opcode, payload, end=True):
+ payload_length = len(payload)
+ write_position = 0
+
+ try:
+ # An inner frame will be fragmented if there is no enough send
+ # quota. This semaphore ensures that fragmented inner frames are
+ # sent in order on the logical channel.
+ # Note that frames that come from other logical channels or
+ # multiplexing control blocks can be inserted between fragmented
+ # inner frames on the physical channel.
+ self._write_inner_frame_semaphore.acquire()
+ while write_position < payload_length:
+ try:
+ self._send_quota_condition.acquire()
+ while self._send_quota == 0:
+ self._logger.debug(
+ 'No quota. Waiting FlowControl message for %d.' %
+ self._request.channel_id)
+ self._send_quota_condition.wait()
+
+ remaining = payload_length - write_position
+ write_length = min(self._send_quota, remaining)
+ inner_frame_end = (
+ end and
+ (write_position + write_length == payload_length))
+
+ inner_frame = self._create_inner_frame(
+ opcode,
+ payload[write_position:write_position+write_length],
+ inner_frame_end)
+ frame_data = self._writer.build(
+ inner_frame, end=True, binary=True)
+ self._send_quota -= write_length
+ self._logger.debug('Consumed quota=%d, remaining=%d' %
+ (write_length, self._send_quota))
+ finally:
+ self._send_quota_condition.release()
+
+ # Writing data will block the worker so we need to release
+ # _send_quota_condition before writing.
+ self._logger.debug('Sending inner frame: %r' % frame_data)
+ self._request.connection.write(frame_data)
+ write_position += write_length
+
+ opcode = common.OPCODE_CONTINUATION
+
+ except ValueError, e:
+ raise BadOperationException(e)
+ finally:
+ self._write_inner_frame_semaphore.release()
+
+ def replenish_send_quota(self, send_quota):
+ """Replenish send quota."""
+
+ self._send_quota_condition.acquire()
+ self._send_quota += send_quota
+ self._logger.debug('Replenished send quota for channel id %d: %d' %
+ (self._request.channel_id, self._send_quota))
+ self._send_quota_condition.notify()
+ self._send_quota_condition.release()
+
+ def consume_receive_quota(self, amount):
+ """Consumes receive quota. Returns False on failure."""
+
+ if self._receive_quota < amount:
+ self._logger.debug('Violate quota on channel id %d: %d < %d' %
+ (self._request.channel_id,
+ self._receive_quota, amount))
+ return False
+ self._receive_quota -= amount
+ return True
+
+ def send_message(self, message, end=True, binary=False):
+ """Override Stream.send_message."""
+
+ if self._request.server_terminated:
+ raise BadOperationException(
+ 'Requested send_message after sending out a closing handshake')
+
+ if binary and isinstance(message, unicode):
+ raise BadOperationException(
+ 'Message for binary frame must be instance of str')
+
+ if binary:
+ opcode = common.OPCODE_BINARY
+ else:
+ opcode = common.OPCODE_TEXT
+ message = message.encode('utf-8')
+
+ self._write_inner_frame(opcode, message, end)
+
+ def _receive_frame(self):
+ """Overrides Stream._receive_frame.
+
+ In addition to call Stream._receive_frame, this method adds the amount
+ of payload to receiving quota and sends FlowControl to the client.
+ We need to do it here because Stream.receive_message() handles
+ control frames internally.
+ """
+
+ opcode, payload, fin, rsv1, rsv2, rsv3 = Stream._receive_frame(self)
+ amount = len(payload)
+ self._receive_quota += amount
+ frame_data = _create_flow_control(self._request.channel_id,
+ amount)
+ self._logger.debug('Sending flow control for %d, replenished=%d' %
+ (self._request.channel_id, amount))
+ self._request.connection.write_control_data(frame_data)
+ return opcode, payload, fin, rsv1, rsv2, rsv3
+
+ def receive_message(self):
+ """Overrides Stream.receive_message."""
+
+ # Just call Stream.receive_message(), but catch
+ # LogicalConnectionClosedException, which is raised when the logical
+ # connection has closed gracefully.
+ try:
+ return Stream.receive_message(self)
+ except LogicalConnectionClosedException, e:
+ self._logger.debug('%s', e)
+ return None
+
+ def _send_closing_handshake(self, code, reason):
+ """Overrides Stream._send_closing_handshake."""
+
+ body = create_closing_handshake_body(code, reason)
+ self._logger.debug('Sending closing handshake for %d: (%r, %r)' %
+ (self._request.channel_id, code, reason))
+ self._write_inner_frame(common.OPCODE_CLOSE, body, end=True)
+
+ self._request.server_terminated = True
+
+ def send_ping(self, body=''):
+ """Overrides Stream.send_ping"""
+
+ self._logger.debug('Sending ping on logical channel %d: %r' %
+ (self._request.channel_id, body))
+ self._write_inner_frame(common.OPCODE_PING, body, end=True)
+
+ self._ping_queue.append(body)
+
+ def _send_pong(self, body):
+ """Overrides Stream._send_pong"""
+
+ self._logger.debug('Sending pong on logical channel %d: %r' %
+ (self._request.channel_id, body))
+ self._write_inner_frame(common.OPCODE_PONG, body, end=True)
+
+ def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason=''):
+ """Overrides Stream.close_connection."""
+
+ # TODO(bashi): Implement
+ self._logger.debug('Closing logical connection %d' %
+ self._request.channel_id)
+ self._request.server_terminated = True
+
+ def _drain_received_data(self):
+ """Overrides Stream._drain_received_data. Nothing need to be done for
+ logical channel.
+ """
+
+ pass
+
+
+class _OutgoingData(object):
+ """A structure that holds data to be sent via physical connection and
+ origin of the data.
+ """
+
+ def __init__(self, channel_id, data):
+ self.channel_id = channel_id
+ self.data = data
+
+
+class _PhysicalConnectionWriter(threading.Thread):
+ """A thread that is responsible for writing data to physical connection.
+
+ TODO(bashi): Make sure there is no thread-safety problem when the reader
+ thread reads data from the same socket at a time.
+ """
+
+ def __init__(self, mux_handler):
+ """Constructs an instance.
+
+ Args:
+ mux_handler: _MuxHandler instance.
+ """
+
+ threading.Thread.__init__(self)
+ self._logger = util.get_class_logger(self)
+ self._mux_handler = mux_handler
+ self.setDaemon(True)
+ self._stop_requested = False
+ self._deque = collections.deque()
+ self._deque_condition = threading.Condition()
+
+ def put_outgoing_data(self, data):
+ """Puts outgoing data.
+
+ Args:
+ data: _OutgoingData instance.
+
+ Raises:
+ BadOperationException: when the thread has been requested to
+ terminate.
+ """
+
+ try:
+ self._deque_condition.acquire()
+ if self._stop_requested:
+ raise BadOperationException('Cannot write data anymore')
+
+ self._deque.append(data)
+ self._deque_condition.notify()
+ finally:
+ self._deque_condition.release()
+
+ def _write_data(self, outgoing_data):
+ try:
+ self._mux_handler.physical_connection.write(outgoing_data.data)
+ except Exception, e:
+ util.prepend_message_to_exception(
+ 'Failed to send message to %r: ' %
+ (self._mux_handler.physical_connection.remote_addr,), e)
+ raise
+
+ # TODO(bashi): It would be better to block the thread that sends
+ # control data as well.
+ if outgoing_data.channel_id != _CONTROL_CHANNEL_ID:
+ self._mux_handler.notify_write_done(outgoing_data.channel_id)
+
+ def run(self):
+ self._deque_condition.acquire()
+ while not self._stop_requested:
+ if len(self._deque) == 0:
+ self._deque_condition.wait()
+ continue
+
+ outgoing_data = self._deque.popleft()
+ self._deque_condition.release()
+ self._write_data(outgoing_data)
+ self._deque_condition.acquire()
+
+ # Flush deque
+ try:
+ while len(self._deque) > 0:
+ outgoing_data = self._deque.popleft()
+ self._write_data(outgoing_data)
+ finally:
+ self._deque_condition.release()
+
+ def stop(self):
+ """Stops the writer thread."""
+
+ self._deque_condition.acquire()
+ self._stop_requested = True
+ self._deque_condition.notify()
+ self._deque_condition.release()
+
+
+class _PhysicalConnectionReader(threading.Thread):
+ """A thread that is responsible for reading data from physical connection.
+ """
+
+ def __init__(self, mux_handler):
+ """Constructs an instance.
+
+ Args:
+ mux_handler: _MuxHandler instance.
+ """
+
+ threading.Thread.__init__(self)
+ self._logger = util.get_class_logger(self)
+ self._mux_handler = mux_handler
+ self.setDaemon(True)
+
+ def run(self):
+ while True:
+ try:
+ physical_stream = self._mux_handler.physical_stream
+ message = physical_stream.receive_message()
+ if message is None:
+ break
+ # Below happens only when a data message is received.
+ opcode = physical_stream.get_last_received_opcode()
+ if opcode != common.OPCODE_BINARY:
+ self._mux_handler.fail_physical_connection(
+ _DROP_CODE_INVALID_ENCAPSULATING_MESSAGE,
+ 'Received a text message on physical connection')
+ break
+
+ except ConnectionTerminatedException, e:
+ self._logger.debug('%s', e)
+ break
+
+ try:
+ self._mux_handler.dispatch_message(message)
+ except PhysicalConnectionError, e:
+ self._mux_handler.fail_physical_connection(
+ e.drop_code, e.message)
+ break
+ except LogicalChannelError, e:
+ self._mux_handler.fail_logical_channel(
+ e.channel_id, e.drop_code, e.message)
+ except Exception, e:
+ self._logger.debug(traceback.format_exc())
+ break
+
+ self._mux_handler.notify_reader_done()
+
+
+class _Worker(threading.Thread):
+ """A thread that is responsible for running the corresponding application
+ handler.
+ """
+
+ def __init__(self, mux_handler, request):
+ """Constructs an instance.
+
+ Args:
+ mux_handler: _MuxHandler instance.
+ request: _LogicalRequest instance.
+ """
+
+ threading.Thread.__init__(self)
+ self._logger = util.get_class_logger(self)
+ self._mux_handler = mux_handler
+ self._request = request
+ self.setDaemon(True)
+
+ def run(self):
+ self._logger.debug('Logical channel worker started. (id=%d)' %
+ self._request.channel_id)
+ try:
+ # Non-critical exceptions will be handled by dispatcher.
+ self._mux_handler.dispatcher.transfer_data(self._request)
+ finally:
+ self._mux_handler.notify_worker_done(self._request.channel_id)
+
+
+class _MuxHandshaker(hybi.Handshaker):
+ """Opening handshake processor for multiplexing."""
+
+ _DUMMY_WEBSOCKET_KEY = 'dGhlIHNhbXBsZSBub25jZQ=='
+
+ def __init__(self, request, dispatcher, send_quota, receive_quota):
+ """Constructs an instance.
+ Args:
+ request: _LogicalRequest instance.
+ dispatcher: Dispatcher instance (dispatch.Dispatcher).
+ send_quota: Initial send quota.
+ receive_quota: Initial receive quota.
+ """
+
+ hybi.Handshaker.__init__(self, request, dispatcher)
+ self._send_quota = send_quota
+ self._receive_quota = receive_quota
+
+ # Append headers which should not be included in handshake field of
+ # AddChannelRequest.
+ # TODO(bashi): Make sure whether we should raise exception when
+ # these headers are included already.
+ request.headers_in[common.UPGRADE_HEADER] = (
+ common.WEBSOCKET_UPGRADE_TYPE)
+ request.headers_in[common.CONNECTION_HEADER] = (
+ common.UPGRADE_CONNECTION_TYPE)
+ request.headers_in[common.SEC_WEBSOCKET_VERSION_HEADER] = (
+ str(common.VERSION_HYBI_LATEST))
+ request.headers_in[common.SEC_WEBSOCKET_KEY_HEADER] = (
+ self._DUMMY_WEBSOCKET_KEY)
+
+ def _create_stream(self, stream_options):
+ """Override hybi.Handshaker._create_stream."""
+
+ self._logger.debug('Creating logical stream for %d' %
+ self._request.channel_id)
+ return _LogicalStream(self._request, self._send_quota,
+ self._receive_quota)
+
+ def _create_handshake_response(self, accept):
+ """Override hybi._create_handshake_response."""
+
+ response = []
+
+ response.append('HTTP/1.1 101 Switching Protocols\r\n')
+
+ # Upgrade, Connection and Sec-WebSocket-Accept should be excluded.
+ if self._request.ws_protocol is not None:
+ response.append('%s: %s\r\n' % (
+ common.SEC_WEBSOCKET_PROTOCOL_HEADER,
+ self._request.ws_protocol))
+ if (self._request.ws_extensions is not None and
+ len(self._request.ws_extensions) != 0):
+ response.append('%s: %s\r\n' % (
+ common.SEC_WEBSOCKET_EXTENSIONS_HEADER,
+ common.format_extensions(self._request.ws_extensions)))
+ response.append('\r\n')
+
+ return ''.join(response)
+
+ def _send_handshake(self, accept):
+ """Override hybi.Handshaker._send_handshake."""
+
+ # Don't send handshake response for the default channel
+ if self._request.channel_id == _DEFAULT_CHANNEL_ID:
+ return
+
+ handshake_response = self._create_handshake_response(accept)
+ frame_data = _create_add_channel_response(
+ self._request.channel_id,
+ handshake_response)
+ self._logger.debug('Sending handshake response for %d: %r' %
+ (self._request.channel_id, frame_data))
+ self._request.connection.write_control_data(frame_data)
+
+
+class _LogicalChannelData(object):
+ """A structure that holds information about logical channel.
+ """
+
+ def __init__(self, request, worker):
+ self.request = request
+ self.worker = worker
+ self.drop_code = _DROP_CODE_NORMAL_CLOSURE
+ self.drop_message = ''
+
+
+class _HandshakeDeltaBase(object):
+ """A class that holds information for delta-encoded handshake."""
+
+ def __init__(self, headers):
+ self._headers = headers
+
+ def create_headers(self, delta=None):
+ """Creates request headers for an AddChannelRequest that has
+ delta-encoded handshake.
+
+ Args:
+ delta: headers should be overridden.
+ """
+
+ headers = copy.copy(self._headers)
+ if delta:
+ for key, value in delta.items():
+ # The spec requires that a header with an empty value is
+ # removed from the delta base.
+ if len(value) == 0 and headers.has_key(key):
+ del headers[key]
+ else:
+ headers[key] = value
+ # TODO(bashi): Support extensions
+ headers['Sec-WebSocket-Extensions'] = ''
+ return headers
+
+
+class _MuxHandler(object):
+ """Multiplexing handler. When a handler starts, it launches three
+ threads; the reader thread, the writer thread, and a worker thread.
+
+ The reader thread reads data from the physical stream, i.e., the
+ ws_stream object of the underlying websocket connection. The reader
+ thread interprets multiplexed frames and dispatches them to logical
+ channels. Methods of this class are mostly called by the reader thread.
+
+ The writer thread sends multiplexed frames which are created by
+ logical channels via the physical connection.
+
+ The worker thread launched at the starting point handles the
+ "Implicitly Opened Connection". If multiplexing handler receives
+ an AddChannelRequest and accepts it, the handler will launch a new worker
+ thread and dispatch the request to it.
+ """
+
+ def __init__(self, request, dispatcher):
+ """Constructs an instance.
+
+ Args:
+ request: mod_python request of the physical connection.
+ dispatcher: Dispatcher instance (dispatch.Dispatcher).
+ """
+
+ self.original_request = request
+ self.dispatcher = dispatcher
+ self.physical_connection = request.connection
+ self.physical_stream = request.ws_stream
+ self._logger = util.get_class_logger(self)
+ self._logical_channels = {}
+ self._logical_channels_condition = threading.Condition()
+ # Holds client's initial quota
+ self._channel_slots = collections.deque()
+ self._handshake_base = None
+ self._worker_done_notify_received = False
+ self._reader = None
+ self._writer = None
+
+ def start(self):
+ """Starts the handler.
+
+ Raises:
+ MuxUnexpectedException: when the handler already started, or when
+ opening handshake of the default channel fails.
+ """
+
+ if self._reader or self._writer:
+ raise MuxUnexpectedException('MuxHandler already started')
+
+ self._reader = _PhysicalConnectionReader(self)
+ self._writer = _PhysicalConnectionWriter(self)
+ self._reader.start()
+ self._writer.start()
+
+ # Create "Implicitly Opened Connection".
+ logical_connection = _LogicalConnection(self, _DEFAULT_CHANNEL_ID)
+ self._handshake_base = _HandshakeDeltaBase(
+ self.original_request.headers_in)
+ logical_request = _LogicalRequest(
+ _DEFAULT_CHANNEL_ID,
+ self.original_request.method,
+ self.original_request.uri,
+ self.original_request.protocol,
+ self._handshake_base.create_headers(),
+ logical_connection)
+ # Client's send quota for the implicitly opened connection is zero,
+ # but we will send FlowControl later so set the initial quota to
+ # _INITIAL_QUOTA_FOR_CLIENT.
+ self._channel_slots.append(_INITIAL_QUOTA_FOR_CLIENT)
+ if not self._do_handshake_for_logical_request(
+ logical_request, send_quota=self.original_request.mux_quota):
+ raise MuxUnexpectedException(
+ 'Failed handshake on the default channel id')
+ self._add_logical_channel(logical_request)
+
+ # Send FlowControl for the implicitly opened connection.
+ frame_data = _create_flow_control(_DEFAULT_CHANNEL_ID,
+ _INITIAL_QUOTA_FOR_CLIENT)
+ logical_request.connection.write_control_data(frame_data)
+
+ def add_channel_slots(self, slots, send_quota):
+ """Adds channel slots.
+
+ Args:
+ slots: number of slots to be added.
+ send_quota: initial send quota for slots.
+ """
+
+ self._channel_slots.extend([send_quota] * slots)
+ # Send NewChannelSlot to client.
+ frame_data = _create_new_channel_slot(slots, send_quota)
+ self.send_control_data(frame_data)
+
+ def wait_until_done(self, timeout=None):
+ """Waits until all workers are done. Returns False when timeout has
+ occurred. Returns True on success.
+
+ Args:
+ timeout: timeout in sec.
+ """
+
+ self._logical_channels_condition.acquire()
+ try:
+ while len(self._logical_channels) > 0:
+ self._logger.debug('Waiting workers(%d)...' %
+ len(self._logical_channels))
+ self._worker_done_notify_received = False
+ self._logical_channels_condition.wait(timeout)
+ if not self._worker_done_notify_received:
+ self._logger.debug('Waiting worker(s) timed out')
+ return False
+
+ finally:
+ self._logical_channels_condition.release()
+
+ # Flush pending outgoing data
+ self._writer.stop()
+ self._writer.join()
+
+ return True
+
+ def notify_write_done(self, channel_id):
+ """Called by the writer thread when a write operation has done.
+
+ Args:
+ channel_id: objective channel id.
+ """
+
+ try:
+ self._logical_channels_condition.acquire()
+ if channel_id in self._logical_channels:
+ channel_data = self._logical_channels[channel_id]
+ channel_data.request.connection.notify_write_done()
+ else:
+ self._logger.debug('Seems that logical channel for %d has gone'
+ % channel_id)
+ finally:
+ self._logical_channels_condition.release()
+
+ def send_control_data(self, data):
+ """Sends data via the control channel.
+
+ Args:
+ data: data to be sent.
+ """
+
+ self._writer.put_outgoing_data(_OutgoingData(
+ channel_id=_CONTROL_CHANNEL_ID, data=data))
+
+ def send_data(self, channel_id, data):
+ """Sends data via given logical channel. This method is called by
+ worker threads.
+
+ Args:
+ channel_id: objective channel id.
+ data: data to be sent.
+ """
+
+ self._writer.put_outgoing_data(_OutgoingData(
+ channel_id=channel_id, data=data))
+
+ def _send_drop_channel(self, channel_id, code=None, message=''):
+ frame_data = _create_drop_channel(channel_id, code, message)
+ self._logger.debug(
+ 'Sending drop channel for channel id %d' % channel_id)
+ self.send_control_data(frame_data)
+
+ def _send_error_add_channel_response(self, channel_id, status=None):
+ if status is None:
+ status = common.HTTP_STATUS_BAD_REQUEST
+
+ if status in _HTTP_BAD_RESPONSE_MESSAGES:
+ message = _HTTP_BAD_RESPONSE_MESSAGES[status]
+ else:
+ self._logger.debug('Response message for %d is not found' % status)
+ message = '???'
+
+ response = 'HTTP/1.1 %d %s\r\n\r\n' % (status, message)
+ frame_data = _create_add_channel_response(channel_id,
+ encoded_handshake=response,
+ encoding=0, rejected=True)
+ self.send_control_data(frame_data)
+
+ def _create_logical_request(self, block):
+ if block.channel_id == _CONTROL_CHANNEL_ID:
+ # TODO(bashi): Raise PhysicalConnectionError with code 2006
+ # instead of MuxUnexpectedException.
+ raise MuxUnexpectedException(
+ 'Received the control channel id (0) as objective channel '
+ 'id for AddChannel')
+
+ if block.encoding > _HANDSHAKE_ENCODING_DELTA:
+ raise PhysicalConnectionError(
+ _DROP_CODE_UNKNOWN_REQUEST_ENCODING)
+
+ method, path, version, headers = _parse_request_text(
+ block.encoded_handshake)
+ if block.encoding == _HANDSHAKE_ENCODING_DELTA:
+ headers = self._handshake_base.create_headers(headers)
+
+ connection = _LogicalConnection(self, block.channel_id)
+ request = _LogicalRequest(block.channel_id, method, path, version,
+ headers, connection)
+ return request
+
+ def _do_handshake_for_logical_request(self, request, send_quota=0):
+ try:
+ receive_quota = self._channel_slots.popleft()
+ except IndexError:
+ raise LogicalChannelError(
+ request.channel_id, _DROP_CODE_NEW_CHANNEL_SLOT_VIOLATION)
+
+ handshaker = _MuxHandshaker(request, self.dispatcher,
+ send_quota, receive_quota)
+ try:
+ handshaker.do_handshake()
+ except handshake.VersionException, e:
+ self._logger.info('%s', e)
+ self._send_error_add_channel_response(
+ request.channel_id, status=common.HTTP_STATUS_BAD_REQUEST)
+ return False
+ except handshake.HandshakeException, e:
+ # TODO(bashi): Should we _Fail the Logical Channel_ with 3001
+ # instead?
+ self._logger.info('%s', e)
+ self._send_error_add_channel_response(request.channel_id,
+ status=e.status)
+ return False
+ except handshake.AbortedByUserException, e:
+ self._logger.info('%s', e)
+ self._send_error_add_channel_response(request.channel_id)
+ return False
+
+ return True
+
+ def _add_logical_channel(self, logical_request):
+ try:
+ self._logical_channels_condition.acquire()
+ if logical_request.channel_id in self._logical_channels:
+ self._logger.debug('Channel id %d already exists' %
+ logical_request.channel_id)
+ raise PhysicalConnectionError(
+ _DROP_CODE_CHANNEL_ALREADY_EXISTS,
+ 'Channel id %d already exists' %
+ logical_request.channel_id)
+ worker = _Worker(self, logical_request)
+ channel_data = _LogicalChannelData(logical_request, worker)
+ self._logical_channels[logical_request.channel_id] = channel_data
+ worker.start()
+ finally:
+ self._logical_channels_condition.release()
+
+ def _process_add_channel_request(self, block):
+ try:
+ logical_request = self._create_logical_request(block)
+ except ValueError, e:
+ self._logger.debug('Failed to create logical request: %r' % e)
+ self._send_error_add_channel_response(
+ block.channel_id, status=common.HTTP_STATUS_BAD_REQUEST)
+ return
+ if self._do_handshake_for_logical_request(logical_request):
+ if block.encoding == _HANDSHAKE_ENCODING_IDENTITY:
+ # Update handshake base.
+ # TODO(bashi): Make sure this is the right place to update
+ # handshake base.
+ self._handshake_base = _HandshakeDeltaBase(
+ logical_request.headers_in)
+ self._add_logical_channel(logical_request)
+ else:
+ self._send_error_add_channel_response(
+ block.channel_id, status=common.HTTP_STATUS_BAD_REQUEST)
+
+ def _process_flow_control(self, block):
+ try:
+ self._logical_channels_condition.acquire()
+ if not block.channel_id in self._logical_channels:
+ return
+ channel_data = self._logical_channels[block.channel_id]
+ channel_data.request.ws_stream.replenish_send_quota(
+ block.send_quota)
+ finally:
+ self._logical_channels_condition.release()
+
+ def _process_drop_channel(self, block):
+ self._logger.debug(
+ 'DropChannel received for %d: code=%r, reason=%r' %
+ (block.channel_id, block.drop_code, block.drop_message))
+ try:
+ self._logical_channels_condition.acquire()
+ if not block.channel_id in self._logical_channels:
+ return
+ channel_data = self._logical_channels[block.channel_id]
+ channel_data.drop_code = _DROP_CODE_ACKNOWLEDGED
+ # Close the logical channel
+ channel_data.request.connection.set_read_state(
+ _LogicalConnection.STATE_TERMINATED)
+ finally:
+ self._logical_channels_condition.release()
+
+ def _process_control_blocks(self, parser):
+ for control_block in parser.read_control_blocks():
+ opcode = control_block.opcode
+ self._logger.debug('control block received, opcode: %d' % opcode)
+ if opcode == _MUX_OPCODE_ADD_CHANNEL_REQUEST:
+ self._process_add_channel_request(control_block)
+ elif opcode == _MUX_OPCODE_ADD_CHANNEL_RESPONSE:
+ raise PhysicalConnectionError(
+ _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+ 'Received AddChannelResponse')
+ elif opcode == _MUX_OPCODE_FLOW_CONTROL:
+ self._process_flow_control(control_block)
+ elif opcode == _MUX_OPCODE_DROP_CHANNEL:
+ self._process_drop_channel(control_block)
+ elif opcode == _MUX_OPCODE_NEW_CHANNEL_SLOT:
+ raise PhysicalConnectionError(
+ _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+ 'Received NewChannelSlot')
+ else:
+ raise MuxUnexpectedException(
+ 'Unexpected opcode %r' % opcode)
+
+ def _process_logical_frame(self, channel_id, parser):
+ self._logger.debug('Received a frame. channel id=%d' % channel_id)
+ try:
+ self._logical_channels_condition.acquire()
+ if not channel_id in self._logical_channels:
+ # We must ignore the message for an inactive channel.
+ return
+ channel_data = self._logical_channels[channel_id]
+ fin, rsv1, rsv2, rsv3, opcode, payload = parser.read_inner_frame()
+ if not channel_data.request.ws_stream.consume_receive_quota(
+ len(payload)):
+ # The client violates quota. Close logical channel.
+ raise LogicalChannelError(
+ channel_id, _DROP_CODE_SEND_QUOTA_VIOLATION)
+ header = create_header(opcode, len(payload), fin, rsv1, rsv2, rsv3,
+ mask=False)
+ frame_data = header + payload
+ channel_data.request.connection.append_frame_data(frame_data)
+ finally:
+ self._logical_channels_condition.release()
+
+ def dispatch_message(self, message):
+ """Dispatches message. The reader thread calls this method.
+
+ Args:
+ message: a message that contains encapsulated frame.
+ Raises:
+ PhysicalConnectionError: if the message contains physical
+ connection level errors.
+ LogicalChannelError: if the message contains logical channel
+ level errors.
+ """
+
+ parser = _MuxFramePayloadParser(message)
+ try:
+ channel_id = parser.read_channel_id()
+ except ValueError, e:
+ raise PhysicalConnectionError(_DROP_CODE_CHANNEL_ID_TRUNCATED)
+ if channel_id == _CONTROL_CHANNEL_ID:
+ self._process_control_blocks(parser)
+ else:
+ self._process_logical_frame(channel_id, parser)
+
+ def notify_worker_done(self, channel_id):
+ """Called when a worker has finished.
+
+ Args:
+ channel_id: channel id corresponded with the worker.
+ """
+
+ self._logger.debug('Worker for channel id %d terminated' % channel_id)
+ try:
+ self._logical_channels_condition.acquire()
+ if not channel_id in self._logical_channels:
+ raise MuxUnexpectedException(
+ 'Channel id %d not found' % channel_id)
+ channel_data = self._logical_channels.pop(channel_id)
+ finally:
+ self._worker_done_notify_received = True
+ self._logical_channels_condition.notify()
+ self._logical_channels_condition.release()
+
+ if not channel_data.request.server_terminated:
+ self._send_drop_channel(
+ channel_id, code=channel_data.drop_code,
+ message=channel_data.drop_message)
+
+ def notify_reader_done(self):
+ """This method is called by the reader thread when the reader has
+ finished.
+ """
+
+ # Terminate all logical connections
+ self._logger.debug('termiating all logical connections...')
+ self._logical_channels_condition.acquire()
+ for channel_data in self._logical_channels.values():
+ try:
+ channel_data.request.connection.set_read_state(
+ _LogicalConnection.STATE_TERMINATED)
+ except Exception:
+ pass
+ self._logical_channels_condition.release()
+
+ def fail_physical_connection(self, code, message):
+ """Fail the physical connection.
+
+ Args:
+ code: drop reason code.
+ message: drop message.
+ """
+
+ self._logger.debug('Failing the physical connection...')
+ self._send_drop_channel(_CONTROL_CHANNEL_ID, code, message)
+ self.physical_stream.close_connection(
+ common.STATUS_INTERNAL_ENDPOINT_ERROR)
+
+ def fail_logical_channel(self, channel_id, code, message):
+ """Fail a logical channel.
+
+ Args:
+ channel_id: channel id.
+ code: drop reason code.
+ message: drop message.
+ """
+
+ self._logger.debug('Failing logical channel %d...' % channel_id)
+ try:
+ self._logical_channels_condition.acquire()
+ if channel_id in self._logical_channels:
+ channel_data = self._logical_channels[channel_id]
+ # Close the logical channel. notify_worker_done() will be
+ # called later and it will send DropChannel.
+ channel_data.drop_code = code
+ channel_data.drop_message = message
+ channel_data.request.connection.set_read_state(
+ _LogicalConnection.STATE_TERMINATED)
+ else:
+ self._send_drop_channel(channel_id, code, message)
+ finally:
+ self._logical_channels_condition.release()
+
+
+def use_mux(request):
+ return hasattr(request, 'mux') and request.mux
+
+
+def start(request, dispatcher):
+ mux_handler = _MuxHandler(request, dispatcher)
+ mux_handler.start()
+
+ mux_handler.add_channel_slots(_INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+ _INITIAL_QUOTA_FOR_CLIENT)
+
+ mux_handler.wait_until_done()
+
+
+# vi:sts=4 sw=4 et
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/standalone.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/standalone.py
index 850aa5cd4..07a33d9c9 100755
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/standalone.py
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/standalone.py
@@ -32,27 +32,44 @@
"""Standalone WebSocket server.
+Use this file to launch pywebsocket without Apache HTTP Server.
+
+
BASIC USAGE
-Use this server to run mod_pywebsocket without Apache HTTP Server.
+Go to the src directory and run
-Usage:
- python standalone.py [-p <ws_port>] [-w <websock_handlers>]
- [-s <scan_dir>]
- [-d <document_root>]
- [-m <websock_handlers_map_file>]
- ... for other options, see _main below ...
+ $ python mod_pywebsocket/standalone.py [-p <ws_port>]
+ [-w <websock_handlers>]
+ [-d <document_root>]
<ws_port> is the port number to use for ws:// connection.
<document_root> is the path to the root directory of HTML files.
<websock_handlers> is the path to the root directory of WebSocket handlers.
-See __init__.py for details of <websock_handlers> and how to write WebSocket
-handlers. If this path is relative, <document_root> is used as the base.
+If not specified, <document_root> will be used. See __init__.py (or
+run $ pydoc mod_pywebsocket) for how to write WebSocket handlers.
+
+For more detail and other options, run
+
+ $ python mod_pywebsocket/standalone.py --help
+
+or see _build_option_parser method below.
+
+For trouble shooting, adding "--log_level debug" might help you.
+
-<scan_dir> is a path under the root directory. If specified, only the
-handlers under scan_dir are scanned. This is useful in saving scan time.
+TRY DEMO
+
+Go to the src directory and run
+
+ $ python standalone.py -d example
+
+to launch pywebsocket with the sample handler and html on port 80. Open
+http://localhost/console.html, click the connect button, type something into
+the text box next to the send button and click the send button. If everything
+is working, you'll see the message you typed echoed by the server.
SUPPORTING TLS
@@ -63,10 +80,10 @@ To support TLS, run standalone.py with -t, -k, and -c options.
SUPPORTING CLIENT AUTHENTICATION
To support client authentication with TLS, run standalone.py with -t, -k, -c,
-and --ca-certificate options.
+and --tls-client-auth, and --tls-client-ca options.
E.g., $./standalone.py -d ../example -p 10443 -t -c ../test/cert/cert.pem -k
-../test/cert/key.pem --ca-certificate=../test/cert/cacert.pem
+../test/cert/key.pem --tls-client-auth --tls-client-ca=../test/cert/cacert.pem
CONFIGURATION FILE
@@ -110,6 +127,7 @@ import CGIHTTPServer
import SimpleHTTPServer
import SocketServer
import ConfigParser
+import base64
import httplib
import logging
import logging.handlers
@@ -224,6 +242,12 @@ class _StandaloneRequest(object):
return self._request_handler.command
method = property(get_method)
+ def get_protocol(self):
+ """Getter to mimic request.protocol."""
+
+ return self._request_handler.request_version
+ protocol = property(get_protocol)
+
def is_https(self):
"""Mimic request.is_https()."""
@@ -264,6 +288,32 @@ class _StandaloneSSLConnection(object):
return socket._fileobject(self._connection, mode, bufsize)
+def _alias_handlers(dispatcher, websock_handlers_map_file):
+ """Set aliases specified in websock_handler_map_file in dispatcher.
+
+ Args:
+ dispatcher: dispatch.Dispatcher instance
+ websock_handler_map_file: alias map file
+ """
+
+ fp = open(websock_handlers_map_file)
+ try:
+ for line in fp:
+ if line[0] == '#' or line.isspace():
+ continue
+ m = re.match('(\S+)\s+(\S+)', line)
+ if not m:
+ logging.warning('Wrong format in map file:' + line)
+ continue
+ try:
+ dispatcher.add_resource_path_alias(
+ m.group(1), m.group(2))
+ except dispatch.DispatchException, e:
+ logging.error(str(e))
+ finally:
+ fp.close()
+
+
class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
"""HTTPServer specialized for WebSocket."""
@@ -278,6 +328,20 @@ class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
if necessary.
"""
+ # Share a Dispatcher among request handlers to save time for
+ # instantiation. Dispatcher can be shared because it is thread-safe.
+ options.dispatcher = dispatch.Dispatcher(
+ options.websock_handlers,
+ options.scan_dir,
+ options.allow_handlers_outside_root_dir)
+ if options.websock_handlers_map_file:
+ _alias_handlers(options.dispatcher,
+ options.websock_handlers_map_file)
+ warnings = options.dispatcher.source_warnings()
+ if warnings:
+ for warning in warnings:
+ logging.warning('mod_pywebsocket: %s' % warning)
+
self._logger = util.get_class_logger(self)
self.request_queue_size = options.request_queue_size
@@ -325,7 +389,7 @@ class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
continue
if self.websocket_server_options.use_tls:
if _HAS_SSL:
- if self.websocket_server_options.ca_certificate:
+ if self.websocket_server_options.tls_client_auth:
client_cert_ = ssl.CERT_REQUIRED
else:
client_cert_ = ssl.CERT_NONE
@@ -333,7 +397,7 @@ class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
keyfile=self.websocket_server_options.private_key,
certfile=self.websocket_server_options.certificate,
ssl_version=ssl.PROTOCOL_SSLv23,
- ca_certs=self.websocket_server_options.ca_certificate,
+ ca_certs=self.websocket_server_options.tls_client_ca,
cert_reqs=client_cert_)
if _HAS_OPEN_SSL:
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
@@ -362,6 +426,15 @@ class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
self._logger.info('Skip by failure: %r', e)
socket_.close()
failed_sockets.append(socketinfo)
+ if self.server_address[1] == 0:
+ # The operating system assigns the actual port number for port
+ # number 0. This case, the second and later sockets should use
+ # the same port number. Also self.server_port is rewritten
+ # because it is exported, and will be used by external code.
+ self.server_address = (
+ self.server_name, socket_.getsockname()[1])
+ self.server_port = self.server_address[1]
+ self._logger.info('Port %r is assigned', self.server_port)
for socketinfo in failed_sockets:
self._sockets.remove(socketinfo)
@@ -386,6 +459,10 @@ class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
for socketinfo in failed_sockets:
self._sockets.remove(socketinfo)
+ if len(self._sockets) == 0:
+ self._logger.critical(
+ 'No sockets activated. Use info log level to see the reason.')
+
def server_close(self):
"""Override SocketServer.TCPServer.server_close to enable multiple
sockets close.
@@ -513,6 +590,17 @@ class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
# attributes).
if not CGIHTTPServer.CGIHTTPRequestHandler.parse_request(self):
return False
+
+ if self._options.use_basic_auth:
+ auth = self.headers.getheader('Authorization')
+ if auth != self._options.basic_auth_credential:
+ self.send_response(401)
+ self.send_header('WWW-Authenticate',
+ 'Basic realm="Pywebsocket"')
+ self.end_headers()
+ self._logger.info('Request basic authentication')
+ return True
+
host, port, resource = http_header_util.parse_uri(self.path)
if resource is None:
self._logger.info('Invalid URI: %r', self.path)
@@ -648,32 +736,6 @@ def _configure_logging(options):
deflate_log_level_name)
-def _alias_handlers(dispatcher, websock_handlers_map_file):
- """Set aliases specified in websock_handler_map_file in dispatcher.
-
- Args:
- dispatcher: dispatch.Dispatcher instance
- websock_handler_map_file: alias map file
- """
-
- fp = open(websock_handlers_map_file)
- try:
- for line in fp:
- if line[0] == '#' or line.isspace():
- continue
- m = re.match('(\S+)\s+(\S+)', line)
- if not m:
- logging.warning('Wrong format in map file:' + line)
- continue
- try:
- dispatcher.add_resource_path_alias(
- m.group(1), m.group(2))
- except dispatch.DispatchException, e:
- logging.error(str(e))
- finally:
- fp.close()
-
-
def _build_option_parser():
parser = optparse.OptionParser()
@@ -700,7 +762,9 @@ def _build_option_parser():
parser.add_option('-w', '--websock-handlers', '--websock_handlers',
dest='websock_handlers',
default='.',
- help='WebSocket handlers root directory.')
+ help=('The root directory of WebSocket handler files. '
+ 'If the path is relative, --document-root is used '
+ 'as the base.'))
parser.add_option('-m', '--websock-handlers-map-file',
'--websock_handlers_map_file',
dest='websock_handlers_map_file',
@@ -710,15 +774,20 @@ def _build_option_parser():
'existing_resource_path, separated by spaces.'))
parser.add_option('-s', '--scan-dir', '--scan_dir', dest='scan_dir',
default=None,
- help=('WebSocket handlers scan directory. '
- 'Must be a directory under websock_handlers.'))
+ help=('Must be a directory under --websock-handlers. '
+ 'Only handlers under this directory are scanned '
+ 'and registered to the server. '
+ 'Useful for saving scan time when the handler '
+ 'root directory contains lots of files that are '
+ 'not handler file or are handler files but you '
+ 'don\'t want them to be registered. '))
parser.add_option('--allow-handlers-outside-root-dir',
'--allow_handlers_outside_root_dir',
dest='allow_handlers_outside_root_dir',
action='store_true',
default=False,
help=('Scans WebSocket handlers even if their canonical '
- 'path is not under websock_handlers.'))
+ 'path is not under --websock-handlers.'))
parser.add_option('-d', '--document-root', '--document_root',
dest='document_root', default='.',
help='Document root directory.')
@@ -735,9 +804,20 @@ def _build_option_parser():
default='', help='TLS private key file.')
parser.add_option('-c', '--certificate', dest='certificate',
default='', help='TLS certificate file.')
- parser.add_option('--ca-certificate', dest='ca_certificate', default='',
- help=('TLS CA certificate file for client '
- 'authentication.'))
+ parser.add_option('--tls-client-auth', dest='tls_client_auth',
+ action='store_true', default=False,
+ help='Requires TLS client auth on every connection.')
+ parser.add_option('--tls-client-ca', dest='tls_client_ca', default='',
+ help=('Specifies a pem file which contains a set of '
+ 'concatenated CA certificates which are used to '
+ 'validate certificates passed from clients'))
+ parser.add_option('--basic-auth', dest='use_basic_auth',
+ action='store_true', default=False,
+ help='Requires Basic authentication.')
+ parser.add_option('--basic-auth-credential',
+ dest='basic_auth_credential', default='test:test',
+ help='Specifies the credential of basic authentication '
+ 'by username:password pair (e.g. test:test).')
parser.add_option('-l', '--log-file', '--log_file', dest='log_file',
default='', help='Log file.')
# Custom log level:
@@ -771,9 +851,9 @@ def _build_option_parser():
help='Log backup count')
parser.add_option('--allow-draft75', dest='allow_draft75',
action='store_true', default=False,
- help='Allow draft 75 handshake')
+ help='Obsolete option. Ignored.')
parser.add_option('--strict', dest='strict', action='store_true',
- default=False, help='Strictly check handshake request')
+ default=False, help='Obsolete option. Ignored.')
parser.add_option('-q', '--queue', dest='request_queue_size', type='int',
default=_DEFAULT_REQUEST_QUEUE_SIZE,
help='request queue size')
@@ -841,6 +921,12 @@ def _parse_args_and_config(args):
def _main(args=None):
+ """You can call this function from your own program, but please note that
+ this function has some side-effects that might affect your program. For
+ example, util.wrap_popen3_for_win use in this method replaces implementation
+ of os.popen3.
+ """
+
options, args = _parse_args_and_config(args=args)
os.chdir(options.document_root)
@@ -877,7 +963,7 @@ def _main(args=None):
'To use TLS, specify private_key and certificate.')
sys.exit(1)
- if options.ca_certificate:
+ if options.tls_client_auth:
if not options.use_tls:
logging.critical('TLS must be enabled for client authentication.')
sys.exit(1)
@@ -887,26 +973,16 @@ def _main(args=None):
if not options.scan_dir:
options.scan_dir = options.websock_handlers
+ if options.use_basic_auth:
+ options.basic_auth_credential = 'Basic ' + base64.b64encode(
+ options.basic_auth_credential)
+
try:
if options.thread_monitor_interval_in_sec > 0:
# Run a thread monitor to show the status of server threads for
# debugging.
ThreadMonitor(options.thread_monitor_interval_in_sec).start()
- # Share a Dispatcher among request handlers to save time for
- # instantiation. Dispatcher can be shared because it is thread-safe.
- options.dispatcher = dispatch.Dispatcher(
- options.websock_handlers,
- options.scan_dir,
- options.allow_handlers_outside_root_dir)
- if options.websock_handlers_map_file:
- _alias_handlers(options.dispatcher,
- options.websock_handlers_map_file)
- warnings = options.dispatcher.source_warnings()
- if warnings:
- for warning in warnings:
- logging.warning('mod_pywebsocket: %s' % warning)
-
server = WebSocketServer(options)
server.serve_forever()
except Exception, e:
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/stream.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/stream.py
index d051eee20..edc533279 100644
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/stream.py
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/stream.py
@@ -51,6 +51,7 @@ from mod_pywebsocket._stream_hybi import create_ping_frame
from mod_pywebsocket._stream_hybi import create_pong_frame
from mod_pywebsocket._stream_hybi import create_binary_frame
from mod_pywebsocket._stream_hybi import create_text_frame
+from mod_pywebsocket._stream_hybi import create_closing_handshake_body
# vi:sts=4 sw=4 et
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/util.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/util.py
index 6146e052f..7bb0b5d9e 100644
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/util.py
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/util.py
@@ -232,6 +232,12 @@ class _Deflater(object):
self._compress = zlib.compressobj(
zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -window_bits)
+ def compress(self, bytes):
+ compressed_bytes = self._compress.compress(bytes)
+ self._logger.debug('Compress input %r', bytes)
+ self._logger.debug('Compress result %r', compressed_bytes)
+ return compressed_bytes
+
def compress_and_flush(self, bytes):
compressed_bytes = self._compress.compress(bytes)
compressed_bytes += self._compress.flush(zlib.Z_SYNC_FLUSH)
@@ -239,6 +245,12 @@ class _Deflater(object):
self._logger.debug('Compress result %r', compressed_bytes)
return compressed_bytes
+ def compress_and_finish(self, bytes):
+ compressed_bytes = self._compress.compress(bytes)
+ compressed_bytes += self._compress.flush(zlib.Z_FINISH)
+ self._logger.debug('Compress input %r', bytes)
+ self._logger.debug('Compress result %r', compressed_bytes)
+ return compressed_bytes
class _Inflater(object):
@@ -318,14 +330,21 @@ class _RFC1979Deflater(object):
self._window_bits = window_bits
self._no_context_takeover = no_context_takeover
- def filter(self, bytes):
- if self._deflater is None or self._no_context_takeover:
+ def filter(self, bytes, flush=True, bfinal=False):
+ if self._deflater is None or (self._no_context_takeover and flush):
self._deflater = _Deflater(self._window_bits)
- # Strip last 4 octets which is LEN and NLEN field of a non-compressed
- # block added for Z_SYNC_FLUSH.
- return self._deflater.compress_and_flush(bytes)[:-4]
-
+ if bfinal:
+ result = self._deflater.compress_and_finish(bytes)
+ # Add a padding block with BFINAL = 0 and BTYPE = 0.
+ result = result + chr(0)
+ self._deflater = None
+ return result
+ if flush:
+ # Strip last 4 octets which is LEN and NLEN field of a
+ # non-compressed block added for Z_SYNC_FLUSH.
+ return self._deflater.compress_and_flush(bytes)[:-4]
+ return self._deflater.compress(bytes)
class _RFC1979Inflater(object):
"""A decompressor class for byte sequence compressed and flushed following
diff --git a/Tools/Scripts/webkitpy/tool/commands/download_unittest.py b/Tools/Scripts/webkitpy/tool/commands/download_unittest.py
index 79b729aad..b71f3daaf 100644
--- a/Tools/Scripts/webkitpy/tool/commands/download_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/commands/download_unittest.py
@@ -285,7 +285,7 @@ Reason
component: MOCK component
cc: MOCK cc
blocked: 50004
-MOCK reopen_bug 50004 with comment 'Re-opened since this is blocked by 60001'
+MOCK reopen_bug 50004 with comment 'Re-opened since this is blocked by bug 60001'
MOCK add_patch_to_bug: bug_id=60001, description=ROLLOUT of r3001, mark_for_review=False, mark_for_commit_queue=True, mark_for_landing=False
-- Begin comment --
Any committer can land this patch automatically by marking it commit-queue+. The commit-queue will build and test the patch before landing to ensure that the rollout will be successful. This process takes approximately 15 minutes.
diff --git a/Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py b/Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py
index 22fb1704b..4b63f2f67 100644
--- a/Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py
@@ -54,17 +54,15 @@ class TestRebaseline(unittest.TestCase):
self.assertEqual(command._baseline_directory("GTK Linux 32-bit Release"), "/mock-checkout/LayoutTests/platform/gtk")
self.assertEqual(command._baseline_directory("EFL Linux 64-bit Debug"), "/mock-checkout/LayoutTests/platform/efl")
self.assertEqual(command._baseline_directory("Qt Linux Release"), "/mock-checkout/LayoutTests/platform/qt")
- self.assertEqual(command._baseline_directory("Webkit Mac10.7"), "/mock-checkout/LayoutTests/platform/chromium-mac")
- self.assertEqual(command._baseline_directory("Webkit Mac10.6"), "/mock-checkout/LayoutTests/platform/chromium-mac-snowleopard")
+ self.assertEqual(command._baseline_directory("WebKit Mac10.7"), "/mock-checkout/LayoutTests/platform/chromium-mac")
+ self.assertEqual(command._baseline_directory("WebKit Mac10.6"), "/mock-checkout/LayoutTests/platform/chromium-mac-snowleopard")
def test_rebaseline_updates_expectations_file_noop(self):
command = RebaselineTest()
tool = MockTool()
command.bind_to_tool(tool)
- lion_port = tool.port_factory.get_from_builder_name("Webkit Mac10.7")
- # FIXME: work around the chromium skia expectations file to avoid getting a bunch of confusing warnings.
- tool.filesystem.write_text_file(lion_port.path_from_chromium_base('skia', 'skia_test_expectations.txt'), '')
+ lion_port = tool.port_factory.get_from_builder_name("WebKit Mac10.7")
for path in lion_port.expectations_files():
tool.filesystem.write_text_file(path, '')
tool.filesystem.write_text_file(lion_port.path_to_test_expectations_file(), """Bug(B) [ Mac Linux XP Debug ] fast/dom/Window/window-postmessage-clone-really-deep-array.html [ Pass ]
@@ -74,11 +72,11 @@ Bug(A) [ Debug ] : fast/css/large-list-of-rules-crash.html [ Failure ]
tool.filesystem.write_text_file(os.path.join(lion_port.layout_tests_dir(), "fast/css/large-list-of-rules-crash.html"), "Dummy test contents")
tool.filesystem.write_text_file(os.path.join(lion_port.layout_tests_dir(), "userscripts/another-test.html"), "Dummy test contents")
- expected_logs = """Retrieving http://example.com/f/builders/Webkit Mac10.7/results/layout-test-results/userscripts/another-test-actual.png.
-Retrieving http://example.com/f/builders/Webkit Mac10.7/results/layout-test-results/userscripts/another-test-actual.wav.
-Retrieving http://example.com/f/builders/Webkit Mac10.7/results/layout-test-results/userscripts/another-test-actual.txt.
+ expected_logs = """Retrieving http://example.com/f/builders/WebKit Mac10.7/results/layout-test-results/userscripts/another-test-actual.png.
+Retrieving http://example.com/f/builders/WebKit Mac10.7/results/layout-test-results/userscripts/another-test-actual.wav.
+Retrieving http://example.com/f/builders/WebKit Mac10.7/results/layout-test-results/userscripts/another-test-actual.txt.
"""
- OutputCapture().assert_outputs(self, command._rebaseline_test_and_update_expectations, ["Webkit Mac10.7", "userscripts/another-test.html", None], expected_logs=expected_logs)
+ OutputCapture().assert_outputs(self, command._rebaseline_test_and_update_expectations, ["WebKit Mac10.7", "userscripts/another-test.html", None], expected_logs=expected_logs)
new_expectations = tool.filesystem.read_text_file(lion_port.path_to_test_expectations_file())
self.assertEqual(new_expectations, """Bug(B) [ Mac Linux XP Debug ] fast/dom/Window/window-postmessage-clone-really-deep-array.html [ Pass ]
@@ -90,54 +88,52 @@ Bug(A) [ Debug ] : fast/css/large-list-of-rules-crash.html [ Failure ]
tool = MockTool()
command.bind_to_tool(tool)
- lion_port = tool.port_factory.get_from_builder_name("Webkit Mac10.7")
- tool.filesystem.write_text_file(lion_port.path_from_chromium_base('skia', 'skia_test_expectations.txt'), '')
+ lion_port = tool.port_factory.get_from_builder_name("WebKit Mac10.7")
tool.filesystem.write_text_file(lion_port.path_to_test_expectations_file(), "Bug(x) [ Mac ] userscripts/another-test.html [ ImageOnlyFailure ]\nbug(z) [ Linux ] userscripts/another-test.html [ ImageOnlyFailure ]\n")
tool.filesystem.write_text_file(os.path.join(lion_port.layout_tests_dir(), "userscripts/another-test.html"), "Dummy test contents")
- expected_logs = """Retrieving http://example.com/f/builders/Webkit Mac10.7/results/layout-test-results/userscripts/another-test-actual.png.
-Retrieving http://example.com/f/builders/Webkit Mac10.7/results/layout-test-results/userscripts/another-test-actual.wav.
-Retrieving http://example.com/f/builders/Webkit Mac10.7/results/layout-test-results/userscripts/another-test-actual.txt.
+ expected_logs = """Retrieving http://example.com/f/builders/WebKit Mac10.7/results/layout-test-results/userscripts/another-test-actual.png.
+Retrieving http://example.com/f/builders/WebKit Mac10.7/results/layout-test-results/userscripts/another-test-actual.wav.
+Retrieving http://example.com/f/builders/WebKit Mac10.7/results/layout-test-results/userscripts/another-test-actual.txt.
"""
- OutputCapture().assert_outputs(self, command._rebaseline_test_and_update_expectations, ["Webkit Mac10.7", "userscripts/another-test.html", None], expected_logs=expected_logs)
+ OutputCapture().assert_outputs(self, command._rebaseline_test_and_update_expectations, ["WebKit Mac10.7", "userscripts/another-test.html", None], expected_logs=expected_logs)
new_expectations = tool.filesystem.read_text_file(lion_port.path_to_test_expectations_file())
- self.assertEqual(new_expectations, "Bug(x) [ SnowLeopard ] userscripts/another-test.html [ ImageOnlyFailure ]\nbug(z) [ Linux ] userscripts/another-test.html [ ImageOnlyFailure ]\n")
+ self.assertEqual(new_expectations, "Bug(x) [ MountainLion SnowLeopard ] userscripts/another-test.html [ ImageOnlyFailure ]\nbug(z) [ Linux ] userscripts/another-test.html [ ImageOnlyFailure ]\n")
def test_rebaseline_does_not_include_overrides(self):
command = RebaselineTest()
tool = MockTool()
command.bind_to_tool(tool)
- lion_port = tool.port_factory.get_from_builder_name("Webkit Mac10.7")
- tool.filesystem.write_text_file(lion_port.path_from_chromium_base('skia', 'skia_test_expectations.txt'), '')
+ lion_port = tool.port_factory.get_from_builder_name("WebKit Mac10.7")
tool.filesystem.write_text_file(lion_port.path_to_test_expectations_file(), "Bug(x) [ Mac ] userscripts/another-test.html [ ImageOnlyFailure ]\nBug(z) [ Linux ] userscripts/another-test.html [ ImageOnlyFailure ]\n")
tool.filesystem.write_text_file(lion_port.path_from_chromium_base('skia', 'skia_test_expectations.txt'), "Bug(y) [ Mac ] other-test.html [ Failure ]\n")
tool.filesystem.write_text_file(os.path.join(lion_port.layout_tests_dir(), "userscripts/another-test.html"), "Dummy test contents")
- expected_logs = """Retrieving http://example.com/f/builders/Webkit Mac10.7/results/layout-test-results/userscripts/another-test-actual.png.
-Retrieving http://example.com/f/builders/Webkit Mac10.7/results/layout-test-results/userscripts/another-test-actual.wav.
-Retrieving http://example.com/f/builders/Webkit Mac10.7/results/layout-test-results/userscripts/another-test-actual.txt.
+ expected_logs = """Retrieving http://example.com/f/builders/WebKit Mac10.7/results/layout-test-results/userscripts/another-test-actual.png.
+Retrieving http://example.com/f/builders/WebKit Mac10.7/results/layout-test-results/userscripts/another-test-actual.wav.
+Retrieving http://example.com/f/builders/WebKit Mac10.7/results/layout-test-results/userscripts/another-test-actual.txt.
"""
- OutputCapture().assert_outputs(self, command._rebaseline_test_and_update_expectations, ["Webkit Mac10.7", "userscripts/another-test.html", None], expected_logs=expected_logs)
+ OutputCapture().assert_outputs(self, command._rebaseline_test_and_update_expectations, ["WebKit Mac10.7", "userscripts/another-test.html", None], expected_logs=expected_logs)
new_expectations = tool.filesystem.read_text_file(lion_port.path_to_test_expectations_file())
- self.assertEqual(new_expectations, "Bug(x) [ SnowLeopard ] userscripts/another-test.html [ ImageOnlyFailure ]\nBug(z) [ Linux ] userscripts/another-test.html [ ImageOnlyFailure ]\n")
+ self.assertEqual(new_expectations, "Bug(x) [ MountainLion SnowLeopard ] userscripts/another-test.html [ ImageOnlyFailure ]\nBug(z) [ Linux ] userscripts/another-test.html [ ImageOnlyFailure ]\n")
def test_rebaseline_test(self):
command = RebaselineTest()
command.bind_to_tool(MockTool())
- expected_logs = "Retrieving http://example.com/f/builders/Webkit Linux/results/layout-test-results/userscripts/another-test-actual.txt.\n"
- OutputCapture().assert_outputs(self, command._rebaseline_test, ["Webkit Linux", "userscripts/another-test.html", None, "txt"], expected_logs=expected_logs)
+ expected_logs = "Retrieving http://example.com/f/builders/WebKit Linux/results/layout-test-results/userscripts/another-test-actual.txt.\n"
+ OutputCapture().assert_outputs(self, command._rebaseline_test, ["WebKit Linux", "userscripts/another-test.html", None, "txt"], expected_logs=expected_logs)
def test_rebaseline_test_and_print_scm_changes(self):
command = RebaselineTest()
command.bind_to_tool(MockTool())
- expected_logs = "Retrieving http://example.com/f/builders/Webkit Linux/results/layout-test-results/userscripts/another-test-actual.txt.\n"
+ expected_logs = "Retrieving http://example.com/f/builders/WebKit Linux/results/layout-test-results/userscripts/another-test-actual.txt.\n"
command._print_scm_changes = True
command._scm_changes = {'add': [], 'delete': []}
command._tool._scm.exists = lambda x: False
- OutputCapture().assert_outputs(self, command._rebaseline_test, ["Webkit Linux", "userscripts/another-test.html", None, "txt"], expected_logs=expected_logs)
+ OutputCapture().assert_outputs(self, command._rebaseline_test, ["WebKit Linux", "userscripts/another-test.html", None, "txt"], expected_logs=expected_logs)
self.assertEquals(command._scm_changes, {'add': ['/mock-checkout/LayoutTests/platform/chromium-linux/userscripts/another-test-expected.txt'], 'delete': []})
def test_rebaseline_and_copy_test(self):
@@ -145,13 +141,13 @@ Retrieving http://example.com/f/builders/Webkit Mac10.7/results/layout-test-resu
tool = MockTool()
command.bind_to_tool(tool)
- lion_port = tool.port_factory.get_from_builder_name("Webkit Mac10.7")
+ lion_port = tool.port_factory.get_from_builder_name("WebKit Mac10.7")
tool.filesystem.write_text_file(os.path.join(lion_port.layout_tests_dir(), "userscripts/another-test-expected.txt"), "Dummy expected result")
expected_logs = """Copying baseline from /mock-checkout/LayoutTests/userscripts/another-test-expected.txt to /mock-checkout/LayoutTests/platform/chromium-mac-snowleopard/userscripts/another-test-expected.txt.
-Retrieving http://example.com/f/builders/Webkit Mac10.7/results/layout-test-results/userscripts/another-test-actual.txt.
+Retrieving http://example.com/f/builders/WebKit Mac10.7/results/layout-test-results/userscripts/another-test-actual.txt.
"""
- OutputCapture().assert_outputs(self, command._rebaseline_test, ["Webkit Mac10.7", "userscripts/another-test.html", ["chromium-mac-snowleopard"], "txt"], expected_logs=expected_logs)
+ OutputCapture().assert_outputs(self, command._rebaseline_test, ["WebKit Mac10.7", "userscripts/another-test.html", ["chromium-mac-snowleopard"], "txt"], expected_logs=expected_logs)
def test_rebaseline_and_copy_test_no_existing_result(self):
command = RebaselineTest()
@@ -159,38 +155,38 @@ Retrieving http://example.com/f/builders/Webkit Mac10.7/results/layout-test-resu
command.bind_to_tool(tool)
expected_logs = """No existing baseline for userscripts/another-test.html.
-Retrieving http://example.com/f/builders/Webkit Mac10.7/results/layout-test-results/userscripts/another-test-actual.txt.
+Retrieving http://example.com/f/builders/WebKit Mac10.7/results/layout-test-results/userscripts/another-test-actual.txt.
"""
- OutputCapture().assert_outputs(self, command._rebaseline_test, ["Webkit Mac10.7", "userscripts/another-test.html", ["chromium-mac-snowleopard"], "txt"], expected_logs=expected_logs)
+ OutputCapture().assert_outputs(self, command._rebaseline_test, ["WebKit Mac10.7", "userscripts/another-test.html", ["chromium-mac-snowleopard"], "txt"], expected_logs=expected_logs)
def test_rebaseline_and_copy_test_with_lion_result(self):
command = RebaselineTest()
tool = MockTool()
command.bind_to_tool(tool)
- lion_port = tool.port_factory.get_from_builder_name("Webkit Mac10.7")
+ lion_port = tool.port_factory.get_from_builder_name("WebKit Mac10.7")
tool.filesystem.write_text_file(os.path.join(lion_port.baseline_path(), "userscripts/another-test-expected.txt"), "Dummy expected result")
expected_logs = """Copying baseline from /mock-checkout/LayoutTests/platform/chromium-mac/userscripts/another-test-expected.txt to /mock-checkout/LayoutTests/platform/chromium-mac-snowleopard/userscripts/another-test-expected.txt.
-Retrieving http://example.com/f/builders/Webkit Mac10.7/results/layout-test-results/userscripts/another-test-actual.txt.
+Retrieving http://example.com/f/builders/WebKit Mac10.7/results/layout-test-results/userscripts/another-test-actual.txt.
"""
- OutputCapture().assert_outputs(self, command._rebaseline_test, ["Webkit Mac10.7", "userscripts/another-test.html", ["chromium-mac-snowleopard"], "txt"], expected_logs=expected_logs)
+ OutputCapture().assert_outputs(self, command._rebaseline_test, ["WebKit Mac10.7", "userscripts/another-test.html", ["chromium-mac-snowleopard"], "txt"], expected_logs=expected_logs)
def test_rebaseline_and_copy_no_overwrite_test(self):
command = RebaselineTest()
tool = MockTool()
command.bind_to_tool(tool)
- lion_port = tool.port_factory.get_from_builder_name("Webkit Mac10.7")
+ lion_port = tool.port_factory.get_from_builder_name("WebKit Mac10.7")
tool.filesystem.write_text_file(os.path.join(lion_port.baseline_path(), "userscripts/another-test-expected.txt"), "Dummy expected result")
- snowleopard_port = tool.port_factory.get_from_builder_name("Webkit Mac10.6")
+ snowleopard_port = tool.port_factory.get_from_builder_name("WebKit Mac10.6")
tool.filesystem.write_text_file(os.path.join(snowleopard_port.baseline_path(), "userscripts/another-test-expected.txt"), "Dummy expected result")
expected_logs = """Existing baseline at /mock-checkout/LayoutTests/platform/chromium-mac-snowleopard/userscripts/another-test-expected.txt, not copying over it.
-Retrieving http://example.com/f/builders/Webkit Mac10.7/results/layout-test-results/userscripts/another-test-actual.txt.
+Retrieving http://example.com/f/builders/WebKit Mac10.7/results/layout-test-results/userscripts/another-test-actual.txt.
"""
- OutputCapture().assert_outputs(self, command._rebaseline_test, ["Webkit Mac10.7", "userscripts/another-test.html", ["chromium-mac-snowleopard"], "txt"], expected_logs=expected_logs)
+ OutputCapture().assert_outputs(self, command._rebaseline_test, ["WebKit Mac10.7", "userscripts/another-test.html", ["chromium-mac-snowleopard"], "txt"], expected_logs=expected_logs)
def test_rebaseline_all(self):
old_exact_matches = builders._exact_matches
@@ -228,9 +224,7 @@ MOCK run_command: ['echo', 'optimize-baselines', '--suffixes', 'txt', 'user-scri
tool = MockTool()
command.bind_to_tool(tool)
- # FIXME: work around the chromium skia expectations file to avoid getting a bunch of confusing warnings.
- lion_port = tool.port_factory.get_from_builder_name("Webkit Mac10.7")
- tool.filesystem.write_text_file(lion_port.path_from_chromium_base('skia', 'skia_test_expectations.txt'), '')
+ lion_port = tool.port_factory.get_from_builder_name("WebKit Mac10.7")
for port_name in tool.port_factory.all_port_names():
port = tool.port_factory.get(port_name)
for path in port.expectations_files():
@@ -245,46 +239,9 @@ MOCK run_command: ['echo', 'optimize-baselines', '--suffixes', 'txt', 'user-scri
tool.executive.run_in_parallel = run_in_parallel
- expected_logs = """Retrieving results for chromium-linux-x86 from Webkit Linux 32.
- userscripts/another-test.html (txt)
- userscripts/images.svg (png)
-Retrieving results for chromium-linux-x86_64 from Webkit Linux.
- userscripts/another-test.html (txt)
- userscripts/images.svg (png)
-Retrieving results for chromium-mac-lion from Webkit Mac10.7.
- userscripts/another-test.html (txt)
- userscripts/images.svg (png)
-Retrieving results for chromium-mac-snowleopard from Webkit Mac10.6.
- userscripts/another-test.html (txt)
- userscripts/images.svg (png)
-Retrieving results for chromium-win-win7 from Webkit Win7.
- userscripts/another-test.html (txt)
- userscripts/images.svg (png)
-Retrieving results for chromium-win-xp from Webkit Win.
- userscripts/another-test.html (txt)
- userscripts/images.svg (png)
-Retrieving results for efl from EFL Linux 64-bit Release.
- userscripts/another-test.html (txt)
- userscripts/images.svg (png)
-Retrieving results for gtk from GTK Linux 64-bit Release.
- userscripts/another-test.html (txt)
- userscripts/images.svg (png)
-Retrieving results for mac-lion from Apple Lion Release WK1 (Tests).
- userscripts/another-test.html (txt)
- userscripts/images.svg (png)
-Retrieving results for mac-mountainlion from Apple MountainLion Release WK1 (Tests).
- userscripts/another-test.html (txt)
- userscripts/images.svg (png)
-Retrieving results for qt-linux from Qt Linux Release.
- userscripts/another-test.html (txt)
- userscripts/images.svg (png)
-Retrieving results for win-7sp0 from Apple Win 7 Release (Tests).
- userscripts/another-test.html (txt)
- userscripts/images.svg (png)
-"""
+ expected_logs = "Retrieving results for chromium-linux-x86 from WebKit Linux 32.\n userscripts/another-test.html (txt)\n userscripts/images.svg (png)\nRetrieving results for chromium-linux-x86_64 from WebKit Linux.\n userscripts/another-test.html (txt)\n userscripts/images.svg (png)\nRetrieving results for chromium-mac-lion from WebKit Mac10.7.\n userscripts/another-test.html (txt)\n userscripts/images.svg (png)\nRetrieving results for chromium-mac-snowleopard from WebKit Mac10.6.\n userscripts/another-test.html (txt)\n userscripts/images.svg (png)\nRetrieving results for chromium-win-win7 from WebKit Win7.\n userscripts/another-test.html (txt)\n userscripts/images.svg (png)\nRetrieving results for chromium-win-xp from WebKit XP.\n userscripts/another-test.html (txt)\n userscripts/images.svg (png)\nRetrieving results for efl from EFL Linux 64-bit Release.\n userscripts/another-test.html (txt)\n userscripts/images.svg (png)\nRetrieving results for gtk from GTK Linux 64-bit Release.\n userscripts/another-test.html (txt)\n userscripts/images.svg (png)\nRetrieving results for mac-lion from Apple Lion Release WK1 (Tests).\n userscripts/another-test.html (txt)\n userscripts/images.svg (png)\nRetrieving results for mac-mountainlion from Apple MountainLion Release WK1 (Tests).\n userscripts/another-test.html (txt)\n userscripts/images.svg (png)\nRetrieving results for qt-linux from Qt Linux Release.\n userscripts/another-test.html (txt)\n userscripts/images.svg (png)\nRetrieving results for win-7sp0 from Apple Win 7 Release (Tests).\n userscripts/another-test.html (txt)\n userscripts/images.svg (png)\n"
- expected_stdout = """[(['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'Webkit Linux 32', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'Webkit Linux', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'Webkit Mac10.6', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'Webkit Mac10.7', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'Webkit Win7', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'Apple Win 7 Release (Tests)', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'EFL Linux 64-bit Release', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'Webkit Win', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'GTK Linux 64-bit Release', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'Qt Linux Release', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'Apple Lion Release WK1 (Tests)', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'Apple MountainLion Release WK1 (Tests)', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'Webkit Linux 32', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'Webkit Linux', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'Webkit Mac10.6', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'Webkit Mac10.7', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'Webkit Win7', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'Apple Win 7 Release (Tests)', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'EFL Linux 64-bit Release', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'Webkit Win', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'GTK Linux 64-bit Release', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'Qt Linux Release', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'Apple Lion Release WK1 (Tests)', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'Apple MountainLion Release WK1 (Tests)', '--test', 'userscripts/images.svg'], '/mock-checkout')]
-"""
+ expected_stdout = "[(['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'WebKit Linux 32', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'WebKit Linux', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'WebKit Mac10.6', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'WebKit Mac10.7', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'WebKit Win7', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'Apple Win 7 Release (Tests)', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'EFL Linux 64-bit Release', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'GTK Linux 64-bit Release', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'Qt Linux Release', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'Apple Lion Release WK1 (Tests)', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'WebKit XP', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'txt', '--builder', 'Apple MountainLion Release WK1 (Tests)', '--test', 'userscripts/another-test.html'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'WebKit Linux 32', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'WebKit Linux', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'WebKit Mac10.6', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'WebKit Mac10.7', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'WebKit Win7', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'Apple Win 7 Release (Tests)', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'EFL Linux 64-bit Release', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'GTK Linux 64-bit Release', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'Qt Linux Release', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'Apple Lion Release WK1 (Tests)', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'WebKit XP', '--test', 'userscripts/images.svg'], '/mock-checkout'), (['echo', 'rebaseline-test-internal', '--suffixes', 'png', '--builder', 'Apple MountainLion Release WK1 (Tests)', '--test', 'userscripts/images.svg'], '/mock-checkout')]\n"
expected_stderr = """MOCK run_command: ['qmake', '-v'], cwd=None
MOCK run_command: ['qmake', '-v'], cwd=None
@@ -321,7 +278,6 @@ MOCK run_command: ['qmake', '-v'], cwd=None
command.bind_to_tool(tool)
port = tool.port_factory.get('chromium-mac-lion')
- tool.filesystem.write_text_file(port.path_from_chromium_base('skia', 'skia_test_expectations.txt'), '')
for port_name in tool.port_factory.all_port_names():
port = tool.port_factory.get(port_name)
diff --git a/Tools/Scripts/webkitpy/tool/main.py b/Tools/Scripts/webkitpy/tool/main.py
index d1fde74b8..68348a05a 100755
--- a/Tools/Scripts/webkitpy/tool/main.py
+++ b/Tools/Scripts/webkitpy/tool/main.py
@@ -91,7 +91,7 @@ class WebKitPatch(MultiCommandTool, Host):
# FIXME: This may be unnecessary since we pass global options to all commands during execute() as well.
def handle_global_options(self, options):
- self._initialize_scm(options.patch_directories)
+ self.initialize_scm(options.patch_directories)
if options.status_host:
self.status_server.set_host(options.status_host)
if options.bot_id:
diff --git a/Tools/Scripts/webkitpy/tool/servers/gardeningserver_unittest.py b/Tools/Scripts/webkitpy/tool/servers/gardeningserver_unittest.py
index 22e853491..6c64bdd7e 100644
--- a/Tools/Scripts/webkitpy/tool/servers/gardeningserver_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/servers/gardeningserver_unittest.py
@@ -90,8 +90,8 @@ class BuildCoverageExtrapolatorTest(unittest.TestCase):
port = host.port_factory.get('chromium-win-win7', None)
converter = TestConfigurationConverter(port.all_test_configurations(), port.configuration_specifier_macros())
extrapolator = BuildCoverageExtrapolator(converter)
- self.assertEquals(extrapolator.extrapolate_test_configurations("Webkit Win"), set([TestConfiguration(version='xp', architecture='x86', build_type='release')]))
- self.assertEquals(extrapolator.extrapolate_test_configurations("Webkit Win7"), set([
+ self.assertEquals(extrapolator.extrapolate_test_configurations("WebKit XP"), set([TestConfiguration(version='xp', architecture='x86', build_type='release')]))
+ self.assertEquals(extrapolator.extrapolate_test_configurations("WebKit Win7"), set([
TestConfiguration(version='win7', architecture='x86', build_type='debug'),
TestConfiguration(version='win7', architecture='x86', build_type='release')]))
self.assertRaises(KeyError, extrapolator.extrapolate_test_configurations, "Potato")
diff --git a/Tools/Scripts/webkitpy/tool/servers/rebaselineserver.py b/Tools/Scripts/webkitpy/tool/servers/rebaselineserver.py
index 0e7458727..9e9c379d6 100644
--- a/Tools/Scripts/webkitpy/tool/servers/rebaselineserver.py
+++ b/Tools/Scripts/webkitpy/tool/servers/rebaselineserver.py
@@ -178,7 +178,7 @@ def get_test_baselines(test_file, test_config):
# FIXME: This should get the Host from the test_config to be mockable!
host = Host()
- host._initialize_scm()
+ host.initialize_scm()
host.filesystem = test_config.filesystem
all_platforms_port = AllPlatformsPort(host)
diff --git a/Tools/Scripts/webkitpy/tool/steps/createbug.py b/Tools/Scripts/webkitpy/tool/steps/createbug.py
index 4605892e7..7e4a83504 100644
--- a/Tools/Scripts/webkitpy/tool/steps/createbug.py
+++ b/Tools/Scripts/webkitpy/tool/steps/createbug.py
@@ -53,4 +53,4 @@ class CreateBug(AbstractStep):
if blocks:
status = self._tool.bugs.fetch_bug(blocks).status()
if status == 'RESOLVED':
- self._tool.bugs.reopen_bug(blocks, "Re-opened since this is blocked by %s" % state["bug_id"])
+ self._tool.bugs.reopen_bug(blocks, "Re-opened since this is blocked by bug %s" % state["bug_id"])