summaryrefslogtreecommitdiff
path: root/Tools/Scripts/webkitpy
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/Scripts/webkitpy')
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/baselineoptimizer_unittest.py20
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/scm/git.py90
-rw-r--r--Tools/Scripts/webkitpy/common/checkout/scm/svn.py7
-rw-r--r--Tools/Scripts/webkitpy/common/config/committers.py12
-rw-r--r--Tools/Scripts/webkitpy/common/host.py20
-rw-r--r--Tools/Scripts/webkitpy/common/net/bugzilla/bugzilla.py17
-rw-r--r--Tools/Scripts/webkitpy/common/net/bugzilla/bugzilla_mock.py2
-rw-r--r--Tools/Scripts/webkitpy/common/net/bugzilla/bugzilla_unittest.py38
-rw-r--r--Tools/Scripts/webkitpy/common/system/executive.py8
-rw-r--r--Tools/Scripts/webkitpy/common/system/executive_unittest.py5
-rw-r--r--Tools/Scripts/webkitpy/common/system/outputcapture.py16
-rw-r--r--Tools/Scripts/webkitpy/common/system/path.py14
-rw-r--r--Tools/Scripts/webkitpy/common/system/path_unittest.py77
-rw-r--r--Tools/Scripts/webkitpy/common/system/platforminfo.py26
-rw-r--r--Tools/Scripts/webkitpy/common/system/platforminfo_mock.py8
-rw-r--r--Tools/Scripts/webkitpy/common/system/platforminfo_unittest.py18
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/controllers/manager.py91
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/controllers/manager_unittest.py58
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/controllers/manager_worker_broker_unittest.py4
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/controllers/single_test_runner.py72
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/controllers/test_expectations_editor_unittest.py10
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/controllers/worker.py56
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py1
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py163
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/models/test_expectations_unittest.py104
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/apple.py4
-rwxr-xr-xTools/Scripts/webkitpy/layout_tests/port/base.py123
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py34
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/builders.py15
-rwxr-xr-xTools/Scripts/webkitpy/layout_tests/port/chromium.py78
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium_android.py31
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py17
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium_linux_unittest.py24
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py32
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium_mac_unittest.py25
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py73
-rwxr-xr-xTools/Scripts/webkitpy/layout_tests/port/chromium_win.py10
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium_win_unittest.py40
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/config.py2
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/config_unittest.py8
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/driver.py7
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/driver_unittest.py11
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/factory.py14
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/factory_unittest.py4
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/google_chrome.py33
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/google_chrome_unittest.py19
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/mac.py31
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/mac_unittest.py39
-rwxr-xr-xTools/Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py13
-rwxr-xr-xTools/Scripts/webkitpy/layout_tests/port/port_testcase.py24
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/qt.py3
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/server_process_unittest.py8
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/test.py16
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/webkit.py27
-rwxr-xr-xTools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py27
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/win.py5
-rwxr-xr-xTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py30
-rwxr-xr-xTools/Scripts/webkitpy/layout_tests/run_webkit_tests_integrationtest.py75
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/servers/http_server_unittest.py5
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/servers/websocket_server.py15
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/views/metered_stream.py14
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/views/printing.py6
-rw-r--r--Tools/Scripts/webkitpy/performance_tests/perftest.py9
-rw-r--r--Tools/Scripts/webkitpy/style/checker.py15
-rw-r--r--Tools/Scripts/webkitpy/style/checkers/cpp.py2
-rw-r--r--Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py37
-rw-r--r--Tools/Scripts/webkitpy/style/checkers/test_expectations.py46
-rw-r--r--Tools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py12
-rw-r--r--Tools/Scripts/webkitpy/test/main.py39
-rw-r--r--Tools/Scripts/webkitpy/test/main_unittest.py54
-rw-r--r--Tools/Scripts/webkitpy/test/test_finder.py3
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/__init__.py11
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/COPYING2
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hybi.py153
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/common.py125
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/extensions.py93
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/_base.py106
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/hybi.py14
-rwxr-xr-xTools/Scripts/webkitpy/thirdparty/mod_pywebsocket/standalone.py60
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/util.py11
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/ordered_dict.py89
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/queries.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/rebaseline.py48
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py124
-rw-r--r--Tools/Scripts/webkitpy/tool/mocktool.py8
-rw-r--r--Tools/Scripts/webkitpy/tool/servers/gardeningserver.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/commit.py6
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/commit_unittest.py14
88 files changed, 1849 insertions, 1115 deletions
diff --git a/Tools/Scripts/webkitpy/common/checkout/baselineoptimizer_unittest.py b/Tools/Scripts/webkitpy/common/checkout/baselineoptimizer_unittest.py
index 82a637f08..b70e5d06f 100644
--- a/Tools/Scripts/webkitpy/common/checkout/baselineoptimizer_unittest.py
+++ b/Tools/Scripts/webkitpy/common/checkout/baselineoptimizer_unittest.py
@@ -78,13 +78,13 @@ class BaselineOptimizerTest(unittest.TestCase):
def test_no_add_mac_future(self):
self._assertOptimization({
'LayoutTests/platform/mac': '29a1715a6470d5dd9486a142f609708de84cdac8',
- 'LayoutTests/platform/win': '453e67177a75b2e79905154ece0efba6e5bfb65d',
- 'LayoutTests/platform/mac-snowleopard': 'c43eaeb358f49d5e835236ae23b7e49d7f2b089f',
+ 'LayoutTests/platform/win-xp': '453e67177a75b2e79905154ece0efba6e5bfb65d',
+ 'LayoutTests/platform/mac-lion': 'c43eaeb358f49d5e835236ae23b7e49d7f2b089f',
'LayoutTests/platform/chromium-mac': 'a9ba153c700a94ae1b206d8e4a75a621a89b4554',
}, {
'LayoutTests/platform/mac': '29a1715a6470d5dd9486a142f609708de84cdac8',
- 'LayoutTests/platform/win': '453e67177a75b2e79905154ece0efba6e5bfb65d',
- 'LayoutTests/platform/mac-snowleopard': 'c43eaeb358f49d5e835236ae23b7e49d7f2b089f',
+ 'LayoutTests/platform/win-xp': '453e67177a75b2e79905154ece0efba6e5bfb65d',
+ 'LayoutTests/platform/mac-lion': 'c43eaeb358f49d5e835236ae23b7e49d7f2b089f',
'LayoutTests/platform/chromium-mac': 'a9ba153c700a94ae1b206d8e4a75a621a89b4554',
})
@@ -107,9 +107,9 @@ class BaselineOptimizerTest(unittest.TestCase):
def test_mac_future(self):
self._assertOptimization({
- 'LayoutTests/platform/mac-snowleopard': '462d03b9c025db1b0392d7453310dbee5f9a9e74',
+ 'LayoutTests/platform/mac-lion': '462d03b9c025db1b0392d7453310dbee5f9a9e74',
}, {
- 'LayoutTests/platform/mac-snowleopard': '462d03b9c025db1b0392d7453310dbee5f9a9e74',
+ 'LayoutTests/platform/mac-lion': '462d03b9c025db1b0392d7453310dbee5f9a9e74',
})
def test_qt_unknown(self):
@@ -154,17 +154,17 @@ class BaselineOptimizerTest(unittest.TestCase):
'LayoutTests/platform/mac': '5daa78e55f05d9f0d1bb1f32b0cd1bc3a01e9364',
'LayoutTests/platform/chromium-win-xp': '462d03b9c025db1b0392d7453310dbee5f9a9e74',
'LayoutTests/platform/chromium-mac-leopard': '65e7d42f8b4882b29d46dc77bb879dd41bc074dc',
- 'LayoutTests/platform/mac-leopard': '7ad045ece7c030e2283c5d21d9587be22bcba56e',
+ 'LayoutTests/platform/mac-lion': '7ad045ece7c030e2283c5d21d9587be22bcba56e',
'LayoutTests/platform/chromium-win-vista': 'f83af9732ce74f702b8c9c4a3d9a4c6636b8d3bd',
- 'LayoutTests/platform/win': '5b1253ef4d5094530d5f1bc6cdb95c90b446bec7',
+ 'LayoutTests/platform/win-xp': '5b1253ef4d5094530d5f1bc6cdb95c90b446bec7',
'LayoutTests/platform/chromium-linux': 'f52fcdde9e4be8bd5142171cd859230bd4471036',
}, {
'LayoutTests/platform/chromium-win': '462d03b9c025db1b0392d7453310dbee5f9a9e74',
'LayoutTests/platform/mac': '5daa78e55f05d9f0d1bb1f32b0cd1bc3a01e9364',
'LayoutTests/platform/chromium-win-xp': '462d03b9c025db1b0392d7453310dbee5f9a9e74',
'LayoutTests/platform/chromium-mac-leopard': '65e7d42f8b4882b29d46dc77bb879dd41bc074dc',
- 'LayoutTests/platform/mac-leopard': '7ad045ece7c030e2283c5d21d9587be22bcba56e',
+ 'LayoutTests/platform/mac-lion': '7ad045ece7c030e2283c5d21d9587be22bcba56e',
'LayoutTests/platform/chromium-win-vista': 'f83af9732ce74f702b8c9c4a3d9a4c6636b8d3bd',
- 'LayoutTests/platform/win': '5b1253ef4d5094530d5f1bc6cdb95c90b446bec7',
+ 'LayoutTests/platform/win-xp': '5b1253ef4d5094530d5f1bc6cdb95c90b446bec7',
'LayoutTests/platform/chromium-linux': 'f52fcdde9e4be8bd5142171cd859230bd4471036'
})
diff --git a/Tools/Scripts/webkitpy/common/checkout/scm/git.py b/Tools/Scripts/webkitpy/common/checkout/scm/git.py
index 43fdb9c00..802d81db0 100644
--- a/Tools/Scripts/webkitpy/common/checkout/scm/git.py
+++ b/Tools/Scripts/webkitpy/common/checkout/scm/git.py
@@ -61,6 +61,8 @@ class Git(SCM, SVNRepository):
# 1 or 128, mostly.
ERROR_FILE_IS_MISSING = 128
+ executable_name = 'git'
+
def __init__(self, cwd, **kwargs):
SCM.__init__(self, cwd, **kwargs)
self._check_git_architecture()
@@ -89,7 +91,7 @@ class Git(SCM, SVNRepository):
# We could path-search entirely in python or with
# which.py (http://code.google.com/p/which), but this is easier:
- git_path = self.run(['which', 'git']).rstrip()
+ git_path = self.run(['which', self.executable_name]).rstrip()
if self._executable_is_64bit(git_path):
return
@@ -100,14 +102,14 @@ class Git(SCM, SVNRepository):
def in_working_directory(cls, path):
try:
# FIXME: This should use an Executive.
- return run_command(['git', 'rev-parse', '--is-inside-work-tree'], cwd=path, error_handler=Executive.ignore_error).rstrip() == "true"
+ return run_command([cls.executable_name, 'rev-parse', '--is-inside-work-tree'], cwd=path, error_handler=Executive.ignore_error).rstrip() == "true"
except OSError, e:
# The Windows bots seem to through a WindowsError when git isn't installed.
return False
def find_checkout_root(self, path):
# "git rev-parse --show-cdup" would be another way to get to the root
- git_output = self._executive.run_command(['git', 'rev-parse', '--git-dir'], cwd=(path or "./"))
+ git_output = self._executive.run_command([self.executable_name, 'rev-parse', '--git-dir'], cwd=(path or "./"))
(checkout_root, dot_git) = self._filesystem.split(git_output)
if not self._filesystem.isabs(checkout_root): # Sometimes git returns relative paths
checkout_root = self._filesystem.join(path, checkout_root)
@@ -125,7 +127,7 @@ class Git(SCM, SVNRepository):
# Pass --get-all for cases where the config has multiple values
# Pass the cwd if provided so that we can handle the case of running webkit-patch outside of the working directory.
# FIXME: This should use an Executive.
- return run_command(["git", "config", "--get-all", key], error_handler=Executive.ignore_error, cwd=cwd).rstrip('\n')
+ return run_command([cls.executable_name, "config", "--get-all", key], error_handler=Executive.ignore_error, cwd=cwd).rstrip('\n')
@staticmethod
def commit_success_regexp():
@@ -133,48 +135,48 @@ class Git(SCM, SVNRepository):
def discard_local_commits(self):
# FIXME: This should probably use cwd=self.checkout_root
- self.run(['git', 'reset', '--hard', self.remote_branch_ref()])
+ self.run([self.executable_name, 'reset', '--hard', self.remote_branch_ref()])
def local_commits(self):
- return self.run(['git', 'log', '--pretty=oneline', 'HEAD...' + self.remote_branch_ref()], cwd=self.checkout_root).splitlines()
+ return self.run([self.executable_name, 'log', '--pretty=oneline', 'HEAD...' + self.remote_branch_ref()], cwd=self.checkout_root).splitlines()
def rebase_in_progress(self):
return self._filesystem.exists(self.absolute_path(self._filesystem.join('.git', 'rebase-apply')))
def working_directory_is_clean(self):
- return self.run(['git', 'diff', 'HEAD', '--no-renames', '--name-only'], cwd=self.checkout_root) == ""
+ return self.run([self.executable_name, 'diff', 'HEAD', '--no-renames', '--name-only'], cwd=self.checkout_root) == ""
def clean_working_directory(self):
# FIXME: These should probably use cwd=self.checkout_root.
# Could run git clean here too, but that wouldn't match working_directory_is_clean
- self.run(['git', 'reset', '--hard', 'HEAD'])
+ self.run([self.executable_name, 'reset', '--hard', 'HEAD'])
# Aborting rebase even though this does not match working_directory_is_clean
if self.rebase_in_progress():
- self.run(['git', 'rebase', '--abort'])
+ self.run([self.executable_name, 'rebase', '--abort'])
def status_command(self):
# git status returns non-zero when there are changes, so we use git diff name --name-status HEAD instead.
# No file contents printed, thus utf-8 autodecoding in self.run is fine.
- return ["git", "diff", "--name-status", "--no-renames", "HEAD"]
+ return [self.executable_name, "diff", "--name-status", "--no-renames", "HEAD"]
def _status_regexp(self, expected_types):
return '^(?P<status>[%s])\t(?P<filename>.+)$' % expected_types
def add_list(self, paths, return_exit_code=False):
- return self.run(["git", "add"] + paths, return_exit_code=return_exit_code)
+ return self.run([self.executable_name, "add"] + paths, return_exit_code=return_exit_code)
def delete_list(self, paths):
- return self.run(["git", "rm", "-f"] + paths)
+ return self.run([self.executable_name, "rm", "-f"] + paths)
def exists(self, path):
- return_code = self.run(["git", "show", "HEAD:%s" % path], return_exit_code=True, decode_output=False)
+ return_code = self.run([self.executable_name, "show", "HEAD:%s" % path], return_exit_code=True, decode_output=False)
return return_code != self.ERROR_FILE_IS_MISSING
def _branch_from_ref(self, ref):
return ref.replace('refs/heads/', '')
def _current_branch(self):
- return self._branch_from_ref(self.run(['git', 'symbolic-ref', '-q', 'HEAD'], cwd=self.checkout_root).strip())
+ return self._branch_from_ref(self.run([self.executable_name, 'symbolic-ref', '-q', 'HEAD'], cwd=self.checkout_root).strip())
def _upstream_branch(self):
current_branch = self._current_branch()
@@ -201,14 +203,14 @@ class Git(SCM, SVNRepository):
def changed_files(self, git_commit=None):
# FIXME: --diff-filter could be used to avoid the "extract_filenames" step.
- status_command = ['git', 'diff', '-r', '--name-status', "--no-renames", "--no-ext-diff", "--full-index", self.merge_base(git_commit)]
+ status_command = [self.executable_name, 'diff', '-r', '--name-status', "--no-renames", "--no-ext-diff", "--full-index", self.merge_base(git_commit)]
# FIXME: I'm not sure we're returning the same set of files that SVN.changed_files is.
# Added (A), Copied (C), Deleted (D), Modified (M), Renamed (R)
return self.run_status_and_extract_filenames(status_command, self._status_regexp("ADM"))
def _changes_files_for_commit(self, git_commit):
# --pretty="format:" makes git show not print the commit log header,
- changed_files = self.run(["git", "show", "--pretty=format:", "--name-only", git_commit]).splitlines()
+ changed_files = self.run([self.executable_name, "show", "--pretty=format:", "--name-only", git_commit]).splitlines()
# instead it just prints a blank line at the top, so we skip the blank line:
return changed_files[1:]
@@ -222,13 +224,13 @@ class Git(SCM, SVNRepository):
raise ScriptError(message="Path %s does not exist." % path)
# git rev-list head --remove-empty --limit=5 -- path would be equivalent.
- commit_ids = self.run(["git", "log", "--remove-empty", "--pretty=format:%H", "-%s" % limit, "--", path]).splitlines()
+ commit_ids = self.run([self.executable_name, "log", "--remove-empty", "--pretty=format:%H", "-%s" % limit, "--", path]).splitlines()
return filter(lambda revision: revision, map(self.svn_revision_from_git_commit, commit_ids))
def conflicted_files(self):
# We do not need to pass decode_output for this diff command
# as we're passing --name-status which does not output any data.
- status_command = ['git', 'diff', '--name-status', '--no-renames', '--diff-filter=U']
+ status_command = [self.executable_name, 'diff', '--name-status', '--no-renames', '--diff-filter=U']
return self.run_status_and_extract_filenames(status_command, self._status_regexp("U"))
def added_files(self):
@@ -246,7 +248,7 @@ class Git(SCM, SVNRepository):
def svn_revision(self, path):
_log.debug('Running git.head_svn_revision... (Temporary logging message)')
- git_log = self.run(['git', 'log', '-25', path])
+ git_log = self.run([self.executable_name, 'log', '-25', path])
match = re.search("^\s*git-svn-id:.*@(?P<svn_revision>\d+)\ ", git_log, re.MULTILINE)
if not match:
return ""
@@ -272,14 +274,14 @@ class Git(SCM, SVNRepository):
if self._filesystem.exists(order_file):
order = "-O%s" % order_file
- command = ['git', 'diff', '--binary', "--no-ext-diff", "--full-index", "--no-renames", order, self.merge_base(git_commit), "--"]
+ command = [self.executable_name, 'diff', '--binary', "--no-ext-diff", "--full-index", "--no-renames", order, self.merge_base(git_commit), "--"]
if changed_files:
command += changed_files
return self.prepend_svn_revision(self.run(command, decode_output=False, cwd=self.checkout_root))
def _run_git_svn_find_rev(self, arg):
# git svn find-rev always exits 0, even when the revision or commit is not found.
- return self.run(['git', 'svn', 'find-rev', arg], cwd=self.checkout_root).rstrip()
+ return self.run([self.executable_name, 'svn', 'find-rev', arg], cwd=self.checkout_root).rstrip()
def _string_to_int_or_none(self, string):
try:
@@ -303,21 +305,21 @@ class Git(SCM, SVNRepository):
def contents_at_revision(self, path, revision):
"""Returns a byte array (str()) containing the contents
of path @ revision in the repository."""
- return self.run(["git", "show", "%s:%s" % (self.git_commit_from_svn_revision(revision), path)], decode_output=False)
+ return self.run([self.executable_name, "show", "%s:%s" % (self.git_commit_from_svn_revision(revision), path)], decode_output=False)
def diff_for_revision(self, revision):
git_commit = self.git_commit_from_svn_revision(revision)
return self.create_patch(git_commit)
def diff_for_file(self, path, log=None):
- return self.run(['git', 'diff', 'HEAD', '--no-renames', '--', path], cwd=self.checkout_root)
+ return self.run([self.executable_name, 'diff', 'HEAD', '--no-renames', '--', path], cwd=self.checkout_root)
def show_head(self, path):
- return self.run(['git', 'show', 'HEAD:' + self.to_object_name(path)], decode_output=False)
+ return self.run([self.executable_name, 'show', 'HEAD:' + self.to_object_name(path)], decode_output=False)
def committer_email_for_revision(self, revision):
git_commit = self.git_commit_from_svn_revision(revision)
- committer_email = self.run(["git", "log", "-1", "--pretty=format:%ce", git_commit])
+ committer_email = self.run([self.executable_name, "log", "-1", "--pretty=format:%ce", git_commit])
# Git adds an extra @repository_hash to the end of every committer email, remove it:
return committer_email.rsplit("@", 1)[0]
@@ -325,10 +327,10 @@ class Git(SCM, SVNRepository):
# Assume the revision is an svn revision.
git_commit = self.git_commit_from_svn_revision(revision)
# I think this will always fail due to ChangeLogs.
- self.run(['git', 'revert', '--no-commit', git_commit], error_handler=Executive.ignore_error)
+ self.run([self.executable_name, 'revert', '--no-commit', git_commit], error_handler=Executive.ignore_error)
def revert_files(self, file_paths):
- self.run(['git', 'checkout', 'HEAD'] + file_paths)
+ self.run([self.executable_name, 'checkout', 'HEAD'] + file_paths)
def _assert_can_squash(self, working_directory_is_clean):
squash = Git.read_git_config('webkit-patch.commit-should-always-squash', cwd=self.checkout_root)
@@ -362,7 +364,7 @@ class Git(SCM, SVNRepository):
if not force_squash:
self._assert_can_squash(working_directory_is_clean)
- self.run(['git', 'reset', '--soft', self.remote_merge_base()], cwd=self.checkout_root)
+ self.run([self.executable_name, 'reset', '--soft', self.remote_merge_base()], cwd=self.checkout_root)
self.commit_locally_with_message(message)
return self.push_local_commits_to_server(username=username, password=password)
@@ -385,16 +387,16 @@ class Git(SCM, SVNRepository):
# We wrap in a try...finally block so if anything goes wrong, we clean up the branches.
commit_succeeded = True
try:
- self.run(['git', 'checkout', '-q', '-b', MERGE_BRANCH_NAME, self.remote_branch_ref()])
+ self.run([self.executable_name, 'checkout', '-q', '-b', MERGE_BRANCH_NAME, self.remote_branch_ref()])
for commit in commit_ids:
# We're on a different branch now, so convert "head" to the branch name.
commit = re.sub(r'(?i)head', branch_name, commit)
# FIXME: Once changed_files and create_patch are modified to separately handle each
# commit in a commit range, commit each cherry pick so they'll get dcommitted separately.
- self.run(['git', 'cherry-pick', '--no-commit', commit])
+ self.run([self.executable_name, 'cherry-pick', '--no-commit', commit])
- self.run(['git', 'commit', '-m', message])
+ self.run([self.executable_name, 'commit', '-m', message])
output = self.push_local_commits_to_server(username=username, password=password)
except Exception, e:
log("COMMIT FAILED: " + str(e))
@@ -403,31 +405,31 @@ class Git(SCM, SVNRepository):
finally:
# And then swap back to the original branch and clean up.
self.clean_working_directory()
- self.run(['git', 'checkout', '-q', branch_name])
+ self.run([self.executable_name, 'checkout', '-q', branch_name])
self.delete_branch(MERGE_BRANCH_NAME)
return output
def svn_commit_log(self, svn_revision):
svn_revision = self.strip_r_from_svn_revision(svn_revision)
- return self.run(['git', 'svn', 'log', '-r', svn_revision])
+ return self.run([self.executable_name, 'svn', 'log', '-r', svn_revision])
def last_svn_commit_log(self):
- return self.run(['git', 'svn', 'log', '--limit=1'])
+ return self.run([self.executable_name, 'svn', 'log', '--limit=1'])
def svn_blame(self, path):
- return self.run(['git', 'svn', 'blame', path])
+ return self.run([self.executable_name, 'svn', 'blame', path])
# Git-specific methods:
def _branch_ref_exists(self, branch_ref):
- return self.run(['git', 'show-ref', '--quiet', '--verify', branch_ref], return_exit_code=True) == 0
+ return self.run([self.executable_name, 'show-ref', '--quiet', '--verify', branch_ref], return_exit_code=True) == 0
def delete_branch(self, branch_name):
if self._branch_ref_exists('refs/heads/' + branch_name):
- self.run(['git', 'branch', '-D', branch_name])
+ self.run([self.executable_name, 'branch', '-D', branch_name])
def remote_merge_base(self):
- return self.run(['git', 'merge-base', self.remote_branch_ref(), 'HEAD'], cwd=self.checkout_root).strip()
+ return self.run([self.executable_name, 'merge-base', self.remote_branch_ref(), 'HEAD'], cwd=self.checkout_root).strip()
def remote_branch_ref(self):
# Use references so that we can avoid collisions, e.g. we don't want to operate on refs/heads/trunk if it exists.
@@ -444,10 +446,10 @@ class Git(SCM, SVNRepository):
return first_remote_branch_ref.split(':')[1]
def commit_locally_with_message(self, message):
- self.run(['git', 'commit', '--all', '-F', '-'], input=message, cwd=self.checkout_root)
+ self.run([self.executable_name, 'commit', '--all', '-F', '-'], input=message, cwd=self.checkout_root)
def push_local_commits_to_server(self, username=None, password=None):
- dcommit_command = ['git', 'svn', 'dcommit']
+ dcommit_command = [self.executable_name, 'svn', 'dcommit']
if (not username or not password) and not self.has_authorization_for_realm(SVN.svn_server_realm):
raise AuthenticationError(SVN.svn_server_host, prompt_for_password=True)
if username:
@@ -469,14 +471,14 @@ class Git(SCM, SVNRepository):
if '...' in commitish:
raise ScriptError(message="'...' is not supported (found in '%s'). Did you mean '..'?" % commitish)
elif '..' in commitish:
- commit_ids += reversed(self.run(['git', 'rev-list', commitish]).splitlines())
+ commit_ids += reversed(self.run([self.executable_name, 'rev-list', commitish]).splitlines())
else:
# Turn single commits or branch or tag names into commit ids.
- commit_ids += self.run(['git', 'rev-parse', '--revs-only', commitish]).splitlines()
+ commit_ids += self.run([self.executable_name, 'rev-parse', '--revs-only', commitish]).splitlines()
return commit_ids
def commit_message_for_local_commit(self, commit_id):
- commit_lines = self.run(['git', 'cat-file', 'commit', commit_id]).splitlines()
+ commit_lines = self.run([self.executable_name, 'cat-file', 'commit', commit_id]).splitlines()
# Skip the git headers.
first_line_after_headers = 0
@@ -487,4 +489,4 @@ class Git(SCM, SVNRepository):
return CommitMessage(commit_lines[first_line_after_headers:])
def files_changed_summary_for_commit(self, commit_id):
- return self.run(['git', 'diff-tree', '--shortstat', '--no-renames', '--no-commit-id', commit_id])
+ return self.run([self.executable_name, 'diff-tree', '--shortstat', '--no-renames', '--no-commit-id', commit_id])
diff --git a/Tools/Scripts/webkitpy/common/checkout/scm/svn.py b/Tools/Scripts/webkitpy/common/checkout/scm/svn.py
index 3c269175c..a22063f0e 100644
--- a/Tools/Scripts/webkitpy/common/checkout/scm/svn.py
+++ b/Tools/Scripts/webkitpy/common/checkout/scm/svn.py
@@ -172,10 +172,6 @@ class SVN(SCM, SVNRepository):
"""Does 'svn add' to the path and its parents."""
if self.in_working_directory(path):
return
- dirname = os.path.dirname(path)
- # We have dirname directry - ensure it added.
- if dirname != path:
- self._add_parent_directories(dirname)
self.add(path)
def add_list(self, paths, return_exit_code=False):
@@ -189,9 +185,6 @@ class SVN(SCM, SVNRepository):
if set(os.listdir(path)) - self._svn_metadata_files:
return # Directory has non-trivial files in it.
self.delete(path)
- dirname = os.path.dirname(path)
- if dirname != path:
- self._delete_parent_directories(dirname)
def delete_list(self, paths):
for path in paths:
diff --git a/Tools/Scripts/webkitpy/common/config/committers.py b/Tools/Scripts/webkitpy/common/config/committers.py
index 673406c33..835b048bc 100644
--- a/Tools/Scripts/webkitpy/common/config/committers.py
+++ b/Tools/Scripts/webkitpy/common/config/committers.py
@@ -160,6 +160,7 @@ contributors_who_are_not_committers = [
Contributor("Tom Hudson", "tomhudson@google.com"),
Contributor("WebKit Review Bot", "webkit.review.bot@gmail.com", "sheriff-bot"),
Contributor("Wyatt Carss", ["wcarss@chromium.org", "wcarss@google.com"], "wcarss"),
+ Contributor("Zeev Lieber", "zlieber@chromium.org"),
Contributor("Zoltan Arvai", "zarvai@inf.u-szeged.hu", "azbest_hu"),
Contributor("Zsolt Feher", "feherzs@inf.u-szeged.hu", "Smith"),
]
@@ -205,6 +206,7 @@ committers_unable_to_review = [
Committer("Benjamin C Meyer", ["ben@meyerhome.net", "ben@webkit.org", "bmeyer@rim.com"], "icefox"),
Committer("Benjamin Kalman", ["kalman@chromium.org", "kalman@google.com"], "kalman"),
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"),
@@ -252,7 +254,7 @@ committers_unable_to_review = [
Committer("Hans Wennborg", "hans@chromium.org", "hwennborg"),
Committer("Hayato Ito", "hayato@chromium.org", "hayato"),
Committer("Hironori Bono", "hbono@chromium.org", "hbono"),
- Committer("Helder Correia", "helder@sencha.com", "helder"),
+ Committer("Helder Correia", "helder.correia@nokia.com", "helder"),
Committer("Hin-Chung Lam", ["hclam@google.com", "hclam@chromium.org"]),
Committer("Igor Trindade Oliveira", ["igor.oliveira@webkit.org", "igor.o@sisa.samsung.com"], "igoroliveira"),
Committer("Ilya Sherman", "isherman@chromium.org", "isherman"),
@@ -280,7 +282,7 @@ committers_unable_to_review = [
Committer("John Knottenbelt", "jknotten@chromium.org", "jknotten"),
Committer("Johnny Ding", ["jnd@chromium.org", "johnnyding.webkit@gmail.com"], "johnnyding"),
Committer("Jon Lee", "jonlee@apple.com", "jonlee"),
- Committer("Joone Hur", "joone@webkit.org", "joone"),
+ Committer("Joone Hur", ["joone@webkit.org", "joone.hur@intel.com"], "joone"),
Committer("Joost de Valk", ["joost@webkit.org", "webkit-dev@joostdevalk.nl"], "Altha"),
Committer("Joshua Bell", ["jsbell@chromium.org", "jsbell@google.com"], "jsbell"),
Committer("Julie Parent", ["jparent@google.com", "jparent@chromium.org"], "jparent"),
@@ -296,6 +298,7 @@ committers_unable_to_review = [
Committer(u"Kim Gr\u00f6nholm", "kim.1.gronholm@nokia.com"),
Committer("Kimmo Kinnunen", ["kimmo.t.kinnunen@nokia.com", "kimmok@iki.fi", "ktkinnun@webkit.org"], "kimmok"),
Committer("Kinuko Yasuda", "kinuko@chromium.org", "kinuko"),
+ Committer("Konrad Piascik", "kpiascik@rim.com", "kpiascik"),
Committer("Kristof Kosztyo", "kkristof@inf.u-szeged.hu", "kkristof"),
Committer("Krzysztof Kowalczyk", "kkowalczyk@gmail.com"),
Committer("Kwang Yul Seo", ["kwangyul.seo@gmail.com", "skyul@company100.net", "kseo@webkit.org"], "kwangseo"),
@@ -341,7 +344,7 @@ committers_unable_to_review = [
Committer("Pierre-Olivier Latour", "pol@apple.com", "pol"),
Committer("Pierre Rossi", "pierre.rossi@gmail.com", "elproxy"),
Committer("Pratik Solanki", "psolanki@apple.com", "psolanki"),
- Committer("Qi Zhang", ["qi.2.zhang@nokia.com", "qi.zhang02180@gmail.com"], "qi"),
+ Committer("Qi Zhang", "qi.zhang02180@gmail.com", "qi"),
Committer("Rafael Antognolli", "antognolli@profusion.mobi", "antognolli"),
Committer("Rafael Brandao", "rafael.lobo@openbossa.org", "rafaelbrandao"),
Committer("Rafael Weinstein", "rafaelw@chromium.org", "rafaelw"),
@@ -349,6 +352,7 @@ committers_unable_to_review = [
Committer("Ravi Kasibhatla", "ravi.kasibhatla@motorola.com", "kphanee"),
Committer("Renata Hodovan", "reni@webkit.org", "reni"),
Committer("Robert Hogan", ["robert@webkit.org", "robert@roberthogan.net", "lists@roberthogan.net"], "mwenge"),
+ Committer("Robert Kroeger", "rjkroege@chromium.org", "rjkroege"),
Committer("Roland Steiner", "rolandsteiner@chromium.org"),
Committer("Ryuan Choi", "ryuan.choi@samsung.com", "ryuan"),
Committer("Satish Sampath", "satish@chromium.org"),
@@ -379,7 +383,7 @@ committers_unable_to_review = [
Committer("Yael Aharon", "yael.aharon@nokia.com", "yael"),
Committer("Yaar Schnitman", ["yaar@chromium.org", "yaar@google.com"]),
Committer("Yi Shen", ["yi.4.shen@nokia.com", "shenyi2006@gmail.com"]),
- Committer("Yong Li", ["yong.li.webkit@gmail.com", "yong.li@torchmobile.com", "yoli@rim.com"], "yong"),
+ Committer("Yong Li", ["yoli@rim.com", "yong.li.webkit@gmail.com"], "yoli"),
Committer("Yongjun Zhang", ["yongjun.zhang@nokia.com", "yongjun_zhang@apple.com"]),
Committer("Yoshifumi Inoue", "yosin@chromium.org", "yosin"),
Committer("Yuqiang Xian", "yuqiang.xian@intel.com"),
diff --git a/Tools/Scripts/webkitpy/common/host.py b/Tools/Scripts/webkitpy/common/host.py
index ffc13bf98..083120227 100644
--- a/Tools/Scripts/webkitpy/common/host.py
+++ b/Tools/Scripts/webkitpy/common/host.py
@@ -104,6 +104,26 @@ class Host(SystemHost):
SVN.executable_name = 'svn.bat'
except OSError, e:
_log.debug('Failed to engage svn.bat Windows hack.')
+ try:
+ self.executive.run_command(['git', 'help'])
+ except OSError, e:
+ try:
+ self.executive.run_command(['git.bat', 'help'])
+ # Chromium Win uses the depot_tools package, which contains a number
+ # of development tools, including Python and git. Instead of using a
+ # real git executable, depot_tools indirects via a batch file, called
+ # git.bat. This batch file allows depot_tools to auto-update the real
+ # git executable, which is contained in a subdirectory.
+ #
+ # That's all fine and good, except that subprocess.popen can detect
+ # the difference between a real git executable and batch file when we
+ # don't provide use shell=True. Rather than use shell=True on Windows,
+ # We hack the git.bat name into the SVN class.
+ _log.debug('Engaging git.bat Windows hack.')
+ from webkitpy.common.checkout.scm.git import Git
+ Git.executable_name = 'git.bat'
+ except OSError, e:
+ _log.debug('Failed to engage git.bat Windows hack.')
def _initialize_scm(self, patch_directories=None):
if sys.platform == "win32":
diff --git a/Tools/Scripts/webkitpy/common/net/bugzilla/bugzilla.py b/Tools/Scripts/webkitpy/common/net/bugzilla/bugzilla.py
index 1afa287de..58d497dc6 100644
--- a/Tools/Scripts/webkitpy/common/net/bugzilla/bugzilla.py
+++ b/Tools/Scripts/webkitpy/common/net/bugzilla/bugzilla.py
@@ -209,12 +209,10 @@ class BugzillaQueries(object):
# Currently this returns all bugs across all components.
# In the future we may wish to extend this API to construct more restricted searches.
- def fetch_bugs_matching_search(self, search_string, author_email=None):
+ def fetch_bugs_matching_search(self, search_string):
query = "buglist.cgi?query_format=advanced"
if search_string:
query += "&short_desc_type=allwordssubstr&short_desc=%s" % urllib.quote(search_string)
- if author_email:
- query += "&emailreporter1=1&emailtype1=substring&email1=%s" % urllib.quote(search_string)
return self._fetch_bugs_from_advanced_query(query)
def fetch_patches_from_pending_commit_list(self):
@@ -515,10 +513,21 @@ class Bugzilla(object):
self.authenticated = True
self.username = username
+ # FIXME: Use enum instead of two booleans
def _commit_queue_flag(self, mark_for_landing, mark_for_commit_queue):
if mark_for_landing:
+ user = self.committers.account_by_email(self.username)
+ mark_for_commit_queue = True
+ if not user:
+ log("Your Bugzilla login is not listed in committers.py. Uploading with cq? instead of cq+")
+ mark_for_landing = False
+ elif not user.can_commit:
+ log("You're not a committer yet or haven't updated committers.py yet. Uploading with cq? instead of cq+")
+ mark_for_landing = False
+
+ if mark_for_landing:
return '+'
- elif mark_for_commit_queue:
+ if mark_for_commit_queue:
return '?'
return 'X'
diff --git a/Tools/Scripts/webkitpy/common/net/bugzilla/bugzilla_mock.py b/Tools/Scripts/webkitpy/common/net/bugzilla/bugzilla_mock.py
index 36b465920..4e50189a1 100644
--- a/Tools/Scripts/webkitpy/common/net/bugzilla/bugzilla_mock.py
+++ b/Tools/Scripts/webkitpy/common/net/bugzilla/bugzilla_mock.py
@@ -269,7 +269,7 @@ class MockBugzillaQueries(object):
def fetch_patches_from_pending_commit_list(self):
return sum([bug.reviewed_patches() for bug in self._all_bugs()], [])
- def fetch_bugs_matching_search(self, search_string, author_email=None):
+ def fetch_bugs_matching_search(self, search_string):
return [self._bugzilla.fetch_bug(50004), self._bugzilla.fetch_bug(50003)]
def fetch_bugs_matching_quicksearch(self, search_string):
diff --git a/Tools/Scripts/webkitpy/common/net/bugzilla/bugzilla_unittest.py b/Tools/Scripts/webkitpy/common/net/bugzilla/bugzilla_unittest.py
index 986ce3bac..6108b5e8a 100644
--- a/Tools/Scripts/webkitpy/common/net/bugzilla/bugzilla_unittest.py
+++ b/Tools/Scripts/webkitpy/common/net/bugzilla/bugzilla_unittest.py
@@ -33,6 +33,7 @@ import StringIO
from .bugzilla import Bugzilla, BugzillaQueries, EditUsersParser
from webkitpy.common.config import urls
+from webkitpy.common.config.committers import Reviewer, Committer, Contributor, CommitterList
from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.common.net.web_mock import MockBrowser
from webkitpy.thirdparty.mock import Mock
@@ -299,6 +300,43 @@ Ignore this bug. Just for testing failure modes of webkit-patch and the commit-
filename = bugzilla._filename_for_upload(StringIO.StringIO(), 1234, extension="patch", timestamp=mock_timestamp)
self.assertEqual(filename, "bug-1234-now.patch")
+ def test_commit_queue_flag(self):
+ bugzilla = Bugzilla()
+
+ bugzilla.committers = CommitterList(reviewers=[Reviewer("WebKit Reviewer", "reviewer@webkit.org")],
+ committers=[Committer("WebKit Committer", "committer@webkit.org")],
+ contributors=[Contributor("WebKit Contributor", "contributor@webkit.org")],
+ watchers=[])
+
+ def assert_commit_queue_flag(mark_for_landing, mark_for_commit_queue, expected, username=None):
+ bugzilla.username = username
+ capture = OutputCapture()
+ capture.capture_output()
+ try:
+ self.assertEqual(bugzilla._commit_queue_flag(mark_for_landing=mark_for_landing, mark_for_commit_queue=mark_for_commit_queue), expected)
+ finally:
+ capture.restore_output()
+
+ assert_commit_queue_flag(mark_for_landing=False, mark_for_commit_queue=False, expected='X', username='unknown@webkit.org')
+ assert_commit_queue_flag(mark_for_landing=False, mark_for_commit_queue=True, expected='?', username='unknown@webkit.org')
+ assert_commit_queue_flag(mark_for_landing=False, mark_for_commit_queue=True, expected='?', username='unknown@webkit.org')
+ assert_commit_queue_flag(mark_for_landing=True, mark_for_commit_queue=True, expected='?', username='unknown@webkit.org')
+
+ assert_commit_queue_flag(mark_for_landing=False, mark_for_commit_queue=False, expected='X', username='contributor@webkit.org')
+ assert_commit_queue_flag(mark_for_landing=False, mark_for_commit_queue=True, expected='?', username='contributor@webkit.org')
+ assert_commit_queue_flag(mark_for_landing=True, mark_for_commit_queue=False, expected='?', username='contributor@webkit.org')
+ assert_commit_queue_flag(mark_for_landing=True, mark_for_commit_queue=True, expected='?', username='contributor@webkit.org')
+
+ assert_commit_queue_flag(mark_for_landing=False, mark_for_commit_queue=False, expected='X', username='committer@webkit.org')
+ assert_commit_queue_flag(mark_for_landing=False, mark_for_commit_queue=True, expected='?', username='committer@webkit.org')
+ assert_commit_queue_flag(mark_for_landing=True, mark_for_commit_queue=False, expected='+', username='committer@webkit.org')
+ assert_commit_queue_flag(mark_for_landing=True, mark_for_commit_queue=True, expected='+', username='committer@webkit.org')
+
+ assert_commit_queue_flag(mark_for_landing=False, mark_for_commit_queue=False, expected='X', username='reviewer@webkit.org')
+ assert_commit_queue_flag(mark_for_landing=False, mark_for_commit_queue=True, expected='?', username='reviewer@webkit.org')
+ assert_commit_queue_flag(mark_for_landing=True, mark_for_commit_queue=False, expected='+', username='reviewer@webkit.org')
+ assert_commit_queue_flag(mark_for_landing=True, mark_for_commit_queue=True, expected='+', username='reviewer@webkit.org')
+
class BugzillaQueriesTest(unittest.TestCase):
_sample_request_page = """
diff --git a/Tools/Scripts/webkitpy/common/system/executive.py b/Tools/Scripts/webkitpy/common/system/executive.py
index 8759c719a..cb36b5dc0 100644
--- a/Tools/Scripts/webkitpy/common/system/executive.py
+++ b/Tools/Scripts/webkitpy/common/system/executive.py
@@ -454,7 +454,13 @@ class Executive(object):
def run_in_parallel(self, command_lines_and_cwds, processes=None):
"""Runs a list of (cmd_line list, cwd string) tuples in parallel and returns a list of (retcode, stdout, stderr) tuples."""
- return multiprocessing.Pool(processes=processes).map(_run_command_thunk, command_lines_and_cwds)
+ if sys.platform in ('cygwin', 'win32'):
+ return map(_run_command_thunk, command_lines_and_cwds)
+ pool = multiprocessing.Pool(processes=processes)
+ results = pool.map(_run_command_thunk, command_lines_and_cwds)
+ pool.close()
+ pool.join()
+ return results
def _run_command_thunk(cmd_line_and_cwd):
diff --git a/Tools/Scripts/webkitpy/common/system/executive_unittest.py b/Tools/Scripts/webkitpy/common/system/executive_unittest.py
index 466e2ec02..212896a4a 100644
--- a/Tools/Scripts/webkitpy/common/system/executive_unittest.py
+++ b/Tools/Scripts/webkitpy/common/system/executive_unittest.py
@@ -218,6 +218,10 @@ class ExecutiveTest(unittest.TestCase):
self.assertTrue(os.getpid() in pids)
def test_run_in_parallel(self):
+ if sys.platform in ("win32", "cygwin"):
+ return # This function isn't implemented properly on windows yet.
+ import multiprocessing
+
NUM_PROCESSES = 4
DELAY_SECS = 0.25
cmd_line = [sys.executable, '-c', 'import time; time.sleep(%f); print "hello"' % DELAY_SECS]
@@ -228,6 +232,7 @@ class ExecutiveTest(unittest.TestCase):
done = time.time()
self.assertTrue(done - start < NUM_PROCESSES * DELAY_SECS)
self.assertEquals([output[1] for output in command_outputs], ["hello\n"] * NUM_PROCESSES)
+ self.assertEquals([], multiprocessing.active_children())
def main(platform, stdin, stdout, cmd, args):
diff --git a/Tools/Scripts/webkitpy/common/system/outputcapture.py b/Tools/Scripts/webkitpy/common/system/outputcapture.py
index 66188c0cb..4f931b7d1 100644
--- a/Tools/Scripts/webkitpy/common/system/outputcapture.py
+++ b/Tools/Scripts/webkitpy/common/system/outputcapture.py
@@ -3,7 +3,7 @@
# 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
@@ -13,7 +13,7 @@
# * 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
@@ -35,6 +35,13 @@ from StringIO import StringIO
class OutputCapture(object):
+ # By default we capture the output to a stream. Other modules may override
+ # this function in order to do things like pass through the output. See
+ # webkitpy.test.main for an example.
+ @staticmethod
+ def stream_wrapper(stream):
+ return StringIO()
+
def __init__(self):
self.saved_outputs = dict()
self._log_level = logging.INFO
@@ -45,8 +52,9 @@ class OutputCapture(object):
self._logs_handler.setLevel(self._log_level)
def _capture_output_with_name(self, output_name):
- self.saved_outputs[output_name] = getattr(sys, output_name)
- captured_output = StringIO()
+ stream = getattr(sys, output_name)
+ captured_output = self.stream_wrapper(stream)
+ self.saved_outputs[output_name] = stream
setattr(sys, output_name, captured_output)
return captured_output
diff --git a/Tools/Scripts/webkitpy/common/system/path.py b/Tools/Scripts/webkitpy/common/system/path.py
index b7ad3723a..e5a66bf87 100644
--- a/Tools/Scripts/webkitpy/common/system/path.py
+++ b/Tools/Scripts/webkitpy/common/system/path.py
@@ -35,11 +35,9 @@ import threading
import urllib
-def abspath_to_uri(path, platform=None):
+def abspath_to_uri(platform, path):
"""Converts a platform-specific absolute path to a file: URL."""
- if platform is None:
- platform = sys.platform
- return "file:" + _escape(_convert_path(path, platform))
+ return "file:" + _escape(_convert_path(platform, path))
def cygpath(path):
@@ -118,12 +116,12 @@ def _escape(path):
return urllib.quote(path, safe='/+:')
-def _convert_path(path, platform):
+def _convert_path(platform, path):
"""Handles any os-specific path separators, mappings, etc."""
- if platform == 'win32':
- return _winpath_to_uri(path)
- if platform == 'cygwin':
+ if platform.is_cygwin():
return _winpath_to_uri(cygpath(path))
+ if platform.is_win():
+ return _winpath_to_uri(path)
return _unixypath_to_uri(path)
diff --git a/Tools/Scripts/webkitpy/common/system/path_unittest.py b/Tools/Scripts/webkitpy/common/system/path_unittest.py
index e08212a2c..954d32d71 100644
--- a/Tools/Scripts/webkitpy/common/system/path_unittest.py
+++ b/Tools/Scripts/webkitpy/common/system/path_unittest.py
@@ -29,69 +29,41 @@
import unittest
import sys
-import path
+from webkitpy.common.system.systemhost import SystemHost
+from webkitpy.common.system.platforminfo import PlatformInfo
+from webkitpy.common.system.platforminfo_mock import MockPlatformInfo
+from webkitpy.common.system import path
class AbspathTest(unittest.TestCase):
- def assertMatch(self, test_path, expected_uri,
- platform=None):
- if platform == 'cygwin' and sys.platform != 'cygwin':
- return
- self.assertEqual(path.abspath_to_uri(test_path, platform=platform),
- expected_uri)
+ def platforminfo(self):
+ return SystemHost().platform
def test_abspath_to_uri_cygwin(self):
if sys.platform != 'cygwin':
return
+ self.assertEquals(path.abspath_to_uri(self.platforminfo(), '/cygdrive/c/foo/bar.html'),
+ 'file:///C:/foo/bar.html')
- self.assertMatch('/cygdrive/c/foo/bar.html',
- 'file:///C:/foo/bar.html',
- platform='cygwin')
- self.assertEqual(path.abspath_to_uri('/cygdrive/c/foo/bar.html',
- platform='cygwin'),
- 'file:///C:/foo/bar.html')
-
- def test_abspath_to_uri_darwin(self):
- self.assertMatch('/foo/bar.html',
- 'file:///foo/bar.html',
- platform='darwin')
- self.assertEqual(path.abspath_to_uri("/foo/bar.html",
- platform='darwin'),
- "file:///foo/bar.html")
-
- def test_abspath_to_uri_linux2(self):
- self.assertMatch('/foo/bar.html',
- 'file:///foo/bar.html',
- platform='darwin')
- self.assertEqual(path.abspath_to_uri("/foo/bar.html",
- platform='linux2'),
- "file:///foo/bar.html")
- self.assertEqual(path.abspath_to_uri("/foo/bar.html",
- platform='linux3'),
- "file:///foo/bar.html")
+ def test_abspath_to_uri_unixy(self):
+ self.assertEquals(path.abspath_to_uri(MockPlatformInfo(), "/foo/bar.html"),
+ 'file:///foo/bar.html')
def test_abspath_to_uri_win(self):
- self.assertMatch('c:\\foo\\bar.html',
- 'file:///c:/foo/bar.html',
- platform='win32')
- self.assertEqual(path.abspath_to_uri("c:\\foo\\bar.html",
- platform='win32'),
- "file:///c:/foo/bar.html")
+ if sys.platform != 'win32':
+ return
+ self.assertEquals(path.abspath_to_uri(self.platforminfo(), 'c:\\foo\\bar.html'),
+ 'file:///c:/foo/bar.html')
- def test_abspath_to_uri_escaping(self):
- self.assertMatch('/foo/bar + baz%?.html',
- 'file:///foo/bar%20+%20baz%25%3F.html',
- platform='darwin')
- self.assertMatch('/foo/bar + baz%?.html',
- 'file:///foo/bar%20+%20baz%25%3F.html',
- platform='linux2')
- self.assertMatch('/foo/bar + baz%?.html',
- 'file:///foo/bar%20+%20baz%25%3F.html',
- platform='linux3')
+ def test_abspath_to_uri_escaping_unixy(self):
+ self.assertEquals(path.abspath_to_uri(MockPlatformInfo(), '/foo/bar + baz%?.html'),
+ 'file:///foo/bar%20+%20baz%25%3F.html')
# Note that you can't have '?' in a filename on windows.
- self.assertMatch('/cygdrive/c/foo/bar + baz%.html',
- 'file:///C:/foo/bar%20+%20baz%25.html',
- platform='cygwin')
+ def test_abspath_to_uri_escaping_cygwin(self):
+ if sys.platform != 'cygwin':
+ return
+ self.assertEquals(path.abspath_to_uri(self.platforminfo(), '/cygdrive/c/foo/bar + baz%.html'),
+ 'file:///C:/foo/bar%20+%20baz%25.html')
def test_stop_cygpath_subprocess(self):
if sys.platform != 'cygwin':
@@ -106,6 +78,3 @@ class AbspathTest(unittest.TestCase):
# Ensure that it is stopped.
self.assertFalse(path._CygPath._singleton.is_running())
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/Tools/Scripts/webkitpy/common/system/platforminfo.py b/Tools/Scripts/webkitpy/common/system/platforminfo.py
index 22cafbbee..74cff5412 100644
--- a/Tools/Scripts/webkitpy/common/system/platforminfo.py
+++ b/Tools/Scripts/webkitpy/common/system/platforminfo.py
@@ -54,6 +54,7 @@ class PlatformInfo(object):
self.os_version = self._determine_mac_version(platform_module.mac_ver()[0])
if self.os_name.startswith('win'):
self.os_version = self._determine_win_version(self._win_version_tuple(sys_module))
+ self._is_cygwin = sys_module.platform == 'cygwin'
def is_mac(self):
return self.os_name == 'mac'
@@ -61,6 +62,9 @@ class PlatformInfo(object):
def is_win(self):
return self.os_name == 'win'
+ def is_cygwin(self):
+ return self._is_cygwin
+
def is_linux(self):
return self.os_name == 'linux'
@@ -77,18 +81,9 @@ class PlatformInfo(object):
# Windows-2008ServerR2-6.1.7600
return self._platform_module.platform()
- def free_bytes_memory(self):
- if self.is_mac():
- vm_stat_output = self._executive.run_command(["vm_stat"])
- free_bytes = self._compute_bytes_from_vm_stat_output("Pages free", vm_stat_output)
- # Per https://bugs.webkit.org/show_bug.cgi?id=74650 include inactive memory since the OS is lazy about freeing memory.
- free_bytes += self._compute_bytes_from_vm_stat_output("Pages inactive", vm_stat_output)
- return free_bytes
- return None
-
def total_bytes_memory(self):
if self.is_mac():
- return int(self._executive.run_command(["sysctl", "-n", "hw.memsize"]))
+ return long(self._executive.run_command(["sysctl", "-n", "hw.memsize"]))
return None
def _determine_os_name(self, sys_platform):
@@ -137,14 +132,3 @@ class PlatformInfo(object):
match_object = re.search(r'(?P<major>\d)\.(?P<minor>\d)\.(?P<build>\d+)', ver_output)
assert match_object, 'cmd returned an unexpected version string: ' + ver_output
return tuple(map(int, match_object.groups()))
-
- def _compute_bytes_from_vm_stat_output(self, label_text, vm_stat_output):
- page_size_match = re.search(r"page size of (\d+) bytes", vm_stat_output)
- free_pages_match = re.search(r"%s:\s+(\d+)." % label_text, vm_stat_output)
-
- # Fail hard if vmstat's output isn't what we expect.
- assert(page_size_match and free_pages_match)
-
- free_page_count = int(free_pages_match.group(1))
- page_size = int(page_size_match.group(1))
- return free_page_count * page_size
diff --git a/Tools/Scripts/webkitpy/common/system/platforminfo_mock.py b/Tools/Scripts/webkitpy/common/system/platforminfo_mock.py
index c953aa185..34fa97fb4 100644
--- a/Tools/Scripts/webkitpy/common/system/platforminfo_mock.py
+++ b/Tools/Scripts/webkitpy/common/system/platforminfo_mock.py
@@ -41,6 +41,9 @@ class MockPlatformInfo(object):
def is_win(self):
return self.os_name == 'win'
+ def is_cygwin(self):
+ return False
+
def is_freebsd(self):
return self.os_name == 'freebsd'
@@ -48,7 +51,4 @@ class MockPlatformInfo(object):
return "MockPlatform 1.0"
def total_bytes_memory(self):
- return 2 * 1024 * 1024 * 1024 # 2GB is a reasonable amount of ram to mock.
-
- def free_bytes_memory(self):
- return 1 * 1024 * 1024 * 1024 # 1GB is a reasonable amount of ram to mock as free.
+ return 3 * 1024 * 1024 * 1024 # 3GB is a reasonable amount of ram to mock.
diff --git a/Tools/Scripts/webkitpy/common/system/platforminfo_unittest.py b/Tools/Scripts/webkitpy/common/system/platforminfo_unittest.py
index 5a1f85fc3..8fc961b08 100644
--- a/Tools/Scripts/webkitpy/common/system/platforminfo_unittest.py
+++ b/Tools/Scripts/webkitpy/common/system/platforminfo_unittest.py
@@ -82,10 +82,8 @@ class TestPlatformInfo(unittest.TestCase):
if info.is_mac():
self.assertTrue(info.total_bytes_memory() > 0)
- self.assertTrue(info.free_bytes_memory() > 0)
else:
self.assertEquals(info.total_bytes_memory(), None)
- self.assertEquals(info.free_bytes_memory(), None)
def test_os_name_and_wrappers(self):
info = self.make_info(fake_sys('linux2'))
@@ -180,22 +178,6 @@ class TestPlatformInfo(unittest.TestCase):
info = self.make_info(fake_sys('freebsd9'))
self.assertEquals(info.total_bytes_memory(), None)
- def test_free_bytes_memory(self):
- vmstat_output = ("Mach Virtual Memory Statistics: (page size of 4096 bytes)\n"
- "Pages free: 1.\n"
- "Pages inactive: 1.\n")
- info = self.make_info(fake_sys('darwin'), fake_platform('10.6.3'), fake_executive(vmstat_output))
- self.assertEquals(info.free_bytes_memory(), 8192)
-
- info = self.make_info(fake_sys('win32', tuple([6, 1, 7600])))
- self.assertEquals(info.free_bytes_memory(), None)
-
- info = self.make_info(fake_sys('linux2'))
- self.assertEquals(info.free_bytes_memory(), None)
-
- info = self.make_info(fake_sys('freebsd9'))
- self.assertEquals(info.free_bytes_memory(), None)
-
if __name__ == '__main__':
unittest.main()
diff --git a/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py b/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py
index e762cfda0..3189563d2 100644
--- a/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py
+++ b/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py
@@ -236,10 +236,13 @@ def summarize_results(port_obj, expectations, result_summary, retry_summary, tes
results['layout_tests_dir'] = port_obj.layout_tests_dir()
results['has_wdiff'] = port_obj.wdiff_available()
results['has_pretty_patch'] = port_obj.pretty_patch_available()
+ results['pixel_tests_enabled'] = port_obj.get_option('pixel_tests')
+
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.
if use_trac_links_in_results_html(port_obj):
+ 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))
@@ -316,6 +319,8 @@ class Manager(object):
# a set of test files, and the same tests as a list
+ self._paths = set()
+
# FIXME: Rename to test_names.
self._test_files = set()
self._test_files_list = None
@@ -340,6 +345,7 @@ class Manager(object):
paths = self._strip_test_dir_prefixes(args)
if self._options.test_list:
paths += self._strip_test_dir_prefixes(read_test_files(self._filesystem, self._options.test_list, self._port.TEST_PATH_SEPARATOR))
+ self._paths = set(paths)
self._test_files = self._port.tests(paths)
def _strip_test_dir_prefixes(self, paths):
@@ -360,6 +366,9 @@ class Manager(object):
def _http_tests(self):
return set(test for test in self._test_files if self._is_http_test(test))
+ def _websocket_tests(self):
+ return set(test for test in self._test_files if self.WEBSOCKET_SUBDIR in test)
+
def _is_perf_test(self, test):
return self.PERF_SUBDIR == test or (self.PERF_SUBDIR + self._port.TEST_PATH_SEPARATOR) in test
@@ -456,24 +465,40 @@ class Manager(object):
# Remove skipped - both fixable and ignored - files from the
# top-level list of files to test.
+ found_test_files = set(self._test_files)
num_all_test_files = len(self._test_files)
- self._printer.print_expected("Found: %d tests" % (len(self._test_files)))
- if not num_all_test_files:
- _log.critical('No tests to run.')
- return None
-
- skipped = set()
+ skipped = self._expectations.get_tests_with_result_type(test_expectations.SKIP)
if not self._options.http:
- skipped = skipped.union(self._http_tests())
+ skipped.update(set(self._http_tests()))
+
+ if self._options.skipped == 'only':
+ self._test_files = self._test_files.intersection(skipped)
+ elif self._options.skipped == 'default':
+ self._test_files -= skipped
+ elif self._options.skipped == 'ignore':
+ pass # just to be clear that we're ignoring the skip list.
- if num_all_test_files > 1 and not self._options.force:
- skipped.update(self._expectations.get_tests_with_result_type(test_expectations.SKIP))
- if self._options.skip_failing_tests:
- skipped.update(self._expectations.get_tests_with_result_type(test_expectations.FAIL))
- skipped.update(self._expectations.get_tests_with_result_type(test_expectations.FLAKY))
+ if self._options.skip_failing_tests:
+ self._test_files -= self._expectations.get_tests_with_result_type(test_expectations.FAIL)
+ self._test_files -= self._expectations.get_tests_with_result_type(test_expectations.FLAKY)
- self._test_files -= skipped
+ # now make sure we're explicitly running any tests passed on the command line.
+ self._test_files.update(found_test_files.intersection(self._paths))
+
+ if not num_all_test_files:
+ _log.critical('No tests to run.')
+ return None
+
+ num_skipped = num_all_test_files - len(self._test_files)
+ if num_skipped:
+ self._printer.print_expected("Running %s (found %d, skipping %d)." % (
+ grammar.pluralize('test', num_all_test_files - num_skipped),
+ num_all_test_files, num_skipped))
+ elif len(self._test_files) > 1:
+ self._printer.print_expected("Running all %d tests." % len(self._test_files))
+ else:
+ self._printer.print_expected("Running %1 test.")
# Create a sorted list of test files so the subset chunk,
# if used, contains alphabetically consecutive tests.
@@ -504,9 +529,7 @@ class Manager(object):
self._print_expected_results_of_type(result_summary, test_expectations.FLAKY, "flaky")
self._print_expected_results_of_type(result_summary, test_expectations.SKIP, "skipped")
- if self._options.force:
- self._printer.print_expected('Running all tests, including skips (--force)')
- else:
+ if self._options.skipped != 'ignore':
# Note that we don't actually run the skipped tests (they were
# subtracted out of self._test_files, above), but we stub out the
# results here so the statistics can remain accurate.
@@ -742,7 +765,7 @@ class Manager(object):
all_shards = locked_shards + unlocked_shards
self._remaining_locked_shards = locked_shards
- if locked_shards and self._options.http:
+ if self._options.http and (self._http_tests() or self._websocket_tests()):
self.start_servers_with_lock()
num_workers = min(num_workers, len(all_shards))
@@ -797,7 +820,8 @@ class Manager(object):
_log.error('Worker %d did not exit in time.' % worker_state.number)
except KeyboardInterrupt:
- self._printer.print_update('Interrupted, exiting ...')
+ self._printer.flush()
+ self._printer.write('Interrupted, exiting ...')
self.cancel_workers()
keyboard_interrupted = True
except TestRunInterruptedException, e:
@@ -946,21 +970,24 @@ class Manager(object):
return self._port.exit_code_from_summarized_results(unexpected_results)
def start_servers_with_lock(self):
- assert(self._options.http)
self._printer.print_update('Acquiring http lock ...')
self._port.acquire_http_lock()
- self._printer.print_update('Starting HTTP server ...')
- self._port.start_http_server()
- self._printer.print_update('Starting WebSocket server ...')
- self._port.start_websocket_server()
+ if self._http_tests():
+ self._printer.print_update('Starting HTTP server ...')
+ self._port.start_http_server()
+ if self._websocket_tests():
+ self._printer.print_update('Starting WebSocket server ...')
+ self._port.start_websocket_server()
self._has_http_lock = True
def stop_servers_with_lock(self):
if self._has_http_lock:
- self._printer.print_update('Stopping HTTP server ...')
- self._port.stop_http_server()
- self._printer.print_update('Stopping WebSocket server ...')
- self._port.stop_websocket_server()
+ if self._http_tests():
+ self._printer.print_update('Stopping HTTP server ...')
+ self._port.stop_http_server()
+ if self._websocket_tests():
+ self._printer.print_update('Stopping WebSocket server ...')
+ self._port.stop_websocket_server()
self._printer.print_update('Releasing server lock ...')
self._port.release_http_lock()
self._has_http_lock = False
@@ -1445,9 +1472,10 @@ class Manager(object):
worker_state.current_test_name = test_info.test_name
worker_state.next_timeout = time.time() + hang_timeout
- def handle_done(self, source):
+ def handle_done(self, source, log_messages=None):
worker_state = self._worker_states[source]
worker_state.done = True
+ self._log_messages(log_messages)
def handle_exception(self, source, exception_type, exception_value, stack):
if exception_type in (KeyboardInterrupt, TestRunInterruptedException):
@@ -1474,16 +1502,21 @@ class Manager(object):
if not self._remaining_locked_shards:
self.stop_servers_with_lock()
- def handle_finished_test(self, source, result, elapsed_time):
+ def handle_finished_test(self, source, result, elapsed_time, log_messages=None):
worker_state = self._worker_states[source]
worker_state.next_timeout = None
worker_state.current_test_name = None
worker_state.stats['total_time'] += elapsed_time
worker_state.stats['num_tests'] += 1
+ self._log_messages(log_messages)
self._all_results.append(result)
self._update_summary_with_result(self._current_result_summary, result)
+ def _log_messages(self, messages):
+ for message in messages:
+ self._printer.writeln(*message)
+
def _log_worker_stack(self, stack):
webkitpydir = self._port.path_from_webkit_base('Tools', 'Scripts', 'webkitpy') + self._filesystem.sep
for filename, line_number, function_name, text in stack:
diff --git a/Tools/Scripts/webkitpy/layout_tests/controllers/manager_unittest.py b/Tools/Scripts/webkitpy/layout_tests/controllers/manager_unittest.py
index 3ddab8ec3..13011883f 100644
--- a/Tools/Scripts/webkitpy/layout_tests/controllers/manager_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/controllers/manager_unittest.py
@@ -280,7 +280,7 @@ class ManagerTest(unittest.TestCase):
host = MockHost()
port = host.port_factory.get('test-win-xp')
test = 'failures/expected/reftest.html'
- port.test_expectations = lambda: 'WONTFIX : failures/expected/reftest.html = IMAGE'
+ port.expectations_dict = lambda: {'': 'WONTFIX : failures/expected/reftest.html = IMAGE'}
expectations = TestExpectations(port, tests=[test])
# Reftests expected to be image mismatch should be respected when pixel_tests=False.
manager = Manager(port=port, options=MockOptions(pixel_tests=False, exit_after_n_failures=None, exit_after_n_crashes_or_timeouts=None), printer=Mock())
@@ -342,6 +342,58 @@ class ManagerTest(unittest.TestCase):
manager = get_manager_with_tests(tests)
manager._look_for_new_crash_logs(rs, time.time())
+ def test_servers_started(self):
+
+ def start_http_server():
+ self.http_started = True
+
+ def start_websocket_server():
+ self.websocket_started = True
+
+ def stop_http_server():
+ self.http_stopped = True
+
+ def stop_websocket_server():
+ self.websocket_stopped = True
+
+ host = MockHost()
+ port = host.port_factory.get('test-mac-leopard')
+ port.start_http_server = start_http_server
+ port.start_websocket_server = start_websocket_server
+ port.stop_http_server = stop_http_server
+ port.stop_websocket_server = stop_websocket_server
+
+ self.http_started = self.http_stopped = self.websocket_started = self.websocket_stopped = False
+ manager = Manager(port=port, options=MockOptions(http=True), printer=Mock())
+ manager._test_files = ['http/tests/pass.txt']
+ manager.start_servers_with_lock()
+ self.assertEquals(self.http_started, True)
+ self.assertEquals(self.websocket_started, False)
+ manager.stop_servers_with_lock()
+ self.assertEquals(self.http_stopped, True)
+ self.assertEquals(self.websocket_stopped, False)
+
+ self.http_started = self.http_stopped = self.websocket_started = self.websocket_stopped = False
+ manager = Manager(port=port, options=MockOptions(http=True), printer=Mock())
+ manager._test_files = ['websocket/pass.txt']
+ manager.start_servers_with_lock()
+ self.assertEquals(self.http_started, True)
+ self.assertEquals(self.websocket_started, True)
+ manager.stop_servers_with_lock()
+ self.assertEquals(self.http_stopped, True)
+ self.assertEquals(self.websocket_stopped, True)
+
+ self.http_started = self.http_stopped = self.websocket_started = self.websocket_stopped = False
+ manager = Manager(port=port, options=MockOptions(http=True), printer=Mock())
+ manager._test_files = ['perf/foo/test.html']
+ manager.start_servers_with_lock()
+ self.assertEquals(self.http_started, False)
+ self.assertEquals(self.websocket_started, False)
+ manager.stop_servers_with_lock()
+ self.assertEquals(self.http_stopped, False)
+ self.assertEquals(self.websocket_stopped, False)
+
+
class NaturalCompareTest(unittest.TestCase):
def assert_cmp(self, x, y, result):
@@ -422,7 +474,7 @@ class ResultSummaryTest(unittest.TestCase):
return test_results.TestResult(test_name, failures=failures, test_run_time=run_time)
def get_result_summary(self, port, test_names, expectations_str):
- port.test_expectations = lambda: expectations_str
+ port.expectations_dict = lambda: {'': expectations_str}
expectations = test_expectations.TestExpectations(port, test_names)
return test_names, result_summary.ResultSummary(expectations, test_names), expectations
@@ -481,7 +533,7 @@ class ResultSummaryTest(unittest.TestCase):
port = host.port_factory.get('test')
port._options.builder_name = 'dummy builder'
port._filesystem.write_text_file(port._filesystem.join(port.layout_tests_dir(), "failures/expected/wontfix.html"), "Dummy test contents")
- expected_results, unexpected_results = self.summarized_results(port, expected=False, passing=False, flaky=False, extra_tests=['failures/expected/wontfix.html'], extra_expectations='BUGX WONTFIX : failures/expected/wontfix.html = FAIL\n')
+ expected_results, unexpected_results = self.summarized_results(port, expected=False, passing=False, flaky=False, extra_tests=['failures/expected/wontfix.html'], extra_expectations='BUGX WONTFIX : failures/expected/wontfix.html = TEXT\n')
self.assertTrue(expected_results['tests']['failures']['expected']['wontfix.html']['wontfix'])
if __name__ == '__main__':
diff --git a/Tools/Scripts/webkitpy/layout_tests/controllers/manager_worker_broker_unittest.py b/Tools/Scripts/webkitpy/layout_tests/controllers/manager_worker_broker_unittest.py
index 93806e7d8..046425664 100644
--- a/Tools/Scripts/webkitpy/layout_tests/controllers/manager_worker_broker_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/controllers/manager_worker_broker_unittest.py
@@ -215,14 +215,14 @@ class InterfaceTest(unittest.TestCase):
def test_managerconnection_is_abstract(self):
# Test that all the base class methods are abstract and have the
# signature we expect.
- broker = make_broker(self, 'inline')
+ broker = make_broker(self, 1)
obj = manager_worker_broker._ManagerConnection(broker._broker, self, None)
self.assertRaises(NotImplementedError, obj.start_worker)
def test_workerconnection_is_abstract(self):
# Test that all the base class methods are abstract and have the
# signature we expect.
- broker = make_broker(self, 'inline')
+ broker = make_broker(self, 1)
obj = manager_worker_broker._WorkerConnection(broker._broker, _TestWorker, None)
self.assertRaises(NotImplementedError, obj.cancel)
self.assertRaises(NotImplementedError, obj.is_alive)
diff --git a/Tools/Scripts/webkitpy/layout_tests/controllers/single_test_runner.py b/Tools/Scripts/webkitpy/layout_tests/controllers/single_test_runner.py
index 529aea222..fa07e88ff 100644
--- a/Tools/Scripts/webkitpy/layout_tests/controllers/single_test_runner.py
+++ b/Tools/Scripts/webkitpy/layout_tests/controllers/single_test_runner.py
@@ -46,7 +46,8 @@ def run_single_test(port, options, test_input, driver, worker_name):
return runner.run()
-class SingleTestRunner:
+class SingleTestRunner(object):
+ (ALONGSIDE_TEST, PLATFORM_DIR, VERSION_DIR, UPDATE) = ('alongside', 'platform', 'version', 'update')
def __init__(self, options, port, driver, test_input, worker_name):
self._options = options
@@ -94,12 +95,12 @@ class SingleTestRunner:
def run(self):
if self._reference_files:
- if self._port.get_option('no_ref_tests') or self._options.new_baseline or self._options.reset_results:
+ if self._port.get_option('no_ref_tests') or self._options.reset_results:
result = TestResult(self._test_name)
result.type = test_expectations.SKIP
return result
return self._run_reftest()
- if self._options.new_baseline or self._options.reset_results:
+ if self._options.reset_results:
return self._run_rebaseline()
return self._run_compare_test()
@@ -121,7 +122,7 @@ class SingleTestRunner:
driver_output = self._driver.run_test(self._driver_input())
failures = self._handle_error(driver_output)
test_result_writer.write_test_result(self._filesystem, self._port, self._test_name, driver_output, None, failures)
- # FIXME: It the test crashed or timed out, it might be bettter to avoid
+ # FIXME: It the test crashed or timed out, it might be better to avoid
# to write new baselines.
self._overwrite_baselines(driver_output)
return TestResult(self._test_name, failures, driver_output.test_time, driver_output.has_stderr())
@@ -131,49 +132,50 @@ class SingleTestRunner:
def _add_missing_baselines(self, test_result, driver_output):
missingImage = test_result.has_failure_matching_types(test_failures.FailureMissingImage, test_failures.FailureMissingImageHash)
if test_result.has_failure_matching_types(test_failures.FailureMissingResult):
- self._save_baseline_data(driver_output.text, ".txt", SingleTestRunner._render_tree_dump_pattern.match(driver_output.text))
+ self._save_baseline_data(driver_output.text, '.txt', self._location_for_new_baseline(driver_output.text, '.txt'))
if test_result.has_failure_matching_types(test_failures.FailureMissingAudio):
- self._save_baseline_data(driver_output.audio, ".wav", generate_new_baseline=False)
+ self._save_baseline_data(driver_output.audio, '.wav', self._location_for_new_baseline(driver_output.audio, '.wav'))
if missingImage:
- self._save_baseline_data(driver_output.image, ".png", generate_new_baseline=True)
+ self._save_baseline_data(driver_output.image, '.png', self._location_for_new_baseline(driver_output.image, '.png'))
+
+ def _location_for_new_baseline(self, data, extension):
+ if self._options.add_platform_exceptions:
+ return self.VERSION_DIR
+ if extension == '.png':
+ return self.PLATFORM_DIR
+ if extension == '.wav':
+ return self.ALONGSIDE_TEST
+ if extension == '.txt' and self._render_tree_dump_pattern.match(data):
+ return self.PLATFORM_DIR
+ return self.ALONGSIDE_TEST
def _overwrite_baselines(self, driver_output):
- # Although all DumpRenderTree output should be utf-8,
- # we do not ever decode it inside run-webkit-tests. For some tests
- # DumpRenderTree may not output utf-8 text (e.g. webarchives).
- self._save_baseline_data(driver_output.text, ".txt", generate_new_baseline=self._options.new_baseline)
- self._save_baseline_data(driver_output.audio, ".wav", generate_new_baseline=self._options.new_baseline)
+ location = self.VERSION_DIR if self._options.add_platform_exceptions else self.UPDATE
+ self._save_baseline_data(driver_output.text, '.txt', location)
+ self._save_baseline_data(driver_output.audio, '.wav', location)
if self._options.pixel_tests:
- self._save_baseline_data(driver_output.image, ".png", generate_new_baseline=self._options.new_baseline)
+ self._save_baseline_data(driver_output.image, '.png', location)
- def _save_baseline_data(self, data, modifier, generate_new_baseline=True):
- """Saves a new baseline file into the port's baseline directory.
-
- The file will be named simply "<test>-expected<modifier>", suitable for
- use as the expected results in a later run.
-
- Args:
- data: result to be saved as the new baseline
- modifier: type of the result file, e.g. ".txt" or ".png"
- generate_new_baseline: whether to enerate a new, platform-specific
- baseline, or update the existing one
- """
+ def _save_baseline_data(self, data, extension, location):
if data is None:
return
port = self._port
fs = self._filesystem
- if generate_new_baseline:
- relative_dir = fs.dirname(self._test_name)
- baseline_path = port.baseline_path()
- output_dir = fs.join(baseline_path, relative_dir)
- output_file = fs.basename(fs.splitext(self._test_name)[0] + "-expected" + modifier)
- fs.maybe_make_directory(output_dir)
- output_path = fs.join(output_dir, output_file)
+ if location == self.ALONGSIDE_TEST:
+ output_dir = fs.dirname(port.abspath_for_test(self._test_name))
+ elif location == self.VERSION_DIR:
+ output_dir = fs.join(port.baseline_version_dir(), fs.dirname(self._test_name))
+ elif location == self.PLATFORM_DIR:
+ output_dir = fs.join(port.baseline_platform_dir(), fs.dirname(self._test_name))
+ elif location == self.UPDATE:
+ output_dir = fs.dirname(port.expected_filename(self._test_name, extension))
else:
- output_path = port.expected_filename(self._test_name, modifier)
+ raise AssertionError('unrecognized baseline location: %s' % location)
- result_name = fs.relpath(output_path, port.layout_tests_dir())
- _log.info('Writing new expected result "%s"' % result_name)
+ fs.maybe_make_directory(output_dir)
+ output_basename = fs.basename(fs.splitext(self._test_name)[0] + "-expected" + extension)
+ output_path = fs.join(output_dir, output_basename)
+ _log.info('Writing new expected result "%s"' % port.relative_test_filename(output_path))
port.update_baseline(output_path, data)
def _handle_error(self, driver_output, reference_filename=None):
diff --git a/Tools/Scripts/webkitpy/layout_tests/controllers/test_expectations_editor_unittest.py b/Tools/Scripts/webkitpy/layout_tests/controllers/test_expectations_editor_unittest.py
index 31029a35f..09f9670a0 100644
--- a/Tools/Scripts/webkitpy/layout_tests/controllers/test_expectations_editor_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/controllers/test_expectations_editor_unittest.py
@@ -77,7 +77,7 @@ class TestExpectationEditorTests(unittest.TestCase):
def make_parsed_expectation_lines(self, in_string):
parser = TestExpectationParser(self.test_port, self.full_test_list, allow_rebaseline_modifier=False)
- expectation_lines = parser.parse(in_string)
+ expectation_lines = parser.parse('path', in_string)
for expectation_line in expectation_lines:
self.assertFalse(expectation_line.is_invalid())
return expectation_lines
@@ -164,8 +164,8 @@ BUGX2 XP DEBUG : failures/expected/keyboard.html = IMAGE""", 'failures/expected/
BUGX2 XP DEBUG : failures/expected/keyboard.html = IMAGE""")
self.assert_remove_roundtrip("""
-BUGX1 WIN : failures/expected = FAIL""", 'failures/expected/keyboard.html', """
-BUGX1 WIN : failures/expected = FAIL""")
+BUGX1 WIN : failures/expected = TEXT""", 'failures/expected/keyboard.html', """
+BUGX1 WIN : failures/expected = TEXT""")
self.assert_remove_roundtrip("""
BUGX1 XP RELEASE : failures/expected/keyboard.html = IMAGE PASS
@@ -326,11 +326,11 @@ BUG_UPDATE2 XP DEBUG : failures/expected/keyboard.html = TEXT
BUG_UPDATE3 WIN RELEASE : failures/expected/keyboard.html = CRASH
BUGX2 WIN : failures/expected/audio.html = IMAGE""")
- editor.update_expectation(test, self.RELEASE_CONFIGS, set([FAIL]), ['BUG_UPDATE4'])
+ editor.update_expectation(test, self.RELEASE_CONFIGS, set([IMAGE_PLUS_TEXT]), ['BUG_UPDATE4'])
self.assertEquals(TestExpectationSerializer.list_to_string(expectation_lines, converter), """
BUGX1 VISTA WIN7 DEBUG : failures/expected/keyboard.html = IMAGE
BUG_UPDATE2 XP DEBUG : failures/expected/keyboard.html = TEXT
-BUG_UPDATE4 RELEASE : failures/expected/keyboard.html = FAIL
+BUG_UPDATE4 RELEASE : failures/expected/keyboard.html = IMAGE+TEXT
BUGX2 WIN : failures/expected/audio.html = IMAGE""")
editor.update_expectation(test, set(self.test_port.all_test_configurations()), set([TIMEOUT]), ['BUG_UPDATE5'])
diff --git a/Tools/Scripts/webkitpy/layout_tests/controllers/worker.py b/Tools/Scripts/webkitpy/layout_tests/controllers/worker.py
index a1e3bee70..d321b5a41 100644
--- a/Tools/Scripts/webkitpy/layout_tests/controllers/worker.py
+++ b/Tools/Scripts/webkitpy/layout_tests/controllers/worker.py
@@ -29,6 +29,7 @@
"""Handle messages from the Manager and executes actual tests."""
import logging
+import os
import sys
import threading
import time
@@ -65,7 +66,9 @@ class Worker(manager_worker_broker.AbstractWorker):
self._driver = None
self._tests_run_file = None
self._tests_run_filename = None
- self._meter = None
+ self._log_messages = []
+ self._logger = None
+ self._log_handler = None
def __del__(self):
self.cleanup()
@@ -83,29 +86,19 @@ class Worker(manager_worker_broker.AbstractWorker):
self._tests_run_file = self._filesystem.open_text_file_for_writing(tests_run_filename)
def _set_up_logging(self):
+ self._logger = logging.getLogger()
+
# The unix multiprocessing implementation clones the MeteredStream log handler
- # into the child process, so we need to remove it before we can
- # add a new one to get the correct pid logged.
- root_logger = logging.getLogger()
- handler_to_remove = None
- for h in root_logger.handlers:
+ # into the child process, so we need to remove it to avoid duplicate logging.
+ for h in self._logger.handlers:
# log handlers don't have names until python 2.7.
if getattr(h, 'name', '') == metered_stream.LOG_HANDLER_NAME:
- handler_to_remove = h
+ self._logger.removeHandler(h)
break
- if handler_to_remove:
- root_logger.removeHandler(handler_to_remove)
-
- # FIXME: This won't work if the calling process is logging
- # somewhere other than sys.stderr, but I'm not sure
- # if this will be an issue in practice. Also, it would be
- # nice if we trapped all of the messages for a given test
- # and sent them back in finished_test() rather than logging
- # them to stderr.
- if not root_logger.handlers:
- options = self._options
- root_logger.setLevel(logging.DEBUG if options.verbose else logging.INFO)
- self._meter = metered_stream.MeteredStream(sys.stderr, options.verbose, logger=root_logger)
+
+ self._logger.setLevel(logging.DEBUG if self._options.verbose else logging.INFO)
+ self._log_handler = _WorkerLogHandler(self)
+ self._logger.addHandler(self._log_handler)
def _set_up_host_and_port(self):
options = self._options
@@ -132,9 +125,9 @@ class Worker(manager_worker_broker.AbstractWorker):
super(Worker, self).run()
finally:
self.kill_driver()
- self._worker_connection.post_message('done')
_log.debug("%s exiting" % self._name)
self.cleanup()
+ self._worker_connection.post_message('done', self._log_messages)
def handle_test_list(self, src, list_name, test_list):
start_time = time.time()
@@ -171,7 +164,9 @@ class Worker(manager_worker_broker.AbstractWorker):
result = self.run_test_with_timeout(test_input, test_timeout_sec)
elapsed_time = time.time() - start
- self._worker_connection.post_message('finished_test', result, elapsed_time)
+ log_messages = self._log_messages
+ self._log_messages = []
+ self._worker_connection.post_message('finished_test', result, elapsed_time, log_messages)
self.clean_up_after_test(test_input, result)
@@ -181,9 +176,10 @@ class Worker(manager_worker_broker.AbstractWorker):
if self._tests_run_file:
self._tests_run_file.close()
self._tests_run_file = None
- if self._meter:
- self._meter.cleanup()
- self._meter = None
+ if self._log_handler and self._logger:
+ self._logger.removeHandler(self._log_handler)
+ self._log_handler = None
+ self._logger = None
def timeout(self, test_input):
"""Compute the appropriate timeout value for a test."""
@@ -300,3 +296,13 @@ class Worker(manager_worker_broker.AbstractWorker):
def run_single_test(self, driver, test_input):
return single_test_runner.run_single_test(self._port, self._options,
test_input, driver, self._name)
+
+
+class _WorkerLogHandler(logging.Handler):
+ def __init__(self, worker):
+ logging.Handler.__init__(self)
+ self._worker = worker
+ self._pid = os.getpid()
+
+ def emit(self, record):
+ self._worker._log_messages.append(tuple([record.getMessage(), record.created, self._pid]))
diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py
index 0ff0d2f19..af36b2733 100644
--- a/Tools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py
+++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py
@@ -42,7 +42,6 @@ class JSONLayoutResultsGenerator(json_results_generator.JSONResultsGeneratorBase
FAILURE_TO_CHAR = {test_expectations.PASS: json_results_generator.JSONResultsGeneratorBase.PASS_RESULT,
test_expectations.SKIP: json_results_generator.JSONResultsGeneratorBase.SKIP_RESULT,
- test_expectations.FAIL: "Y",
test_expectations.CRASH: "C",
test_expectations.TIMEOUT: "T",
test_expectations.IMAGE: "I",
diff --git a/Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py b/Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py
index c18452421..93ef517f4 100644
--- a/Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py
+++ b/Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py
@@ -57,8 +57,6 @@ def result_was_expected(result, expected_results, test_needs_rebaselining, test_
test_is_skipped: whether test was marked as SKIP"""
if result in expected_results:
return True
- if result in (IMAGE, TEXT, IMAGE_PLUS_TEXT) and FAIL in expected_results:
- return True
if result == MISSING and test_needs_rebaselining:
return True
if result == SKIP and test_is_skipped:
@@ -85,6 +83,21 @@ def has_pixel_failures(actual_results):
return IMAGE in actual_results or IMAGE_PLUS_TEXT in actual_results
+# FIXME: Perhas these two routines should be part of the Port instead?
+BASELINE_SUFFIX_LIST = ('png', 'wav', 'txt')
+
+
+def suffixes_for_expectations(expectations):
+ suffixes = set()
+ if expectations.intersection(set([TEXT, IMAGE_PLUS_TEXT])):
+ suffixes.add('txt')
+ if expectations.intersection(set([IMAGE, IMAGE_PLUS_TEXT])):
+ suffixes.add('png')
+ if AUDIO in expectations:
+ suffixes.add('wav')
+ return set(suffixes)
+
+
# FIXME: This method is no longer used here in this module. Remove remaining callsite in manager.py and this method.
def strip_comments(line):
"""Strips comments from a line and return None if the line is empty
@@ -210,8 +223,8 @@ class TestExpectationParser(object):
self._full_test_list = full_test_list
self._allow_rebaseline_modifier = allow_rebaseline_modifier
- def parse(self, expectations_string):
- expectations = TestExpectationParser._tokenize_list(expectations_string)
+ def parse(self, filename, expectations_string):
+ expectations = TestExpectationParser._tokenize_list(filename, expectations_string)
for expectation_line in expectations:
self._parse_line(expectation_line)
return expectations
@@ -221,6 +234,9 @@ class TestExpectationParser(object):
expectation_line.original_string = test_name
expectation_line.modifiers = [TestExpectationParser.DUMMY_BUG_MODIFIER, TestExpectationParser.SKIP_MODIFIER]
expectation_line.name = test_name
+ # FIXME: we should pass in a more descriptive string here.
+ expectation_line.filename = '<Skipped file>'
+ expectation_line.line_number = 0
expectation_line.expectations = [TestExpectationParser.PASS_EXPECTATION]
self._parse_line(expectation_line)
return expectation_line
@@ -292,7 +308,7 @@ class TestExpectationParser(object):
if (not self._port.test_exists(expectation_line.name)
and not self._port.test_exists(expectation_line.name + '-disabled')):
# Log a warning here since you hit this case any
- # time you update test_expectations.txt without syncing
+ # time you update TestExpectations without syncing
# the LayoutTests directory
expectation_line.warnings.append('Path does not exist.')
return True
@@ -322,8 +338,8 @@ class TestExpectationParser(object):
expectation_line.matching_tests.append(expectation_line.path)
@classmethod
- def _tokenize(cls, expectation_string, line_number=None):
- """Tokenizes a line from test_expectations.txt and returns an unparsed TestExpectationLine instance.
+ def _tokenize(cls, filename, expectation_string, line_number):
+ """Tokenizes a line from TestExpectations and returns an unparsed TestExpectationLine instance.
The format of a test expectation line is:
@@ -335,6 +351,7 @@ class TestExpectationParser(object):
expectation_line = TestExpectationLine()
expectation_line.original_string = expectation_string
expectation_line.line_number = line_number
+ expectation_line.filename = filename
comment_index = expectation_string.find("//")
if comment_index == -1:
comment_index = len(expectation_string)
@@ -361,13 +378,13 @@ class TestExpectationParser(object):
return expectation_line
@classmethod
- def _tokenize_list(cls, expectations_string):
+ def _tokenize_list(cls, filename, expectations_string):
"""Returns a list of TestExpectationLines, one for each line in expectations_string."""
expectation_lines = []
line_number = 0
for line in expectations_string.split("\n"):
line_number += 1
- expectation_lines.append(cls._tokenize(line, line_number))
+ expectation_lines.append(cls._tokenize(filename, line, line_number))
return expectation_lines
@classmethod
@@ -377,7 +394,7 @@ class TestExpectationParser(object):
return [part.strip().lower() for part in space_separated_string.strip().split(' ')]
-class TestExpectationLine:
+class TestExpectationLine(object):
"""Represents a line in test expectations file."""
def __init__(self):
@@ -417,7 +434,7 @@ class TestExpectationLine:
class TestExpectationsModel(object):
"""Represents relational store of all expectations and provides CRUD semantics to manage it."""
- def __init__(self):
+ def __init__(self, shorten_filename=None):
# Maps a test to its list of expectations.
self._test_to_expectations = {}
@@ -427,18 +444,13 @@ class TestExpectationsModel(object):
# Maps a test to a TestExpectationLine instance.
self._test_to_expectation_line = {}
- # List of tests that are in the overrides file (used for checking for
- # duplicates inside the overrides file itself). Note that just because
- # a test is in this set doesn't mean it's necessarily overridding a
- # expectation in the regular expectations; the test might not be
- # mentioned in the regular expectations file at all.
- self._overridding_tests = set()
-
self._modifier_to_tests = self._dict_of_sets(TestExpectations.MODIFIERS)
self._expectation_to_tests = self._dict_of_sets(TestExpectations.EXPECTATIONS)
self._timeline_to_tests = self._dict_of_sets(TestExpectations.TIMELINES)
self._result_type_to_tests = self._dict_of_sets(TestExpectations.RESULT_TYPES)
+ self._shorten_filename = shorten_filename or (lambda x: x)
+
def _dict_of_sets(self, strings_to_constants):
"""Takes a dict of strings->constants and returns a dict mapping
each constant to an empty set."""
@@ -503,31 +515,26 @@ class TestExpectationsModel(object):
def get_expectations(self, test):
return self._test_to_expectations[test]
- def add_expectation_line(self, expectation_line, in_overrides=False, in_skipped=False):
+ def add_expectation_line(self, expectation_line, in_skipped=False):
"""Returns a list of warnings encountered while matching modifiers."""
if expectation_line.is_invalid():
return
for test in expectation_line.matching_tests:
- if not in_skipped and self._already_seen_better_match(test, expectation_line, in_overrides):
+ if not in_skipped and self._already_seen_better_match(test, expectation_line):
continue
self._clear_expectations_for_test(test, expectation_line)
self._test_to_expectation_line[test] = expectation_line
- self._add_test(test, expectation_line, in_overrides)
+ self._add_test(test, expectation_line)
- def _add_test(self, test, expectation_line, in_overrides):
+ def _add_test(self, test, expectation_line):
"""Sets the expected state for a given test.
This routine assumes the test has not been added before. If it has,
use _clear_expectations_for_test() to reset the state prior to
- calling this.
-
- Args:
- test: test to add
- expectation_line: expectation to add
- in_overrides: whether we're parsing the regular expectations or the overridding expectations"""
+ calling this."""
self._test_to_expectations[test] = expectation_line.parsed_expectations
for expectation in expectation_line.parsed_expectations:
self._expectation_to_tests[expectation].add(test)
@@ -549,11 +556,9 @@ class TestExpectationsModel(object):
elif expectation_line.is_flaky():
self._result_type_to_tests[FLAKY].add(test)
else:
+ # FIXME: What is this?
self._result_type_to_tests[FAIL].add(test)
- if in_overrides:
- self._overridding_tests.add(test)
-
def _clear_expectations_for_test(self, test, expectation_line):
"""Remove prexisting expectations for this test.
This happens if we are seeing a more precise path
@@ -576,7 +581,7 @@ class TestExpectationsModel(object):
if test in set_of_tests:
set_of_tests.remove(test)
- def _already_seen_better_match(self, test, expectation_line, in_overrides):
+ def _already_seen_better_match(self, test, expectation_line):
"""Returns whether we've seen a better match already in the file.
Returns True if we've already seen a expectation_line.name that matches more of the test
@@ -589,6 +594,10 @@ class TestExpectationsModel(object):
prev_expectation_line = self._test_to_expectation_line[test]
+ if prev_expectation_line.filename != expectation_line.filename:
+ # We've moved on to a new expectation file, which overrides older ones.
+ return False
+
if len(prev_expectation_line.path) > len(expectation_line.path):
# The previous path matched more of the test.
return True
@@ -597,20 +606,9 @@ class TestExpectationsModel(object):
# This path matches more of the test.
return False
- if in_overrides and test not in self._overridding_tests:
- # We have seen this path, but that's okay because it is
- # in the overrides and the earlier path was in the
- # expectations (not the overrides).
- return False
-
# At this point we know we have seen a previous exact match on this
# base path, so we need to check the two sets of modifiers.
- if in_overrides:
- expectation_source = "override"
- else:
- expectation_source = "expectation"
-
# FIXME: This code was originally designed to allow lines that matched
# more modifiers to override lines that matched fewer modifiers.
# However, we currently view these as errors.
@@ -619,20 +617,28 @@ class TestExpectationsModel(object):
# to be warnings and return False".
if prev_expectation_line.matching_configurations == expectation_line.matching_configurations:
- expectation_line.warnings.append('Duplicate or ambiguous %s.' % expectation_source)
+ expectation_line.warnings.append('Duplicate or ambiguous entry for %s on lines %s:%d and %s:%d.' % (expectation_line.name,
+ self._shorten_filename(prev_expectation_line.filename), prev_expectation_line.line_number,
+ self._shorten_filename(expectation_line.filename), expectation_line.line_number))
return True
if prev_expectation_line.matching_configurations >= expectation_line.matching_configurations:
- expectation_line.warnings.append('More specific entry on line %d overrides line %d' % (expectation_line.line_number, prev_expectation_line.line_number))
+ expectation_line.warnings.append('More specific entry for %s on line %s:%d overrides line %s:%d.' % (expectation_line.name,
+ self._shorten_filename(prev_expectation_line.filename), prev_expectation_line.line_number,
+ self._shorten_filename(expectation_line.filename), expectation_line.line_number))
# FIXME: return False if we want more specific to win.
return True
if prev_expectation_line.matching_configurations <= expectation_line.matching_configurations:
- expectation_line.warnings.append('More specific entry on line %d overrides line %d' % (prev_expectation_line.line_number, expectation_line.line_number))
+ expectation_line.warnings.append('More specific entry for %s on line %s:%d overrides line %s:%d.' % (expectation_line.name,
+ self._shorten_filename(expectation_line.filename), expectation_line.line_number,
+ self._shorten_filename(prev_expectation_line.filename), prev_expectation_line.line_number))
return True
if prev_expectation_line.matching_configurations & expectation_line.matching_configurations:
- expectation_line.warnings.append('Entries on line %d and line %d match overlapping sets of configurations' % (prev_expectation_line.line_number, expectation_line.line_number))
+ expectation_line.warnings.append('Entries for %s on lines %s:%d and %s:%d match overlapping sets of configurations.' % (expectation_line.name,
+ self._shorten_filename(prev_expectation_line.filename), prev_expectation_line.line_number,
+ self._shorten_filename(expectation_line.filename), expectation_line.line_number))
return True
# Configuration sets are disjoint, then.
@@ -645,9 +651,9 @@ class TestExpectations(object):
in which case the expectations apply to all test cases in that
directory and any subdirectory. The format is along the lines of:
- LayoutTests/fast/js/fixme.js = FAIL
- LayoutTests/fast/js/flaky.js = FAIL PASS
- LayoutTests/fast/js/crash.js = CRASH TIMEOUT FAIL PASS
+ LayoutTests/fast/js/fixme.js = TEXT
+ LayoutTests/fast/js/flaky.js = TEXT PASS
+ LayoutTests/fast/js/crash.js = CRASH TIMEOUT TEXT PASS
...
To add modifiers:
@@ -663,20 +669,13 @@ class TestExpectations(object):
Notes:
-A test cannot be both SLOW and TIMEOUT
- -A test should only be one of IMAGE, TEXT, IMAGE+TEXT, AUDIO, or FAIL.
- FAIL is a legacy value that currently means either IMAGE,
- TEXT, or IMAGE+TEXT. Once we have finished migrating the expectations,
- we should change FAIL to have the meaning of IMAGE+TEXT and remove the
- IMAGE+TEXT identifier.
+ -A test should only be one of IMAGE, TEXT, IMAGE+TEXT, or AUDIO.
-A test can be included twice, but not via the same path.
-If a test is included twice, then the more precise path wins.
-CRASH tests cannot be WONTFIX
"""
- TEST_LIST = "test_expectations.txt"
-
EXPECTATIONS = {'pass': PASS,
- 'fail': FAIL,
'text': TEXT,
'image': IMAGE,
'image+text': IMAGE_PLUS_TEXT,
@@ -687,7 +686,6 @@ class TestExpectations(object):
EXPECTATION_DESCRIPTIONS = {SKIP: ('skipped', 'skipped'),
PASS: ('pass', 'passes'),
- FAIL: ('failure', 'failures'),
TEXT: ('text diff mismatch',
'text diff mismatch'),
IMAGE: ('image mismatch', 'image mismatch'),
@@ -699,7 +697,7 @@ class TestExpectations(object):
MISSING: ('no expected result found',
'no expected results found')}
- EXPECTATION_ORDER = (PASS, CRASH, TIMEOUT, MISSING, IMAGE_PLUS_TEXT, TEXT, IMAGE, AUDIO, FAIL, SKIP)
+ EXPECTATION_ORDER = (PASS, CRASH, TIMEOUT, MISSING, IMAGE_PLUS_TEXT, TEXT, IMAGE, AUDIO, SKIP)
BUILD_TYPES = ('debug', 'release')
@@ -722,23 +720,24 @@ class TestExpectations(object):
assert(' ' not in string) # This only handles one expectation at a time.
return cls.EXPECTATIONS.get(string.lower())
- def __init__(self, port, tests=None, is_lint_mode=False):
+ def __init__(self, port, tests=None, is_lint_mode=False, include_overrides=True):
self._full_test_list = tests
self._test_config = port.test_configuration()
self._is_lint_mode = is_lint_mode
- self._model = TestExpectationsModel()
+ self._model = TestExpectationsModel(self._shorten_filename)
self._parser = TestExpectationParser(port, tests, is_lint_mode)
self._port = port
self._skipped_tests_warnings = []
- self._expectations = self._parser.parse(port.test_expectations())
- self._add_expectations(self._expectations, in_overrides=False)
+ expectations_dict = port.expectations_dict()
+ self._expectations = self._parser.parse(expectations_dict.keys()[0], expectations_dict.values()[0])
+ self._add_expectations(self._expectations)
- overrides = port.test_expectations_overrides()
- if overrides:
- overrides_expectations = self._parser.parse(overrides)
- self._add_expectations(overrides_expectations, in_overrides=True)
- self._expectations += overrides_expectations
+ if len(expectations_dict) > 1 and include_overrides:
+ for name in expectations_dict.keys()[1:]:
+ expectations = self._parser.parse(name, expectations_dict[name])
+ self._add_expectations(expectations)
+ self._expectations += expectations
# FIXME: move ignore_tests into port.skipped_layout_tests()
self._add_skipped_tests(port.skipped_layout_tests(tests).union(set(port.get_option('ignore_tests', []))))
@@ -753,8 +752,7 @@ class TestExpectations(object):
return self._model
def get_rebaselining_failures(self):
- return (self._model.get_test_set(REBASELINE, FAIL) |
- self._model.get_test_set(REBASELINE, IMAGE) |
+ return (self._model.get_test_set(REBASELINE, IMAGE) |
self._model.get_test_set(REBASELINE, TEXT) |
self._model.get_test_set(REBASELINE, IMAGE_PLUS_TEXT) |
self._model.get_test_set(REBASELINE, AUDIO))
@@ -813,17 +811,16 @@ class TestExpectations(object):
def is_rebaselining(self, test):
return self._model.has_modifier(test, REBASELINE)
+ def _shorten_filename(self, filename):
+ if filename.startswith(self._port.path_from_webkit_base()):
+ return self._port.host.filesystem.relpath(filename, self._port.path_from_webkit_base())
+ return filename
+
def _report_warnings(self):
warnings = []
- test_expectation_path = self._port.path_to_test_expectations_file()
- if test_expectation_path.startswith(self._port.path_from_webkit_base()):
- test_expectation_path = self._port.host.filesystem.relpath(test_expectation_path, self._port.path_from_webkit_base())
for expectation in self._expectations:
for warning in expectation.warnings:
- warnings.append('%s:%d %s %s' % (test_expectation_path, expectation.line_number, warning, expectation.name if expectation.expectations else expectation.original_string))
-
- for warning in self._skipped_tests_warnings:
- warnings.append('%s%s' % (test_expectation_path, warning))
+ warnings.append('%s:%d %s %s' % (self._shorten_filename(expectation.filename), expectation.line_number, warning, expectation.name if expectation.expectations else expectation.original_string))
if warnings:
self._has_warnings = True
@@ -846,7 +843,7 @@ class TestExpectations(object):
for expectation in self._expectations:
if expectation.name != test or expectation.is_flaky() or not expectation.parsed_expectations:
continue
- if iter(expectation.parsed_expectations).next() not in (FAIL, TEXT, IMAGE, IMAGE_PLUS_TEXT, AUDIO):
+ if iter(expectation.parsed_expectations).next() not in (TEXT, IMAGE, IMAGE_PLUS_TEXT, AUDIO):
continue
if test_configuration not in expectation.matching_configurations:
continue
@@ -869,20 +866,20 @@ class TestExpectations(object):
return TestExpectationSerializer.list_to_string(filter(without_rebaseline_modifier, self._expectations))
- def _add_expectations(self, expectation_list, in_overrides):
+ def _add_expectations(self, expectation_list):
for expectation_line in expectation_list:
if not expectation_line.expectations:
continue
if self._is_lint_mode or self._test_config in expectation_line.matching_configurations:
- self._model.add_expectation_line(expectation_line, in_overrides)
+ self._model.add_expectation_line(expectation_line)
def _add_skipped_tests(self, tests_to_skip):
if not tests_to_skip:
return
- for index, test in enumerate(self._expectations, start=1):
+ for test in self._expectations:
if test.name and test.name in tests_to_skip:
- self._skipped_tests_warnings.append(':%d %s is also in a Skipped file.' % (index, test.name))
+ test.warnings.append('%s:%d %s is also in a Skipped file.' % (test.filename, test.line_number, test.name))
for test_name in tests_to_skip:
expectation_line = self._parser.expectation_for_skipped_test(test_name)
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 0453a0818..7b589b501 100644
--- a/Tools/Scripts/webkitpy/layout_tests/models/test_expectations_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/models/test_expectations_unittest.py
@@ -35,6 +35,12 @@ from webkitpy.layout_tests.models.test_configuration import *
from webkitpy.layout_tests.models.test_expectations import *
from webkitpy.layout_tests.models.test_configuration import *
+try:
+ from collections import OrderedDict
+except ImportError:
+ # Needed for Python < 2.7
+ from webkitpy.thirdparty.ordered_dict import OrderedDict
+
class MockBugManager(object):
def close_bug(self, bug_id, reference_bug_id=None):
@@ -52,16 +58,6 @@ class FunctionsTest(unittest.TestCase):
self.assertEquals(result_was_expected(TEXT, set([PASS]),
False, False), False)
- # test handling of FAIL expectations
- self.assertEquals(result_was_expected(IMAGE_PLUS_TEXT, set([FAIL]),
- False, False), True)
- self.assertEquals(result_was_expected(IMAGE, set([FAIL]),
- False, False), True)
- self.assertEquals(result_was_expected(TEXT, set([FAIL]),
- False, False), True)
- self.assertEquals(result_was_expected(CRASH, set([FAIL]),
- False, False), False)
-
# test handling of SKIPped tests and results
self.assertEquals(result_was_expected(SKIP, set([CRASH]),
False, True), True)
@@ -86,6 +82,14 @@ class FunctionsTest(unittest.TestCase):
self.assertEquals(remove_pixel_failures(set([PASS, IMAGE, CRASH])),
set([PASS, CRASH]))
+ def test_suffixes_for_expectations(self):
+ self.assertEquals(suffixes_for_expectations(set([TEXT])), set(['txt']))
+ self.assertEquals(suffixes_for_expectations(set([IMAGE_PLUS_TEXT])), set(['txt', 'png']))
+ self.assertEquals(suffixes_for_expectations(set([IMAGE])), set(['png']))
+ self.assertEquals(suffixes_for_expectations(set([AUDIO])), set(['wav']))
+ self.assertEquals(suffixes_for_expectations(set([TEXT, IMAGE, CRASH])), set(['txt', 'png']))
+ self.assertEquals(suffixes_for_expectations(set()), set())
+
class Base(unittest.TestCase):
# Note that all of these tests are written assuming the configuration
@@ -119,8 +123,11 @@ BUG_TEST WONTFIX MAC : failures/expected/image.html = IMAGE
"""
def parse_exp(self, expectations, overrides=None, is_lint_mode=False):
- self._port.test_expectations = lambda: expectations
- self._port.test_expectations_overrides = lambda: overrides
+ self._expectations_dict = OrderedDict()
+ self._expectations_dict['expectations'] = expectations
+ if overrides:
+ self._expectations_dict['overrides'] = overrides
+ self._port.expectations_dict = lambda: self._expectations_dict
self._exp = TestExpectations(self._port, self.get_basic_tests(), is_lint_mode)
def assert_exp(self, test, result):
@@ -197,17 +204,17 @@ BUGX WONTFIX : failures/expected = IMAGE
SKIP : failures/expected/image.html""", is_lint_mode=True)
self.assertFalse(True, "ParseError wasn't raised")
except ParseError, e:
- warnings = [u":1 Test lacks BUG modifier. failures/expected/text.html",
- u":1 Unrecognized modifier 'foo' failures/expected/text.html",
- u":2 Missing expectations SKIP : failures/expected/image.html"]
- self.assertEqual(str(e), '\n'.join(self._port.path_to_test_expectations_file() + str(warning) for warning in warnings))
+ warnings = ("expectations:1 Test lacks BUG modifier. failures/expected/text.html\n"
+ "expectations:1 Unrecognized modifier 'foo' failures/expected/text.html\n"
+ "expectations:2 Missing expectations SKIP : failures/expected/image.html")
+ self.assertEqual(str(e), warnings)
try:
self.parse_exp('SKIP : failures/expected/text.html = TEXT', is_lint_mode=True)
self.assertFalse(True, "ParseError wasn't raised")
except ParseError, e:
- warnings = [u':1 Test lacks BUG modifier. failures/expected/text.html']
- self.assertEqual(str(e), '\n'.join(self._port.path_to_test_expectations_file() + str(warning) for warning in warnings))
+ warnings = u'expectations:1 Test lacks BUG modifier. failures/expected/text.html'
+ self.assertEqual(str(e), warnings)
def test_error_on_different_platform(self):
# parse_exp uses a Windows port. Assert errors on Mac show up in lint mode.
@@ -226,6 +233,12 @@ SKIP : failures/expected/image.html""", is_lint_mode=True)
"BUG_OVERRIDE : failures/expected/text.html = IMAGE")
self.assert_exp('failures/expected/text.html', IMAGE)
+ def test_overrides__directory(self):
+ self.parse_exp("BUG_EXP: failures/expected/text.html = TEXT",
+ "BUG_OVERRIDE: failures/expected = CRASH")
+ self.assert_exp('failures/expected/text.html', CRASH)
+ self.assert_exp('failures/expected/image.html', CRASH)
+
def test_overrides__duplicate(self):
self.assert_bad_expectations("BUG_EXP: failures/expected/text.html = TEXT",
"BUG_OVERRIDE : failures/expected/text.html = IMAGE\n"
@@ -261,8 +274,11 @@ class SkippedTests(Base):
def check(self, expectations, overrides, skips, lint=False):
port = MockHost().port_factory.get('qt')
port._filesystem.write_text_file(port._filesystem.join(port.layout_tests_dir(), 'failures/expected/text.html'), 'foo')
- port.test_expectations = lambda: expectations
- port.test_expectations_overrides = lambda: overrides
+ self._expectations_dict = OrderedDict()
+ self._expectations_dict['expectations'] = expectations
+ if overrides:
+ self._expectations_dict['overrides'] = overrides
+ port.expectations_dict = lambda: self._expectations_dict
port.skipped_layout_tests = lambda tests: set(skips)
exp = TestExpectations(port, ['failures/expected/text.html'], lint)
@@ -407,9 +423,9 @@ class RemoveConfigurationsTest(Base):
test_port.test_isfile = lambda test: True
test_config = test_port.test_configuration()
- test_port.test_expectations = lambda: """BUGX LINUX WIN RELEASE : failures/expected/foo.html = TEXT
+ test_port.expectations_dict = lambda: {"expectations": """BUGX LINUX WIN RELEASE : failures/expected/foo.html = TEXT
BUGY WIN MAC DEBUG : failures/expected/foo.html = CRASH
-"""
+"""}
expectations = TestExpectations(test_port, self.get_basic_tests())
actual_expectations = expectations.remove_configuration_from_test('failures/expected/foo.html', test_config)
@@ -425,9 +441,9 @@ BUGY WIN MAC DEBUG : failures/expected/foo.html = CRASH
test_port.test_isfile = lambda test: True
test_config = test_port.test_configuration()
- test_port.test_expectations = lambda: """BUGX WIN RELEASE : failures/expected/foo.html = TEXT
+ test_port.expectations_dict = lambda: {'expectations': """BUGX WIN RELEASE : failures/expected/foo.html = TEXT
BUGY WIN DEBUG : failures/expected/foo.html = CRASH
-"""
+"""}
expectations = TestExpectations(test_port)
actual_expectations = expectations.remove_configuration_from_test('failures/expected/foo.html', test_config)
@@ -459,51 +475,54 @@ class RebaseliningTest(Base):
class TestExpectationParserTests(unittest.TestCase):
+ def _tokenize(self, line):
+ return TestExpectationParser._tokenize('path', line, 0)
+
def test_tokenize_blank(self):
- expectation = TestExpectationParser._tokenize('')
+ expectation = self._tokenize('')
self.assertEqual(expectation.comment, None)
self.assertEqual(len(expectation.warnings), 0)
def test_tokenize_missing_colon(self):
- expectation = TestExpectationParser._tokenize('Qux.')
+ expectation = self._tokenize('Qux.')
self.assertEqual(str(expectation.warnings), '["Missing a \':\'"]')
def test_tokenize_extra_colon(self):
- expectation = TestExpectationParser._tokenize('FOO : : bar')
+ expectation = self._tokenize('FOO : : bar')
self.assertEqual(str(expectation.warnings), '["Extraneous \':\'"]')
def test_tokenize_empty_comment(self):
- expectation = TestExpectationParser._tokenize('//')
+ expectation = self._tokenize('//')
self.assertEqual(expectation.comment, '')
self.assertEqual(len(expectation.warnings), 0)
def test_tokenize_comment(self):
- expectation = TestExpectationParser._tokenize('//Qux.')
+ expectation = self._tokenize('//Qux.')
self.assertEqual(expectation.comment, 'Qux.')
self.assertEqual(len(expectation.warnings), 0)
def test_tokenize_missing_equal(self):
- expectation = TestExpectationParser._tokenize('FOO : bar')
+ expectation = self._tokenize('FOO : bar')
self.assertEqual(str(expectation.warnings), "['Missing expectations\']")
def test_tokenize_extra_equal(self):
- expectation = TestExpectationParser._tokenize('FOO : bar = BAZ = Qux.')
+ expectation = self._tokenize('FOO : bar = BAZ = Qux.')
self.assertEqual(str(expectation.warnings), '["Extraneous \'=\'"]')
def test_tokenize_valid(self):
- expectation = TestExpectationParser._tokenize('FOO : bar = BAZ')
+ expectation = self._tokenize('FOO : bar = BAZ')
self.assertEqual(expectation.comment, None)
self.assertEqual(len(expectation.warnings), 0)
def test_tokenize_valid_with_comment(self):
- expectation = TestExpectationParser._tokenize('FOO : bar = BAZ //Qux.')
+ expectation = self._tokenize('FOO : bar = BAZ //Qux.')
self.assertEqual(expectation.comment, 'Qux.')
self.assertEqual(str(expectation.modifiers), '[\'foo\']')
self.assertEqual(str(expectation.expectations), '[\'baz\']')
self.assertEqual(len(expectation.warnings), 0)
def test_tokenize_valid_with_multiple_modifiers(self):
- expectation = TestExpectationParser._tokenize('FOO1 FOO2 : bar = BAZ //Qux.')
+ expectation = self._tokenize('FOO1 FOO2 : bar = BAZ //Qux.')
self.assertEqual(expectation.comment, 'Qux.')
self.assertEqual(str(expectation.modifiers), '[\'foo1\', \'foo2\']')
self.assertEqual(str(expectation.expectations), '[\'baz\']')
@@ -515,7 +534,7 @@ class TestExpectationParserTests(unittest.TestCase):
test_port.test_exists = lambda test: True
test_config = test_port.test_configuration()
full_test_list = []
- expectation_line = TestExpectationParser._tokenize('')
+ expectation_line = self._tokenize('')
parser = TestExpectationParser(test_port, full_test_list, allow_rebaseline_modifier=False)
parser._parse_line(expectation_line)
self.assertFalse(expectation_line.is_invalid())
@@ -529,14 +548,17 @@ class TestExpectationSerializerTests(unittest.TestCase):
self._serializer = TestExpectationSerializer(self._converter)
unittest.TestCase.__init__(self, testFunc)
+ def _tokenize(self, line):
+ return TestExpectationParser._tokenize('path', line, 0)
+
def assert_round_trip(self, in_string, expected_string=None):
- expectation = TestExpectationParser._tokenize(in_string)
+ expectation = self._tokenize(in_string)
if expected_string is None:
expected_string = in_string
self.assertEqual(expected_string, self._serializer.to_string(expectation))
def assert_list_round_trip(self, in_string, expected_string=None):
- expectations = TestExpectationParser._tokenize_list(in_string)
+ expectations = TestExpectationParser._tokenize_list('path', in_string)
if expected_string is None:
expected_string = in_string
self.assertEqual(expected_string, TestExpectationSerializer.list_to_string(expectations, self._converter))
@@ -588,10 +610,10 @@ class TestExpectationSerializerTests(unittest.TestCase):
self.assertEqual(self._serializer._parsed_expectations_string(expectation_line), '')
expectation_line.parsed_expectations = set([IMAGE_PLUS_TEXT])
self.assertEqual(self._serializer._parsed_expectations_string(expectation_line), 'image+text')
- expectation_line.parsed_expectations = set([PASS, FAIL])
- self.assertEqual(self._serializer._parsed_expectations_string(expectation_line), 'pass fail')
- expectation_line.parsed_expectations = set([FAIL, PASS])
- self.assertEqual(self._serializer._parsed_expectations_string(expectation_line), 'pass fail')
+ expectation_line.parsed_expectations = set([PASS, IMAGE])
+ self.assertEqual(self._serializer._parsed_expectations_string(expectation_line), 'pass image')
+ expectation_line.parsed_expectations = set([TEXT, PASS])
+ self.assertEqual(self._serializer._parsed_expectations_string(expectation_line), 'pass text')
def test_parsed_modifier_string(self):
expectation_line = TestExpectationLine()
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/apple.py b/Tools/Scripts/webkitpy/layout_tests/port/apple.py
index b6e3b1d2e..8807d40d7 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/apple.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/apple.py
@@ -90,6 +90,6 @@ class ApplePort(WebKitPort):
version = self.FUTURE_VERSION
for build_type in self.ALL_BUILD_TYPES:
- # But at some later point we may need to make these configurable by the MacPort and WinPort subclasses.
- configurations.append(TestConfiguration(version=version, architecture='x86', build_type=build_type))
+ for architecture in self.ARCHITECTURES:
+ configurations.append(TestConfiguration(version=version, architecture=architecture, build_type=build_type))
return configurations
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/base.py b/Tools/Scripts/webkitpy/layout_tests/port/base.py
index c0e13dfcb..9e73b2c55 100755
--- a/Tools/Scripts/webkitpy/layout_tests/port/base.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/base.py
@@ -34,8 +34,16 @@ import cgi
import difflib
import errno
import os
+import optparse
import re
+try:
+ from collections import OrderedDict
+except ImportError:
+ # Needed for Python < 2.7
+ from webkitpy.thirdparty.ordered_dict import OrderedDict
+
+
from webkitpy.common import find_files
from webkitpy.common import read_checksum_from_png
from webkitpy.common.memoized import memoized
@@ -54,19 +62,6 @@ from webkitpy.layout_tests.servers import websocket_server
_log = logutils.get_logger(__file__)
-class DummyOptions(object):
- """Fake implementation of optparse.Values. Cloned from webkitpy.tool.mocktool.MockOptions."""
-
- def __init__(self, *args, **kwargs):
- # The caller can set option values using keyword arguments. We don't
- # set any values by default because we don't know how this
- # object will be used. Generally speaking unit tests should
- # subclass this or provider wrapper functions that set a common
- # set of options.
- for key, value in kwargs.items():
- self.__dict__[key] = value
-
-
# FIXME: This class should merge with WebKitPort now that Chromium behaves mostly like other webkit ports.
class Port(object):
"""Abstract class for Port-specific hooks for the layout_test package."""
@@ -104,7 +99,7 @@ class Port(object):
# FIXME: Ideally we'd have a package-wide way to get a
# well-formed options object that had all of the necessary
# options defined on it.
- self._options = options or DummyOptions()
+ self._options = options or optparse.Values()
self.host = host
self._executive = host.executive
@@ -159,17 +154,7 @@ class Port(object):
def default_child_processes(self):
"""Return the number of DumpRenderTree instances to use for this port."""
- cpu_count = self._executive.cpu_count()
- # Make sure we have enough ram to support that many instances:
- free_memory = self.host.platform.free_bytes_memory()
- if free_memory:
- bytes_per_drt = 200 * 1024 * 1024 # Assume each DRT needs 200MB to run.
- supportable_instances = max(free_memory / bytes_per_drt, 1) # Always use one process, even if we don't have space for it.
- if supportable_instances < cpu_count:
- # FIXME: The Printer isn't initialized when this is called, so using _log would just show an unitialized logger error.
- print "This machine could support %s child processes, but only has enough memory for %s." % (cpu_count, supportable_instances)
- return min(supportable_instances, cpu_count)
- return cpu_count
+ return self._executive.cpu_count()
def worker_startup_delay_secs(self):
# FIXME: If we start workers up too quickly, DumpRenderTree appears
@@ -180,6 +165,15 @@ class Port(object):
def baseline_path(self):
"""Return the absolute path to the directory to store new baselines in for this port."""
+ # FIXME: remove once all callers are calling either baseline_version_dir() or baseline_platform_dir()
+ return self.baseline_version_dir()
+
+ def baseline_platform_dir(self):
+ """Return the absolute path to the default (version-independent) platform-specific results."""
+ return self._filesystem.join(self.layout_tests_dir(), 'platform', self.port_name)
+
+ def baseline_version_dir(self):
+ """Return the absolute path to the platform-and-version-specific results."""
baseline_search_paths = self.get_option('additional_platform_directory', []) + self.baseline_search_path()
return baseline_search_paths[0]
@@ -489,8 +483,22 @@ class Port(object):
return reftest_list.get(self._filesystem.join(self.layout_tests_dir(), test_name), [])
def tests(self, paths):
- """Return the list of tests found."""
- return self._real_tests(paths).union(self._virtual_tests(paths, self.populated_virtual_test_suites()))
+ """Return the list of tests found. Both generic and platform-specific tests matching paths should be returned."""
+ expanded_paths = self._expanded_paths(paths)
+ return self._real_tests(expanded_paths).union(self._virtual_tests(expanded_paths, self.populated_virtual_test_suites()))
+
+ def _expanded_paths(self, paths):
+ expanded_paths = []
+ fs = self._filesystem
+ all_platform_dirs = [path for path in fs.glob(fs.join(self.layout_tests_dir(), 'platform', '*')) if fs.isdir(path)]
+ for path in paths:
+ expanded_paths.append(path)
+ if self.test_isdir(path) and not path.startswith('platform'):
+ for platform_dir in all_platform_dirs:
+ if fs.isdir(fs.join(platform_dir, path)):
+ expanded_paths.append(self.relative_test_filename(fs.join(platform_dir, path)))
+
+ return expanded_paths
def _real_tests(self, paths):
# When collecting test cases, skip these directories
@@ -657,30 +665,31 @@ class Port(object):
return self._architecture
def get_option(self, name, default_value=None):
- # FIXME: Eventually we should not have to do a test for
- # hasattr(), and we should be able to just do
- # self.options.value. See additional FIXME in the constructor.
- if hasattr(self._options, name):
- return getattr(self._options, name)
- return default_value
+ return getattr(self._options, name, default_value)
def set_option_default(self, name, default_value):
- # FIXME: Callers could also use optparse_parser.Values.ensure_value,
- # since this should always be a optparse_parser.Values object.
- if not hasattr(self._options, name) or getattr(self._options, name) is None:
- return setattr(self._options, name, default_value)
+ return self._options.ensure_value(name, default_value)
def path_from_webkit_base(self, *comps):
"""Returns the full path to path made by joining the top of the
WebKit source tree and the list of path components in |*comps|."""
return self._config.path_from_webkit_base(*comps)
+ @memoized
def path_to_test_expectations_file(self):
"""Update the test expectations to the passed-in string.
This is used by the rebaselining tool. Raises NotImplementedError
if the port does not use expectations files."""
- raise NotImplementedError('Port.path_to_test_expectations_file')
+
+ # FIXME: We need to remove this when we make rebaselining work with multiple files and just generalize expectations_files().
+
+ # test_expectations are always in mac/ not mac-leopard/ by convention, hence we use port_name instead of name().
+ port_name = self.port_name
+ if port_name.startswith('chromium') or port_name.startswith('google-chrome'):
+ port_name = 'chromium'
+
+ return self._filesystem.join(self._webkit_baseline_path(port_name), 'TestExpectations')
def relative_test_filename(self, filename):
"""Returns a test_name a realtive unix-style path for a filename under the LayoutTests
@@ -765,7 +774,7 @@ class Port(object):
def show_results_html_file(self, results_filename):
"""This routine should display the HTML file pointed at by
results_filename in a users' browser."""
- return self.host.user.open_url(path.abspath_to_uri(results_filename))
+ return self.host.user.open_url(path.abspath_to_uri(self.host.platform, results_filename))
def create_driver(self, worker_number, no_timeout=False):
"""Return a newly created Driver subclass for starting/stopping the test driver."""
@@ -876,28 +885,32 @@ 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 test_expectations(self):
- """Returns the test expectations for this port.
-
- Basically this string should contain the equivalent of a
- test_expectations file. See test_expectations.py for more details."""
- return self._filesystem.read_text_file(self.path_to_test_expectations_file())
+ 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.
+ If the name is a path, the file can be considered updatable for things like rebaselining,
+ so don't use names that are paths if they're not paths.
+ Generally speaking the ordering should be files in the filesystem in cascade order
+ (TestExpectations followed by Skipped, if the port honors both formats),
+ then any built-in expectations (e.g., from compile-time exclusions), then --additional-expectations options."""
+ # FIXME: rename this to test_expectations() once all the callers are updated to know about the ordered dict.
+ expectations = OrderedDict()
- def test_expectations_overrides(self):
- """Returns an optional set of overrides for the test_expectations.
+ for path in self.expectations_files():
+ expectations[path] = self._filesystem.read_text_file(path)
- This is used by ports that have code in two repositories, and where
- it is possible that you might need "downstream" expectations that
- temporarily override the "upstream" expectations until the port can
- sync up the two repos."""
- overrides = ''
for path in self.get_option('additional_expectations', []):
- if self._filesystem.exists(self._filesystem.expanduser(path)):
+ expanded_path = self._filesystem.expanduser(path)
+ if self._filesystem.exists(expanded_path):
_log.debug("reading additional_expectations from path '%s'" % path)
- overrides += self._filesystem.read_text_file(self._filesystem.expanduser(path))
+ expectations[path] = self._filesystem.read_text_file(expanded_path)
else:
_log.warning("additional_expectations path '%s' does not exist" % path)
- return overrides or None
+ return expectations
+
+ def expectations_files(self):
+ # FIXME: see comment in path_to_expectations_file().
+ return [self.path_to_test_expectations_file()]
def repository_paths(self):
"""Returns a list of (repository_name, repository_path) tuples of its depending code base.
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py
index 92edfe232..f7afb7b22 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py
@@ -60,21 +60,7 @@ class PortTest(unittest.TestCase):
def test_default_child_processes(self):
port = self.make_port()
- # Even though the MockPlatformInfo shows 1GB free memory (enough for 5 DRT instances)
- # we're still limited by the 2 mock cores we have:
- self.assertEqual(port.default_child_processes(), 2)
- bytes_for_drt = 200 * 1024 * 1024
-
- port.host.platform.free_bytes_memory = lambda: bytes_for_drt
- expected_stdout = "This machine could support 2 child processes, but only has enough memory for 1.\n"
- child_processes = OutputCapture().assert_outputs(self, port.default_child_processes, (), expected_stdout=expected_stdout)
- self.assertEqual(child_processes, 1)
-
- # Make sure that we always use one process, even if we don't have the memory for it.
- port.host.platform.free_bytes_memory = lambda: bytes_for_drt - 1
- expected_stdout = "This machine could support 2 child processes, but only has enough memory for 1.\n"
- child_processes = OutputCapture().assert_outputs(self, port.default_child_processes, (), expected_stdout=expected_stdout)
- self.assertEqual(child_processes, 1)
+ self.assertNotEquals(port.default_child_processes(), None)
def test_format_wdiff_output_as_html(self):
output = "OUTPUT %s %s %s" % (Port._WDIFF_DEL, Port._WDIFF_ADD, Port._WDIFF_END)
@@ -280,31 +266,33 @@ class PortTest(unittest.TestCase):
def test_additional_expectations(self):
port = self.make_port(port_name='foo')
-
+ port.port_name = 'foo'
+ port._filesystem.write_text_file('/mock-checkout/LayoutTests/platform/foo/TestExpectations', '')
port._filesystem.write_text_file(
'/tmp/additional-expectations-1.txt', 'content1\n')
port._filesystem.write_text_file(
'/tmp/additional-expectations-2.txt', 'content2\n')
- self.assertEquals(None, port.test_expectations_overrides())
+ self.assertEquals('\n'.join(port.expectations_dict().values()), '')
port._options.additional_expectations = [
'/tmp/additional-expectations-1.txt']
- self.assertEquals('content1\n', port.test_expectations_overrides())
+ self.assertEquals('\n'.join(port.expectations_dict().values()), '\ncontent1\n')
port._options.additional_expectations = [
'/tmp/nonexistent-file', '/tmp/additional-expectations-1.txt']
- self.assertEquals('content1\n', port.test_expectations_overrides())
+ self.assertEquals('\n'.join(port.expectations_dict().values()), '\ncontent1\n')
port._options.additional_expectations = [
'/tmp/additional-expectations-1.txt', '/tmp/additional-expectations-2.txt']
- self.assertEquals('content1\ncontent2\n', port.test_expectations_overrides())
+ self.assertEquals('\n'.join(port.expectations_dict().values()), '\ncontent1\n\ncontent2\n')
def test_uses_test_expectations_file(self):
port = self.make_port(port_name='foo')
- port.path_to_test_expectations_file = lambda: '/mock-results/test_expectations.txt'
+ port.port_name = 'foo'
+ port.path_to_test_expectations_file = lambda: '/mock-results/TestExpectations'
self.assertFalse(port.uses_test_expectations_file())
- port._filesystem = MockFileSystem({'/mock-results/test_expectations.txt': ''})
+ port._filesystem = MockFileSystem({'/mock-results/TestExpectations': ''})
self.assertTrue(port.uses_test_expectations_file())
def test_find_no_paths_specified(self):
@@ -406,9 +394,7 @@ class PortTest(unittest.TestCase):
self.assertVirtual(port.check_image_diff)
self.assertVirtual(port.create_driver, 0)
self.assertVirtual(port.diff_image, None, None)
- self.assertVirtual(port.path_to_test_expectations_file)
self.assertVirtual(port.default_results_directory)
- self.assertVirtual(port.test_expectations)
self.assertVirtual(port._path_to_apache)
self.assertVirtual(port._path_to_apache_config_file)
self.assertVirtual(port._path_to_driver)
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/builders.py b/Tools/Scripts/webkitpy/layout_tests/port/builders.py
index 1249bb543..cc0fa6932 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/builders.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/builders.py
@@ -53,13 +53,16 @@ _exact_matches = {
"Webkit Mac10.7": {"port_name": "chromium-mac-lion", "specifiers": set(["lion"])},
# These builders are on build.webkit.org.
+ "Apple Lion Release WK1 (Tests)": {"port_name": "mac-lion", "specifiers": set(["lion"])},
+ "Apple Lion Debug WK1 (Tests)": {"port_name": "mac-lion", "specifiers": set(["lion", "debug"])},
+ "Apple Lion Release WK2 (Tests)": {"port_name": "mac-lion", "specifiers": set(["lion", "wk2"])},
+ "Apple Lion Debug WK2 (Tests)": {"port_name": "mac-lion", "specifiers": set(["lion", "wk2", "debug"])},
+
+ "Apple Win XP Debug (Tests)": {"port_name": "win-xp", "specifiers": set(["win", "debug"])},
+ "Apple Win 7 Release (Tests)": {"port_name": "win-xp", "specifiers": set(["win"])},
+
"GTK Linux 32-bit Debug": {"port_name": "gtk", "specifiers": set(["gtk"])},
- "Leopard Intel Debug (Tests)": {"port_name": "mac-leopard", "specifiers": set(["leopard"])},
- "SnowLeopard Intel Release (Tests)": {"port_name": "mac-snowleopard", "specifiers": set(["snowleopard"])},
- "SnowLeopard Intel Release (WebKit2 Tests)": {"port_name": "mac-snowleopard-wk2", "specifiers": set(["wk2"])},
"Qt Linux Release": {"port_name": "qt-linux", "specifiers": set(["win", "linux", "mac"])},
- "Windows XP Debug (Tests)": {"port_name": "win-xp", "specifiers": set(["win"])},
- "Windows 7 Release (WebKit2 Tests)": {"port_name": "win-future-wk2", "specifiers": set(["wk2"])},
"EFL Linux Release": {"port_name": "efl", "specifiers": set(["efl"])},
}
@@ -67,7 +70,7 @@ _exact_matches = {
_fuzzy_matches = {
# These builders are on build.webkit.org.
r"SnowLeopard": "mac-snowleopard",
- r"Lion": "mac-lion",
+ r"Apple Lion": "mac-lion",
r"Leopard": "mac-leopard",
r"Windows": "win",
r"GTK": "gtk",
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium.py
index 9db4cb570..2c1911072 100755
--- a/Tools/Scripts/webkitpy/layout_tests/port/chromium.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium.py
@@ -37,20 +37,12 @@ import signal
import subprocess
import sys
import time
-import webbrowser
-from webkitpy.common.config import urls
from webkitpy.common.system import executive
-from webkitpy.common.system.path import cygpath
-from webkitpy.layout_tests.controllers.manager import Manager
-from webkitpy.layout_tests.models import test_expectations
from webkitpy.layout_tests.models.test_configuration import TestConfiguration
from webkitpy.layout_tests.port.base import Port, VirtualTestSuite
from webkitpy.layout_tests.port.driver import DriverOutput
from webkitpy.layout_tests.port.webkit import WebKitDriver
-from webkitpy.layout_tests.port import builders
-from webkitpy.layout_tests.servers import http_server
-from webkitpy.layout_tests.servers import websocket_server
_log = logging.getLogger(__name__)
@@ -85,6 +77,26 @@ class ChromiumPort(Port):
'android': ['icecreamsandwich'],
}
+ DEFAULT_BUILD_DIRECTORIES = ('out',)
+
+ @classmethod
+ def _static_build_path(cls, filesystem, build_directory, chromium_base, webkit_base, *comps):
+ if build_directory:
+ return filesystem.join(build_directory, *comps)
+
+ for directory in cls.DEFAULT_BUILD_DIRECTORIES:
+ base_dir = filesystem.join(chromium_base, directory)
+ if filesystem.exists(base_dir):
+ return filesystem.join(base_dir, *comps)
+
+ for directory in cls.DEFAULT_BUILD_DIRECTORIES:
+ base_dir = filesystem.join(webkit_base, directory)
+ if filesystem.exists(base_dir):
+ return filesystem.join(base_dir, *comps)
+
+ # We have to default to something, so pick the last one.
+ return filesystem.join(base_dir, *comps)
+
@classmethod
def _chromium_base_dir(cls, filesystem):
module_path = filesystem.path_to_module(cls.__module__)
@@ -232,9 +244,6 @@ class ChromiumPort(Port):
self._chromium_base_dir_path = self._chromium_base_dir(self._filesystem)
return self._filesystem.join(self._chromium_base_dir_path, *comps)
- def path_to_test_expectations_file(self):
- return self.path_from_webkit_base('LayoutTests', 'platform', 'chromium', 'test_expectations.txt')
-
def setup_environ_for_server(self, server_name=None):
clean_env = super(ChromiumPort, self).setup_environ_for_server(server_name)
# Webkit Linux (valgrind layout) bot needs these envvars.
@@ -293,14 +302,6 @@ class ChromiumPort(Port):
def all_baseline_variants(self):
return self.ALL_BASELINE_VARIANTS
- def test_expectations(self):
- """Returns the test expectations for this port.
-
- Basically this string should contain the equivalent of a
- test_expectations file. See test_expectations.py for more details."""
- expectations_path = self.path_to_test_expectations_file()
- return self._filesystem.read_text_file(expectations_path)
-
def _generate_all_test_configurations(self):
"""Returns a sequence of the TestConfigurations the port supports."""
# By default, we assume we want to test every graphics type in
@@ -320,37 +321,12 @@ class ChromiumPort(Port):
'win_layout_rel',
])
- def _expectations_file_contents(self, filetype, filepath):
- if self._filesystem.exists(filepath):
- _log.debug(
- "reading %s test_expectations overrides from file '%s'" %
- (filetype, filepath))
- return (self._filesystem.read_text_file(filepath) or '')
- else:
- # FIXME: This allows this to work with mock checkouts;
- # This probably shouldn't be used with a mock checkout, though.
- if not 'mock-checkout' in filepath:
- _log.warning(
- "%s test_expectations overrides file '%s' does not exist" %
- (filetype, filepath))
- return ''
-
- def test_expectations_overrides(self):
- combined_overrides = ''
- combined_overrides += self._expectations_file_contents(
- 'skia', self.path_from_chromium_base(
- 'skia', 'skia_test_expectations.txt'))
- # FIXME: It seems bad that run_webkit_tests.py uses a hardcoded dummy
- # builder string instead of just using None.
+ def expectations_files(self):
+ paths = [self.path_to_test_expectations_file(), self.path_from_chromium_base('skia', 'skia_test_expectations.txt')]
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:
- combined_overrides += self._expectations_file_contents(
- 'chromium', self.path_from_chromium_base(
- 'webkit', 'tools', 'layout_tests', 'test_expectations.txt'))
-
- base_overrides = super(ChromiumPort, self).test_expectations_overrides()
- combined_overrides += (base_overrides or '')
- return combined_overrides
+ paths.append(self.path_from_chromium_base('webkit', 'tools', 'layout_tests', 'test_expectations.txt'))
+ return paths
def repository_paths(self):
repos = super(ChromiumPort, self).repository_paths()
@@ -387,6 +363,9 @@ class ChromiumPort(Port):
# or any subclasses.
#
+ def _build_path(self, *comps):
+ return self._static_build_path(self._filesystem, self.get_option('build_directory'), self.path_from_chromium_base(), self.path_from_webkit_base(), *comps)
+
def _check_driver_build_up_to_date(self, configuration):
if configuration in ('Debug', 'Release'):
try:
@@ -667,6 +646,9 @@ class ChromiumDriver(WebKitDriver):
test_time=run_time, timeout=timeout, error=error)
def start(self, pixel_tests, per_test_args):
+ if not self._test_shell:
+ return super(ChromiumDriver, self).start(pixel_tests, per_test_args)
+
if not self._proc:
self._start(pixel_tests, per_test_args)
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_android.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_android.py
index c701fbfb3..6568692e8 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_android.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_android.py
@@ -152,6 +152,12 @@ class ChromiumAndroidPort(chromium.ChromiumPort):
def __init__(self, host, port_name, **kwargs):
chromium.ChromiumPort.__init__(self, host, port_name, **kwargs)
+ # FIXME: Stop using test_shell mode: https://bugs.webkit.org/show_bug.cgi?id=88542
+ if not hasattr(self._options, 'additional_drt_flag'):
+ self._options.additional_drt_flag = []
+ if not '--test-shell' in self._options.additional_drt_flag:
+ self._options.additional_drt_flag.append('--test-shell')
+
# The Chromium port for Android always uses the hardware GPU path.
self._options.enable_hardware_gpu = True
@@ -209,7 +215,7 @@ class ChromiumAndroidPort(chromium.ChromiumPort):
# chromium-android.
# FIXME: This is a temporary measure to reduce the manual work when
# updating WebKit. This method should be removed when we merge
- # test_expectations_android.txt into test_expectations.txt.
+ # test_expectations_android.txt into TestExpectations.
expectations = chromium.ChromiumPort.test_expectations(self)
return expectations.replace('LINUX ', 'LINUX ANDROID ')
@@ -267,7 +273,7 @@ class ChromiumAndroidPort(chromium.ChromiumPort):
def _path_to_driver(self, configuration=None):
if not configuration:
configuration = self.get_option('configuration')
- return self._build_path(configuration, 'DumpRenderTree_apk/ChromeNativeTests-debug.apk')
+ return self._build_path(configuration, 'DumpRenderTree_apk/DumpRenderTree-debug.apk')
def _path_to_helper(self):
return self._build_path(self.get_option('configuration'), 'forwarder')
@@ -477,9 +483,23 @@ class ChromiumAndroidDriver(chromium.ChromiumDriver):
ChromiumAndroidDriver._started_driver = self
+ retries = 0
+ while not self._start_once(pixel_tests, per_test_args):
+ _log.error('Failed to start DumpRenderTree application. Log:\n' + self._port._get_logcat())
+ retries += 1
+ if retries >= 3:
+ raise AssertionError('Failed to start DumpRenderTree application multiple times. Give up.')
+ self.stop()
+ time.sleep(2)
+
+ def _start_once(self, pixel_tests, per_test_args):
self._port._run_adb_command(['logcat', '-c'])
self._port._run_adb_command(['shell', 'echo'] + self.cmd_line(pixel_tests, per_test_args) + ['>', COMMAND_LINE_FILE])
- self._port._run_adb_command(['shell', 'am', 'start', '-n', DRT_ACTIVITY_FULL_NAME])
+ start_result = self._port._run_adb_command(['shell', 'am', 'start', '-n', DRT_ACTIVITY_FULL_NAME])
+ if start_result.find('Exception') != -1:
+ _log.error('Failed to start DumpRenderTree application. Exception:\n' + start_result)
+ return False
+
seconds = 0
while (not self._file_exists_on_device(self._in_fifo_path) or
not self._file_exists_on_device(self._out_fifo_path) or
@@ -487,8 +507,7 @@ class ChromiumAndroidDriver(chromium.ChromiumDriver):
time.sleep(1)
seconds += 1
if seconds >= DRT_START_STOP_TIMEOUT_SECS:
- _log.error('Failed to start DumpRenderTreeApplication. Log:\n' + self._port._get_logcat())
- raise AssertionError('Failed to start DumpRenderTree application.')
+ return False
shell_cmd = self._port._adb_command + ['shell']
executive = self._port._executive
@@ -531,7 +550,7 @@ class ChromiumAndroidDriver(chromium.ChromiumDriver):
else:
# Inform the deadlock detector that the startup is successful without deadlock.
normal_startup_event.set()
- return
+ return True
def run_test(self, driver_input):
driver_output = chromium.ChromiumDriver.run_test(self, driver_input)
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py
index 3e07a6a73..05ee62dd6 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py
@@ -57,6 +57,8 @@ class ChromiumLinuxPort(chromium.ChromiumPort):
],
}
+ DEFAULT_BUILD_DIRECTORIES = ('sconsbuild', 'out')
+
@classmethod
def _determine_driver_path_statically(cls, host, options):
config_object = config.Config(host.executive, host.filesystem)
@@ -70,18 +72,6 @@ class ChromiumLinuxPort(chromium.ChromiumPort):
return cls._static_build_path(host.filesystem, build_directory, chromium_base, webkit_base, configuration, 'DumpRenderTree')
@staticmethod
- def _static_build_path(filesystem, build_directory, chromium_base, webkit_base, *comps):
- if build_directory:
- return filesystem.join(build_directory, *comps)
- if filesystem.exists(filesystem.join(chromium_base, 'sconsbuild')):
- return filesystem.join(chromium_base, 'sconsbuild', *comps)
- if filesystem.exists(filesystem.join(chromium_base, 'out')):
- return filesystem.join(chromium_base, 'out', *comps)
- if filesystem.exists(filesystem.join(webkit_base, 'sconsbuild')):
- return filesystem.join(webkit_base, 'sconsbuild', *comps)
- return filesystem.join(webkit_base, 'out', *comps)
-
- @staticmethod
def _determine_architecture(filesystem, executive, driver_path):
file_output = ''
if filesystem.exists(driver_path):
@@ -137,9 +127,6 @@ class ChromiumLinuxPort(chromium.ChromiumPort):
# PROTECTED METHODS
#
- def _build_path(self, *comps):
- return self._static_build_path(self._filesystem, self.get_option('build_directory'), self.path_from_chromium_base(), self.path_from_webkit_base(), *comps)
-
def _check_apache_install(self):
result = self._check_file_exists(self._path_to_apache(), "apache2")
result = self._check_file_exists(self._path_to_apache_config_file(), "apache2 config file") and result
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux_unittest.py
index 647596227..e0ef728bd 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux_unittest.py
@@ -30,6 +30,7 @@ import unittest
from webkitpy.common.system import executive_mock
from webkitpy.common.system.systemhost_mock import MockSystemHost
+from webkitpy.tool.mocktool import MockOptions
from webkitpy.layout_tests.port import chromium_linux
from webkitpy.layout_tests.port import port_testcase
@@ -89,6 +90,29 @@ class ChromiumLinuxPortTest(port_testcase.PortTestCase):
def test_operating_system(self):
self.assertEqual('linux', self.make_port().operating_system())
+ def test_build_path(self):
+ # Test that optional paths are used regardless of whether they exist.
+ options = MockOptions(configuration='Release', build_directory='/foo')
+ self.assert_build_path(options, ['/mock-checkout/Source/WebKit/chromium/out'], '/foo')
+
+ # Test that optional relative paths are returned unmodified.
+ options = MockOptions(configuration='Release', build_directory='foo')
+ self.assert_build_path(options, ['/mock-checkout/Source/WebKit/chromium/out'], 'foo')
+
+ # Test that we look in a chromium directory before the webkit directory.
+ options = MockOptions(configuration='Release', build_directory=None)
+ self.assert_build_path(options, ['/mock-checkout/Source/WebKit/chromium/out', '/mock-checkout/out'], '/mock-checkout/Source/WebKit/chromium/out')
+
+ # Test that we prefer the legacy dir over the new dir.
+ options = MockOptions(configuration='Release', build_directory=None)
+ self.assert_build_path(options, ['/mock-checkout/Source/WebKit/chromium/sconsbuild', '/mock-checkout/Source/WebKit/chromium/out'], '/mock-checkout/Source/WebKit/chromium/sconsbuild')
+
+ def test_driver_name_option(self):
+ self.assertTrue(self.make_port()._path_to_driver().endswith('DumpRenderTree'))
+ self.assertTrue(self.make_port(options=MockOptions(driver_name='OtherDriver'))._path_to_driver().endswith('OtherDriver'))
+
+ def test_path_to_image_diff(self):
+ self.assertEquals(self.make_port()._path_to_image_diff(), '/mock-checkout/out/Release/ImageDiff')
if __name__ == '__main__':
port_testcase.main()
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py
index e1cafccfd..698db7266 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py
@@ -68,6 +68,8 @@ class ChromiumMacPort(chromium.ChromiumPort):
],
}
+ DEFAULT_BUILD_DIRECTORIES = ('xcodebuild', 'out')
+
@classmethod
def determine_full_port_name(cls, host, options, port_name):
if port_name.endswith('-mac'):
@@ -99,40 +101,10 @@ class ChromiumMacPort(chromium.ChromiumPort):
def operating_system(self):
return 'mac'
- def default_child_processes(self):
- # FIXME: As a temporary workaround while we figure out what's going
- # on with https://bugs.webkit.org/show_bug.cgi?id=83076, reduce by
- # half the # of workers we run by default on bigger machines.
- default_count = super(ChromiumMacPort, self).default_child_processes()
- if default_count >= 8:
- cpu_count = self._executive.cpu_count()
- return max(1, min(default_count, int(cpu_count / 2)))
- return default_count
-
#
# PROTECTED METHODS
#
- def _build_path(self, *comps):
- if self.get_option('build_directory'):
- return self._filesystem.join(self.get_option('build_directory'),
- *comps)
- base = self.path_from_chromium_base()
- path = self._filesystem.join(base, 'out', *comps)
- if self._filesystem.exists(path):
- return path
-
- path = self._filesystem.join(base, 'xcodebuild', *comps)
- if self._filesystem.exists(path):
- return path
-
- base = self.path_from_webkit_base()
- path = self._filesystem.join(base, 'out', *comps)
- if self._filesystem.exists(path):
- return path
-
- return self._filesystem.join(base, 'xcodebuild', *comps)
-
def check_wdiff(self, logging=True):
try:
# We're ignoring the return and always returning True
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 51948eceb..a85beca74 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac_unittest.py
@@ -30,6 +30,7 @@ import unittest
from webkitpy.layout_tests.port import chromium_mac
from webkitpy.layout_tests.port import port_testcase
+from webkitpy.tool.mocktool import MockOptions
class ChromiumMacPortTest(port_testcase.PortTestCase):
@@ -81,6 +82,30 @@ class ChromiumMacPortTest(port_testcase.PortTestCase):
def test_operating_system(self):
self.assertEqual('mac', self.make_port().operating_system())
+ def test_build_path(self):
+ # Test that optional paths are used regardless of whether they exist.
+ options = MockOptions(configuration='Release', build_directory='/foo')
+ self.assert_build_path(options, ['/mock-checkout/Source/WebKit/chromium/out'], '/foo')
+
+ # Test that optional relative paths are returned unmodified.
+ options = MockOptions(configuration='Release', build_directory='foo')
+ self.assert_build_path(options, ['/mock-checkout/Source/WebKit/chromium/out'], 'foo')
+
+ # Test that we look in a chromium directory before the webkit directory.
+ options = MockOptions(configuration='Release', build_directory=None)
+ self.assert_build_path(options, ['/mock-checkout/Source/WebKit/chromium/out', '/mock-checkout/out'], '/mock-checkout/Source/WebKit/chromium/out')
+
+ # Test that we prefer the legacy dir over the new dir.
+ options = MockOptions(configuration='Release', build_directory=None)
+ self.assert_build_path(options, ['/mock-checkout/Source/WebKit/chromium/xcodebuild', '/mock-checkout/Source/WebKit/chromium/out'], '/mock-checkout/Source/WebKit/chromium/xcodebuild')
+
+ def test_driver_name_option(self):
+ self.assertTrue(self.make_port()._path_to_driver().endswith('DumpRenderTree'))
+ self.assertTrue(self.make_port(options=MockOptions(driver_name='OtherDriver'))._path_to_driver().endswith('OtherDriver'))
+
+ def test_path_to_image_diff(self):
+ self.assertEquals(self.make_port()._path_to_image_diff(), '/mock-checkout/out/Release/ImageDiff')
+
if __name__ == '__main__':
port_testcase.main()
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py
index 16275d70e..532843a9f 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py
@@ -160,6 +160,21 @@ class ChromiumDriverTest(unittest.TestCase):
driver2.stop()
self.assertTrue(time.time() - start_time < 20)
+ def test_stop_cleans_up_properly(self):
+ self.driver._test_shell = False
+ self.driver.start(True, [])
+ last_tmpdir = self.port._filesystem.last_tmpdir
+ self.assertNotEquals(last_tmpdir, None)
+ self.driver.stop()
+ self.assertFalse(self.port._filesystem.isdir(last_tmpdir))
+
+ def test_two_starts_cleans_up_properly(self):
+ # clone the WebKitDriverTest tests here since we override start() and stop()
+ self.driver._test_shell = False
+ self.driver.start(True, [])
+ last_tmpdir = self.port._filesystem.last_tmpdir
+ self.driver._start(True, [])
+ self.assertFalse(self.port._filesystem.isdir(last_tmpdir))
class ChromiumPortTest(port_testcase.PortTestCase):
port_name = 'chromium-mac'
@@ -237,12 +252,6 @@ class ChromiumPortTest(port_testcase.PortTestCase):
self.default_configuration_called = True
return 'default'
- def test_path_to_image_diff(self):
- # FIXME: These don't need to use endswith now that the port uses a MockFileSystem.
- self.assertTrue(ChromiumPortTest.TestLinuxPort()._path_to_image_diff().endswith('/out/default/ImageDiff'))
- self.assertTrue(ChromiumPortTest.TestMacPort()._path_to_image_diff().endswith('/xcodebuild/default/ImageDiff'))
- self.assertTrue(ChromiumPortTest.TestWinPort()._path_to_image_diff().endswith('/default/ImageDiff.exe'))
-
def test_default_configuration(self):
mock_options = MockOptions()
port = ChromiumPortTest.TestLinuxPort(options=mock_options)
@@ -283,62 +292,30 @@ class ChromiumPortTest(port_testcase.PortTestCase):
exception_raised = True
self.assertFalse(exception_raised)
- def test_overrides_and_builder_names(self):
+ def test_expectations_files(self):
port = self.make_port()
+ port.port_name = 'chromium'
- filesystem = MockFileSystem()
- port._filesystem = filesystem
- port.path_from_chromium_base = lambda *comps: '/' + '/'.join(comps)
-
+ expectations_path = port.path_to_test_expectations_file()
chromium_overrides_path = port.path_from_chromium_base(
'webkit', 'tools', 'layout_tests', 'test_expectations.txt')
- CHROMIUM_OVERRIDES = 'contents of %s\n' % chromium_overrides_path
- filesystem.write_text_file(chromium_overrides_path, CHROMIUM_OVERRIDES)
skia_overrides_path = port.path_from_chromium_base(
'skia', 'skia_test_expectations.txt')
- SKIA_OVERRIDES = 'contents of %s\n' % skia_overrides_path
- filesystem.write_text_file(skia_overrides_path, SKIA_OVERRIDES)
-
- additional_expectations_path = port.path_from_chromium_base(
- 'additional_expectations.txt')
- ADDITIONAL_EXPECTATIONS = 'contents of %s\n' % additional_expectations_path
- filesystem.write_text_file(additional_expectations_path, ADDITIONAL_EXPECTATIONS)
port._options.builder_name = 'DUMMY_BUILDER_NAME'
- port._options.additional_expectations = []
- self.assertEquals(port.test_expectations_overrides(),
- SKIA_OVERRIDES + CHROMIUM_OVERRIDES)
- port._options.additional_expectations = [additional_expectations_path]
- self.assertEquals(port.test_expectations_overrides(),
- SKIA_OVERRIDES + CHROMIUM_OVERRIDES + ADDITIONAL_EXPECTATIONS)
+ self.assertEquals(port.expectations_files(), [expectations_path, skia_overrides_path, chromium_overrides_path])
port._options.builder_name = 'builder (deps)'
- port._options.additional_expectations = []
- self.assertEquals(port.test_expectations_overrides(),
- SKIA_OVERRIDES + CHROMIUM_OVERRIDES)
- port._options.additional_expectations = [additional_expectations_path]
- self.assertEquals(port.test_expectations_overrides(),
- SKIA_OVERRIDES + CHROMIUM_OVERRIDES + ADDITIONAL_EXPECTATIONS)
+ self.assertEquals(port.expectations_files(), [expectations_path, skia_overrides_path, chromium_overrides_path])
# A builder which does NOT observe the Chromium test_expectations,
# but still observes the Skia test_expectations...
port._options.builder_name = 'builder'
- port._options.additional_expectations = []
- self.assertEquals(port.test_expectations_overrides(),
- SKIA_OVERRIDES)
- port._options.additional_expectations = [additional_expectations_path]
- self.assertEquals(port.test_expectations_overrides(),
- SKIA_OVERRIDES + ADDITIONAL_EXPECTATIONS)
-
- def test_driver_name_option(self):
- self.assertTrue(ChromiumPortTest.TestLinuxPort()._path_to_driver().endswith('/out/default/DumpRenderTree'))
- self.assertTrue(ChromiumPortTest.TestMacPort()._path_to_driver().endswith('/xcodebuild/default/DumpRenderTree.app/Contents/MacOS/DumpRenderTree'))
- self.assertTrue(ChromiumPortTest.TestWinPort()._path_to_driver().endswith('/default/DumpRenderTree.exe'))
-
- options = MockOptions(driver_name='OtherDriver')
- self.assertTrue(ChromiumPortTest.TestLinuxPort(options)._path_to_driver().endswith('/out/default/OtherDriver'))
- self.assertTrue(ChromiumPortTest.TestMacPort(options)._path_to_driver().endswith('/xcodebuild/default/OtherDriver.app/Contents/MacOS/OtherDriver'))
- self.assertTrue(ChromiumPortTest.TestWinPort(options)._path_to_driver().endswith('/default/OtherDriver.exe'))
+ self.assertEquals(port.expectations_files(), [expectations_path, skia_overrides_path])
+
+ def test_expectations_ordering(self):
+ # since we don't implement self.port_name in ChromiumPort.
+ pass
class ChromiumPortLoggingTest(logtesting.LoggingTestCase):
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_win.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_win.py
index b06fe9135..b76f7ee68 100755
--- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_win.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_win.py
@@ -66,6 +66,8 @@ class ChromiumWinPort(chromium.ChromiumPort):
],
}
+ DEFAULT_BUILD_DIRECTORIES = ('build', 'out')
+
@classmethod
def determine_full_port_name(cls, host, options, port_name):
if port_name.endswith('-win'):
@@ -128,14 +130,6 @@ class ChromiumWinPort(chromium.ChromiumPort):
#
# PROTECTED ROUTINES
#
- def _build_path(self, *comps):
- if self.get_option('build_directory'):
- return self._filesystem.join(self.get_option('build_directory'), *comps)
-
- p = self.path_from_chromium_base('build', *comps)
- if self._filesystem.exists(p):
- return p
- return self._filesystem.join(self.path_from_webkit_base(), 'Source', 'WebKit', 'chromium', 'build', *comps)
def _uses_apache(self):
return False
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_win_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_win_unittest.py
index 5bff7e129..759bc7e1d 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_win_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_win_unittest.py
@@ -34,14 +34,10 @@ from webkitpy.common.system.executive_mock import MockExecutive
from webkitpy.common.system.filesystem_mock import MockFileSystem
from webkitpy.layout_tests.port import chromium_win
from webkitpy.layout_tests.port import port_testcase
+from webkitpy.tool.mocktool import MockOptions
class ChromiumWinTest(port_testcase.PortTestCase):
- class RegisterCygwinOption(object):
- def __init__(self):
- self.register_cygwin = True
- self.results_directory = '/'
-
port_name = 'chromium-win'
port_maker = chromium_win.ChromiumWinPort
os_name = 'win'
@@ -66,7 +62,7 @@ class ChromiumWinTest(port_testcase.PortTestCase):
self.assertEquals(env['CYGWIN_PATH'], '/mock-checkout/Source/WebKit/chromium/third_party/cygwin/bin')
def test_setup_environ_for_server_register_cygwin(self):
- port = self.make_port(options=ChromiumWinTest.RegisterCygwinOption())
+ port = self.make_port(options=MockOptions(register_cygwin=True, results_directory='/'))
port._executive = MockExecutive(should_log=True)
expected_stderr = "MOCK run_command: ['/mock-checkout/Source/WebKit/chromium/third_party/cygwin/setup_mount.bat'], cwd=None\n"
output = outputcapture.OutputCapture()
@@ -111,16 +107,28 @@ class ChromiumWinTest(port_testcase.PortTestCase):
self.assertEquals(port.baseline_path(), port._webkit_baseline_path('chromium-win'))
def test_build_path(self):
- port = self.make_port()
- port._filesystem.files = {
- '/mock-checkout/Source/WebKit/chromium/build/Release/DumpRenderTree.exe': 'exe',
- }
- self.assertEquals(
- '/mock-checkout/Source/WebKit/chromium/build/Release/DumpRenderTree.exe',
- port._path_to_driver('Release'))
- self.assertEquals(
- '/mock-checkout/Source/WebKit/chromium/build/Debug/DumpRenderTree.exe',
- port._path_to_driver('Debug'))
+ # Test that optional paths are used regardless of whether they exist.
+ options = MockOptions(configuration='Release', build_directory='/foo')
+ self.assert_build_path(options, ['/mock-checkout/Source/WebKit/chromium/out'], '/foo')
+
+ # Test that optional relative paths are returned unmodified.
+ options = MockOptions(configuration='Release', build_directory='foo')
+ self.assert_build_path(options, ['/mock-checkout/Source/WebKit/chromium/out'], 'foo')
+
+ # Test that we look in a chromium directory before the webkit directory.
+ options = MockOptions(configuration='Release', build_directory=None)
+ self.assert_build_path(options, ['/mock-checkout/Source/WebKit/chromium/out', '/mock-checkout/out'], '/mock-checkout/Source/WebKit/chromium/out')
+
+ # Test that we prefer the legacy dir over the new dir.
+ options = MockOptions(configuration='Release', build_directory=None)
+ self.assert_build_path(options, ['/mock-checkout/Source/WebKit/chromium/build', '/mock-checkout/Source/WebKit/chromium/out'], '/mock-checkout/Source/WebKit/chromium/build')
def test_operating_system(self):
self.assertEqual('win', self.make_port().operating_system())
+
+ def test_driver_name_option(self):
+ self.assertTrue(self.make_port()._path_to_driver().endswith('DumpRenderTree.exe'))
+ self.assertTrue(self.make_port(options=MockOptions(driver_name='OtherDriver'))._path_to_driver().endswith('OtherDriver.exe'))
+
+ def test_path_to_image_diff(self):
+ self.assertEquals(self.make_port()._path_to_image_diff(), '/mock-checkout/out/Release/ImageDiff.exe')
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/config.py b/Tools/Scripts/webkitpy/layout_tests/port/config.py
index 6a1045f12..9297b8872 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/config.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/config.py
@@ -78,7 +78,7 @@ class Config(object):
if not self._build_directories.get(configuration):
args = ["perl", self.script_path("webkit-build-directory")] + flags
- output = self._executive.run_command(args, cwd=self.webkit_base_dir()).rstrip()
+ output = self._executive.run_command(args, cwd=self.webkit_base_dir(), return_stderr=False).rstrip()
parts = output.split("\n")
self._build_directories[configuration] = parts[0]
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/config_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/config_unittest.py
index 08b9af16c..c7d22c966 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/config_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/config_unittest.py
@@ -46,8 +46,8 @@ class ConfigTest(unittest.TestCase):
def tearDown(self):
config.clear_cached_configuration()
- def make_config(self, output='', files=None, exit_code=0, exception=None, run_command_fn=None):
- e = MockExecutive2(output=output, exit_code=exit_code, exception=exception, run_command_fn=run_command_fn)
+ def make_config(self, output='', files=None, exit_code=0, exception=None, run_command_fn=None, stderr=''):
+ e = MockExecutive2(output=output, exit_code=exit_code, exception=exception, run_command_fn=run_command_fn, stderr=stderr)
fs = MockFileSystem(files)
return config.Config(e, fs)
@@ -87,6 +87,10 @@ class ConfigTest(unittest.TestCase):
self.assertTrue(c.build_directory('Debug').endswith('/Debug'))
self.assertRaises(KeyError, c.build_directory, 'Unknown')
+ # Test that stderr output from webkit-build-directory won't mangle the build dir
+ c = self.make_config(output='/WebKitBuild/', stderr="mock stderr output from webkit-build-directory")
+ self.assertEqual(c.build_directory(None), '/WebKitBuild/')
+
def test_default_configuration__release(self):
self.assert_configuration('Release', 'Release')
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/driver.py b/Tools/Scripts/webkitpy/layout_tests/port/driver.py
index a113da3a8..f65db2f7b 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/driver.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/driver.py
@@ -132,7 +132,7 @@ class Driver(object):
def test_to_uri(self, test_name):
"""Convert a test name to a URI."""
if not self.is_http_test(test_name):
- return path.abspath_to_uri(self._port.abspath_for_test(test_name))
+ return path.abspath_to_uri(self._port.host.platform, self._port.abspath_for_test(test_name))
relative_path = test_name[len(self.HTTP_DIR):]
@@ -150,7 +150,10 @@ class Driver(object):
"""
if uri.startswith("file:///"):
- return uri[len(path.abspath_to_uri(self._port.layout_tests_dir()) + "/"):]
+ prefix = path.abspath_to_uri(self._port.host.platform, self._port.layout_tests_dir())
+ if not prefix.endswith('/'):
+ prefix += '/'
+ return uri[len(prefix):]
if uri.startswith("http://"):
return uri.replace('http://127.0.0.1:8000/', self.HTTP_DIR)
if uri.startswith("https://"):
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/driver_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/driver_unittest.py
index 08d4d9882..00a42f42f 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/driver_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/driver_unittest.py
@@ -29,7 +29,6 @@
import sys
import unittest
-from webkitpy.common.system.path import abspath_to_uri
from webkitpy.common.system.systemhost_mock import MockSystemHost
from webkitpy.layout_tests.port import Port, Driver, DriverOutput
@@ -105,20 +104,14 @@ class DriverTest(unittest.TestCase):
def test_test_to_uri(self):
port = self.make_port()
driver = Driver(port, None, pixel_tests=False)
- if sys.platform in ('cygwin', 'win32'):
- self.assertEqual(driver.test_to_uri('foo/bar.html'), 'file:///%s/foo/bar.html' % port.layout_tests_dir())
- else:
- self.assertEqual(driver.test_to_uri('foo/bar.html'), 'file://%s/foo/bar.html' % port.layout_tests_dir())
+ self.assertEqual(driver.test_to_uri('foo/bar.html'), 'file://%s/foo/bar.html' % port.layout_tests_dir())
self.assertEqual(driver.test_to_uri('http/tests/foo.html'), 'http://127.0.0.1:8000/foo.html')
self.assertEqual(driver.test_to_uri('http/tests/ssl/bar.html'), 'https://127.0.0.1:8443/ssl/bar.html')
def test_uri_to_test(self):
port = self.make_port()
driver = Driver(port, None, pixel_tests=False)
- if sys.platform in ('cygwin', 'win32'):
- self.assertEqual(driver.uri_to_test('file:///%s/foo/bar.html' % port.layout_tests_dir()), 'foo/bar.html')
- else:
- self.assertEqual(driver.uri_to_test('file://%s/foo/bar.html' % port.layout_tests_dir()), 'foo/bar.html')
+ self.assertEqual(driver.uri_to_test('file://%s/foo/bar.html' % port.layout_tests_dir()), 'foo/bar.html')
self.assertEqual(driver.uri_to_test('http://127.0.0.1:8000/foo.html'), 'http/tests/foo.html')
self.assertEqual(driver.uri_to_test('https://127.0.0.1:8443/ssl/bar.html'), 'http/tests/ssl/bar.html')
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/factory.py b/Tools/Scripts/webkitpy/layout_tests/port/factory.py
index 9f5df7c7e..881a80bec 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/factory.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/factory.py
@@ -53,13 +53,15 @@ def port_options(**help_strings):
optparse.make_option('--gtk', action='store_const', const='gtk', dest="platform",
help='Alias for --platform=gtk'),
optparse.make_option('--qt', action='store_const', const='qt', dest="platform",
- help='Alias for --platform=qt')]
+ help='Alias for --platform=qt'),
+ optparse.make_option('--32-bit', action='store_const', const='x86', default=None, dest="architecture",
+ help='use 32-bit binaries by default (x86 instead of x86_64)')]
-class BuilderOptions(object):
- def __init__(self, builder_name):
- self.configuration = "Debug" if re.search(r"[d|D](ebu|b)g", builder_name) else "Release"
- self.builder_name = builder_name
+def _builder_options(builder_name):
+ configuration = "Debug" if re.search(r"[d|D](ebu|b)g", builder_name) else "Release"
+ builder_name = builder_name
+ return optparse.Values({'builder_name': builder_name, 'configuration': configuration})
class PortFactory(object):
@@ -127,6 +129,6 @@ class PortFactory(object):
def get_from_builder_name(self, builder_name):
port_name = builders.port_name_for_builder_name(builder_name)
assert(port_name) # Need to update port_name_for_builder_name
- port = self.get(port_name, BuilderOptions(builder_name))
+ port = self.get(port_name, _builder_options(builder_name))
assert(port) # Need to update port_name_for_builder_name
return port
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/factory_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/factory_unittest.py
index 9fc20582f..700399e28 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/factory_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/factory_unittest.py
@@ -120,6 +120,10 @@ class FactoryTest(unittest.TestCase):
def test_unknown_default(self):
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(),
+ 'chromium-mac-lion')
+
if __name__ == '__main__':
unittest.main()
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/google_chrome.py b/Tools/Scripts/webkitpy/layout_tests/port/google_chrome.py
index 6d5d9276b..8d2df0ee9 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/google_chrome.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/google_chrome.py
@@ -29,21 +29,8 @@ import chromium_mac
import chromium_win
-def _test_expectations_overrides(port, super):
- # The chrome ports use the regular overrides plus anything in the
- # official test_expectations as well. Hopefully we don't get collisions.
- chromium_overrides = super.test_expectations_overrides(port)
-
- # FIXME: It used to be that AssertionError would get raised by
- # path_from_chromium_base() if we weren't in a Chromium checkout, but
- # this changed in r60427. This should probably be changed back.
- overrides_path = port.path_from_chromium_base('webkit', 'tools',
- 'layout_tests', 'test_expectations_chrome.txt')
- if not port._filesystem.exists(overrides_path):
- return chromium_overrides
-
- chromium_overrides = chromium_overrides or ''
- return chromium_overrides + port._filesystem.read_text_file(overrides_path)
+def _expectations_files(port, super):
+ return super.expectations_files(port) + [port.path_from_chromium_base('webkit', 'tools', 'layout_tests', 'test_expectations_chrome.txt')]
class GoogleChromeLinux32Port(chromium_linux.ChromiumLinuxPort):
@@ -59,8 +46,8 @@ class GoogleChromeLinux32Port(chromium_linux.ChromiumLinuxPort):
paths.insert(0, self._webkit_baseline_path('google-chrome-linux32'))
return paths
- def test_expectations_overrides(self):
- return _test_expectations_overrides(self, chromium_linux.ChromiumLinuxPort)
+ def expectations_files(self):
+ return _expectations_files(self, chromium_linux.ChromiumLinuxPort)
def architecture(self):
return 'x86'
@@ -79,8 +66,8 @@ class GoogleChromeLinux64Port(chromium_linux.ChromiumLinuxPort):
paths.insert(0, self._webkit_baseline_path('google-chrome-linux64'))
return paths
- def test_expectations_overrides(self):
- return _test_expectations_overrides(self, chromium_linux.ChromiumLinuxPort)
+ def expectations_files(self):
+ return _expectations_files(self, chromium_linux.ChromiumLinuxPort)
def architecture(self):
return 'x86_64'
@@ -99,8 +86,8 @@ class GoogleChromeMacPort(chromium_mac.ChromiumMacPort):
paths.insert(0, self._webkit_baseline_path('google-chrome-mac'))
return paths
- def test_expectations_overrides(self):
- return _test_expectations_overrides(self, chromium_mac.ChromiumMacPort)
+ def expectations_files(self):
+ return _expectations_files(self, chromium_mac.ChromiumMacPort)
class GoogleChromeWinPort(chromium_win.ChromiumWinPort):
@@ -116,5 +103,5 @@ class GoogleChromeWinPort(chromium_win.ChromiumWinPort):
paths.insert(0, self._webkit_baseline_path('google-chrome-win'))
return paths
- def test_expectations_overrides(self):
- return _test_expectations_overrides(self, chromium_win.ChromiumWinPort)
+ def expectations_files(self):
+ return _expectations_files(self, chromium_win.ChromiumWinPort)
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/google_chrome_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/google_chrome_unittest.py
index 9d3653ae5..6d3c1a7dd 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/google_chrome_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/google_chrome_unittest.py
@@ -38,23 +38,10 @@ class TestGoogleChromePort(unittest.TestCase):
def _verify_expectations_overrides(self, port_name):
host = MockSystemHost()
- chromium_port = PortFactory(host).get("chromium-mac-leopard")
- chromium_base = chromium_port.path_from_chromium_base()
port = PortFactory(host).get(port_name=port_name, options=None)
-
- expected_chromium_overrides = '// chromium overrides\n'
- expected_chrome_overrides = '// chrome overrides\n'
- chromium_path = host.filesystem.join(chromium_base, 'webkit', 'tools', 'layout_tests', 'test_expectations.txt')
- chrome_path = host.filesystem.join(chromium_base, 'webkit', 'tools', 'layout_tests', 'test_expectations_chrome.txt')
-
- host.filesystem.files[chromium_path] = expected_chromium_overrides
- host.filesystem.files[chrome_path] = None
- actual_chrome_overrides = port.test_expectations_overrides()
- self.assertEqual(expected_chromium_overrides, actual_chrome_overrides)
-
- host.filesystem.files[chrome_path] = expected_chrome_overrides
- actual_chrome_overrides = port.test_expectations_overrides()
- self.assertEqual(actual_chrome_overrides, expected_chromium_overrides + expected_chrome_overrides)
+ self.assertTrue('TestExpectations' in port.expectations_files()[0])
+ self.assertTrue('skia_test_expectations.txt' in port.expectations_files()[1])
+ self.assertTrue('test_expectations_chrome.txt' in port.expectations_files()[-1])
def test_get_google_chrome_port(self):
self._verify_baseline_search_path_startswith('google-chrome-linux32', ['google-chrome-linux32', 'chromium-linux-x86'])
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/mac.py b/Tools/Scripts/webkitpy/layout_tests/port/mac.py
index fa57cc3f7..a3ec6a4b9 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/mac.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/mac.py
@@ -31,6 +31,7 @@ import logging
import os
import re
import subprocess
+import sys
import time
from webkitpy.common.system.crashlogs import CrashLogs
@@ -49,14 +50,24 @@ class MacPort(ApplePort):
# and the order of fallback between them. Matches ORWT.
VERSION_FALLBACK_ORDER = ["mac-leopard", "mac-snowleopard", "mac-lion", "mac"]
+ ARCHITECTURES = ['x86_64', 'x86']
+
def __init__(self, host, port_name, **kwargs):
ApplePort.__init__(self, host, port_name, **kwargs)
+ self._architecture = self.get_option('architecture')
+
+ if not self._architecture:
+ self._architecture = 'x86_64'
+
self._leak_detector = LeakDetector(self)
if self.get_option("leaks"):
# DumpRenderTree slows down noticably if we run more than about 1000 tests in a batch
# with MallocStackLogging enabled.
self.set_option_default("batch_size", 1000)
+ def _build_driver_flags(self):
+ return ['ARCHS=i386'] if self.architecture() == 'x86' else []
+
def _most_recent_version(self):
# This represents the most recently-shipping version of the operating system.
return self.VERSION_FALLBACK_ORDER[-2]
@@ -107,18 +118,22 @@ class MacPort(ApplePort):
return self._version == "lion"
def default_child_processes(self):
+ # FIXME: The Printer isn't initialized when this is called, so using _log would just show an unitialized logger error.
+
if self.is_snowleopard():
- _log.warn("Cannot run tests in parallel on Snow Leopard due to rdar://problem/10621525.")
+ print >> sys.stderr, "Cannot run tests in parallel on Snow Leopard due to rdar://problem/10621525."
return 1
- # FIXME: As a temporary workaround while we figure out what's going
- # on with https://bugs.webkit.org/show_bug.cgi?id=83076, reduce by
- # half the # of workers we run by default on bigger machines.
default_count = super(MacPort, self).default_child_processes()
- if default_count >= 8:
- cpu_count = self._executive.cpu_count()
- return max(1, min(default_count, int(cpu_count / 2)))
- return default_count
+
+ # Make sure we have enough ram to support that many instances:
+ total_memory = self.host.platform.total_bytes_memory()
+ bytes_per_drt = 256 * 1024 * 1024 # Assume each DRT needs 256MB to run.
+ overhead = 2048 * 1024 * 1024 # Assume we need 2GB free for the O/S
+ supportable_instances = max((total_memory - overhead) / bytes_per_drt, 1) # Always use one process, even if we don't have space for it.
+ if supportable_instances < default_count:
+ print >> sys.stderr, "This machine could support %s child processes, but only has enough memory for %s." % (default_count, supportable_instances)
+ return min(supportable_instances, default_count)
def _build_java_test_support(self):
java_tests_path = self._filesystem.join(self.layout_tests_dir(), "java")
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/mac_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/mac_unittest.py
index 0b9bca978..2ebf255ae 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/mac_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/mac_unittest.py
@@ -183,10 +183,22 @@ java/
# MockPlatformInfo only has 2 mock cores. The important part is that 2 > 1.
self.assertEqual(port.default_child_processes(), 2)
+ bytes_for_drt = 200 * 1024 * 1024
+ port.host.platform.total_bytes_memory = lambda: bytes_for_drt
+ expected_stderr = "This machine could support 2 child processes, but only has enough memory for 1.\n"
+ child_processes = OutputCapture().assert_outputs(self, port.default_child_processes, (), expected_stderr=expected_stderr)
+ self.assertEqual(child_processes, 1)
+
+ # Make sure that we always use one process, even if we don't have the memory for it.
+ port.host.platform.total_bytes_memory = lambda: bytes_for_drt - 1
+ expected_stderr = "This machine could support 2 child processes, but only has enough memory for 1.\n"
+ child_processes = OutputCapture().assert_outputs(self, port.default_child_processes, (), expected_stderr=expected_stderr)
+ self.assertEqual(child_processes, 1)
+
# SnowLeopard has a CFNetwork bug which causes crashes if we execute more than one copy of DRT at once.
port = self.make_port(port_name='mac-snowleopard')
- expected_logs = "Cannot run tests in parallel on Snow Leopard due to rdar://problem/10621525.\n"
- child_processes = OutputCapture().assert_outputs(self, port.default_child_processes, (), expected_logs=expected_logs)
+ expected_stderr = "Cannot run tests in parallel on Snow Leopard due to rdar://problem/10621525.\n"
+ child_processes = OutputCapture().assert_outputs(self, port.default_child_processes, (), expected_stderr=expected_stderr)
self.assertEqual(child_processes, 1)
def test_get_crash_log(self):
@@ -253,3 +265,26 @@ java/
port = self.make_port()
port._executive = MockExecutive2(run_command_fn=throwing_run_command)
OutputCapture().assert_outputs(self, port.sample_process, args=['test', 42])
+
+ def test_32bit(self):
+ port = self.make_port(options=MockOptions(architecture='x86'))
+
+ def run_script(script, args=None, env=None):
+ self.args = args
+
+ port._run_script = run_script
+ self.assertEquals(port.architecture(), 'x86')
+ port._build_driver()
+ self.assertEquals(self.args, ['ARCHS=i386'])
+
+ def test_64bit(self):
+ # Apple Mac port is 64-bit by default
+ port = self.make_port()
+ self.assertEquals(port.architecture(), 'x86_64')
+
+ def run_script(script, args=None, env=None):
+ self.args = args
+
+ port._run_script = run_script
+ port._build_driver()
+ self.assertEquals(self.args, [])
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py
index 605071805..570a9e4ad 100755
--- a/Tools/Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py
@@ -49,11 +49,6 @@ class MockDRTPortTest(port_testcase.PortTestCase):
def make_port(self, options=mock_options):
host = MockSystemHost()
test.add_unit_tests_to_mock_filesystem(host.filesystem)
- if sys.platform == 'win32':
- # We use this because the 'win' port doesn't work yet.
- host.platform.os_name = 'win'
- host.platform.os_version = 'xp'
- return mock_drt.MockDRTPort(host, port_name='mock-chromium-win', options=options)
return mock_drt.MockDRTPort(host, port_name='mock-mac', options=options)
def test_port_name_in_constructor(self):
@@ -93,10 +88,7 @@ class MockDRTTest(unittest.TestCase):
def input_line(self, port, test_name, checksum=None):
url = port.create_driver(0).test_to_uri(test_name)
if url.startswith('file://'):
- if sys.platform == 'win32':
- url = url[len('file:///'):]
- else:
- url = url[len('file://'):]
+ url = url[len('file://'):]
if checksum:
return url + "'" + checksum + '\n'
@@ -247,9 +239,6 @@ class MockChromiumDRTTest(MockDRTTest):
def test_pixeltest__fails(self):
host = MockSystemHost()
url = '#URL:file://'
- if sys.platform == 'win32':
- host = MockSystemHost(os_name='win', os_version='xp')
- url = '#URL:file:///'
url = url + '%s/failures/expected/image_checksum.html' % PortFactory(host).get('test').layout_tests_dir()
self.assertTest('failures/expected/image_checksum.html', pixel_tests=True,
expected_checksum='image_checksum',
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py b/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py
index 4bdc3ed13..f37273f78 100755
--- a/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py
@@ -333,6 +333,30 @@ class PortTestCase(unittest.TestCase):
u'STDOUT: foo\ufffdbar\n'
u'STDERR: foo\ufffdbar\n'))
+ def assert_build_path(self, options, dirs, expected_path):
+ port = self.make_port(options=options)
+ for directory in dirs:
+ port.host.filesystem.maybe_make_directory(directory)
+ self.assertEquals(port._build_path(), expected_path)
+
+ def test_expectations_ordering(self):
+ port = self.make_port()
+ for path in port.expectations_files():
+ port._filesystem.write_text_file(path, '')
+ ordered_dict = port.expectations_dict()
+ self.assertEquals(port.path_to_test_expectations_file(), ordered_dict.keys()[0])
+
+ options = MockOptions(additional_expectations=['/tmp/foo', '/tmp/bar'])
+ port = self.make_port(options=options)
+ for path in port.expectations_files():
+ port._filesystem.write_text_file(path, '')
+ port._filesystem.write_text_file('/tmp/foo', 'foo')
+ port._filesystem.write_text_file('/tmp/bar', 'bar')
+ ordered_dict = port.expectations_dict()
+ self.assertEquals(ordered_dict.keys()[-2:], options.additional_expectations)
+ self.assertEquals(ordered_dict.values()[-2:], ['foo', 'bar'])
+
+
# FIXME: This class and main() should be merged into test-webkitpy.
class EnhancedTestLoader(unittest.TestLoader):
integration_tests = False
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/qt.py b/Tools/Scripts/webkitpy/layout_tests/port/qt.py
index 520575952..2488936fe 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/qt.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/qt.py
@@ -81,9 +81,6 @@ class QtPort(WebKitPort):
# The Qt port builds DRT as part of the main build step
return True
- def _driver_class(self):
- return XvfbDriver
-
def _path_to_driver(self):
return self._build_path('bin/%s' % self.driver_name())
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 4257d5455..b5ce05391 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/server_process_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/server_process_unittest.py
@@ -100,11 +100,15 @@ class TestServerProcess(unittest.TestCase):
line = proc.read_stdout_line(now - 1)
self.assertEquals(line, None)
+ # FIXME: This part appears to be flaky. line should always be non-None.
+ # FIXME: https://bugs.webkit.org/show_bug.cgi?id=88280
line = proc.read_stdout_line(now + 1.0)
- self.assertEquals(line.strip(), "stdout")
+ if line:
+ self.assertEquals(line.strip(), "stdout")
line = proc.read_stderr_line(now + 1.0)
- self.assertEquals(line.strip(), "stderr")
+ if line:
+ self.assertEquals(line.strip(), "stderr")
proc.stop()
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/test.py b/Tools/Scripts/webkitpy/layout_tests/port/test.py
index 8abd81235..25c6a13be 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/test.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/test.py
@@ -174,6 +174,7 @@ layer at (0,0) size 800x34
tests.add('passes/checksum_in_image.html',
expected_checksum=None,
expected_image='tEXtchecksum\x00checksum_in_image-checksum')
+ tests.add('passes/skipped/skip.html')
# Note that here the checksums don't match but the images do, so this test passes "unexpectedly".
# See https://bugs.webkit.org/show_bug.cgi?id=69444 .
@@ -233,12 +234,8 @@ layer at (0,0) size 800x34
# this works. The path contains a '.' in the name because we've seen bugs
# related to this before.
-if sys.platform == 'win32':
- LAYOUT_TEST_DIR = 'c:/test.checkout/LayoutTests'
- PERF_TEST_DIR = 'c:/test.checkout/PerformanceTests'
-else:
- LAYOUT_TEST_DIR = '/test.checkout/LayoutTests'
- PERF_TEST_DIR = '/test.checkout/PerformanceTests'
+LAYOUT_TEST_DIR = '/test.checkout/LayoutTests'
+PERF_TEST_DIR = '/test.checkout/PerformanceTests'
# Here we synthesize an in-memory filesystem from the test list
@@ -247,8 +244,8 @@ else:
def add_unit_tests_to_mock_filesystem(filesystem):
# Add the test_expectations file.
filesystem.maybe_make_directory(LAYOUT_TEST_DIR + '/platform/test')
- if not filesystem.exists(LAYOUT_TEST_DIR + '/platform/test/test_expectations.txt'):
- filesystem.write_text_file(LAYOUT_TEST_DIR + '/platform/test/test_expectations.txt', """
+ if not filesystem.exists(LAYOUT_TEST_DIR + '/platform/test/TestExpectations'):
+ filesystem.write_text_file(LAYOUT_TEST_DIR + '/platform/test/TestExpectations', """
WONTFIX : failures/expected/crash.html = CRASH
WONTFIX : failures/expected/image.html = IMAGE
WONTFIX : failures/expected/audio.html = AUDIO
@@ -267,6 +264,7 @@ WONTFIX : failures/expected/timeout.html = TIMEOUT
WONTFIX SKIP : failures/expected/hang.html = TIMEOUT
WONTFIX SKIP : failures/expected/keyboard.html = CRASH
WONTFIX SKIP : failures/expected/exception.html = CRASH
+WONTFIX SKIP : passes/skipped/skip.html = PASS
""")
filesystem.maybe_make_directory(LAYOUT_TEST_DIR + '/reftests/foo')
@@ -340,7 +338,7 @@ class TestPort(Port):
Port.__init__(self, host, port_name, **kwargs)
self._tests = unit_test_list()
self._flakes = set()
- self._expectations_path = LAYOUT_TEST_DIR + '/platform/test/test_expectations.txt'
+ self._expectations_path = LAYOUT_TEST_DIR + '/platform/test/TestExpectations'
self._results_directory = None
self._operating_system = 'mac'
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/webkit.py b/Tools/Scripts/webkitpy/layout_tests/port/webkit.py
index 879c79abf..ab35a6463 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/webkit.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/webkit.py
@@ -77,10 +77,6 @@ class WebKitPort(Port):
search_paths.append(self.port_name)
return map(self._webkit_baseline_path, search_paths)
- def path_to_test_expectations_file(self):
- # test_expectations are always in mac/ not mac-leopard/ by convention, hence we use port_name instead of name().
- return self._filesystem.join(self._webkit_baseline_path(self.port_name), 'test_expectations.txt')
-
def _port_flag_for_scripts(self):
# This is overrriden by ports which need a flag passed to scripts to distinguish the use of that port.
# For example --qt on linux, since a user might have both Gtk and Qt libraries installed.
@@ -118,14 +114,17 @@ class WebKitPort(Port):
# These two projects should be factored out into their own
# projects.
try:
- self._run_script("build-dumprendertree", env=env)
+ self._run_script("build-dumprendertree", args=self._build_driver_flags(), env=env)
if self.get_option('webkit_test_runner'):
- self._run_script("build-webkittestrunner", env=env)
+ self._run_script("build-webkittestrunner", args=self._build_driver_flags(), env=env)
except ScriptError, e:
_log.error(e.message_with_output(output_limit=None))
return False
return True
+ def _build_driver_flags(self):
+ return []
+
def _check_driver(self):
driver_path = self._path_to_driver()
if not self._filesystem.exists(driver_path):
@@ -360,15 +359,6 @@ class WebKitPort(Port):
return search_paths
- def test_expectations(self):
- # This allows ports to use a combination of test_expectations.txt files and Skipped lists.
- expectations = ''
- expectations_path = self.path_to_test_expectations_file()
- if self._filesystem.exists(expectations_path):
- _log.debug("Using test_expectations.txt: %s" % expectations_path)
- expectations = self._filesystem.read_text_file(expectations_path)
- return expectations
-
def skipped_layout_tests(self, test_list):
tests_to_skip = set(self._expectations_from_skipped_files(self._skipped_file_search_paths()))
tests_to_skip.update(self._tests_for_other_platforms())
@@ -462,8 +452,7 @@ class WebKitDriver(Driver):
self._server_process = None
def __del__(self):
- assert(self._server_process is None)
- assert(self._driver_tempdir is None)
+ self.stop()
def cmd_line(self, pixel_tests, per_test_args):
cmd = self._command_wrapper(self._port.get_option('wrapper'))
@@ -488,6 +477,7 @@ class WebKitDriver(Driver):
return cmd
def _start(self, pixel_tests, per_test_args):
+ self.stop()
self._driver_tempdir = self._port._filesystem.mkdtemp(prefix='%s-' % self._port.driver_name())
server_name = self._port.driver_name()
environment = self._port.setup_environ_for_server(server_name)
@@ -565,8 +555,7 @@ class WebKitDriver(Driver):
def run_test(self, driver_input):
start_time = time.time()
- if not self._server_process:
- self._start(driver_input.should_run_pixel_test, driver_input.args)
+ self.start(driver_input.should_run_pixel_test, driver_input.args)
self.error_from_test = str()
self.err_seen_eof = False
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py
index 33e422866..f7e9525d6 100755
--- a/Tools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py
@@ -94,9 +94,16 @@ class WebKitPortTest(port_testcase.PortTestCase):
def test_path_to_test_expectations_file(self):
port = TestWebKitPort()
port._options = MockOptions(webkit_test_runner=False)
- self.assertEqual(port.path_to_test_expectations_file(), '/mock-checkout/LayoutTests/platform/testwebkitport/test_expectations.txt')
+ self.assertEqual(port.path_to_test_expectations_file(), '/mock-checkout/LayoutTests/platform/testwebkitport/TestExpectations')
+
+ port = TestWebKitPort()
port._options = MockOptions(webkit_test_runner=True)
- self.assertEqual(port.path_to_test_expectations_file(), '/mock-checkout/LayoutTests/platform/testwebkitport/test_expectations.txt')
+ self.assertEqual(port.path_to_test_expectations_file(), '/mock-checkout/LayoutTests/platform/testwebkitport/TestExpectations')
+
+ port = TestWebKitPort()
+ port.host.filesystem.files['/mock-checkout/LayoutTests/platform/testwebkitport/TestExpectations'] = 'some content'
+ port._options = MockOptions(webkit_test_runner=False)
+ self.assertEqual(port.path_to_test_expectations_file(), '/mock-checkout/LayoutTests/platform/testwebkitport/TestExpectations')
def test_skipped_directories_for_symbols(self):
# This first test confirms that the commonly found symbols result in the expected skipped directories.
@@ -165,10 +172,10 @@ class WebKitPortTest(port_testcase.PortTestCase):
def test_test_expectations(self):
# Check that we read the expectations file
host = MockSystemHost()
- host.filesystem.write_text_file('/mock-checkout/LayoutTests/platform/testwebkitport/test_expectations.txt',
- 'BUG_TESTEXPECTATIONS SKIP : fast/html/article-element.html = FAIL\n')
+ host.filesystem.write_text_file('/mock-checkout/LayoutTests/platform/testwebkitport/TestExpectations',
+ 'BUG_TESTEXPECTATIONS SKIP : fast/html/article-element.html = TEXT\n')
port = TestWebKitPort(host=host)
- self.assertEqual(port.test_expectations(), 'BUG_TESTEXPECTATIONS SKIP : fast/html/article-element.html = FAIL\n')
+ self.assertEqual(''.join(port.expectations_dict().values()), 'BUG_TESTEXPECTATIONS SKIP : fast/html/article-element.html = TEXT\n')
def test_build_driver(self):
output = OutputCapture()
@@ -349,7 +356,7 @@ class WebKitDriverTest(unittest.TestCase):
driver._server_process = FakeServerProcess(False)
driver._subprocess_was_unresponsive = False
assert_crash(driver, '#CRASHED - WebProcess (pid 8675)\n', True, 'WebProcess', 8675)
-
+
driver._crashed_process_name = None
driver._crashed_pid = None
driver._server_process = FakeServerProcess(False)
@@ -376,3 +383,11 @@ class WebKitDriverTest(unittest.TestCase):
self.assertNotEquals(last_tmpdir, None)
driver.stop()
self.assertFalse(port._filesystem.isdir(last_tmpdir))
+
+ def test_two_starts_cleans_up_properly(self):
+ port = TestWebKitPort()
+ driver = WebKitDriver(port, 0, pixel_tests=True)
+ driver.start(True, [])
+ last_tmpdir = port._filesystem.last_tmpdir
+ driver._start(True, [])
+ self.assertFalse(port._filesystem.isdir(last_tmpdir))
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/win.py b/Tools/Scripts/webkitpy/layout_tests/port/win.py
index da3209ead..420f4db04 100644
--- a/Tools/Scripts/webkitpy/layout_tests/port/win.py
+++ b/Tools/Scripts/webkitpy/layout_tests/port/win.py
@@ -30,6 +30,7 @@ import logging
import re
import sys
+from webkitpy.common.system.systemhost import SystemHost
from webkitpy.common.system.executive import ScriptError, Executive
from webkitpy.common.system.path import abspath_to_uri
from webkitpy.layout_tests.port.apple import ApplePort
@@ -45,6 +46,8 @@ class WinPort(ApplePort):
# and the order of fallback between them. Matches ORWT.
VERSION_FALLBACK_ORDER = ["win-xp", "win-vista", "win-7sp0", "win"]
+ ARCHITECTURES = ['x86']
+
def do_text_results_differ(self, expected_text, actual_text):
# Sanity was restored in WK2, so we don't need this hack there.
if self.get_option('webkit_test_runner'):
@@ -75,7 +78,7 @@ class WinPort(ApplePort):
return 'win'
def show_results_html_file(self, results_filename):
- self._run_script('run-safari', [abspath_to_uri(results_filename)])
+ self._run_script('run-safari', [abspath_to_uri(SystemHost().platform, results_filename)])
# FIXME: webkitperl/httpd.pm installs /usr/lib/apache/libphp4.dll on cycwin automatically
# as part of running old-run-webkit-tests. That's bad design, but we may need some similar hack.
diff --git a/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py b/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py
index d4eccb0fa..3d7b20f41 100755
--- a/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py
+++ b/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py
@@ -157,8 +157,8 @@ def _set_up_derived_options(port, options):
normalized_platform_directories.append(port.host.filesystem.normpath(path))
options.additional_platform_directory = normalized_platform_directories
- if not options.http and options.force:
- warnings.append("--no-http is ignored since --force is also provided")
+ if not options.http and options.skipped in ('ignore', 'only'):
+ warnings.append("--force/--skipped=%s overrides --no-http." % (options.skipped))
options.http = True
if options.skip_pixel_test_if_no_baseline and not options.pixel_tests:
@@ -167,6 +167,10 @@ def _set_up_derived_options(port, options):
if options.ignore_metrics and (options.new_baseline or options.reset_results):
warnings.append("--ignore-metrics has no effect with --new-baselines or with --reset-results")
+ if options.new_baseline:
+ options.reset_results = True
+ options.add_platform_exceptions = True
+
return warnings
@@ -277,10 +281,12 @@ def parse_args(args=None):
optparse.make_option("--results-directory", help="Location of test results"),
optparse.make_option("--build-directory",
help="Path to the directory under which build files are kept (should not include configuration)"),
+ optparse.make_option("--add-platform-exceptions", action="store_true", default=False,
+ help="Save generated results into the *most-specific-platform* directory rather than the *generic-platform* directory"),
optparse.make_option("--new-baseline", action="store_true",
default=False, help="Save generated results as new baselines "
- "into the *platform* directory, overwriting whatever's "
- "already there."),
+ "into the *most-specific-platform* directory, overwriting whatever's "
+ "already there. Equivalent to --reset-results --add-platform-exceptions"),
optparse.make_option("--reset-results", action="store_true",
default=False, help="Reset expectations to the "
"generated results in their existing location."),
@@ -347,17 +353,14 @@ def parse_args(args=None):
help="wrapper command to insert before invocations of "
"DumpRenderTree; option is split on whitespace before "
"running. (Example: --wrapper='valgrind --smc-check=all')"),
- # old-run-webkit-tests:
- # -i|--ignore-tests Comma-separated list of directories
- # or tests to ignore
optparse.make_option("-i", "--ignore-tests", action="append", default=[],
help="directories or test to ignore (may specify multiple times)"),
optparse.make_option("--test-list", action="append",
help="read list of tests to run from file", metavar="FILE"),
- # old-run-webkit-tests uses --skipped==[default|ignore|only]
- # instead of --force:
- optparse.make_option("--force", action="store_true", default=False,
- help="Run all tests, even those marked SKIP in the test list"),
+ 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."),
+ 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",
help="Set the timeout for each test"),
# old-run-webkit-tests calls --randomize-order --random:
@@ -383,11 +386,11 @@ def parse_args(args=None):
# FIXME: Display default number of child processes that will run.
optparse.make_option("-f", "--fully-parallel", action="store_true",
help="run all tests in parallel"),
- optparse.make_option("--exit-after-n-failures", type="int", default=500,
+ optparse.make_option("--exit-after-n-failures", type="int", default=None,
help="Exit after the first N failures instead of running all "
"tests"),
optparse.make_option("--exit-after-n-crashes-or-timeouts", type="int",
- default=20, help="Exit after the first N crashes instead of "
+ default=None, help="Exit after the first N crashes instead of "
"running all tests"),
optparse.make_option("--iterations", type="int", help="Number of times to run the set of tests (e.g. ABCABCABC)"),
optparse.make_option("--repeat-each", type="int", help="Number of times to run each test (e.g. AAABBBCCC)"),
@@ -443,7 +446,6 @@ def main():
host = MockHost()
else:
host = Host()
- host._initialize_scm()
port = host.port_factory.get(options.platform, options)
logging.getLogger().setLevel(logging.DEBUG if options.verbose else logging.INFO)
return run(port, options, args)
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 96fb6853d..bfdb55cc9 100755
--- a/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_integrationtest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_integrationtest.py
@@ -197,18 +197,15 @@ class LintTest(unittest.TestCase, StreamTestingMixin):
self.name = name
self.path = path
- def test_expectations(self):
- self.host.ports_parsed.append(self.name)
- return ''
-
def path_to_test_expectations_file(self):
return self.path
def test_configuration(self):
return None
- def test_expectations_overrides(self):
- return None
+ def expectations_dict(self):
+ self.host.ports_parsed.append(self.name)
+ return {self.path: ''}
def skipped_layout_tests(self, tests):
return set([])
@@ -261,7 +258,7 @@ class LintTest(unittest.TestCase, StreamTestingMixin):
options, parsed_args = parse_args(['--lint-test-files'])
host = MockHost()
port_obj = host.port_factory.get(options.platform, options=options)
- port_obj.test_expectations = lambda: "# syntax error"
+ port_obj.expectations_dict = lambda: {'': '# syntax error'}
res, out, err = run_and_capture(port_obj, options, parsed_args)
self.assertEqual(res, -1)
@@ -404,19 +401,29 @@ class MainTest(unittest.TestCase, StreamTestingMixin):
tests_run = get_tests_run(['--skip-pixel-test-if-no-baseline'] + tests_to_run, tests_included=True, flatten_batches=True)
self.assertEquals(tests_run, ['passes/image.html', 'passes/text.html'])
- def test_ignore_tests(self):
- def assert_ignored(args, tests_expected_to_run):
- tests_to_run = ['failures/expected/image.html', 'passes/image.html']
- tests_run = get_tests_run(args + tests_to_run, tests_included=True, flatten_batches=True)
- self.assertEquals(tests_run, tests_expected_to_run)
+ def test_ignore_flag(self):
+ # Note that passes/image.html is expected to be run since we specified it directly.
+ tests_run = get_tests_run(['-i', 'passes', 'passes/image.html'], flatten_batches=True, tests_included=True)
+ self.assertFalse('passes/text.html' in tests_run)
+ self.assertTrue('passes/image.html' in tests_run)
+
+ def test_skipped_flag(self):
+ tests_run = get_tests_run(['passes'], tests_included=True, flatten_batches=True)
+ self.assertFalse('passes/skipped/skip.html' in tests_run)
+ num_tests_run_by_default = len(tests_run)
- assert_ignored(['-i', 'failures/expected/image.html'], ['passes/image.html'])
- assert_ignored(['-i', 'passes'], ['failures/expected/image.html'])
+ # Check that nothing changes when we specify skipped=default.
+ self.assertEquals(len(get_tests_run(['--skipped=default', 'passes'], tests_included=True, flatten_batches=True)),
+ num_tests_run_by_default)
- # Note here that there is an expectation for failures/expected/image.html already, but
- # it is overriden by the command line arg. This might be counter-intuitive.
- # FIXME: This isn't currently working ...
- # assert_ignored(['-i', 'failures/expected'], ['passes/image.html'])
+ # Now check that we run one more test (the skipped one).
+ tests_run = get_tests_run(['--skipped=ignore', 'passes'], tests_included=True, flatten_batches=True)
+ self.assertTrue('passes/skipped/skip.html' in tests_run)
+ self.assertEquals(len(tests_run), num_tests_run_by_default + 1)
+
+ # Now check that we only run the skipped test.
+ self.assertEquals(get_tests_run(['--skipped=only', 'passes'], tests_included=True, flatten_batches=True),
+ ['passes/skipped/skip.html'])
def test_iterations(self):
tests_to_run = ['passes/image.html', 'passes/text.html']
@@ -537,7 +544,7 @@ class MainTest(unittest.TestCase, StreamTestingMixin):
self.assertEqual(res, unexpected_tests_count)
self.assertNotEmpty(out)
self.assertNotEmpty(err)
- self.assertEqual(user.opened_urls, [path.abspath_to_uri('/tmp/layout-test-results/results.html')])
+ self.assertEqual(user.opened_urls, [path.abspath_to_uri(MockHost().platform, '/tmp/layout-test-results/results.html')])
def test_missing_and_unexpected_results(self):
# Test that we update expectations in place. If the expectation
@@ -715,7 +722,7 @@ class MainTest(unittest.TestCase, StreamTestingMixin):
with host.filesystem.mkdtemp() as tmpdir:
res, out, err, user = logging_run(['--results-directory=' + str(tmpdir)],
tests_included=True, host=host)
- self.assertEqual(user.opened_urls, [path.abspath_to_uri(host.filesystem.join(tmpdir, 'results.html'))])
+ self.assertEqual(user.opened_urls, [path.abspath_to_uri(host.platform, host.filesystem.join(tmpdir, 'results.html'))])
def test_results_directory_default(self):
# We run a configuration that should fail, to generate output, then
@@ -723,7 +730,7 @@ class MainTest(unittest.TestCase, StreamTestingMixin):
# This is the default location.
res, out, err, user = logging_run(tests_included=True)
- self.assertEqual(user.opened_urls, [path.abspath_to_uri('/tmp/layout-test-results/results.html')])
+ self.assertEqual(user.opened_urls, [path.abspath_to_uri(MockHost().platform, '/tmp/layout-test-results/results.html')])
def test_results_directory_relative(self):
# We run a configuration that should fail, to generate output, then
@@ -733,7 +740,7 @@ class MainTest(unittest.TestCase, StreamTestingMixin):
host.filesystem.chdir('/tmp/cwd')
res, out, err, user = logging_run(['--results-directory=foo'],
tests_included=True, host=host)
- self.assertEqual(user.opened_urls, [path.abspath_to_uri('/tmp/cwd/foo/results.html')])
+ self.assertEqual(user.opened_urls, [path.abspath_to_uri(host.platform, '/tmp/cwd/foo/results.html')])
def test_retrying_and_flaky_tests(self):
host = MockHost()
@@ -874,6 +881,10 @@ class MainTest(unittest.TestCase, StreamTestingMixin):
self.assertTrue(MainTest.has_test_of_type(batch_tests_run_http, 'http'))
self.assertTrue(MainTest.has_test_of_type(batch_tests_run_http, 'websocket'))
+ def test_platform_tests_are_found(self):
+ tests_run = get_tests_run(['http'], tests_included=True, flatten_batches=True)
+ self.assertTrue('platform/test-snow-leopard/http/test.html' in tests_run)
+
class EndToEndTest(unittest.TestCase):
def parse_full_results(self, full_results_text):
@@ -919,7 +930,7 @@ class RebaselineTest(unittest.TestCase, StreamTestingMixin):
"assert that the file_list contains the baselines."""
for ext in extensions:
baseline = file + "-expected" + ext
- baseline_msg = 'Writing new expected result "%s"\n' % baseline[1:]
+ baseline_msg = 'Writing new expected result "%s"\n' % baseline
self.assertTrue(any(f.find(baseline) != -1 for f in file_list))
self.assertContainsLine(err, baseline_msg)
@@ -940,8 +951,8 @@ class RebaselineTest(unittest.TestCase, StreamTestingMixin):
self.assertEquals(res, 0)
self.assertEmpty(out)
self.assertEqual(len(file_list), 4)
- self.assertBaselines(file_list, "/passes/image", [".txt", ".png"], err)
- self.assertBaselines(file_list, "/failures/expected/missing_image", [".txt", ".png"], err)
+ self.assertBaselines(file_list, "passes/image", [".txt", ".png"], err)
+ self.assertBaselines(file_list, "failures/expected/missing_image", [".txt", ".png"], err)
def test_missing_results(self):
# Test that we update expectations in place. If the expectation
@@ -958,13 +969,13 @@ class RebaselineTest(unittest.TestCase, StreamTestingMixin):
self.assertEquals(res, 0)
self.assertNotEmpty(out)
self.assertEqual(len(file_list), 6)
- self.assertBaselines(file_list, "/failures/unexpected/missing_text", [".txt"], err)
- self.assertBaselines(file_list, "/platform/test-mac-leopard/failures/unexpected/missing_image", [".png"], err)
- self.assertBaselines(file_list, "/platform/test-mac-leopard/failures/unexpected/missing_render_tree_dump", [".txt"], err)
+ self.assertBaselines(file_list, "failures/unexpected/missing_text", [".txt"], err)
+ self.assertBaselines(file_list, "platform/test/failures/unexpected/missing_image", [".png"], err)
+ self.assertBaselines(file_list, "platform/test/failures/unexpected/missing_render_tree_dump", [".txt"], err)
def test_new_baseline(self):
- # Test that we update the platform expectations. If the expectation
- # is mssing, then create a new expectation in the platform dir.
+ # Test that we update the platform expectations in the version-specific directories
+ # for both existing and new baselines.
host = MockHost()
res, out, err, _ = logging_run(['--pixel-tests',
'--new-baseline',
@@ -977,9 +988,9 @@ class RebaselineTest(unittest.TestCase, StreamTestingMixin):
self.assertEmpty(out)
self.assertEqual(len(file_list), 4)
self.assertBaselines(file_list,
- "/platform/test-mac-leopard/passes/image", [".txt", ".png"], err)
+ "platform/test-mac-leopard/passes/image", [".txt", ".png"], err)
self.assertBaselines(file_list,
- "/platform/test-mac-leopard/failures/expected/missing_image", [".txt", ".png"], err)
+ "platform/test-mac-leopard/failures/expected/missing_image", [".txt", ".png"], err)
if __name__ == '__main__':
diff --git a/Tools/Scripts/webkitpy/layout_tests/servers/http_server_unittest.py b/Tools/Scripts/webkitpy/layout_tests/servers/http_server_unittest.py
index a037a37dd..7a14526d1 100644
--- a/Tools/Scripts/webkitpy/layout_tests/servers/http_server_unittest.py
+++ b/Tools/Scripts/webkitpy/layout_tests/servers/http_server_unittest.py
@@ -28,6 +28,7 @@
import unittest
import re
+import sys
from webkitpy.common.host_mock import MockHost
from webkitpy.layout_tests.port import test
@@ -37,6 +38,10 @@ from webkitpy.layout_tests.servers.http_server_base import ServerError
class TestHttpServer(unittest.TestCase):
def test_start_cmd(self):
+ # Fails on win - see https://bugs.webkit.org/show_bug.cgi?id=84726
+ if sys.platform in ('cygwin', 'win32'):
+ return
+
host = MockHost()
test_port = test.TestPort(host)
host.filesystem.write_text_file(
diff --git a/Tools/Scripts/webkitpy/layout_tests/servers/websocket_server.py b/Tools/Scripts/webkitpy/layout_tests/servers/websocket_server.py
index 296ab9882..93747f690 100644
--- a/Tools/Scripts/webkitpy/layout_tests/servers/websocket_server.py
+++ b/Tools/Scripts/webkitpy/layout_tests/servers/websocket_server.py
@@ -51,6 +51,7 @@ _DEFAULT_WSS_PORT = 9323
class PyWebSocket(http_server.Lighttpd):
def __init__(self, port_obj, output_dir, port=_DEFAULT_WS_PORT,
root=None, use_tls=False,
+ private_key=None, certificate=None, ca_certificate=None,
pidfile=None):
"""Args:
output_dir: the absolute path to the layout test result directory
@@ -70,8 +71,15 @@ class PyWebSocket(http_server.Lighttpd):
if self._use_tls:
self._name = 'pywebsocket_secure'
- self._private_key = self._pem_file
- self._certificate = self._pem_file
+ if private_key:
+ self._private_key = private_key
+ else:
+ self._private_key = self._pem_file
+ if certificate:
+ self._certificate = certificate
+ else:
+ self._certificate = self._pem_file
+ self._ca_certificate = ca_certificate
if self._port:
self._port = int(self._port)
self._wsin = None
@@ -137,6 +145,9 @@ class PyWebSocket(http_server.Lighttpd):
if self._use_tls:
start_cmd.extend(['-t', '-k', self._private_key,
'-c', self._certificate])
+ if self._ca_certificate:
+ start_cmd.append('--ca-certificate')
+ start_cmd.append(self._ca_certificate)
self._start_cmd = start_cmd
server_name = self._filesystem.basename(pywebsocket_script)
diff --git a/Tools/Scripts/webkitpy/layout_tests/views/metered_stream.py b/Tools/Scripts/webkitpy/layout_tests/views/metered_stream.py
index 49a507ed6..814437780 100644
--- a/Tools/Scripts/webkitpy/layout_tests/views/metered_stream.py
+++ b/Tools/Scripts/webkitpy/layout_tests/views/metered_stream.py
@@ -91,14 +91,15 @@ class MeteredStream(object):
if self._erasing:
self._last_partial_line = txt[txt.rfind('\n') + 1:]
- def write(self, txt, now=None):
+ def write(self, txt, now=None, pid=None):
now = now or self._time_fn()
+ pid = pid or self._pid
self._last_write_time = now
if self._last_partial_line:
self._erase_last_partial_line()
if self._verbose:
now_tuple = time.localtime(now)
- msg = '%02d:%02d:%02d.%03d %d %s' % (now_tuple.tm_hour, now_tuple.tm_min, now_tuple.tm_sec, int((now * 1000) % 1000), self._pid, self._ensure_newline(txt))
+ msg = '%02d:%02d:%02d.%03d %d %s' % (now_tuple.tm_hour, now_tuple.tm_min, now_tuple.tm_sec, int((now * 1000) % 1000), pid, self._ensure_newline(txt))
elif self._isatty:
msg = txt
else:
@@ -106,14 +107,19 @@ class MeteredStream(object):
self._stream.write(msg)
- def writeln(self, txt, now=None):
- self.write(self._ensure_newline(txt), now)
+ def writeln(self, txt, now=None, pid=None):
+ self.write(self._ensure_newline(txt), now, pid)
def _erase_last_partial_line(self):
num_chars = len(self._last_partial_line)
self._stream.write(self._erasure(self._last_partial_line))
self._last_partial_line = ''
+ def flush(self):
+ if self._last_partial_line:
+ self._stream.write('\n')
+ self._last_partial_line = ''
+ self._stream.flush()
class _LogHandler(logging.Handler):
def __init__(self, meter):
diff --git a/Tools/Scripts/webkitpy/layout_tests/views/printing.py b/Tools/Scripts/webkitpy/layout_tests/views/printing.py
index 3df2956c1..3d98c6c59 100644
--- a/Tools/Scripts/webkitpy/layout_tests/views/printing.py
+++ b/Tools/Scripts/webkitpy/layout_tests/views/printing.py
@@ -407,5 +407,11 @@ class Printer(object):
return
self._write(msg)
+ def writeln(self, *args, **kwargs):
+ self._meter.writeln(*args, **kwargs)
+
def _write(self, msg):
self._meter.writeln(msg)
+
+ def flush(self):
+ self._meter.flush()
diff --git a/Tools/Scripts/webkitpy/performance_tests/perftest.py b/Tools/Scripts/webkitpy/performance_tests/perftest.py
index 20d3d5838..896bad4fa 100644
--- a/Tools/Scripts/webkitpy/performance_tests/perftest.py
+++ b/Tools/Scripts/webkitpy/performance_tests/perftest.py
@@ -36,10 +36,13 @@ import os
import signal
import socket
import subprocess
+import sys
import time
# Import for auto-install
-import webkitpy.thirdparty.autoinstalled.webpagereplay.replay
+if sys.platform != 'win32':
+ # FIXME: webpagereplay doesn't work on win32. See https://bugs.webkit.org/show_bug.cgi?id=88279.
+ import webkitpy.thirdparty.autoinstalled.webpagereplay.replay
from webkitpy.layout_tests.controllers.test_result_writer import TestResultWriter
from webkitpy.layout_tests.port.driver import DriverInput
@@ -216,10 +219,10 @@ class ReplayServer(object):
def __init__(self, archive, record):
self._process = None
- # FIXME: Should error if local proxy isn't set to forward requests to localhost:8080 and localhost:8413
+ # FIXME: Should error if local proxy isn't set to forward requests to localhost:8080 and localhost:8443
replay_path = webkitpy.thirdparty.autoinstalled.webpagereplay.replay.__file__
- args = ['python', replay_path, '--no-dns_forwarding', '--port', '8080', '--ssl_port', '8413', '--use_closest_match', '--log_level', 'warning']
+ args = ['python', replay_path, '--no-dns_forwarding', '--port', '8080', '--ssl_port', '8443', '--use_closest_match', '--log_level', 'warning']
if record:
args.append('--record')
args.append(archive)
diff --git a/Tools/Scripts/webkitpy/style/checker.py b/Tools/Scripts/webkitpy/style/checker.py
index e72a025f4..dff790ec1 100644
--- a/Tools/Scripts/webkitpy/style/checker.py
+++ b/Tools/Scripts/webkitpy/style/checker.py
@@ -140,11 +140,17 @@ _PATH_RULES_SPECIFIER = [
# Qt code uses '_' in some places (such as private slots
# and on test xxx_data methos on tests)
"Source/JavaScriptCore/qt/",
- "Source/WebKit/qt/Api/",
"Source/WebKit/qt/tests/",
"Source/WebKit/qt/declarative/",
"Source/WebKit/qt/examples/"],
["-readability/naming"]),
+
+ ([# The Qt APIs use Qt declaration style, it puts the * to
+ # the variable name, not to the class.
+ "Source/WebKit/qt/Api/"],
+ ["-readability/naming",
+ "-whitespace/declaration"]),
+
([# Qt's MiniBrowser has no config.h
"Tools/MiniBrowser/qt"],
["-build/include"]),
@@ -517,15 +523,14 @@ class CheckerDispatcher(object):
# Since "LayoutTests" is in _SKIPPED_FILES_WITHOUT_WARNING, make
# an exception to prevent files like "LayoutTests/ChangeLog" and
# "LayoutTests/ChangeLog-2009-06-16" from being skipped.
- # Files like 'test_expectations.txt' and 'drt_expectations.txt'
- # are also should not be skipped.
+ # Files like 'TestExpectations' are also should not be skipped.
#
# FIXME: Figure out a good way to avoid having to add special logic
# for this special case.
basename = os.path.basename(file_path)
if basename.startswith('ChangeLog'):
return False
- elif basename == 'test_expectations.txt' or basename == 'drt_expectations.txt':
+ elif basename == 'TestExpectations':
return False
for skipped_file in _SKIPPED_FILES_WITHOUT_WARNING:
if self._should_skip_file_path(file_path, skipped_file):
@@ -593,7 +598,7 @@ class CheckerDispatcher(object):
checker = PNGChecker(file_path, handle_style_error)
elif file_type == FileType.TEXT:
basename = os.path.basename(file_path)
- if basename == 'test_expectations.txt' or basename == 'drt_expectations.txt':
+ if basename == 'TestExpectations':
checker = TestExpectationsChecker(file_path, handle_style_error)
else:
checker = TextChecker(file_path, handle_style_error)
diff --git a/Tools/Scripts/webkitpy/style/checkers/cpp.py b/Tools/Scripts/webkitpy/style/checkers/cpp.py
index 1eea4973f..aaad85d5c 100644
--- a/Tools/Scripts/webkitpy/style/checkers/cpp.py
+++ b/Tools/Scripts/webkitpy/style/checkers/cpp.py
@@ -2632,7 +2632,7 @@ def _classify_include(filename, include, is_system, include_state):
"""
# If it is a system header we know it is classified as _OTHER_HEADER.
- if is_system:
+ if is_system and not include.startswith('public/'):
return _OTHER_HEADER
# If the include is named config.h then this is WebCore/config.h.
diff --git a/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py b/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py
index cba917108..1675c7010 100644
--- a/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py
+++ b/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py
@@ -2696,6 +2696,30 @@ class OrderOfIncludesTest(CppStyleTestBase):
os.path.isfile = self.os_path_isfile_orig
+ def test_public_primary_header(self):
+ # System header is not considered a primary header.
+ self.assert_language_rules_check('foo.cpp',
+ '#include "config.h"\n'
+ '#include <other/foo.h>\n'
+ '\n'
+ '#include "a.h"\n',
+ 'Alphabetical sorting problem. [build/include_order] [4]')
+
+ # ...except that it starts with public/.
+ self.assert_language_rules_check('foo.cpp',
+ '#include "config.h"\n'
+ '#include <public/foo.h>\n'
+ '\n'
+ '#include "a.h"\n',
+ '')
+
+ # Even if it starts with public/ its base part must match with the source file name.
+ self.assert_language_rules_check('foo.cpp',
+ '#include "config.h"\n'
+ '#include <public/foop.h>\n'
+ '\n'
+ '#include "a.h"\n',
+ 'Alphabetical sorting problem. [build/include_order] [4]')
def test_check_wtf_includes(self):
self.assert_language_rules_check('foo.cpp',
@@ -2751,6 +2775,19 @@ class OrderOfIncludesTest(CppStyleTestBase):
classify_include('foo.cpp',
'moc_foo.cpp',
False, include_state))
+ # <public/foo.h> must be considered as primary even if is_system is True.
+ self.assertEqual(cpp_style._PRIMARY_HEADER,
+ classify_include('foo/foo.cpp',
+ 'public/foo.h',
+ True, include_state))
+ self.assertEqual(cpp_style._OTHER_HEADER,
+ classify_include('foo.cpp',
+ 'foo.h',
+ True, include_state))
+ self.assertEqual(cpp_style._OTHER_HEADER,
+ classify_include('foo.cpp',
+ 'public/foop.h',
+ True, include_state))
# Qt private APIs use _p.h suffix.
self.assertEqual(cpp_style._PRIMARY_HEADER,
classify_include('foo.cpp',
diff --git a/Tools/Scripts/webkitpy/style/checkers/test_expectations.py b/Tools/Scripts/webkitpy/style/checkers/test_expectations.py
index 616959f76..c95c4120f 100644
--- a/Tools/Scripts/webkitpy/style/checkers/test_expectations.py
+++ b/Tools/Scripts/webkitpy/style/checkers/test_expectations.py
@@ -29,27 +29,27 @@
"""Checks WebKit style for test_expectations files."""
import logging
+import optparse
import os
import re
import sys
from common import TabChecker
from webkitpy.common.host import Host
-from webkitpy.layout_tests.models import test_expectations
-from webkitpy.layout_tests.port.base import DummyOptions
+from webkitpy.layout_tests.models.test_expectations import TestExpectationParser
_log = logging.getLogger(__name__)
class TestExpectationsChecker(object):
- """Processes test_expectations.txt lines for validating the syntax."""
+ """Processes TestExpectations lines for validating the syntax."""
categories = set(['test/expectations'])
def _determine_port_from_expectations_path(self, host, expectations_path):
# Pass a configuration to avoid calling default_configuration() when initializing the port (takes 0.5 seconds on a Mac Pro!).
- options = DummyOptions(configuration='Release')
+ options = optparse.Values({'configuration': 'Release'})
for port_name in host.port_factory.all_port_names():
port = host.port_factory.get(port_name, options=options)
if port.path_to_test_expectations_file().replace(port.path_from_webkit_base() + host.filesystem.sep, '') == expectations_path:
@@ -61,7 +61,6 @@ class TestExpectationsChecker(object):
self._handle_style_error = handle_style_error
self._handle_style_error.turn_off_line_filtering()
self._tab_checker = TabChecker(file_path, handle_style_error)
- self._output_regex = re.compile('.*test_expectations.txt:(?P<line>\d+)\s*(?P<message>.+)')
# FIXME: host should be a required parameter, not an optional one.
host = host or Host()
@@ -77,32 +76,17 @@ class TestExpectationsChecker(object):
pass
def check_test_expectations(self, expectations_str, tests=None, overrides=None):
- err = None
- expectations = None
- # FIXME: We need to rework how we lint strings so that we can do it independently of what a
- # port's existing expectations are. Linting should probably just call the parser directly.
- # For now we override the port hooks. This will also need to be reworked when expectations
- # can cascade arbitrarily, rather than just have expectations and overrides.
- orig_expectations = self._port_obj.test_expectations
- orig_overrides = self._port_obj.test_expectations_overrides
- try:
- self._port_obj.test_expectations = lambda: expectations_str
- self._port_obj.test_expectations_overrides = lambda: overrides
- expectations = test_expectations.TestExpectations(self._port_obj, tests, True)
- except test_expectations.ParseError, error:
- err = error
- finally:
- self._port_obj.text_expectations = orig_expectations
- self._port_obj.text_expectations_overrides = orig_overrides
-
- if err:
- level = 5
- for warning in err.warnings:
- matched = self._output_regex.match(warning)
- if matched:
- lineno, message = matched.group('line', 'message')
- self._handle_style_error(int(lineno), 'test/expectations', level, message)
-
+ # FIXME: we should pass in the filenames here if possible, and ensure
+ # that this works with with cascading expectations files and remove the overrides param.
+ parser = TestExpectationParser(self._port_obj, tests, False)
+ expectations = parser.parse('expectations', expectations_str)
+ if overrides:
+ expectations += parser.parse('overrides', overrides)
+
+ level = 5
+ for expectation_line in expectations:
+ for warning in expectation_line.warnings:
+ self._handle_style_error(expectation_line.line_number, 'test/expectations', level, warning)
def check_tabs(self, lines):
self._tab_checker.check(lines)
diff --git a/Tools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py b/Tools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py
index b6e3595fa..8cc91b1ac 100644
--- a/Tools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py
+++ b/Tools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py
@@ -75,16 +75,16 @@ class TestExpectationsTestCase(unittest.TestCase):
def test_determine_port_from_expectations_path(self):
self._expect_port_for_expectations_path(None, '/')
- self._expect_port_for_expectations_path(None, 'LayoutTests/chromium-mac/test_expectations.txt')
- self._expect_port_for_expectations_path('chromium', 'LayoutTests/platform/chromium/test_expectations.txt')
- self._expect_port_for_expectations_path(None, '/mock-checkout/LayoutTests/platform/win/test_expectations.txt')
- self._expect_port_for_expectations_path('win', 'LayoutTests/platform/win/test_expectations.txt')
+ self._expect_port_for_expectations_path(None, 'LayoutTests/chromium-mac/TestExpectations')
+ self._expect_port_for_expectations_path('chromium', 'LayoutTests/platform/chromium/TestExpectations')
+ self._expect_port_for_expectations_path(None, '/mock-checkout/LayoutTests/platform/win/TestExpectations')
+ self._expect_port_for_expectations_path('win', 'LayoutTests/platform/win/TestExpectations')
def assert_lines_lint(self, lines, should_pass, expected_output=None):
self._error_collector.reset_errors()
host = MockHost()
- checker = TestExpectationsChecker('test/test_expectations.txt',
+ checker = TestExpectationsChecker('test/TestExpectations',
self._error_collector, host=host)
# We should have failed to find a valid port object for that path.
@@ -105,7 +105,7 @@ class TestExpectationsTestCase(unittest.TestCase):
self.assertTrue(self._error_collector.turned_off_filtering)
def test_valid_expectations(self):
- self.assert_lines_lint(["BUGCR1234 MAC : passes/text.html = PASS FAIL"], should_pass=True)
+ self.assert_lines_lint(["BUGCR1234 MAC : passes/text.html = PASS TEXT"], should_pass=True)
def test_invalid_expectations(self):
self.assert_lines_lint(["BUG1234 : passes/text.html = GIVE UP"], should_pass=False)
diff --git a/Tools/Scripts/webkitpy/test/main.py b/Tools/Scripts/webkitpy/test/main.py
index af3123a01..c5dc39433 100644
--- a/Tools/Scripts/webkitpy/test/main.py
+++ b/Tools/Scripts/webkitpy/test/main.py
@@ -25,12 +25,14 @@
import logging
import optparse
+import os
import StringIO
import sys
import traceback
import unittest
from webkitpy.common.system.filesystem import FileSystem
+from webkitpy.common.system import outputcapture
from webkitpy.test.test_finder import TestFinder
from webkitpy.test.runner import TestRunner
@@ -59,6 +61,8 @@ class Tester(object):
help='verbose output (specify once for individual test results, twice for debug messages)')
parser.add_option('--skip-integrationtests', action='store_true', default=False,
help='do not run the integration tests')
+ parser.add_option('-p', '--pass-through', action='store_true', default=False,
+ help='be debugger friendly by passing captured output through to the system')
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.')
@@ -135,6 +139,10 @@ class Tester(object):
self.finder.clean_trees()
names = self.finder.find_names(args, self._options.skip_integrationtests, self._options.all)
+ if not names:
+ _log.error('No tests to run')
+ return False
+
return self._run_tests(names)
def _run_tests(self, names):
@@ -172,6 +180,8 @@ class Tester(object):
test_runner = TestRunner(self.stream, self._options, loader)
_log.debug("Running the tests.")
+ if self._options.pass_through:
+ outputcapture.OutputCapture.stream_wrapper = _CaptureAndPassThroughStream
result = test_runner.run(test_suite)
if self._options.coverage:
cov.stop()
@@ -184,3 +194,32 @@ class Tester(object):
traceback.print_exc(file=s)
for l in s.buflist:
_log.error(' ' + l.rstrip())
+
+
+class _CaptureAndPassThroughStream(object):
+ def __init__(self, stream):
+ self._buffer = StringIO.StringIO()
+ self._stream = stream
+
+ def write(self, msg):
+ self._stream.write(msg)
+
+ # Note that we don't want to capture any output generated by the debugger
+ # because that could cause the results of capture_output() to be invalid.
+ if not self._message_is_from_pdb():
+ self._buffer.write(msg)
+
+ def _message_is_from_pdb(self):
+ # We will assume that if the pdb module is in the stack then the output
+ # is being generated by the python debugger (or the user calling something
+ # from inside the debugger).
+ import inspect
+ import pdb
+ stack = inspect.stack()
+ return any(frame[1] == pdb.__file__.replace('.pyc', '.py') for frame in stack)
+
+ def flush(self):
+ self._stream.flush()
+
+ def getvalue(self):
+ return self._buffer.getvalue()
diff --git a/Tools/Scripts/webkitpy/test/main_unittest.py b/Tools/Scripts/webkitpy/test/main_unittest.py
new file mode 100644
index 000000000..1a60beef3
--- /dev/null
+++ b/Tools/Scripts/webkitpy/test/main_unittest.py
@@ -0,0 +1,54 @@
+# Copyright (C) 2012 Google, Inc.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import logging
+import unittest
+import StringIO
+
+from webkitpy.common.system.outputcapture import OutputCapture
+from webkitpy.test.main import Tester
+
+
+class TesterTest(unittest.TestCase):
+
+ def test_no_tests_found(self):
+ tester = Tester()
+ errors = StringIO.StringIO()
+
+ # Here we need to remove any existing log handlers so that they
+ # don't log the messages webkitpy.test while we're testing it.
+ root_logger = logging.getLogger()
+ root_handlers = root_logger.handlers
+ root_logger.handlers = []
+
+ tester.stream = errors
+ tester.finder.find_names = lambda args, skip_integration, run_all: []
+ oc = OutputCapture()
+ try:
+ oc.capture_output()
+ self.assertFalse(tester.run())
+ finally:
+ out, err, logs = oc.restore_output()
+ root_logger.handlers = root_handlers
+
+ self.assertTrue('No tests to run' in errors.getvalue())
+ self.assertTrue('No tests to run' in logs)
diff --git a/Tools/Scripts/webkitpy/test/test_finder.py b/Tools/Scripts/webkitpy/test/test_finder.py
index b2671e917..3a90197e9 100644
--- a/Tools/Scripts/webkitpy/test/test_finder.py
+++ b/Tools/Scripts/webkitpy/test/test_finder.py
@@ -166,9 +166,6 @@ class TestFinder(object):
'webkitpy.tool')
self._exclude(modules, win32_blacklist, 'fail horribly on win32', 54526)
- win32_blacklist_84726 = ('webkitpy.layout_tests.servers.http_server_unittest.TestHttpServer.test_start_cmd')
- self._exclude(modules, win32_blacklist_84726, 'fails on win32', 84726)
-
return modules
def _exclude(self, modules, module_prefixes, reason, bugid):
diff --git a/Tools/Scripts/webkitpy/thirdparty/__init__.py b/Tools/Scripts/webkitpy/thirdparty/__init__.py
index 26245bacc..078e18041 100644
--- a/Tools/Scripts/webkitpy/thirdparty/__init__.py
+++ b/Tools/Scripts/webkitpy/thirdparty/__init__.py
@@ -106,7 +106,12 @@ class AutoinstallImportHook(object):
installer.install(url="http://pypi.python.org/packages/source/J/Jinja2/Jinja2-2.6.tar.gz#md5=1c49a8825c993bfdcf55bb36897d28a2",
url_subpath="Jinja2-2.6/jinja2")
- self._install("http://pypi.python.org/packages/source/b/buildbot/buildbot-0.8.4p2.tar.gz#md5=7597d945724c80c0ab476e833a1026cb", "buildbot-0.8.4p2/buildbot")
+ SQLAlchemy_dir = self._fs.join(_AUTOINSTALLED_DIR, "sqlalchemy")
+ installer = AutoInstaller(append_to_search_path=True, target_dir=SQLAlchemy_dir)
+ installer.install(url="http://pypi.python.org/packages/source/S/SQLAlchemy/SQLAlchemy-0.7.7.tar.gz#md5=ddf6df7e014cea318fa981364f3f93b9",
+ url_subpath="SQLAlchemy-0.7.7/lib/sqlalchemy")
+
+ self._install("http://pypi.python.org/packages/source/b/buildbot/buildbot-0.8.6p1.tar.gz#md5=b6727d2810c692062c657492bcbeac6a", "buildbot-0.8.6p1/buildbot")
def _install_coverage(self):
installer = AutoInstaller(target_dir=_AUTOINSTALLED_DIR)
@@ -130,8 +135,8 @@ class AutoinstallImportHook(object):
def _install_webpagereplay(self):
if not self._fs.exists(self._fs.join(_AUTOINSTALLED_DIR, "webpagereplay")):
- self._install("http://web-page-replay.googlecode.com/files/webpagereplay-1.1.1.tar.gz", "webpagereplay-1.1.1")
- self._fs.move(self._fs.join(_AUTOINSTALLED_DIR, "webpagereplay-1.1.1"), self._fs.join(_AUTOINSTALLED_DIR, "webpagereplay"))
+ self._install("http://web-page-replay.googlecode.com/files/webpagereplay-1.1.2.tar.gz", "webpagereplay-1.1.2")
+ self._fs.move(self._fs.join(_AUTOINSTALLED_DIR, "webpagereplay-1.1.2"), self._fs.join(_AUTOINSTALLED_DIR, "webpagereplay"))
init_path = self._fs.join(_AUTOINSTALLED_DIR, "webpagereplay", "__init__.py")
if not self._fs.exists(init_path):
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/COPYING b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/COPYING
index ab9d52ddf..989d02e4c 100644
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/COPYING
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/COPYING
@@ -1,4 +1,4 @@
-Copyright 2009, Google Inc.
+Copyright 2012, Google Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hybi.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hybi.py
index c00f5692b..34fa7a60e 100644
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hybi.py
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hybi.py
@@ -39,6 +39,7 @@ http://tools.ietf.org/html/rfc6455
from collections import deque
import os
import struct
+import time
from mod_pywebsocket import common
from mod_pywebsocket import util
@@ -208,7 +209,17 @@ class FragmentedFrameBuilder(object):
def _create_control_frame(opcode, body, mask, frame_filters):
frame = Frame(opcode=opcode, payload=body)
- return _filter_and_format_frame_object(frame, mask, frame_filters)
+ for frame_filter in frame_filters:
+ frame_filter.filter(frame)
+
+ if len(frame.payload) > 125:
+ raise BadOperationException(
+ 'Payload data size of control frames must be 125 bytes or less')
+
+ header = create_header(
+ frame.opcode, len(frame.payload), frame.fin,
+ frame.rsv1, frame.rsv2, frame.rsv3, mask)
+ return _build_frame(header, frame.payload, mask)
def create_ping_frame(body, mask=False, frame_filters=[]):
@@ -286,6 +297,9 @@ 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])
@@ -299,6 +313,11 @@ class Stream(StreamBase):
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 '
@@ -310,6 +329,9 @@ class Stream(StreamBase):
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]
@@ -319,7 +341,13 @@ class Stream(StreamBase):
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]
@@ -327,6 +355,9 @@ class Stream(StreamBase):
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 '
@@ -335,12 +366,38 @@ class Stream(StreamBase):
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
- bytes = masker.mask(self.receive_bytes(payload_length))
+ 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)
+
+ 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
@@ -359,8 +416,8 @@ class Stream(StreamBase):
Raises:
BadOperationException: when called on a server-terminated
- connection or called with inconsistent message type or binary
- parameter.
+ connection or called with inconsistent message type or
+ binary parameter.
"""
if self._request.server_terminated:
@@ -408,6 +465,15 @@ class Stream(StreamBase):
frame = self._receive_frame_as_frame_object()
+ # Check the constraint on the payload size for control frames
+ # before extension processes the frame.
+ # See also http://tools.ietf.org/html/rfc6455#section-5.5
+ if (common.is_control_opcode(frame.opcode) and
+ len(frame.payload) > 125):
+ raise InvalidFrameException(
+ 'Payload data size of control frames must be 125 bytes or '
+ 'less')
+
for frame_filter in self._options.incoming_frame_filters:
frame_filter.filter(frame)
@@ -450,12 +516,6 @@ class Stream(StreamBase):
if frame.fin:
# Unfragmented frame
- if (common.is_control_opcode(frame.opcode) and
- len(frame.payload) > 125):
- raise InvalidFrameException(
- 'Application data size of control frames must be '
- '125 bytes or less')
-
self._original_opcode = frame.opcode
message = frame.payload
else:
@@ -488,7 +548,11 @@ class Stream(StreamBase):
# - 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) == 1:
+ 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')
@@ -507,8 +571,7 @@ class Stream(StreamBase):
if self._request.server_terminated:
self._logger.debug(
- 'Received ack for server-initiated closing '
- 'handshake')
+ 'Received ack for server-initiated closing handshake')
return None
self._logger.debug(
@@ -520,9 +583,16 @@ class Stream(StreamBase):
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')
+ 'Sent ack for client-initiated closing handshake '
+ '(code=%r, reason=%r)', code, reason)
return None
elif self._original_opcode == common.OPCODE_PING:
try:
@@ -571,17 +641,21 @@ class Stream(StreamBase):
'Opcode %d is not supported' % self._original_opcode)
def _send_closing_handshake(self, code, reason):
- if code >= (1 << 16) or code < 0:
- raise BadOperationException('Status code is out of range')
-
- encoded_reason = reason.encode('utf-8')
- if len(encoded_reason) + 2 > 125:
- raise BadOperationException(
- 'Application data size of close frames must be 125 bytes or '
- 'less')
+ 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
frame = create_close_frame(
- struct.pack('!H', code) + encoded_reason,
+ body,
self._options.mask_send,
self._options.outgoing_frame_filters)
@@ -590,15 +664,36 @@ class Stream(StreamBase):
self._write(frame)
def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason=''):
- """Closes a WebSocket connection."""
+ """Closes a WebSocket connection.
+
+ Args:
+ code: Status code for close frame. If code is None, a close
+ frame with empty body will be sent.
+ reason: string representing close reason.
+ Raises:
+ BadOperationException: when reason is specified with code None
+ or reason is not an instance of both str and unicode.
+ """
if self._request.server_terminated:
self._logger.debug(
'Requested close_connection but server is already terminated')
return
+ if code is None:
+ if reason is not None and len(reason) > 0:
+ raise BadOperationException(
+ 'close reason must not be specified if code is None')
+ reason = ''
+ else:
+ if not isinstance(reason, str) and not isinstance(reason, unicode):
+ raise BadOperationException(
+ 'close reason must be an instance of str or unicode')
+
self._send_closing_handshake(code, reason)
- self._logger.debug('Sent server-initiated closing handshake')
+ self._logger.debug(
+ 'Sent server-initiated closing handshake (code=%r, reason=%r)',
+ code, reason)
if (code == common.STATUS_GOING_AWAY or
code == common.STATUS_PROTOCOL_ERROR):
@@ -621,10 +716,6 @@ class Stream(StreamBase):
# note: mod_python Connection (mp_conn) doesn't have close method.
def send_ping(self, body=''):
- if len(body) > 125:
- raise ValueError(
- 'Application data size of control frames must be 125 bytes or '
- 'less')
frame = create_ping_frame(
body,
self._options.mask_send,
@@ -634,10 +725,6 @@ class Stream(StreamBase):
self._ping_queue.append(body)
def _send_pong(self, body):
- if len(body) > 125:
- raise ValueError(
- 'Application data size of control frames must be 125 bytes or '
- 'less')
frame = create_pong_frame(
body,
self._options.mask_send,
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/common.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/common.py
index ba670bb42..710967c80 100644
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/common.py
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/common.py
@@ -28,6 +28,16 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+"""This file must not depend on any module specific to the WebSocket protocol.
+"""
+
+
+from mod_pywebsocket import http_header_util
+
+
+# Additional log level definitions.
+LOGLEVEL_FINE = 9
+
# Constants indicating WebSocket protocol version.
VERSION_HIXIE75 = -1
VERSION_HYBI00 = 0
@@ -93,6 +103,7 @@ SEC_WEBSOCKET_LOCATION_HEADER = 'Sec-WebSocket-Location'
# Extensions
DEFLATE_STREAM_EXTENSION = 'deflate-stream'
DEFLATE_FRAME_EXTENSION = 'deflate-frame'
+PERFRAME_COMPRESSION_EXTENSION = 'perframe-compress'
X_WEBKIT_DEFLATE_FRAME_EXTENSION = 'x-webkit-deflate-frame'
# Status codes
@@ -176,4 +187,118 @@ class ExtensionParameter(object):
return param_value
+class ExtensionParsingException(Exception):
+ def __init__(self, name):
+ super(ExtensionParsingException, self).__init__(name)
+
+
+def _parse_extension_param(state, definition, allow_quoted_string):
+ param_name = http_header_util.consume_token(state)
+
+ if param_name is None:
+ raise ExtensionParsingException('No valid parameter name found')
+
+ http_header_util.consume_lwses(state)
+
+ if not http_header_util.consume_string(state, '='):
+ definition.add_parameter(param_name, None)
+ return
+
+ http_header_util.consume_lwses(state)
+
+ if allow_quoted_string:
+ # TODO(toyoshim): Add code to validate that parsed param_value is token
+ param_value = http_header_util.consume_token_or_quoted_string(state)
+ else:
+ param_value = http_header_util.consume_token(state)
+ if param_value is None:
+ raise ExtensionParsingException(
+ 'No valid parameter value found on the right-hand side of '
+ 'parameter %r' % param_name)
+
+ definition.add_parameter(param_name, param_value)
+
+
+def _parse_extension(state, allow_quoted_string):
+ extension_token = http_header_util.consume_token(state)
+ if extension_token is None:
+ return None
+
+ extension = ExtensionParameter(extension_token)
+
+ while True:
+ http_header_util.consume_lwses(state)
+
+ if not http_header_util.consume_string(state, ';'):
+ break
+
+ http_header_util.consume_lwses(state)
+
+ try:
+ _parse_extension_param(state, extension, allow_quoted_string)
+ except ExtensionParsingException, e:
+ raise ExtensionParsingException(
+ 'Failed to parse parameter for %r (%r)' %
+ (extension_token, e))
+
+ return extension
+
+
+def parse_extensions(data, allow_quoted_string=False):
+ """Parses Sec-WebSocket-Extensions header value returns a list of
+ ExtensionParameter objects.
+
+ Leading LWSes must be trimmed.
+ """
+
+ state = http_header_util.ParsingState(data)
+
+ extension_list = []
+ while True:
+ extension = _parse_extension(state, allow_quoted_string)
+ if extension is not None:
+ extension_list.append(extension)
+
+ http_header_util.consume_lwses(state)
+
+ if http_header_util.peek(state) is None:
+ break
+
+ if not http_header_util.consume_string(state, ','):
+ raise ExtensionParsingException(
+ 'Failed to parse Sec-WebSocket-Extensions header: '
+ 'Expected a comma but found %r' %
+ http_header_util.peek(state))
+
+ http_header_util.consume_lwses(state)
+
+ if len(extension_list) == 0:
+ raise ExtensionParsingException(
+ 'No valid extension entry found')
+
+ return extension_list
+
+
+def format_extension(extension):
+ """Formats an ExtensionParameter object."""
+
+ formatted_params = [extension.name()]
+ for param_name, param_value in extension.get_parameters():
+ if param_value is None:
+ formatted_params.append(param_name)
+ else:
+ quoted_value = http_header_util.quote_if_necessary(param_value)
+ formatted_params.append('%s=%s' % (param_name, quoted_value))
+ return '; '.join(formatted_params)
+
+
+def format_extensions(extension_list):
+ """Formats a list of ExtensionParameter objects."""
+
+ formatted_extension_list = []
+ for extension in extension_list:
+ formatted_extension_list.append(format_extension(extension))
+ return ', '.join(formatted_extension_list)
+
+
# vi:sts=4 sw=4 et
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/extensions.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/extensions.py
index ce3784658..52b7a4a19 100644
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/extensions.py
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/extensions.py
@@ -1,4 +1,4 @@
-# Copyright 2011, Google Inc.
+# Copyright 2012, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -30,6 +30,7 @@
from mod_pywebsocket import common
from mod_pywebsocket import util
+from mod_pywebsocket.http_header_util import quote_if_necessary
_available_processors = {}
@@ -254,6 +255,96 @@ _available_processors[common.X_WEBKIT_DEFLATE_FRAME_EXTENSION] = (
DeflateFrameExtensionProcessor)
+def _parse_compression_method(data):
+ """Parses the value of "method" extension parameter."""
+
+ return common.parse_extensions(data, allow_quoted_string=True)
+
+
+def _create_accepted_method_desc(method_name, method_params):
+ """Creates accepted-method-desc from given method name and parameters"""
+
+ extension = common.ExtensionParameter(method_name)
+ for name, value in method_params:
+ extension.add_parameter(name, value)
+ return common.format_extension(extension)
+
+
+class PerFrameCompressionExtensionProcessor(ExtensionProcessorInterface):
+ """WebSocket Per-frame compression extension processor."""
+
+ _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
+
+ 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):
+ """Looks up the compression processor based on the self._request and
+ returns the compression processor's response.
+ """
+
+ method_list = self._request.get_parameter_value(self._METHOD_PARAM)
+ if method_list is None:
+ return None
+ methods = _parse_compression_method(method_list)
+ if methods is None:
+ return None
+ comression_processor = None
+ # The current implementation tries only the first method that matches
+ # supported algorithm. Following methods aren't tried even if the
+ # first one is rejected.
+ # TODO(bashi): Need to clarify this behavior.
+ for method_desc in methods:
+ compression_processor = self._lookup_compression_processor(
+ method_desc)
+ if compression_processor is not None:
+ self._compression_method_name = method_desc.name()
+ break
+ if compression_processor is None:
+ return None
+ processor_response = compression_processor.get_extension_response()
+ if processor_response is None:
+ return None
+ self._compression_processor = compression_processor
+ return processor_response
+
+ def get_extension_response(self):
+ processor_response = self._get_compression_processor_response()
+ if processor_response is None:
+ return None
+
+ response = common.ExtensionParameter(self._request.name())
+ accepted_method_desc = _create_accepted_method_desc(
+ self._compression_method_name,
+ processor_response.get_parameters())
+ response.add_parameter(self._METHOD_PARAM, accepted_method_desc)
+ self._logger.debug(
+ 'Enable %s extension (method: %s)' %
+ (self._request.name(), self._compression_method_name))
+ return response
+
+ def setup_stream_options(self, stream_options):
+ if self._compression_processor is None:
+ return
+ self._compression_processor.setup_stream_options(stream_options)
+
+ def get_compression_processor(self):
+ return self._compression_processor
+
+
+_available_processors[common.PERFRAME_COMPRESSION_EXTENSION] = (
+ PerFrameCompressionExtensionProcessor)
+
+
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/_base.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/_base.py
index 4d7c32e35..bc095b129 100644
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/_base.py
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/_base.py
@@ -1,4 +1,4 @@
-# Copyright 2011, Google Inc.
+# Copyright 2012, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -216,108 +216,4 @@ def parse_token_list(data):
return token_list
-def _parse_extension_param(state, definition, allow_quoted_string):
- param_name = http_header_util.consume_token(state)
-
- if param_name is None:
- raise HandshakeException('No valid parameter name found')
-
- http_header_util.consume_lwses(state)
-
- if not http_header_util.consume_string(state, '='):
- definition.add_parameter(param_name, None)
- return
-
- http_header_util.consume_lwses(state)
-
- if allow_quoted_string:
- # TODO(toyoshim): Add code to validate that parsed param_value is token
- param_value = http_header_util.consume_token_or_quoted_string(state)
- else:
- param_value = http_header_util.consume_token(state)
- if param_value is None:
- raise HandshakeException(
- 'No valid parameter value found on the right-hand side of '
- 'parameter %r' % param_name)
-
- definition.add_parameter(param_name, param_value)
-
-
-def _parse_extension(state, allow_quoted_string):
- extension_token = http_header_util.consume_token(state)
- if extension_token is None:
- return None
-
- extension = common.ExtensionParameter(extension_token)
-
- while True:
- http_header_util.consume_lwses(state)
-
- if not http_header_util.consume_string(state, ';'):
- break
-
- http_header_util.consume_lwses(state)
-
- try:
- _parse_extension_param(state, extension, allow_quoted_string)
- except HandshakeException, e:
- raise HandshakeException(
- 'Failed to parse Sec-WebSocket-Extensions header: '
- 'Failed to parse parameter for %r (%r)' %
- (extension_token, e))
-
- return extension
-
-
-def parse_extensions(data, allow_quoted_string=False):
- """Parses Sec-WebSocket-Extensions header value returns a list of
- common.ExtensionParameter objects.
-
- Leading LWSes must be trimmed.
- """
-
- state = http_header_util.ParsingState(data)
-
- extension_list = []
- while True:
- extension = _parse_extension(state, allow_quoted_string)
- if extension is not None:
- extension_list.append(extension)
-
- http_header_util.consume_lwses(state)
-
- if http_header_util.peek(state) is None:
- break
-
- if not http_header_util.consume_string(state, ','):
- raise HandshakeException(
- 'Failed to parse Sec-WebSocket-Extensions header: '
- 'Expected a comma but found %r' %
- http_header_util.peek(state))
-
- http_header_util.consume_lwses(state)
-
- if len(extension_list) == 0:
- raise HandshakeException(
- 'Sec-WebSocket-Extensions header contains no valid extension')
-
- return extension_list
-
-
-def format_extensions(extension_list):
- formatted_extension_list = []
- for extension in extension_list:
- formatted_params = [extension.name()]
- for param_name, param_value in extension.get_parameters():
- if param_value is None:
- formatted_params.append(param_name)
- else:
- quoted_value = http_header_util.quote_if_necessary(param_value)
- formatted_params.append('%s=%s' % (param_name, quoted_value))
-
- formatted_extension_list.append('; '.join(formatted_params))
-
- return ', '.join(formatted_extension_list)
-
-
# 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 3bc84bd26..2883acbf8 100644
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/hybi.py
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/handshake/hybi.py
@@ -1,4 +1,4 @@
-# Copyright 2011, Google Inc.
+# Copyright 2012, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -50,11 +50,9 @@ import re
from mod_pywebsocket import common
from mod_pywebsocket.extensions import get_extension_processor
from mod_pywebsocket.handshake._base import check_request_line
-from mod_pywebsocket.handshake._base import format_extensions
from mod_pywebsocket.handshake._base import format_header
from mod_pywebsocket.handshake._base import get_mandatory_header
from mod_pywebsocket.handshake._base import HandshakeException
-from mod_pywebsocket.handshake._base import parse_extensions
from mod_pywebsocket.handshake._base import parse_token_list
from mod_pywebsocket.handshake._base import validate_mandatory_header
from mod_pywebsocket.handshake._base import validate_subprotocol
@@ -290,8 +288,12 @@ class Handshaker(object):
allow_quoted_string=False
else:
allow_quoted_string=True
- self._request.ws_requested_extensions = parse_extensions(
- extensions_header, allow_quoted_string=allow_quoted_string)
+ try:
+ self._request.ws_requested_extensions = common.parse_extensions(
+ extensions_header, allow_quoted_string=allow_quoted_string)
+ except common.ExtensionParsingException, e:
+ raise HandshakeException(
+ 'Failed to parse Sec-WebSocket-Extensions header: %r' % e)
self._logger.debug(
'Extensions requested: %r',
@@ -358,7 +360,7 @@ class Handshaker(object):
len(self._request.ws_extensions) != 0):
response.append(format_header(
common.SEC_WEBSOCKET_EXTENSIONS_HEADER,
- format_extensions(self._request.ws_extensions)))
+ common.format_extensions(self._request.ws_extensions)))
response.append('\r\n')
raw_response = ''.join(response)
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/standalone.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/standalone.py
index dc143ea00..850aa5cd4 100755
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/standalone.py
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/standalone.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
#
-# Copyright 2011, Google Inc.
+# Copyright 2012, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -55,6 +55,20 @@ handlers. If this path is relative, <document_root> is used as the base.
handlers under scan_dir are scanned. This is useful in saving scan time.
+SUPPORTING TLS
+
+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.
+
+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
+
+
CONFIGURATION FILE
You can also write a configuration file and use it by specifying the path to
@@ -311,10 +325,16 @@ class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
continue
if self.websocket_server_options.use_tls:
if _HAS_SSL:
+ if self.websocket_server_options.ca_certificate:
+ client_cert_ = ssl.CERT_REQUIRED
+ else:
+ client_cert_ = ssl.CERT_NONE
socket_ = ssl.wrap_socket(socket_,
keyfile=self.websocket_server_options.private_key,
certfile=self.websocket_server_options.certificate,
- ssl_version=ssl.PROTOCOL_SSLv23)
+ ssl_version=ssl.PROTOCOL_SSLv23,
+ ca_certs=self.websocket_server_options.ca_certificate,
+ cert_reqs=client_cert_)
if _HAS_OPEN_SSL:
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
ctx.use_privatekey_file(
@@ -601,7 +621,13 @@ class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
return False
+def _get_logger_from_class(c):
+ return logging.getLogger('%s.%s' % (c.__module__, c.__name__))
+
+
def _configure_logging(options):
+ logging.addLevelName(common.LOGLEVEL_FINE, 'FINE')
+
logger = logging.getLogger()
logger.setLevel(logging.getLevelName(options.log_level.upper()))
if options.log_file:
@@ -614,6 +640,13 @@ def _configure_logging(options):
handler.setFormatter(formatter)
logger.addHandler(handler)
+ deflate_log_level_name = logging.getLevelName(
+ options.deflate_log_level.upper())
+ _get_logger_from_class(util._Deflater).setLevel(
+ deflate_log_level_name)
+ _get_logger_from_class(util._Inflater).setLevel(
+ deflate_log_level_name)
+
def _alias_handlers(dispatcher, websock_handlers_map_file):
"""Set aliases specified in websock_handler_map_file in dispatcher.
@@ -702,13 +735,25 @@ 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('-l', '--log-file', '--log_file', dest='log_file',
default='', help='Log file.')
+ # Custom log level:
+ # - FINE: Prints status of each frame processing step
parser.add_option('--log-level', '--log_level', type='choice',
dest='log_level', default='warn',
- choices=['debug', 'info', 'warning', 'warn', 'error',
+ choices=['fine',
+ 'debug', 'info', 'warning', 'warn', 'error',
'critical'],
help='Log level.')
+ parser.add_option('--deflate-log-level', '--deflate_log_level',
+ type='choice',
+ dest='deflate_log_level', default='warn',
+ choices=['debug', 'info', 'warning', 'warn', 'error',
+ 'critical'],
+ help='Log level for _Deflater and _Inflater.')
parser.add_option('--thread-monitor-interval-in-sec',
'--thread_monitor_interval_in_sec',
dest='thread_monitor_interval_in_sec',
@@ -825,13 +870,20 @@ def _main(args=None):
if options.use_tls:
if not (_HAS_SSL or _HAS_OPEN_SSL):
- logging.critical('TLS support requires ssl or pyOpenSSL.')
+ logging.critical('TLS support requires ssl or pyOpenSSL module.')
sys.exit(1)
if not options.private_key or not options.certificate:
logging.critical(
'To use TLS, specify private_key and certificate.')
sys.exit(1)
+ if options.ca_certificate:
+ if not options.use_tls:
+ logging.critical('TLS must be enabled for client authentication.')
+ sys.exit(1)
+ if not _HAS_SSL:
+ logging.critical('Client authentication requires ssl module.')
+
if not options.scan_dir:
options.scan_dir = options.websock_handlers
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/util.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/util.py
index 9a0ab5de6..6146e052f 100644
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/util.py
+++ b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/util.py
@@ -177,9 +177,16 @@ class RepeatedXorMasker(object):
def mask(self, s):
result = array.array('B')
result.fromstring(s)
+ # Use temporary local variables to eliminate the cost to access
+ # attributes
+ count = self._count
+ mask = self._mask
+ mask_size = self._mask_size
for i in xrange(len(result)):
- result[i] ^= self._mask[self._count]
- self._count = (self._count + 1) % self._mask_size
+ result[i] ^= mask[count]
+ count = (count + 1) % mask_size
+ self._count = count
+
return result.tostring()
diff --git a/Tools/Scripts/webkitpy/thirdparty/ordered_dict.py b/Tools/Scripts/webkitpy/thirdparty/ordered_dict.py
new file mode 100644
index 000000000..3dc735a4a
--- /dev/null
+++ b/Tools/Scripts/webkitpy/thirdparty/ordered_dict.py
@@ -0,0 +1,89 @@
+# Copyright (c) 2009 Raymond Hettinger.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# This code is obtained from http://code.activestate.com/recipes/576669/
+
+from collections import MutableMapping
+
+class OrderedDict(dict, MutableMapping):
+
+ # Methods with direct access to underlying attributes
+
+ def __init__(self, *args, **kwds):
+ if len(args) > 1:
+ raise TypeError('expected at 1 argument, got %d', len(args))
+ if not hasattr(self, '_keys'):
+ self._keys = []
+ self.update(*args, **kwds)
+
+ def clear(self):
+ del self._keys[:]
+ dict.clear(self)
+
+ def __setitem__(self, key, value):
+ if key not in self:
+ self._keys.append(key)
+ dict.__setitem__(self, key, value)
+
+ def __delitem__(self, key):
+ dict.__delitem__(self, key)
+ self._keys.remove(key)
+
+ def __iter__(self):
+ return iter(self._keys)
+
+ def __reversed__(self):
+ return reversed(self._keys)
+
+ def popitem(self):
+ if not self:
+ raise KeyError
+ key = self._keys.pop()
+ value = dict.pop(self, key)
+ return key, value
+
+ def __reduce__(self):
+ items = [[k, self[k]] for k in self]
+ inst_dict = vars(self).copy()
+ inst_dict.pop('_keys', None)
+ return (self.__class__, (items,), inst_dict)
+
+ # Methods with indirect access via the above methods
+
+ setdefault = MutableMapping.setdefault
+ update = MutableMapping.update
+ pop = MutableMapping.pop
+ keys = MutableMapping.keys
+ values = MutableMapping.values
+ items = MutableMapping.items
+
+ def __repr__(self):
+ pairs = ', '.join(map('%r: %r'.__mod__, self.items()))
+ return '%s({%s})' % (self.__class__.__name__, pairs)
+
+ def copy(self):
+ return self.__class__(self)
+
+ @classmethod
+ def fromkeys(cls, iterable, value=None):
+ d = cls()
+ for key in iterable:
+ d[key] = value
+ return d
diff --git a/Tools/Scripts/webkitpy/tool/commands/queries.py b/Tools/Scripts/webkitpy/tool/commands/queries.py
index ab9db4aca..f69971e59 100644
--- a/Tools/Scripts/webkitpy/tool/commands/queries.py
+++ b/Tools/Scripts/webkitpy/tool/commands/queries.py
@@ -395,7 +395,7 @@ class PrintExpectations(AbstractDeclarativeCommand):
make_option('--csv', action='store_true', default=False,
help='Print a CSV-style report that includes the port name, modifiers, tests, and expectations'),
make_option('-f', '--full', action='store_true', default=False,
- help='Print a full test_expectations.txt-style line for every match'),
+ help='Print a full TestExpectations-style line for every match'),
] + port_options(platform='port/platform to use. Use glob-style wildcards for multiple ports (implies --csv)')
AbstractDeclarativeCommand.__init__(self, options=options)
diff --git a/Tools/Scripts/webkitpy/tool/commands/rebaseline.py b/Tools/Scripts/webkitpy/tool/commands/rebaseline.py
index 5a184f19e..bd890cc8e 100644
--- a/Tools/Scripts/webkitpy/tool/commands/rebaseline.py
+++ b/Tools/Scripts/webkitpy/tool/commands/rebaseline.py
@@ -43,16 +43,12 @@ from webkitpy.common.system.user import User
from webkitpy.layout_tests.controllers.test_result_writer import TestResultWriter
from webkitpy.layout_tests.models import test_failures
from webkitpy.layout_tests.models.test_configuration import TestConfiguration
-from webkitpy.layout_tests.models.test_expectations import TestExpectations
+from webkitpy.layout_tests.models.test_expectations import TestExpectations, suffixes_for_expectations, BASELINE_SUFFIX_LIST
from webkitpy.layout_tests.port import builders
from webkitpy.tool.grammar import pluralize
from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand
-# FIXME: Pull this from Port.baseline_extensions().
-_baseline_suffix_list = ['png', 'wav', 'txt']
-
-
_log = logging.getLogger(__name__)
# FIXME: Should TestResultWriter know how to compute this string?
@@ -64,10 +60,10 @@ class AbstractRebaseliningCommand(AbstractDeclarativeCommand):
def __init__(self, options=None):
options = options or []
options.extend([
- optparse.make_option('--suffixes', default=','.join(_baseline_suffix_list), action='store',
+ optparse.make_option('--suffixes', default=','.join(BASELINE_SUFFIX_LIST), action='store',
help='file types to rebaseline')])
AbstractDeclarativeCommand.__init__(self, options=options)
- self._baseline_suffix_list = _baseline_suffix_list
+ self._baseline_suffix_list = BASELINE_SUFFIX_LIST
class RebaselineTest(AbstractRebaseliningCommand):
@@ -140,7 +136,7 @@ class RebaselineTest(AbstractRebaseliningCommand):
def _update_expectations_file(self, builder_name, test_name):
port = self._tool.port_factory.get_from_builder_name(builder_name)
- expectations = TestExpectations(port)
+ expectations = TestExpectations(port, include_overrides=False)
for test_configuration in port.all_test_configurations():
if test_configuration.version == port.test_configuration().version:
@@ -239,7 +235,7 @@ class AnalyzeBaselines(AbstractRebaseliningCommand):
class RebaselineExpectations(AbstractDeclarativeCommand):
name = "rebaseline-expectations"
- help_text = "Rebaselines the tests indicated in test_expectations.txt."
+ help_text = "Rebaselines the tests indicated in TestExpectations."
def __init__(self):
options = [
@@ -260,19 +256,25 @@ class RebaselineExpectations(AbstractDeclarativeCommand):
# FIXME: Support non-Chromium ports.
return port_name.startswith('chromium-')
- def _expectations(self, port):
- return TestExpectations(port)
-
def _update_expectations_file(self, port_name):
if not self._is_supported_port(port_name):
return
port = self._tool.port_factory.get(port_name)
- expectations = self._expectations(port)
+
+ # FIXME: This will intentionally skip over any REBASELINE expectations that were in an overrides file.
+ # This is not good, but avoids having the overrides getting written into the main file.
+ # See https://bugs.webkit.org/show_bug.cgi?id=88456 for context. This will no longer be needed
+ # once we properly support cascading expectations files.
+ expectations = TestExpectations(port, include_overrides=False)
path = port.path_to_test_expectations_file()
self._tool.filesystem.write_text_file(path, expectations.remove_rebaselined_tests(expectations.get_rebaselining_failures()))
def _tests_to_rebaseline(self, port):
- return self._expectations(port).get_rebaselining_failures()
+ tests_to_rebaseline = {}
+ expectations = TestExpectations(port, include_overrides=True)
+ for test in expectations.get_rebaselining_failures():
+ tests_to_rebaseline[test] = suffixes_for_expectations(expectations.get_expectations(test))
+ return tests_to_rebaseline
def _rebaseline_port(self, port_name):
if not self._is_supported_port(port_name):
@@ -281,23 +283,23 @@ class RebaselineExpectations(AbstractDeclarativeCommand):
if not builder_name:
return
_log.info("Retrieving results for %s from %s." % (port_name, builder_name))
- for test_name in self._tests_to_rebaseline(self._tool.port_factory.get(port_name)):
- self._touched_test_names.add(test_name)
- _log.info(" %s" % test_name)
- # FIXME: need to extract the correct list of suffixes here.
- self._run_webkit_patch(['rebaseline-test', builder_name, test_name])
+ for test_name, suffixes in self._tests_to_rebaseline(self._tool.port_factory.get(port_name)).iteritems():
+ self._touched_tests.setdefault(test_name, set()).update(set(suffixes))
+ _log.info(" %s (%s)" % (test_name, ','.join(suffixes)))
+ # FIXME: we should use executive.run_in_parallel() to speed this up.
+ self._run_webkit_patch(['rebaseline-test', '--suffixes', ','.join(suffixes), builder_name, test_name])
def execute(self, options, args, tool):
- self._touched_test_names = set([])
+ self._touched_tests = {}
for port_name in tool.port_factory.all_port_names():
self._rebaseline_port(port_name)
for port_name in tool.port_factory.all_port_names():
self._update_expectations_file(port_name)
if not options.optimize:
return
- for test_name in self._touched_test_names:
- _log.info("Optimizing baselines for %s." % test_name)
- self._run_webkit_patch(['optimize-baselines', test_name])
+ for test_name, suffixes in self._touched_tests.iteritems():
+ _log.info("Optimizing baselines for %s (%s)." % (test_name, ','.join(suffixes)))
+ self._run_webkit_patch(['optimize-baselines', '--suffixes', ','.join(suffixes), test_name])
class Rebaseline(AbstractDeclarativeCommand):
diff --git a/Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py b/Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py
index f8d9dfc1b..b5a043ff8 100644
--- a/Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py
@@ -48,6 +48,8 @@ class TestRebaseline(unittest.TestCase):
command.bind_to_tool(tool)
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(), """BUGB MAC LINUX XP DEBUG : fast/dom/Window/window-postmessage-clone-really-deep-array.html = PASS
BUGA DEBUG : fast/css/large-list-of-rules-crash.html = TEXT
""")
@@ -72,6 +74,7 @@ BUGA DEBUG : fast/css/large-list-of-rules-crash.html = TEXT
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'), '')
tool.filesystem.write_text_file(lion_port.path_to_test_expectations_file(), "BUGX MAC : userscripts/another-test.html = IMAGE\nBUGZ LINUX : userscripts/another-test.html = IMAGE\n")
tool.filesystem.write_text_file(os.path.join(lion_port.layout_tests_dir(), "userscripts/another-test.html"), "Dummy test contents")
@@ -84,6 +87,26 @@ Retrieving http://example.com/f/builders/Webkit Mac10.7/results/layout-test-resu
new_expectations = tool.filesystem.read_text_file(lion_port.path_to_test_expectations_file())
self.assertEqual(new_expectations, "BUGX LEOPARD SNOWLEOPARD : userscripts/another-test.html = IMAGE\nBUGZ LINUX : userscripts/another-test.html = IMAGE\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'), '')
+ tool.filesystem.write_text_file(lion_port.path_to_test_expectations_file(), "BUGX MAC : userscripts/another-test.html = IMAGE\nBUGZ LINUX : userscripts/another-test.html = IMAGE\n")
+ tool.filesystem.write_text_file(lion_port.path_from_chromium_base('skia', 'skia_test_expectations.txt'), "BUGY MAC : other-test.html = TEXT\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.
+"""
+ 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, "BUGX LEOPARD SNOWLEOPARD : userscripts/another-test.html = IMAGE\nBUGZ LINUX : userscripts/another-test.html = IMAGE\n")
+
def test_rebaseline_test(self):
command = RebaselineTest()
command.bind_to_tool(MockTool())
@@ -160,64 +183,87 @@ Retrieving http://example.com/f/builders/Webkit Mac10.7/results/layout-test-resu
for port_name in tool.port_factory.all_port_names():
port = tool.port_factory.get(port_name)
- tool.filesystem.write_text_file(port.path_to_test_expectations_file(), '')
+ for path in port.expectations_files():
+ tool.filesystem.write_text_file(path, '')
# Don't enable logging until after we create the mock expectation files as some Port.__init__'s run subcommands.
tool.executive = MockExecutive(should_log=True)
expected_logs = """Retrieving results for chromium-linux-x86 from Webkit Linux 32.
- userscripts/another-test.html
- userscripts/images.svg
+ userscripts/another-test.html (txt)
+ userscripts/images.svg (png)
Retrieving results for chromium-linux-x86_64 from Webkit Linux.
- userscripts/another-test.html
- userscripts/images.svg
+ userscripts/another-test.html (txt)
+ userscripts/images.svg (png)
Retrieving results for chromium-mac-leopard from Webkit Mac10.5.
- userscripts/another-test.html
- userscripts/images.svg
+ userscripts/another-test.html (txt)
+ userscripts/images.svg (png)
Retrieving results for chromium-mac-lion from Webkit Mac10.7.
- userscripts/another-test.html
- userscripts/images.svg
+ userscripts/another-test.html (txt)
+ userscripts/images.svg (png)
Retrieving results for chromium-mac-snowleopard from Webkit Mac10.6.
- userscripts/another-test.html
- userscripts/images.svg
+ userscripts/another-test.html (txt)
+ userscripts/images.svg (png)
Retrieving results for chromium-win-vista from Webkit Vista.
- userscripts/another-test.html
- userscripts/images.svg
+ userscripts/another-test.html (txt)
+ userscripts/images.svg (png)
Retrieving results for chromium-win-win7 from Webkit Win7.
- userscripts/another-test.html
- userscripts/images.svg
+ userscripts/another-test.html (txt)
+ userscripts/images.svg (png)
Retrieving results for chromium-win-xp from Webkit Win.
- userscripts/another-test.html
- userscripts/images.svg
+ userscripts/another-test.html (txt)
+ userscripts/images.svg (png)
"""
- expected_stderr = """MOCK run_command: ['echo', 'rebaseline-test', 'Webkit Linux 32', 'userscripts/another-test.html'], cwd=/mock-checkout
-MOCK run_command: ['echo', 'rebaseline-test', 'Webkit Linux 32', 'userscripts/images.svg'], cwd=/mock-checkout
-MOCK run_command: ['echo', 'rebaseline-test', 'Webkit Linux', 'userscripts/another-test.html'], cwd=/mock-checkout
-MOCK run_command: ['echo', 'rebaseline-test', 'Webkit Linux', 'userscripts/images.svg'], cwd=/mock-checkout
-MOCK run_command: ['echo', 'rebaseline-test', 'Webkit Mac10.5', 'userscripts/another-test.html'], cwd=/mock-checkout
-MOCK run_command: ['echo', 'rebaseline-test', 'Webkit Mac10.5', 'userscripts/images.svg'], cwd=/mock-checkout
-MOCK run_command: ['echo', 'rebaseline-test', 'Webkit Mac10.7', 'userscripts/another-test.html'], cwd=/mock-checkout
-MOCK run_command: ['echo', 'rebaseline-test', 'Webkit Mac10.7', 'userscripts/images.svg'], cwd=/mock-checkout
-MOCK run_command: ['echo', 'rebaseline-test', 'Webkit Mac10.6', 'userscripts/another-test.html'], cwd=/mock-checkout
-MOCK run_command: ['echo', 'rebaseline-test', 'Webkit Mac10.6', 'userscripts/images.svg'], cwd=/mock-checkout
-MOCK run_command: ['echo', 'rebaseline-test', 'Webkit Vista', 'userscripts/another-test.html'], cwd=/mock-checkout
-MOCK run_command: ['echo', 'rebaseline-test', 'Webkit Vista', 'userscripts/images.svg'], cwd=/mock-checkout
-MOCK run_command: ['echo', 'rebaseline-test', 'Webkit Win7', 'userscripts/another-test.html'], cwd=/mock-checkout
-MOCK run_command: ['echo', 'rebaseline-test', 'Webkit Win7', 'userscripts/images.svg'], cwd=/mock-checkout
-MOCK run_command: ['echo', 'rebaseline-test', 'Webkit Win', 'userscripts/another-test.html'], cwd=/mock-checkout
-MOCK run_command: ['echo', 'rebaseline-test', 'Webkit Win', 'userscripts/images.svg'], cwd=/mock-checkout
+ expected_stderr = """MOCK run_command: ['echo', 'rebaseline-test', '--suffixes', 'txt', 'Webkit Linux 32', 'userscripts/another-test.html'], cwd=/mock-checkout
+MOCK run_command: ['echo', 'rebaseline-test', '--suffixes', 'png', 'Webkit Linux 32', 'userscripts/images.svg'], cwd=/mock-checkout
+MOCK run_command: ['echo', 'rebaseline-test', '--suffixes', 'txt', 'Webkit Linux', 'userscripts/another-test.html'], cwd=/mock-checkout
+MOCK run_command: ['echo', 'rebaseline-test', '--suffixes', 'png', 'Webkit Linux', 'userscripts/images.svg'], cwd=/mock-checkout
+MOCK run_command: ['echo', 'rebaseline-test', '--suffixes', 'txt', 'Webkit Mac10.5', 'userscripts/another-test.html'], cwd=/mock-checkout
+MOCK run_command: ['echo', 'rebaseline-test', '--suffixes', 'png', 'Webkit Mac10.5', 'userscripts/images.svg'], cwd=/mock-checkout
+MOCK run_command: ['echo', 'rebaseline-test', '--suffixes', 'txt', 'Webkit Mac10.7', 'userscripts/another-test.html'], cwd=/mock-checkout
+MOCK run_command: ['echo', 'rebaseline-test', '--suffixes', 'png', 'Webkit Mac10.7', 'userscripts/images.svg'], cwd=/mock-checkout
+MOCK run_command: ['echo', 'rebaseline-test', '--suffixes', 'txt', 'Webkit Mac10.6', 'userscripts/another-test.html'], cwd=/mock-checkout
+MOCK run_command: ['echo', 'rebaseline-test', '--suffixes', 'png', 'Webkit Mac10.6', 'userscripts/images.svg'], cwd=/mock-checkout
+MOCK run_command: ['echo', 'rebaseline-test', '--suffixes', 'txt', 'Webkit Vista', 'userscripts/another-test.html'], cwd=/mock-checkout
+MOCK run_command: ['echo', 'rebaseline-test', '--suffixes', 'png', 'Webkit Vista', 'userscripts/images.svg'], cwd=/mock-checkout
+MOCK run_command: ['echo', 'rebaseline-test', '--suffixes', 'txt', 'Webkit Win7', 'userscripts/another-test.html'], cwd=/mock-checkout
+MOCK run_command: ['echo', 'rebaseline-test', '--suffixes', 'png', 'Webkit Win7', 'userscripts/images.svg'], cwd=/mock-checkout
+MOCK run_command: ['echo', 'rebaseline-test', '--suffixes', 'txt', 'Webkit Win', 'userscripts/another-test.html'], cwd=/mock-checkout
+MOCK run_command: ['echo', 'rebaseline-test', '--suffixes', 'png', 'Webkit Win', 'userscripts/images.svg'], cwd=/mock-checkout
"""
- command._tests_to_rebaseline = lambda port: ['userscripts/another-test.html', 'userscripts/images.svg']
+ command._tests_to_rebaseline = lambda port: {'userscripts/another-test.html': set(['txt']), 'userscripts/images.svg': set(['png'])}
OutputCapture().assert_outputs(self, command.execute, [MockOptions(optimize=False), [], tool], expected_logs=expected_logs, expected_stderr=expected_stderr)
expected_logs_with_optimize = expected_logs + (
- "Optimizing baselines for userscripts/another-test.html.\n"
- "Optimizing baselines for userscripts/images.svg.\n")
+ "Optimizing baselines for userscripts/another-test.html (txt).\n"
+ "Optimizing baselines for userscripts/images.svg (png).\n")
expected_stderr_with_optimize = expected_stderr + (
- "MOCK run_command: ['echo', 'optimize-baselines', 'userscripts/another-test.html'], cwd=/mock-checkout\n"
- "MOCK run_command: ['echo', 'optimize-baselines', 'userscripts/images.svg'], cwd=/mock-checkout\n")
+ "MOCK run_command: ['echo', 'optimize-baselines', '--suffixes', 'txt', 'userscripts/another-test.html'], cwd=/mock-checkout\n"
+ "MOCK run_command: ['echo', 'optimize-baselines', '--suffixes', 'png', 'userscripts/images.svg'], cwd=/mock-checkout\n")
- command._tests_to_rebaseline = lambda port: ['userscripts/another-test.html', 'userscripts/images.svg']
+ command._tests_to_rebaseline = lambda port: {'userscripts/another-test.html': set(['txt']), 'userscripts/images.svg': set(['png'])}
OutputCapture().assert_outputs(self, command.execute, [MockOptions(optimize=True), [], tool], expected_logs=expected_logs_with_optimize, expected_stderr=expected_stderr_with_optimize)
+
+ def test_overrides_are_included_correctly(self):
+ command = RebaselineExpectations()
+ tool = MockTool()
+ command.bind_to_tool(tool)
+ port = tool.port_factory.get('chromium-mac-lion')
+
+ # This tests that the any tests marked as REBASELINE in the overrides are found, but
+ # that the overrides do not get written into the main file.
+ expectations_path = port.expectations_files()[0]
+ expectations_contents = ''
+ port._filesystem.write_text_file(expectations_path, expectations_contents)
+ port.expectations_dict = lambda: {
+ expectations_path: expectations_contents,
+ 'overrides': ('BUGX REBASELINE : userscripts/another-test.html = TEXT\n'
+ 'BUGY : userscripts/test.html = CRASH\n')}
+
+ for path in port.expectations_files():
+ port._filesystem.write_text_file(path, '')
+ port._filesystem.write_text_file(port.layout_tests_dir() + '/userscripts/another-test.html', '')
+ self.assertEquals(command._tests_to_rebaseline(port), {'userscripts/another-test.html': set(['txt'])})
+ self.assertEquals(port._filesystem.read_text_file(expectations_path), expectations_contents)
diff --git a/Tools/Scripts/webkitpy/tool/mocktool.py b/Tools/Scripts/webkitpy/tool/mocktool.py
index 55fde64ad..21ee91fc9 100644
--- a/Tools/Scripts/webkitpy/tool/mocktool.py
+++ b/Tools/Scripts/webkitpy/tool/mocktool.py
@@ -36,8 +36,7 @@ from webkitpy.common.net.irc.irc_mock import MockIRC
from webkitpy.common.config.ports_mock import MockPort
-# FIXME: This should be moved somewhere in common and renamed
-# something without Mock in the name.
+# FIXME: We should just replace this with optparse.Values(default=kwargs)
class MockOptions(object):
"""Mock implementation of optparse.Values."""
@@ -53,6 +52,11 @@ class MockOptions(object):
self.__dict__.update(**kwargs)
return self
+ def ensure_value(self, key, value):
+ if getattr(self, key, None) == None:
+ self.__dict__[key] = value
+ return self.__dict__[key]
+
# FIXME: This should be renamed MockWebKitPatch.
class MockTool(MockHost):
diff --git a/Tools/Scripts/webkitpy/tool/servers/gardeningserver.py b/Tools/Scripts/webkitpy/tool/servers/gardeningserver.py
index 369070982..bfe003fe9 100644
--- a/Tools/Scripts/webkitpy/tool/servers/gardeningserver.py
+++ b/Tools/Scripts/webkitpy/tool/servers/gardeningserver.py
@@ -69,7 +69,7 @@ class GardeningExpectationsUpdater(BugManager):
return "BUG_NEW"
def update_expectations(self, failure_info_list):
- expectation_lines = self._parser.parse(self._tool.filesystem.read_text_file(self._path_to_test_expectations_file))
+ expectation_lines = self._parser.parse(self._path_to_test_expectations_file, self._tool.filesystem.read_text_file(self._path_to_test_expectations_file))
editor = TestExpectationsEditor(expectation_lines, self)
updated_expectation_lines = []
# FIXME: Group failures by testName+failureTypeList.
diff --git a/Tools/Scripts/webkitpy/tool/steps/commit.py b/Tools/Scripts/webkitpy/tool/steps/commit.py
index 2f245e024..0e5ca9157 100644
--- a/Tools/Scripts/webkitpy/tool/steps/commit.py
+++ b/Tools/Scripts/webkitpy/tool/steps/commit.py
@@ -54,11 +54,7 @@ class Commit(AbstractStep):
error.num_local_commits, working_directory_message))
def _check_test_expectations(self, changed_files):
- test_expectations_files = []
- for filename in changed_files:
- if filename.endswith('test_expectations.txt'):
- test_expectations_files.append(filename)
-
+ test_expectations_files = [filename for filename in changed_files if filename.endswith('TestExpectations')]
if not test_expectations_files:
return
diff --git a/Tools/Scripts/webkitpy/tool/steps/commit_unittest.py b/Tools/Scripts/webkitpy/tool/steps/commit_unittest.py
index 50dfaea2b..25d9b61a1 100644
--- a/Tools/Scripts/webkitpy/tool/steps/commit_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/steps/commit_unittest.py
@@ -36,7 +36,7 @@ from webkitpy.tool.steps.commit import Commit
class CommitTest(unittest.TestCase):
- def test_check_test_expectations(self):
+ def _test_check_test_expectations(self, filename):
capture = OutputCapture()
options = MockOptions()
options.git_commit = ""
@@ -46,16 +46,20 @@ class CommitTest(unittest.TestCase):
tool.user = None # Will cause any access of tool.user to raise an exception.
step = Commit(tool, options)
state = {
- "changed_files": ["test_expectations.txtXXX"],
+ "changed_files": [filename + "XXX"],
}
tool.executive = MockExecutive(should_log=True, should_throw_when_run=False)
capture.assert_outputs(self, step.run, [state], expected_stderr="Committed r49824: <http://trac.webkit.org/changeset/49824>\n")
state = {
- "changed_files": ["platform/chromium/test_expectations.txt"],
+ "changed_files": ["platform/chromium/" + filename],
}
- capture.assert_outputs(self, step.run, [state], expected_stderr="MOCK run_and_throw_if_fail: ['mock-check-webkit-style', '--diff-files', 'platform/chromium/test_expectations.txt'], cwd=/mock-checkout\nCommitted r49824: <http://trac.webkit.org/changeset/49824>\n")
+ capture.assert_outputs(self, step.run, [state], expected_stderr="MOCK run_and_throw_if_fail: ['mock-check-webkit-style', '--diff-files', 'platform/chromium/"
+ + filename + "'], cwd=/mock-checkout\nCommitted r49824: <http://trac.webkit.org/changeset/49824>\n")
- tool.executive = MockExecutive(should_log=True, should_throw_when_run=set(["platform/chromium/test_expectations.txt"]))
+ tool.executive = MockExecutive(should_log=True, should_throw_when_run=set(["platform/chromium/" + filename]))
self.assertRaises(ScriptError, capture.assert_outputs, self, step.run, [state])
+
+ def test_check_test_expectations(self):
+ self._test_check_test_expectations('TestExpectations')