diff options
author | Ben Murdoch <benm@google.com> | 2011-05-16 16:25:10 +0100 |
---|---|---|
committer | Ben Murdoch <benm@google.com> | 2011-05-23 18:54:14 +0100 |
commit | ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddb (patch) | |
tree | db769fadd053248f85db67434a5b275224defef7 /Tools/Scripts/webkitpy | |
parent | 52e2557aeb8477967e97fd24f20f8f407a10fa15 (diff) | |
download | external_webkit-ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddb.zip external_webkit-ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddb.tar.gz external_webkit-ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddb.tar.bz2 |
Merge WebKit at r76408: Initial merge by git.
Change-Id: I5b91decbd693ccbf5c1b8354b37cd68cc9a1ea53
Diffstat (limited to 'Tools/Scripts/webkitpy')
55 files changed, 1101 insertions, 891 deletions
diff --git a/Tools/Scripts/webkitpy/common/config/committers.py b/Tools/Scripts/webkitpy/common/config/committers.py index 6a235f5..f7d59fe 100644 --- a/Tools/Scripts/webkitpy/common/config/committers.py +++ b/Tools/Scripts/webkitpy/common/config/committers.py @@ -150,7 +150,7 @@ committers_unable_to_review = [ Committer("Krzysztof Kowalczyk", "kkowalczyk@gmail.com"), Committer("Kwang Yul Seo", ["kwangyul.seo@gmail.com", "skyul@company100.net", "kseo@webkit.org"], "kwangseo"), Committer("Leandro Pereira", ["leandro@profusion.mobi", "leandro@webkit.org"], "acidx"), - Committer("Levi Weintraub", "lweintraub@apple.com"), + Committer("Levi Weintraub", ["leviw@chromium.org", "leviw@google.com", "lweintraub@apple.com"], "leviw"), Committer("Lucas De Marchi", ["lucas.demarchi@profusion.mobi", "demarchi@webkit.org"], "demarchi"), Committer("Luiz Agostini", ["luiz@webkit.org", "luiz.agostini@openbossa.org"], "lca"), Committer("Mads Ager", "ager@chromium.org"), @@ -198,6 +198,7 @@ committers_unable_to_review = [ Committer("Yaar Schnitman", ["yaar@chromium.org", "yaar@google.com"]), Committer("Yong Li", ["yong.li.webkit@gmail.com", "yong.li@torchmobile.com"], "yong"), Committer("Yongjun Zhang", "yongjun.zhang@nokia.com"), + Committer("Yi Shen", ["yi.4.shen@nokia.com", "shenyi2006@gmail.com"]), Committer("Yuta Kitamura", "yutak@chromium.org", "yutak"), Committer("Yuzo Fujishima", "yuzo@google.com", "yuzo"), Committer("Zhenyao Mo", "zmo@google.com", "zhenyao"), diff --git a/Tools/Scripts/webkitpy/common/host.py b/Tools/Scripts/webkitpy/common/host.py new file mode 100644 index 0000000..8ec271e --- /dev/null +++ b/Tools/Scripts/webkitpy/common/host.py @@ -0,0 +1,80 @@ +# Copyright (c) 2010 Google Inc. All rights reserved. +# Copyright (c) 2009 Apple Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +from webkitpy.common.checkout.api import Checkout +from webkitpy.common.checkout.scm import default_scm +from webkitpy.common.config.ports import WebKitPort +from webkitpy.common.net import bugzilla, buildbot, irc, statusserver +from webkitpy.common.system import executive, filesystem, platforminfo, user, workspace +from webkitpy.layout_tests import port + + +class Host(object): + def __init__(self): + self.bugs = bugzilla.Bugzilla() + self.buildbot = buildbot.BuildBot() + self.executive = executive.Executive() + self._irc = None + self.filesystem = filesystem.FileSystem() + self.workspace = workspace.Workspace(self.filesystem, self.executive) + self._port = None + self.user = user.User() + self._scm = None + self._checkout = None + self.status_server = statusserver.StatusServer() + self.port_factory = port.factory + self.platform = platforminfo.PlatformInfo() + + def _initialize_scm(self, patch_directories=None): + self._scm = default_scm(patch_directories) + self._checkout = Checkout(self.scm()) + + def scm(self): + return self._scm + + def checkout(self): + return self._checkout + + def port(self): + return self._port + + def ensure_irc_connected(self, irc_delegate): + if not self._irc: + self._irc = irc.ircproxy.IRCProxy(irc_delegate) + + def irc(self): + # We don't automatically construct IRCProxy here because constructing + # IRCProxy actually connects to IRC. We want clients to explicitly + # connect to IRC. + return self._irc + + def command_completed(self): + if self._irc: + self._irc.disconnect() diff --git a/Tools/Scripts/webkitpy/common/system/executive_mock.py b/Tools/Scripts/webkitpy/common/system/executive_mock.py index c1cf999..943b70c 100644 --- a/Tools/Scripts/webkitpy/common/system/executive_mock.py +++ b/Tools/Scripts/webkitpy/common/system/executive_mock.py @@ -30,6 +30,8 @@ # FIXME: Unify with tool/mocktool.MockExecutive. +from webkitpy.common.system import executive + class MockExecutive2(object): def __init__(self, output='', exit_code=0, exception=None, @@ -48,7 +50,7 @@ class MockExecutive2(object): def kill_process(self, pid): pass - def run_command(self, arg_list, return_exit_code=False, + def run_command(self, arg_list, error_handler=None, return_exit_code=False, decode_output=False): if self._exception: raise self._exception @@ -56,4 +58,10 @@ class MockExecutive2(object): return self._exit_code if self._run_command_fn: return self._run_command_fn(arg_list) + if self._exit_code and error_handler: + script_error = executive.ScriptError(script_args=arg_list, + exit_code=self._exit_code, + output=self._output) + error_handler(script_error) + return self._output diff --git a/Tools/Scripts/webkitpy/common/system/filesystem.py b/Tools/Scripts/webkitpy/common/system/filesystem.py index 527b6bd..05513a9 100644 --- a/Tools/Scripts/webkitpy/common/system/filesystem.py +++ b/Tools/Scripts/webkitpy/common/system/filesystem.py @@ -33,6 +33,7 @@ from __future__ import with_statement import codecs import errno import exceptions +import glob import os import shutil import tempfile @@ -43,11 +44,71 @@ class FileSystem(object): Unless otherwise noted, all paths are allowed to be either absolute or relative.""" + def __init__(self): + self.sep = os.sep + + def abspath(self, path): + return os.path.abspath(path) + + def basename(self, path): + """Wraps os.path.basename().""" + return os.path.basename(path) + + def copyfile(self, source, destination): + """Copies the contents of the file at the given path to the destination + path.""" + shutil.copyfile(source, destination) + + def dirname(self, path): + """Wraps os.path.dirname().""" + return os.path.dirname(path) def exists(self, path): """Return whether the path exists in the filesystem.""" return os.path.exists(path) + def files_under(self, path, dirs_to_skip=[], file_filter=None): + """Return the list of all files under the given path in topdown order. + + Args: + dirs_to_skip: a list of directories to skip over during the + traversal (e.g., .svn, resources, etc.) + file_filter: if not None, the filter will be invoked + with the filesystem object and the dirname and basename of + each file found. The file is included in the result if the + callback returns True. + """ + def filter_all(fs, dirpath, basename): + return True + + file_filter = file_filter or filter_all + files = [] + if self.isfile(path): + if file_filter(self, self.dirname(path), self.basename(path)): + files.append(path) + return files + + if self.basename(path) in dirs_to_skip: + return [] + + for (dirpath, dirnames, filenames) in os.walk(path): + for d in dirs_to_skip: + if d in dirnames: + dirnames.remove(d) + + for filename in filenames: + if file_filter(self, dirpath, filename): + files.append(self.join(dirpath, filename)) + return files + + def glob(self, path): + """Wraps glob.glob().""" + return glob.glob(path) + + def isabs(self, path): + """Return whether the path is an absolute path.""" + return os.path.isabs(path) + def isfile(self, path): """Return whether the path refers to a file.""" return os.path.isfile(path) @@ -71,14 +132,20 @@ class FileSystem(object): the directory will self-delete at the end of the block (if the directory is empty; non-empty directories raise errors). The directory can be safely deleted inside the block as well, if so - desired.""" + desired. + + Note that the object returned is not a string and does not support all of the string + methods. If you need a string, coerce the object to a string and go from there. + """ class TemporaryDirectory(object): def __init__(self, **kwargs): self._kwargs = kwargs - self._directory_path = None + self._directory_path = tempfile.mkdtemp(**self._kwargs) + + def __str__(self): + return self._directory_path def __enter__(self): - self._directory_path = tempfile.mkdtemp(**self._kwargs) return self._directory_path def __exit__(self, type, value, traceback): @@ -98,6 +165,41 @@ class FileSystem(object): if e.errno != errno.EEXIST: raise + def move(self, src, dest): + shutil.move(src, dest) + + def mtime(self, path): + return os.stat(path).st_mtime + + def normpath(self, path): + """Wraps os.path.normpath().""" + return os.path.normpath(path) + + def open_binary_tempfile(self, suffix): + """Create, open, and return a binary temp file. Returns a tuple of the file and the name.""" + temp_fd, temp_name = tempfile.mkstemp(suffix) + f = os.fdopen(temp_fd, 'wb') + return f, temp_name + + def open_text_file_for_writing(self, path, append=False): + """Returns a file handle suitable for writing to.""" + mode = 'w' + if append: + mode = 'a' + return codecs.open(path, mode, 'utf8') + + def read_binary_file(self, path): + """Return the contents of the file at the given path as a byte string.""" + with file(path, 'rb') as f: + return f.read() + + def read_text_file(self, path): + """Return the contents of the file at the given path as a Unicode string. + + The file is read assuming it is a UTF-8 encoded file with no BOM.""" + with codecs.open(path, 'r', 'utf8') as f: + return f.read() + class _WindowsError(exceptions.OSError): """Fake exception for Linux and Mac.""" pass @@ -124,8 +226,9 @@ class FileSystem(object): if retry_timeout_sec < 0: raise e - def remove_tree(self, path, ignore_errors=False): - shutil.rmtree(path, ignore_errors) + def rmtree(self, path): + """Delete the directory rooted at path, empty or no.""" + shutil.rmtree(path, ignore_errors=True) def read_binary_file(self, path): """Return the contents of the file at the given path as a byte string.""" @@ -139,6 +242,10 @@ class FileSystem(object): with codecs.open(path, 'r', 'utf8') as f: return f.read() + def splitext(self, path): + """Return (dirname + os.sep + basename, '.' + ext)""" + return os.path.splitext(path) + def write_binary_file(self, path, contents): """Write the contents to the file at the given location.""" with file(path, 'wb') as f: @@ -150,14 +257,3 @@ class FileSystem(object): The file is written encoded as UTF-8 with no BOM.""" with codecs.open(path, 'w', 'utf8') as f: f.write(contents) - - def copyfile(self, source, destination): - """Copies the contents of the file at the given path to the destination - path.""" - shutil.copyfile(source, destination) - - def files_under(self, path): - """Return the list of all files under the given path.""" - return [self.join(path_to_file, filename) - for (path_to_file, _, filenames) in os.walk(path) - for filename in filenames] diff --git a/Tools/Scripts/webkitpy/common/system/filesystem_mock.py b/Tools/Scripts/webkitpy/common/system/filesystem_mock.py index 809c4c6..0004944 100644 --- a/Tools/Scripts/webkitpy/common/system/filesystem_mock.py +++ b/Tools/Scripts/webkitpy/common/system/filesystem_mock.py @@ -43,10 +43,82 @@ class MockFileSystem(object): not exist. """ self.files = files or {} + self.written_files = {} + self.sep = '/' + self.current_tmpno = 0 + + def _raise_not_found(self, path): + raise IOError(errno.ENOENT, path, os.strerror(errno.ENOENT)) + + def _split(self, path): + idx = path.rfind('/') + return (path[0:idx], path[idx + 1:]) + + def abspath(self, path): + return path + + def basename(self, path): + return self._split(path)[1] + + def copyfile(self, source, destination): + if not self.exists(source): + self._raise_not_found(source) + if self.isdir(source): + raise IOError(errno.EISDIR, source, os.strerror(errno.ISDIR)) + if self.isdir(destination): + raise IOError(errno.EISDIR, destination, os.strerror(errno.ISDIR)) + + self.files[destination] = self.files[source] + + def dirname(self, path): + return self._split(path)[0] def exists(self, path): return self.isfile(path) or self.isdir(path) + def files_under(self, path, dirs_to_skip=[], file_filter=None): + def filter_all(fs, dirpath, basename): + return True + + file_filter = file_filter or filter_all + files = [] + if self.isfile(path): + if file_filter(self, self.dirname(path), self.basename(path)): + files.append(path) + return files + + if self.basename(path) in dirs_to_skip: + return [] + + if not path.endswith('/'): + path += '/' + + dir_substrings = ['/' + d + '/' for d in dirs_to_skip] + for filename in self.files: + if not filename.startswith(path): + continue + + suffix = filename[len(path) - 1:] + if any(dir_substring in suffix for dir_substring in dir_substrings): + continue + + dirpath, basename = self._split(filename) + if file_filter(self, dirpath, basename): + files.append(filename) + + return files + + def glob(self, path): + # FIXME: This only handles a wildcard '*' at the end of the path. + # Maybe it should handle more? + if path[-1] == '*': + return [f for f in self.files if f.startswith(path[:-1])] + else: + return [f for f in self.files if f == path] + + def isabs(self, path): + return path.startswith('/') + def isfile(self, path): return path in self.files and self.files[path] is not None @@ -55,7 +127,12 @@ class MockFileSystem(object): return False if not path.endswith('/'): path += '/' - return any(f.startswith(path) for f in self.files) + + # We need to use a copy of the keys here in order to avoid switching + # to a different thread and potentially modifying the dict in + # mid-iteration. + files = self.files.keys()[:] + return any(f.startswith(path) for f in files) def join(self, *comps): return re.sub(re.escape(os.path.sep), '/', os.path.join(*comps)) @@ -80,42 +157,114 @@ class MockFileSystem(object): files.append(remaining) return dirs + files + def mtime(self, path): + if self.exists(path): + return 0 + self._raise_not_found(path) + + def _mktemp(self, suffix='', prefix='tmp', dir=None, **kwargs): + if dir is None: + dir = '/__im_tmp' + curno = self.current_tmpno + self.current_tmpno += 1 + return self.join(dir, "%s_%u_%s" % (prefix, curno, suffix)) + + def mkdtemp(self, **kwargs): + class TemporaryDirectory(object): + def __init__(self, fs, **kwargs): + self._kwargs = kwargs + self._filesystem = fs + self._directory_path = fs._mktemp(**kwargs) + fs.maybe_make_directory(self._directory_path) + + def __str__(self): + return self._directory_path + + def __enter__(self): + return self._directory_path + + def __exit__(self, type, value, traceback): + # Only self-delete if necessary. + + # FIXME: Should we delete non-empty directories? + if self._filesystem.exists(self._directory_path): + self._filesystem.rmtree(self._directory_path) + + return TemporaryDirectory(fs=self, **kwargs) + def maybe_make_directory(self, *path): # FIXME: Implement such that subsequent calls to isdir() work? pass + def move(self, src, dst): + if self.files[src] is None: + self._raise_not_found(src) + self.files[dst] = self.files[src] + self.files[src] = None + + def normpath(self, path): + return path + + def open_binary_tempfile(self, suffix): + path = self._mktemp(suffix) + return WritableFileObject(self, path), path + + def open_text_file_for_writing(self, path, append=False): + return WritableFileObject(self, path, append) + def read_text_file(self, path): return self.read_binary_file(path) def read_binary_file(self, path): - if path in self.files: - if self.files[path] is None: - raise IOError(errno.ENOENT, path, os.strerror(errno.ENOENT)) - return self.files[path] + # Intentionally raises KeyError if we don't recognize the path. + if self.files[path] is None: + self._raise_not_found(path) + return self.files[path] + + def remove(self, path): + if self.files[path] is None: + self._raise_not_found(path) + self.files[path] = None + + def rmtree(self, path): + if not path.endswith('/'): + path += '/' + + for f in self.files: + if f.startswith(path): + self.files[f] = None + + def splitext(self, path): + idx = path.rfind('.') + if idx == -1: + idx = 0 + return (path[0:idx], path[idx:]) def write_text_file(self, path, contents): return self.write_binary_file(path, contents) def write_binary_file(self, path, contents): self.files[path] = contents + self.written_files[path] = contents - def copyfile(self, source, destination): - if not self.exists(source): - raise IOError(errno.ENOENT, source, os.strerror(errno.ENOENT)) - if self.isdir(source): - raise IOError(errno.EISDIR, source, os.strerror(errno.ISDIR)) - if self.isdir(destination): - raise IOError(errno.EISDIR, destination, os.strerror(errno.ISDIR)) - self.files[destination] = self.files[source] +class WritableFileObject(object): + def __init__(self, fs, path, append=False, encoding=None): + self.fs = fs + self.path = path + self.closed = False + if path not in self.fs.files or not append: + self.fs.files[path] = "" - def files_under(self, path): - if not path.endswith('/'): - path += '/' - return [file for file in self.files if file.startswith(path)] + def __enter__(self): + return self - def remove(self, path): - del self.files[path] + def __exit__(self, type, value, traceback): + self.close() + + def close(self): + self.closed = True - def remove_tree(self, path, ignore_errors=False): - self.files = [file for file in self.files if not file.startswith(path)] + def write(self, str): + self.fs.files[self.path] += str + self.fs.written_files[self.path] = self.fs.files[self.path] diff --git a/Tools/Scripts/webkitpy/common/system/workspace.py b/Tools/Scripts/webkitpy/common/system/workspace.py index 3b755ad..afb0009 100644 --- a/Tools/Scripts/webkitpy/common/system/workspace.py +++ b/Tools/Scripts/webkitpy/common/system/workspace.py @@ -36,7 +36,7 @@ class Workspace(object): self._filesystem = filesystem self._executive = executive # FIXME: Remove if create_zip is moved to python. - def find_unused_filename(self, directory, name, extension, search_limit=10): + def find_unused_filename(self, directory, name, extension, search_limit=100): for count in range(search_limit): if count: target_name = "%s-%s.%s" % (name, count, extension) diff --git a/Tools/Scripts/webkitpy/common/system/workspace_unittest.py b/Tools/Scripts/webkitpy/common/system/workspace_unittest.py index e5fbb26..6be7664 100644 --- a/Tools/Scripts/webkitpy/common/system/workspace_unittest.py +++ b/Tools/Scripts/webkitpy/common/system/workspace_unittest.py @@ -40,10 +40,13 @@ class WorkspaceTest(unittest.TestCase): filesystem = MockFileSystem({ "dir/foo.jpg": "", "dir/foo-1.jpg": "", + "dir/foo-2.jpg": "", }) workspace = Workspace(filesystem, None) self.assertEqual(workspace.find_unused_filename("bar", "bar", "bar"), "bar/bar.bar") - self.assertEqual(workspace.find_unused_filename("dir", "foo", "jpg"), "dir/foo-2.jpg") + self.assertEqual(workspace.find_unused_filename("dir", "foo", "jpg", search_limit=1), None) + self.assertEqual(workspace.find_unused_filename("dir", "foo", "jpg", search_limit=2), None) + self.assertEqual(workspace.find_unused_filename("dir", "foo", "jpg"), "dir/foo-3.jpg") def test_create_zip(self): workspace = Workspace(None, MockExecutive(should_log=True)) diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py index 2bb2d02..050eefa 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py @@ -36,9 +36,6 @@ the output. When there are no more URLs to process in the shared queue, the thread exits. """ -from __future__ import with_statement - -import codecs import copy import logging import os @@ -86,6 +83,7 @@ def _process_output(port, options, test_input, test_types, test_args, Returns: a TestResult object """ failures = [] + fs = port._filesystem if test_output.crash: failures.append(test_failures.FailureCrash()) @@ -96,11 +94,10 @@ def _process_output(port, options, test_input, test_types, test_args, if test_output.crash: _log.debug("%s Stacktrace for %s:\n%s" % (worker_name, test_name, test_output.error)) - filename = os.path.join(options.results_directory, test_name) - filename = os.path.splitext(filename)[0] + "-stack.txt" - port.maybe_make_directory(os.path.split(filename)[0]) - with codecs.open(filename, "wb", "utf-8") as file: - file.write(test_output.error) + filename = fs.join(options.results_directory, test_name) + filename = fs.splitext(filename)[0] + "-stack.txt" + fs.maybe_make_directory(fs.dirname(filename)) + fs.write_text_file(filename, test_output.error) elif test_output.error: _log.debug("%s %s output stderr lines:\n%s" % (worker_name, test_name, test_output.error)) @@ -385,10 +382,9 @@ class TestShellThread(WatchableThread): # Append tests we're running to the existing tests_run.txt file. # This is created in run_webkit_tests.py:_PrepareListsAndPrintOutput. - tests_run_filename = os.path.join(self._options.results_directory, + tests_run_filename = self._port._filesystem.join(self._options.results_directory, "tests_run.txt") - tests_run_file = codecs.open(tests_run_filename, "a", "utf-8") - + tests_run_file = self._port._filesystem.open_text_file_for_writing(tests_run_filename, append=False) while True: if self._canceled: _log.debug('Testing cancelled') 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 b054c5b..3267fb7 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 @@ -27,7 +27,6 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import logging -import os from webkitpy.layout_tests.layout_package import json_results_generator from webkitpy.layout_tests.layout_package import test_expectations @@ -66,12 +65,11 @@ class JSONLayoutResultsGenerator(json_results_generator.JSONResultsGeneratorBase results. """ super(JSONLayoutResultsGenerator, self).__init__( - builder_name, build_name, build_number, results_file_base_path, + port, builder_name, build_name, build_number, results_file_base_path, builder_base_url, {}, port.test_repository_paths(), generate_incremental_results, test_results_server, test_type, master_name) - self._port = port self._expectations = expectations # We want relative paths to LayoutTest root for JSON output. @@ -181,9 +179,9 @@ class JSONLayoutResultsGenerator(json_results_generator.JSONResultsGeneratorBase test, test_name, tests) # Remove tests that don't exist anymore. - full_path = os.path.join(self._port.layout_tests_dir(), test_name) - full_path = os.path.normpath(full_path) - if not os.path.exists(full_path): + full_path = self._fs.join(self._port.layout_tests_dir(), test_name) + full_path = self._fs.normpath(full_path) + if not self._fs.exists(full_path): del tests[test_name] def _get_failure_summary_entry(self, timeline): diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py index 12e65b2..32ffd71 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py @@ -26,11 +26,7 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from __future__ import with_statement - -import codecs import logging -import os import subprocess import sys import time @@ -118,7 +114,7 @@ class JSONResultsGeneratorBase(object): URL_FOR_TEST_LIST_JSON = \ "http://%s/testfile?builder=%s&name=%s&testlistjson=1&testtype=%s" - def __init__(self, builder_name, build_name, build_number, + def __init__(self, port, builder_name, build_name, build_number, results_file_base_path, builder_base_url, test_results_map, svn_repositories=None, generate_incremental_results=False, @@ -129,6 +125,7 @@ class JSONResultsGeneratorBase(object): if it is not found locally. Args + port: port-specific wrapper builder_name: the builder name (e.g. Webkit). build_name: the build name (e.g. webkit-rel). build_number: the build number. @@ -146,14 +143,16 @@ class JSONResultsGeneratorBase(object): test_type: test type string (e.g. 'layout-tests'). master_name: the name of the buildbot master. """ + self._port = port + self._fs = port._filesystem self._builder_name = builder_name self._build_name = build_name self._build_number = build_number self._builder_base_url = builder_base_url self._results_directory = results_file_base_path - self._results_file_path = os.path.join(results_file_base_path, + self._results_file_path = self._fs.join(results_file_base_path, self.RESULTS_FILENAME) - self._incremental_results_file_path = os.path.join( + self._incremental_results_file_path = self._fs.join( results_file_base_path, self.INCREMENTAL_RESULTS_FILENAME) self._test_results_map = test_results_map @@ -254,7 +253,7 @@ class JSONResultsGeneratorBase(object): ("testtype", self._test_type), ("master", self._master_name)] - files = [(file, os.path.join(self._results_directory, file)) + files = [(file, self._fs.join(self._results_directory, file)) for file in json_files] uploader = test_results_uploader.TestResultsUploader( @@ -273,10 +272,7 @@ class JSONResultsGeneratorBase(object): # Specify separators in order to get compact encoding. json_data = simplejson.dumps(json, separators=(',', ':')) json_string = self.JSON_PREFIX + json_data + self.JSON_SUFFIX - - results_file = codecs.open(file_path, "w", "utf-8") - results_file.write(json_string) - results_file.close() + self._fs.write_text_file(file_path, json_string) def _get_test_timing(self, test_name): """Returns test timing data (elapsed time) in second @@ -330,7 +326,7 @@ class JSONResultsGeneratorBase(object): Args: in_directory: The directory where svn is to be run. """ - if os.path.exists(os.path.join(in_directory, '.svn')): + if self._fs.exists(self._fs.join(in_directory, '.svn')): # Note: Not thread safe: http://bugs.python.org/issue2320 output = subprocess.Popen(["svn", "info", "--xml"], cwd=in_directory, @@ -358,9 +354,8 @@ class JSONResultsGeneratorBase(object): old_results = None error = None - if os.path.exists(self._results_file_path) and not for_incremental: - with codecs.open(self._results_file_path, "r", "utf-8") as file: - old_results = file.read() + if self._fs.exists(self._results_file_path) and not for_incremental: + old_results = self._fs.read_text_file(self._results_file_path) elif self._builder_base_url or for_incremental: if for_incremental: if not self._test_results_server: diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator_unittest.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator_unittest.py index dad549a..ce99765 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator_unittest.py @@ -31,11 +31,11 @@ import unittest import optparse import random -import shutil -import tempfile +from webkitpy.common.system import filesystem_mock from webkitpy.layout_tests.layout_package import json_results_generator from webkitpy.layout_tests.layout_package import test_expectations +from webkitpy.thirdparty.mock import Mock class JSONGeneratorTest(unittest.TestCase): @@ -83,7 +83,9 @@ class JSONGeneratorTest(unittest.TestCase): failed=(test in failed_tests), elapsed_time=test_timings[test]) - generator = json_results_generator.JSONResultsGeneratorBase( + port = Mock() + port._filesystem = filesystem_mock.MockFileSystem() + generator = json_results_generator.JSONResultsGeneratorBase(port, self.builder_name, self.build_name, self.build_number, '', None, # don't fetch past json results archive diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/message_broker.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/message_broker.py index e0ca8db..481c617 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/message_broker.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/message_broker.py @@ -144,6 +144,13 @@ class MultiThreadedBroker(WorkerMessageBroker): some_thread_is_alive = False t = time.time() for thread in threads: + if thread.isAlive(): + some_thread_is_alive = True + next_timeout = thread.next_timeout() + if next_timeout and t > next_timeout: + log_wedged_worker(thread.getName(), thread.id()) + thread.clear_next_timeout() + exception_info = thread.exception_info() if exception_info is not None: # Re-raise the thread's exception here to make it @@ -152,13 +159,6 @@ class MultiThreadedBroker(WorkerMessageBroker): # to have passed. raise exception_info[0], exception_info[1], exception_info[2] - if thread.isAlive(): - some_thread_is_alive = True - next_timeout = thread.next_timeout() - if next_timeout and t > next_timeout: - log_wedged_worker(thread.getName(), thread.id()) - thread.clear_next_timeout() - self._test_runner.update() if some_thread_is_alive: diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/printing.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/printing.py index 7a6aad1..e10ad99 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/printing.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/printing.py @@ -31,8 +31,6 @@ import logging import optparse -import os -import pdb from webkitpy.layout_tests.layout_package import metered_stream from webkitpy.layout_tests.layout_package import test_expectations @@ -411,7 +409,7 @@ class Printer(object): return next_test = test_list[self._current_test_number] - next_dir = os.path.dirname( + next_dir = self._port._filesystem.dirname( self._port.relative_test_filename(next_test)) if self._current_progress_str == "": self._current_progress_str = "%s: " % (next_dir) @@ -437,7 +435,7 @@ class Printer(object): break next_test = test_list[self._current_test_number] - next_dir = os.path.dirname( + next_dir = self._port._filesystem.dirname( self._port.relative_test_filename(next_test)) if result_summary.remaining: diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/printing_unittest.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/printing_unittest.py index 9280b02..12a786e 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/printing_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/printing_unittest.py @@ -29,10 +29,7 @@ """Unit tests for printing.py.""" -import os import optparse -import pdb -import sys import unittest import logging @@ -117,6 +114,7 @@ class TestUtilityFunctions(unittest.TestCase): class Testprinter(unittest.TestCase): def get_printer(self, args=None, single_threaded=False, is_fully_parallel=False): + args = args or [] printing_options = printing.print_options() option_parser = optparse.OptionParser(option_list=printing_options) options, args = option_parser.parse_args(args) @@ -138,11 +136,11 @@ class Testprinter(unittest.TestCase): failures = [test_failures.FailureTimeout()] elif result_type == test_expectations.CRASH: failures = [test_failures.FailureCrash()] - path = os.path.join(self._port.layout_tests_dir(), test) + path = self._port._filesystem.join(self._port.layout_tests_dir(), test) return test_results.TestResult(path, failures=failures, test_run_time=run_time) def get_result_summary(self, tests, expectations_str): - test_paths = [os.path.join(self._port.layout_tests_dir(), test) for + test_paths = [self._port._filesystem.join(self._port.layout_tests_dir(), test) for test in tests] expectations = test_expectations.TestExpectations( self._port, test_paths, expectations_str, diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_expectations.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_expectations.py index 8645fc1..806b663 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_expectations.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_expectations.py @@ -32,9 +32,7 @@ for layout tests. """ import logging -import os import re -import sys import webkitpy.thirdparty.simplejson as simplejson @@ -322,6 +320,7 @@ class TestExpectationsFile: """ self._port = port + self._fs = port._filesystem self._expectations = expectations self._full_test_list = full_test_list self._test_platform_name = test_platform_name @@ -690,9 +689,9 @@ class TestExpectationsFile: 'indefinitely, then it should be just timeout.', test_list_path) - full_path = os.path.join(self._port.layout_tests_dir(), - test_list_path) - full_path = os.path.normpath(full_path) + full_path = self._fs.join(self._port.layout_tests_dir(), + test_list_path) + full_path = self._fs.normpath(full_path) # WebKit's way of skipping tests is to add a -disabled suffix. # So we should consider the path existing if the path or the # -disabled version exists. @@ -736,11 +735,11 @@ class TestExpectationsFile: # lists to represent the tree of tests, leaves being test # files and nodes being categories. - path = os.path.join(self._port.layout_tests_dir(), test_list_path) - path = os.path.normpath(path) - if self._port.path_isdir(path): + path = self._fs.join(self._port.layout_tests_dir(), test_list_path) + path = self._fs.normpath(path) + if self._fs.isdir(path): # this is a test category, return all the tests of the category. - path = os.path.join(path, '') + path = self._fs.join(path, '') return [test for test in self._full_test_list if test.startswith(path)] @@ -817,7 +816,7 @@ class TestExpectationsFile: self._remove_from_sets(test, self._timeline_to_tests) self._remove_from_sets(test, self._result_type_to_tests) - self._test_list_paths[test] = os.path.normpath(test_list_path) + self._test_list_paths[test] = self._fs.normpath(test_list_path) def _remove_from_sets(self, test, dict): """Removes the given test from the sets in the dictionary. @@ -838,7 +837,7 @@ class TestExpectationsFile: return False prev_base_path = self._test_list_paths[test] - if (prev_base_path == os.path.normpath(test_list_path)): + if (prev_base_path == self._fs.normpath(test_list_path)): if (not allow_overrides or test in self._overridding_tests): if allow_overrides: expectation_source = "override" @@ -854,7 +853,7 @@ class TestExpectationsFile: return False # Check if we've already seen a more precise path. - return prev_base_path.startswith(os.path.normpath(test_list_path)) + return prev_base_path.startswith(self._fs.normpath(test_list_path)) def _add_error(self, lineno, msg, path): """Reports an error that will prevent running the tests. Does not diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_expectations_unittest.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_expectations_unittest.py index 34771f3..8f9e5dd 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_expectations_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_expectations_unittest.py @@ -29,8 +29,6 @@ """Unit tests for test_expectations.py.""" -import os -import sys import unittest from webkitpy.layout_tests import port @@ -82,11 +80,12 @@ class FunctionsTest(unittest.TestCase): class Base(unittest.TestCase): def __init__(self, testFunc, setUp=None, tearDown=None, description=None): self._port = port.get('test', None) + self._fs = self._port._filesystem self._exp = None unittest.TestCase.__init__(self, testFunc) def get_test(self, test_name): - return os.path.join(self._port.layout_tests_dir(), test_name) + return self._fs.join(self._port.layout_tests_dir(), test_name) def get_basic_tests(self): return [self.get_test('failures/expected/text.html'), diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_failures.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_failures.py index 2b8190b..eb59d36 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_failures.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_failures.py @@ -29,7 +29,6 @@ """Classes for failures that occur during tests.""" -import os import test_expectations import cPickle @@ -121,7 +120,10 @@ class TestFailure(object): Return: The relative windows path to the output filename """ - return os.path.splitext(filename)[0] + modifier + # FIXME: technically this breaks if files don't use ".ext" to indicate + # the extension, but passing in a Filesystem object here is a huge + # hassle. + return filename[:filename.rfind('.')] + modifier class FailureWithType(TestFailure): diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_failures_unittest.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_failures_unittest.py index b2698d1..c5aa2d6 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_failures_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_failures_unittest.py @@ -89,6 +89,12 @@ class Test(unittest.TestCase): crash_set = set([FailureCrash(), "FailureCrash"]) self.assertEqual(len(crash_set), 2) + def test_relative_output_filename(self): + # This could be any Failure* object, since we're testing a method + # on the base class. + failure_obj = FailureTextMismatch() + actual_filename = failure_obj.relative_output_filename("fast/html/article-element.html", "-actual.txt") + self.assertEquals(actual_filename, "fast/html/article-element-actual.txt") if __name__ == '__main__': unittest.main() diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_runner.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_runner.py index 5b02a00..6c07850 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_runner.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_runner.py @@ -37,14 +37,11 @@ create a final report. from __future__ import with_statement -import codecs import errno import logging import math -import os import Queue import random -import shutil import sys import time @@ -68,8 +65,6 @@ _log = logging.getLogger("webkitpy.layout_tests.run_webkit_tests") # Builder base URL where we have the archived test results. BUILDER_BASE_URL = "http://build.chromium.org/buildbot/layout_test_results/" -LAYOUT_TESTS_DIRECTORY = "LayoutTests" + os.sep - TestExpectationsFile = test_expectations.TestExpectationsFile @@ -160,8 +155,6 @@ class TestRunner: """A class for managing running a series of tests on a series of layout test files.""" - HTTP_SUBDIR = os.sep.join(['', 'http', '']) - WEBSOCKET_SUBDIR = os.sep.join(['', 'websocket', '']) # The per-test timeout in milliseconds, if no --time-out-ms option was # given to run_webkit_tests. This should correspond to the default timeout @@ -177,10 +170,16 @@ class TestRunner: printer: a Printer object to record updates to. """ self._port = port + self._fs = port._filesystem self._options = options self._printer = printer self._message_broker = None + self.HTTP_SUBDIR = self._fs.join('', 'http', '') + self.WEBSOCKET_SUBDIR = self._fs.join('', 'websocket', '') + self.LAYOUT_TESTS_DIRECTORY = "LayoutTests" + self._fs.sep + + # disable wss server. need to install pyOpenSSL on buildbots. # self._websocket_secure_server = websocket_server.PyWebSocket( # options.results_directory, use_tls=True, port=9323) @@ -199,15 +198,18 @@ class TestRunner: last_unexpected_results: list of unexpected results to retest, if any """ - paths = [self._strip_test_dir_prefix(arg) for arg in args if arg and arg != ''] + paths = self._strip_test_dir_prefixes(args) paths += last_unexpected_results if self._options.test_list: - paths += read_test_files(self._options.test_list) + paths += self._strip_test_dir_prefixes(read_test_files(self._fs, self._options.test_list)) self._test_files = self._port.tests(paths) + def _strip_test_dir_prefixes(self, paths): + return [self._strip_test_dir_prefix(path) for path in paths if path] + def _strip_test_dir_prefix(self, path): - if path.startswith(LAYOUT_TESTS_DIRECTORY): - return path[len(LAYOUT_TESTS_DIRECTORY):] + if path.startswith(self.LAYOUT_TESTS_DIRECTORY): + return path[len(self.LAYOUT_TESTS_DIRECTORY):] return path def lint(self): @@ -339,10 +341,9 @@ class TestRunner: self._printer.print_expected(extra_msg) tests_run_msg += "\n" + extra_msg files.extend(test_files[0:extra]) - tests_run_filename = os.path.join(self._options.results_directory, + tests_run_filename = self._fs.join(self._options.results_directory, "tests_run.txt") - with codecs.open(tests_run_filename, "w", "utf-8") as file: - file.write(tests_run_msg + "\n") + self._fs.write_text_file(tests_run_filename, tests_run_msg) len_skip_chunk = int(len(files) * len(skipped) / float(len(self._test_files))) @@ -390,15 +391,20 @@ class TestRunner: result_summary.add(result, expected=True) self._printer.print_expected('') + # Check to make sure we didn't filter out all of the tests. + if not len(self._test_files): + _log.info("All tests are being skipped") + return None + return result_summary def _get_dir_for_test_file(self, test_file): """Returns the highest-level directory by which to shard the given test file.""" - index = test_file.rfind(os.sep + LAYOUT_TESTS_DIRECTORY) + index = test_file.rfind(self._fs.sep + self.LAYOUT_TESTS_DIRECTORY) - test_file = test_file[index + len(LAYOUT_TESTS_DIRECTORY):] - test_file_parts = test_file.split(os.sep, 1) + test_file = test_file[index + len(self.LAYOUT_TESTS_DIRECTORY):] + test_file_parts = test_file.split(self._fs.sep, 1) directory = test_file_parts[0] test_file = test_file_parts[1] @@ -408,10 +414,10 @@ class TestRunner: # what made them stable on linux/mac. return_value = directory while ((directory != 'http' or sys.platform in ('darwin', 'linux2')) - and test_file.find(os.sep) >= 0): - test_file_parts = test_file.split(os.sep, 1) + and test_file.find(self._fs.sep) >= 0): + test_file_parts = test_file.split(self._fs.sep, 1) directory = test_file_parts[0] - return_value = os.path.join(return_value, directory) + return_value = self._fs.join(return_value, directory) test_file = test_file_parts[1] return return_value @@ -427,7 +433,7 @@ class TestRunner: def _test_requires_lock(self, test_file): """Return True if the test needs to be locked when running multiple copies of NRWTs.""" - split_path = test_file.split(os.sep) + split_path = test_file.split(self._port._filesystem.sep) return 'http' in split_path or 'websocket' in split_path def _test_is_slow(self, test_file): @@ -757,10 +763,9 @@ class TestRunner: layout_tests_dir = self._port.layout_tests_dir() possible_dirs = self._port.test_dirs() for dirname in possible_dirs: - if os.path.isdir(os.path.join(layout_tests_dir, dirname)): - shutil.rmtree(os.path.join(self._options.results_directory, - dirname), - ignore_errors=True) + if self._fs.isdir(self._fs.join(layout_tests_dir, dirname)): + self._fs.rmtree(self._fs.join(self._options.results_directory, + dirname)) def _get_failures(self, result_summary, include_crashes): """Filters a dict of results and returns only the failures. @@ -803,17 +808,17 @@ class TestRunner: """ results_directory = self._options.results_directory _log.debug("Writing JSON files in %s." % results_directory) - unexpected_json_path = os.path.join(results_directory, "unexpected_results.json") - with codecs.open(unexpected_json_path, "w", "utf-8") as file: + unexpected_json_path = self._fs.join(results_directory, "unexpected_results.json") + with self._fs.open_text_file_for_writing(unexpected_json_path) as file: simplejson.dump(unexpected_results, file, sort_keys=True, indent=2) # Write a json file of the test_expectations.txt file for the layout # tests dashboard. - expectations_path = os.path.join(results_directory, "expectations.json") + expectations_path = self._fs.join(results_directory, "expectations.json") expectations_json = \ self._expectations.get_expectations_json_for_all_platforms() - with codecs.open(expectations_path, "w", "utf-8") as file: - file.write(u"ADD_EXPECTATIONS(%s);" % expectations_json) + self._fs.write_text_file(expectations_path, + u"ADD_EXPECTATIONS(%s);" % expectations_json) generator = json_layout_results_generator.JSONLayoutResultsGenerator( self._port, self._options.builder_name, self._options.build_name, @@ -1184,9 +1189,9 @@ class TestRunner: if not len(test_files): return False - out_filename = os.path.join(self._options.results_directory, - "results.html") - with codecs.open(out_filename, "w", "utf-8") as results_file: + out_filename = self._fs.join(self._options.results_directory, + "results.html") + with self._fs.open_text_file_for_writing(out_filename) as results_file: html = self._results_html(test_files, result_summary.failures, results_title) results_file.write(html) @@ -1194,21 +1199,20 @@ class TestRunner: def _show_results_html_file(self): """Shows the results.html page.""" - results_filename = os.path.join(self._options.results_directory, - "results.html") + results_filename = self._fs.join(self._options.results_directory, + "results.html") self._port.show_results_html_file(results_filename) -def read_test_files(files): +def read_test_files(fs, files): tests = [] for file in files: try: - with codecs.open(file, 'r', 'utf-8') as file_contents: - # FIXME: This could be cleaner using a list comprehension. - for line in file_contents: - line = test_expectations.strip_comments(line) - if line: - tests.append(line) + file_contents = fs.read_text_file(file).split('\n') + for line in file_contents: + line = test_expectations.strip_comments(line) + if line: + tests.append(line) except IOError, e: if e.errno == errno.ENOENT: _log.critical('') diff --git a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_runner_unittest.py b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_runner_unittest.py index 3c564ae..97f8630 100644 --- a/Tools/Scripts/webkitpy/layout_tests/layout_package/test_runner_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/layout_package/test_runner_unittest.py @@ -32,6 +32,7 @@ import unittest +from webkitpy.common.system import filesystem_mock from webkitpy.thirdparty.mock import Mock import test_runner @@ -45,6 +46,7 @@ class TestRunnerWrapper(test_runner.TestRunner): class TestRunnerTest(unittest.TestCase): def test_results_html(self): mock_port = Mock() + mock_port._filesystem = filesystem_mock.MockFileSystem() mock_port.relative_test_filename = lambda name: name mock_port.filename_to_uri = lambda name: name @@ -66,7 +68,9 @@ class TestRunnerTest(unittest.TestCase): def test_shard_tests(self): # Test that _shard_tests in test_runner.TestRunner really # put the http tests first in the queue. - runner = TestRunnerWrapper(port=Mock(), options=Mock(), + port = Mock() + port._filesystem = filesystem_mock.MockFileSystem() + runner = TestRunnerWrapper(port=port, options=Mock(), printer=Mock()) test_list = [ diff --git a/Tools/Scripts/webkitpy/layout_tests/port/__init__.py b/Tools/Scripts/webkitpy/layout_tests/port/__init__.py index e3ad6f4..59ab2ef 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/__init__.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/__init__.py @@ -30,3 +30,5 @@ """Port-specific entrypoints for the layout tests test infrastructure.""" from factory import get + +from test import unit_test_filesystem diff --git a/Tools/Scripts/webkitpy/layout_tests/port/base.py b/Tools/Scripts/webkitpy/layout_tests/port/base.py index 97b54c9..6e5fabc 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/base.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/base.py @@ -117,7 +117,10 @@ class Port(object): self._pretty_patch_path = self.path_from_webkit_base("Websites", "bugs.webkit.org", "PrettyPatch", "prettify.rb") - self._pretty_patch_available = True + # If we're running on a mocked-out filesystem, this file almost + # certainly won't be available, so it's a good test to keep us + # from erroring out later. + self._pretty_patch_available = self._filesystem.exists(self._pretty_patch_path) self.set_option_default('configuration', None) if self._options.configuration is None: self._options.configuration = self.default_configuration() @@ -244,7 +247,7 @@ class Port(object): tree) results_filename - relative path from top of tree to the results file - (os.path.join of the two gives you the full path to the file, + (port.join() of the two gives you the full path to the file, unless None was returned.) Return values will be in the format appropriate for the current platform (e.g., "\\" for path separators on Windows). If the results @@ -255,7 +258,7 @@ class Port(object): conjunction with the other baseline and filename routines that are platform specific. """ - testname = os.path.splitext(self.relative_test_filename(filename))[0] + testname = self._filesystem.splitext(self.relative_test_filename(filename))[0] baseline_filename = testname + '-expected' + suffix @@ -360,7 +363,7 @@ class Port(object): protocol = "http" return "%s://127.0.0.1:%u/%s" % (protocol, port, relative_path) - return path.abspath_to_uri(os.path.abspath(filename)) + return path.abspath_to_uri(self._filesystem.abspath(filename)) def tests(self, paths): """Return the list of tests found (relative to layout_tests_dir().""" @@ -702,7 +705,7 @@ class Port(object): def pretty_patch_text(self, diff_path): if not self._pretty_patch_available: return self._pretty_patch_error_html - command = ("ruby", "-I", os.path.dirname(self._pretty_patch_path), + command = ("ruby", "-I", self._filesystem.dirname(self._pretty_patch_path), self._pretty_patch_path, diff_path) try: # Diffs are treated as binary (we pass decode_output=False) as they diff --git a/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py index 8d586e3..72f2d05 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py @@ -27,7 +27,6 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import optparse -import os import sys import tempfile import unittest @@ -194,7 +193,7 @@ class PortTest(unittest.TestCase): def test_filename_to_uri(self): port = base.Port() layout_test_dir = port.layout_tests_dir() - test_file = os.path.join(layout_test_dir, "foo", "bar.html") + test_file = port._filesystem.join(layout_test_dir, "foo", "bar.html") # On Windows, absolute paths are of the form "c:\foo.txt". However, # all current browsers (except for Opera) normalize file URLs by diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium.py index 7e934a8..ad1bea6 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/chromium.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium.py @@ -29,21 +29,16 @@ """Chromium implementations of the Port interface.""" -from __future__ import with_statement - -import codecs import errno import logging -import os import re -import shutil import signal import subprocess import sys -import tempfile import time import webbrowser +from webkitpy.common.system import executive from webkitpy.common.system.path import cygpath from webkitpy.layout_tests.layout_package import test_expectations from webkitpy.layout_tests.layout_package import test_output @@ -61,26 +56,6 @@ _log = logging.getLogger("webkitpy.layout_tests.port.chromium") # FIXME: This function doesn't belong in this package. -def check_file_exists(path_to_file, file_description, override_step=None, - logging=True): - """Verify the file is present where expected or log an error. - - Args: - file_name: The (human friendly) name or description of the file - you're looking for (e.g., "HTTP Server"). Used for error logging. - override_step: An optional string to be logged if the check fails. - logging: Whether or not log the error messages.""" - if not os.path.exists(path_to_file): - if logging: - _log.error('Unable to find %s' % file_description) - _log.error(' at %s' % path_to_file) - if override_step: - _log.error(' %s' % override_step) - _log.error('') - return False - return True - - class ChromiumPort(base.Port): """Abstract base class for Chromium implementations of the Port class.""" @@ -88,6 +63,26 @@ class ChromiumPort(base.Port): base.Port.__init__(self, **kwargs) self._chromium_base_dir = None + def _check_file_exists(self, path_to_file, file_description, + override_step=None, logging=True): + """Verify the file is present where expected or log an error. + + Args: + file_name: The (human friendly) name or description of the file + you're looking for (e.g., "HTTP Server"). Used for error logging. + override_step: An optional string to be logged if the check fails. + logging: Whether or not log the error messages.""" + if not self._filesystem.exists(path_to_file): + if logging: + _log.error('Unable to find %s' % file_description) + _log.error(' at %s' % path_to_file) + if override_step: + _log.error(' %s' % override_step) + _log.error('') + return False + return True + + def baseline_path(self): return self._webkit_baseline_path(self._name) @@ -95,8 +90,8 @@ class ChromiumPort(base.Port): result = True dump_render_tree_binary_path = self._path_to_driver() - result = check_file_exists(dump_render_tree_binary_path, - 'test driver') and result + result = self._check_file_exists(dump_render_tree_binary_path, + 'test driver') and result if result and self.get_option('build'): result = self._check_driver_build_up_to_date( self.get_option('configuration')) @@ -105,8 +100,8 @@ class ChromiumPort(base.Port): helper_path = self._path_to_helper() if helper_path: - result = check_file_exists(helper_path, - 'layout test helper') and result + result = self._check_file_exists(helper_path, + 'layout test helper') and result if self.get_option('pixel_tests'): result = self.check_image_diff( @@ -120,29 +115,35 @@ class ChromiumPort(base.Port): def check_sys_deps(self, needs_http): cmd = [self._path_to_driver(), '--check-layout-test-sys-deps'] - if self._executive.run_command(cmd, return_exit_code=True): + + local_error = executive.ScriptError() + + def error_handler(script_error): + local_error.exit_code = script_error.exit_code + + output = self._executive.run_command(cmd, error_handler=error_handler) + if local_error.exit_code: _log.error('System dependencies check failed.') _log.error('To override, invoke with --nocheck-sys-deps') _log.error('') + _log.error(output) return False return True def check_image_diff(self, override_step=None, logging=True): image_diff_path = self._path_to_image_diff() - return check_file_exists(image_diff_path, 'image diff exe', - override_step, logging) + return self._check_file_exists(image_diff_path, 'image diff exe', + override_step, logging) def diff_image(self, expected_contents, actual_contents, diff_filename=None): executable = self._path_to_image_diff() - tempdir = tempfile.mkdtemp() - expected_filename = os.path.join(tempdir, "expected.png") - with open(expected_filename, 'w+b') as file: - file.write(expected_contents) - actual_filename = os.path.join(tempdir, "actual.png") - with open(actual_filename, 'w+b') as file: - file.write(actual_contents) + tempdir = self._filesystem.mkdtemp() + expected_filename = self._filesystem.join(str(tempdir), "expected.png") + self._filesystem.write_binary_file(expected_filename, expected_contents) + actual_filename = self._filesystem.join(str(tempdir), "actual.png") + self._filesystem.write_binary_file(actual_filename, actual_contents) if diff_filename: cmd = [executable, '--diff', expected_filename, @@ -171,7 +172,7 @@ class ChromiumPort(base.Port): else: raise e finally: - shutil.rmtree(tempdir, ignore_errors=True) + self._filesystem.rmtree(str(tempdir)) return result def driver_name(self): @@ -183,15 +184,15 @@ class ChromiumPort(base.Port): """Returns the full path to path made by joining the top of the Chromium source tree and the list of path components in |*comps|.""" if not self._chromium_base_dir: - abspath = os.path.abspath(__file__) + abspath = self._filesystem.abspath(__file__) offset = abspath.find('third_party') if offset == -1: - self._chromium_base_dir = os.path.join( + self._chromium_base_dir = self._filesystem.join( abspath[0:abspath.find('Tools')], 'WebKit', 'chromium') else: self._chromium_base_dir = abspath[0:offset] - return os.path.join(self._chromium_base_dir, *comps) + return self._filesystem.join(self._chromium_base_dir, *comps) def path_to_test_expectations_file(self): return self.path_from_webkit_base('LayoutTests', 'platform', @@ -209,10 +210,10 @@ class ChromiumPort(base.Port): def setup_test_run(self): # Delete the disk cache if any to ensure a clean test run. dump_render_tree_binary_path = self._path_to_driver() - cachedir = os.path.split(dump_render_tree_binary_path)[0] - cachedir = os.path.join(cachedir, "cache") - if os.path.exists(cachedir): - shutil.rmtree(cachedir) + cachedir = self._filesystem.dirname(dump_render_tree_binary_path) + cachedir = self._filesystem.join(cachedir, "cache") + if self._filesystem.exists(cachedir): + self._filesystem.rmtree(cachedir) def create_driver(self, worker_number): """Starts a new Driver and returns a handle to it.""" @@ -249,8 +250,7 @@ class ChromiumPort(base.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() - with codecs.open(expectations_path, "r", "utf-8") as file: - return file.read() + return self._filesystem.read_text_file(expectations_path) def test_expectations_overrides(self): try: @@ -258,10 +258,9 @@ class ChromiumPort(base.Port): 'layout_tests', 'test_expectations.txt') except AssertionError: return None - if not os.path.exists(overrides_path): + if not self._filesystem.exists(overrides_path): return None - with codecs.open(overrides_path, "r", "utf-8") as file: - return file.read() + return self._filesystem.read_text_file(overrides_path) def skipped_layout_tests(self, extra_test_files=None): expectations_str = self.test_expectations() @@ -310,8 +309,8 @@ class ChromiumPort(base.Port): debug_path = self._path_to_driver('Debug') release_path = self._path_to_driver('Release') - debug_mtime = os.stat(debug_path).st_mtime - release_mtime = os.stat(release_path).st_mtime + debug_mtime = self._filesystem.mtime(debug_path) + release_mtime = self._filesystem.mtime(release_path) if (debug_mtime > release_mtime and configuration == 'Release' or release_mtime > debug_mtime and configuration == 'Debug'): @@ -354,7 +353,7 @@ class ChromiumDriver(base.Driver): self._worker_number = worker_number self._image_path = None if self._port.get_option('pixel_tests'): - self._image_path = os.path.join( + self._image_path = self._port._filesystem.join( self._port.get_option('results_directory'), 'png_result%s.png' % self._worker_number) @@ -446,9 +445,8 @@ class ChromiumDriver(base.Driver): def _output_image(self): """Returns the image output which driver generated.""" png_path = self._image_path - if png_path and os.path.isfile(png_path): - with open(png_path, 'rb') as image_file: - return image_file.read() + if png_path and self._port._filesystem.isfile(png_path): + return self._port._filesystem.read_binary_file(png_path) else: return None diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py index c1f5c8d..b88d8aa 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py @@ -24,10 +24,6 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from __future__ import with_statement - -import codecs -import os import sys import chromium_linux @@ -82,10 +78,9 @@ def _gpu_overrides(port): 'layout_tests', 'test_expectations_gpu.txt') except AssertionError: return None - if not os.path.exists(overrides_path): + if not port._filesystem.exists(overrides_path): return None - with codecs.open(overrides_path, "r", "utf-8") as file: - return file.read() + return port._filesystem.read_text_file(overrides_path) class ChromiumGpuLinuxPort(chromium_linux.ChromiumLinuxPort): diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py index ad0404c..0bfb127 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py @@ -24,7 +24,6 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import os import unittest from webkitpy.tool import mocktool diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py index 5d9dd87..c1c85f8 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py @@ -78,20 +78,24 @@ class ChromiumLinuxPort(chromium.ChromiumPort): # 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() - if os.path.exists(os.path.join(base, 'sconsbuild')): - return os.path.join(base, 'sconsbuild', *comps) - if os.path.exists(os.path.join(base, 'out', *comps)) or self.get_option('use_test_shell'): - return os.path.join(base, 'out', *comps) + if self._filesystem.exists(self._filesystem.join(base, 'sconsbuild')): + return self._filesystem.join(base, 'sconsbuild', *comps) + if self._filesystem.exists(self._filesystem.join(base, 'out', *comps)) or self.get_option('use_test_shell'): + return self._filesystem.join(base, 'out', *comps) base = self.path_from_webkit_base() - if os.path.exists(os.path.join(base, 'sconsbuild')): - return os.path.join(base, 'sconsbuild', *comps) - return os.path.join(base, 'out', *comps) + if self._filesystem.exists(self._filesystem.join(base, 'sconsbuild')): + return self._filesystem.join(base, 'sconsbuild', *comps) + return self._filesystem.join(base, 'out', *comps) def _check_apache_install(self): - result = chromium.check_file_exists(self._path_to_apache(), + result = self._check_file_exists(self._path_to_apache(), "apache2") - result = chromium.check_file_exists(self._path_to_apache_config_file(), + result = self._check_file_exists(self._path_to_apache_config_file(), "apache2 config file") and result if not result: _log.error(' Please install using: "sudo apt-get install ' @@ -100,11 +104,11 @@ class ChromiumLinuxPort(chromium.ChromiumPort): return result def _check_lighttpd_install(self): - result = chromium.check_file_exists( + result = self._check_file_exists( self._path_to_lighttpd(), "LigHTTPd executable") - result = chromium.check_file_exists(self._path_to_lighttpd_php(), + result = self._check_file_exists(self._path_to_lighttpd_php(), "PHP CGI executable") and result - result = chromium.check_file_exists(self._path_to_lighttpd_modules(), + result = self._check_file_exists(self._path_to_lighttpd_modules(), "LigHTTPd modules") and result if not result: _log.error(' Please install using: "sudo apt-get install ' @@ -113,7 +117,7 @@ class ChromiumLinuxPort(chromium.ChromiumPort): return result def _check_wdiff_install(self): - result = chromium.check_file_exists(self._path_to_wdiff(), 'wdiff') + result = self._check_file_exists(self._path_to_wdiff(), 'wdiff') if not result: _log.error(' Please install using: "sudo apt-get install ' 'wdiff"') @@ -133,7 +137,7 @@ class ChromiumLinuxPort(chromium.ChromiumPort): else: config_name = 'apache2-debian-httpd.conf' - return os.path.join(self.layout_tests_dir(), 'http', 'conf', + return self._filesystem.join(self.layout_tests_dir(), 'http', 'conf', config_name) def _path_to_lighttpd(self): @@ -163,7 +167,7 @@ class ChromiumLinuxPort(chromium.ChromiumPort): return '/usr/bin/wdiff' def _is_redhat_based(self): - return os.path.exists(os.path.join('/etc', 'redhat-release')) + return self._filesystem.exists(self._filesystem.join('/etc', 'redhat-release')) def _shut_down_http_server(self, server_pid): """Shut down the lighttpd web server. Blocks until it's fully diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py index f638e01..5360ab3 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py @@ -105,11 +105,15 @@ class ChromiumMacPort(chromium.ChromiumPort): # def _build_path(self, *comps): + if self.get_option('build_directory'): + return self._filesystem.join(self.get_option('build_directory'), + *comps) + path = self.path_from_chromium_base('xcodebuild', *comps) - if os.path.exists(path) or self.get_option('use_test_shell'): + if self._filesystem.exists(path) or self.get_option('use_test_shell'): return path - return self.path_from_webkit_base('WebKit', 'chromium', 'xcodebuild', - *comps) + return self.path_from_webkit_base( + 'Source', 'WebKit', 'chromium', 'xcodebuild', *comps) def _check_wdiff_install(self): try: @@ -128,8 +132,8 @@ class ChromiumMacPort(chromium.ChromiumPort): return '/usr/sbin/httpd' def _path_to_apache_config_file(self): - return os.path.join(self.layout_tests_dir(), 'http', 'conf', - 'apache2-httpd.conf') + return self._filesystem.join(self.layout_tests_dir(), 'http', 'conf', + 'apache2-httpd.conf') def _path_to_lighttpd(self): return self._lighttpd_path('bin', 'lighttpd') diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py index c87984f..6c8987b 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py @@ -26,13 +26,16 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import os import unittest import StringIO +from webkitpy.common.system import logtesting +from webkitpy.common.system import executive_mock +from webkitpy.common.system import filesystem_mock from webkitpy.tool import mocktool from webkitpy.thirdparty.mock import Mock + import chromium import chromium_linux import chromium_mac @@ -98,7 +101,8 @@ class ChromiumPortTest(unittest.TestCase): def __init__(self, options): chromium_linux.ChromiumLinuxPort.__init__(self, port_name='test-port', - options=options) + options=options, + filesystem=filesystem_mock.MockFileSystem()) def default_configuration(self): self.default_configuration_called = True @@ -126,7 +130,7 @@ class ChromiumPortTest(unittest.TestCase): mock_options = mocktool.MockOptions() port = ChromiumPortTest.TestLinuxPort(options=mock_options) - fake_test = os.path.join(port.layout_tests_dir(), "fast/js/not-good.js") + fake_test = port._filesystem.join(port.layout_tests_dir(), "fast/js/not-good.js") port.test_expectations = lambda: """BUG_TEST SKIP : fast/js/not-good.js = TEXT LINUX WIN : fast/js/very-good.js = TIMEOUT PASS""" @@ -153,35 +157,19 @@ LINUX WIN : fast/js/very-good.js = TIMEOUT PASS""" def _path_to_image_diff(self): return "/path/to/image_diff" - class MockExecute: - def __init__(self, result): - self._result = result - - def run_command(self, - args, - cwd=None, - input=None, - error_handler=None, - return_exit_code=False, - return_stderr=True, - decode_output=False): - if return_exit_code: - return self._result - return '' - mock_options = mocktool.MockOptions() port = ChromiumPortTest.TestLinuxPort(mock_options) # Images are different. - port._executive = MockExecute(0) + port._executive = executive_mock.MockExecutive2(exit_code=0) self.assertEquals(False, port.diff_image("EXPECTED", "ACTUAL")) # Images are the same. - port._executive = MockExecute(1) + port._executive = executive_mock.MockExecutive2(exit_code=1) self.assertEquals(True, port.diff_image("EXPECTED", "ACTUAL")) # There was some error running image_diff. - port._executive = MockExecute(2) + port._executive = executive_mock.MockExecutive2(exit_code=2) exception_raised = False try: port.diff_image("EXPECTED", "ACTUAL") @@ -189,5 +177,25 @@ LINUX WIN : fast/js/very-good.js = TIMEOUT PASS""" exception_raised = True self.assertFalse(exception_raised) + +class ChromiumPortLoggingTest(logtesting.LoggingTestCase): + def test_check_sys_deps(self): + mock_options = mocktool.MockOptions() + port = ChromiumPortTest.TestLinuxPort(options=mock_options) + + # Success + port._executive = executive_mock.MockExecutive2(exit_code=0) + self.assertTrue(port.check_sys_deps(needs_http=False)) + + # Failure + port._executive = executive_mock.MockExecutive2(exit_code=1, + output='testing output failure') + self.assertFalse(port.check_sys_deps(needs_http=False)) + self.assertLog([ + 'ERROR: System dependencies check failed.\n', + 'ERROR: To override, invoke with --nocheck-sys-deps\n', + 'ERROR: \n', + 'ERROR: testing output failure\n']) + if __name__ == '__main__': unittest.main() diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_win.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_win.py index d080f82..14f2777 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_win.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_win.py @@ -30,7 +30,6 @@ """Chromium Win implementation of the Port interface.""" import logging -import os import sys import chromium @@ -106,14 +105,17 @@ 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('webkit', *comps) - if os.path.exists(p): + if self._filesystem.exists(p): return p p = self.path_from_chromium_base('chrome', *comps) - if os.path.exists(p) or self.get_option('use_test_shell'): + if self._filesystem.exists(p) or self.get_option('use_test_shell'): return p - return os.path.join(self.path_from_webkit_base(), 'WebKit', 'chromium', - *comps) + return self._filesystem.join(self.path_from_webkit_base(), 'WebKit', 'chromium', *comps) def _lighttpd_path(self, *comps): return self.path_from_chromium_base('third_party', 'lighttpd', 'win', @@ -124,8 +126,7 @@ class ChromiumWinPort(chromium.ChromiumPort): 'sbin', 'httpd') def _path_to_apache_config_file(self): - return os.path.join(self.layout_tests_dir(), 'http', 'conf', - 'cygwin-httpd.conf') + return self._filesystem.join(self.layout_tests_dir(), 'http', 'conf', 'cygwin-httpd.conf') def _path_to_lighttpd(self): return self._lighttpd_path('LightTPD.exe') 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 36f3c6b..d677589 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/chromium_win_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_win_unittest.py @@ -45,13 +45,15 @@ class ChromiumWinTest(unittest.TestCase): def tearDown(self): sys.platform = self.orig_platform + self._port = None def _mock_path_from_chromium_base(self, *comps): - return os.path.join("/chromium/src", *comps) + return self._port._filesystem.join("/chromium/src", *comps) def test_setup_environ_for_server(self): port = chromium_win.ChromiumWinPort() port._executive = mocktool.MockExecutive(should_log=True) + self._port = port port.path_from_chromium_base = self._mock_path_from_chromium_base output = outputcapture.OutputCapture() orig_environ = os.environ.copy() @@ -65,6 +67,7 @@ class ChromiumWinTest(unittest.TestCase): options=ChromiumWinTest.RegisterCygwinOption()) port._executive = mocktool.MockExecutive(should_log=True) port.path_from_chromium_base = self._mock_path_from_chromium_base + self._port = port setup_mount = self._mock_path_from_chromium_base("third_party", "cygwin", "setup_mount.bat") diff --git a/Tools/Scripts/webkitpy/layout_tests/port/config.py b/Tools/Scripts/webkitpy/layout_tests/port/config.py index e08ed9d..6016639 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/config.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/config.py @@ -32,8 +32,6 @@ # FIXME: This file needs to be unified with common/checkout/scm.py and # common/config/ports.py . -import os - from webkitpy.common.system import logutils from webkitpy.common.system import executive @@ -131,7 +129,7 @@ class Config(object): # # This code will also work if there is no SCM system at all. if not self._webkit_base_dir: - abspath = os.path.abspath(__file__) + abspath = self._filesystem.abspath(__file__) self._webkit_base_dir = abspath[0:abspath.find('Tools') - 1] return self._webkit_base_dir diff --git a/Tools/Scripts/webkitpy/layout_tests/port/google_chrome.py b/Tools/Scripts/webkitpy/layout_tests/port/google_chrome.py index 8d94bb5..ae90374 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/google_chrome.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/google_chrome.py @@ -24,11 +24,6 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from __future__ import with_statement - -import codecs -import os - def _test_expectations_overrides(port, super): # The chrome ports use the regular overrides plus anything in the @@ -40,14 +35,11 @@ def _test_expectations_overrides(port, super): # 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 os.path.exists(overrides_path): + if not port._filesystem.exists(overrides_path): return chromium_overrides - with codecs.open(overrides_path, "r", "utf-8") as file: - if chromium_overrides: - return chromium_overrides + file.read() - else: - return file.read() + chromium_overrides = chromium_overrides or '' + return chromium_overrides + port._filesystem.read_text_file(overrides_path) def GetGoogleChromePort(**kwargs): """Some tests have slightly different results when compiled as Google 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 e60c274..aab8dd1 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/google_chrome_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/google_chrome_unittest.py @@ -24,11 +24,9 @@ # (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 codecs -import os import unittest -from webkitpy.common import newstringio +from webkitpy.common.system import filesystem_mock import factory import google_chrome @@ -50,7 +48,7 @@ class GetGoogleChromePortTest(unittest.TestCase): port = google_chrome.GetGoogleChromePort(port_name=port_name, options=None) path = port.baseline_search_path()[0] - self.assertEqual(expected_path, os.path.split(path)[1]) + self.assertEqual(expected_path, port._filesystem.basename(path)) def _verify_expectations_overrides(self, port_name): # FIXME: make this more robust when we have the Tree() abstraction. @@ -58,45 +56,27 @@ class GetGoogleChromePortTest(unittest.TestCase): # be able to control the contents better. chromium_port = factory.get("chromium-mac") - chromium_overrides = chromium_port.test_expectations_overrides() + chromium_base = chromium_port.path_from_chromium_base() + fs = filesystem_mock.MockFileSystem() port = google_chrome.GetGoogleChromePort(port_name=port_name, - options=None) - - orig_exists = os.path.exists - orig_open = codecs.open - expected_string = "// hello, world\n" - - def mock_exists_chrome_not_found(path): - if 'test_expectations_chrome.txt' in path: - return False - return orig_exists(path) - - def mock_exists_chrome_found(path): - if 'test_expectations_chrome.txt' in path: - return True - return orig_exists(path) - - def mock_open(path, mode, encoding): - if 'test_expectations_chrome.txt' in path: - return newstringio.StringIO(expected_string) - return orig_open(path, mode, encoding) - - try: - os.path.exists = mock_exists_chrome_not_found - chrome_overrides = port.test_expectations_overrides() - self.assertEqual(chromium_overrides, chrome_overrides) - - os.path.exists = mock_exists_chrome_found - codecs.open = mock_open - chrome_overrides = port.test_expectations_overrides() - if chromium_overrides: - self.assertEqual(chrome_overrides, - chromium_overrides + expected_string) - else: - self.assertEqual(chrome_overrides, expected_string) - finally: - os.path.exists = orig_exists - codecs.open = orig_open + options=None, filesystem=fs) + + expected_chromium_overrides = '// chromium overrides\n' + expected_chrome_overrides = '// chrome overrides\n' + chromium_path = fs.join(chromium_base, 'webkit', 'tools', + 'layout_tests', 'test_expectations.txt') + chrome_path = fs.join(chromium_base, 'webkit', 'tools', + 'layout_tests', 'test_expectations_chrome.txt') + + fs.files[chromium_path] = expected_chromium_overrides + fs.files[chrome_path] = None + actual_chrome_overrides = port.test_expectations_overrides() + self.assertEqual(expected_chromium_overrides, actual_chrome_overrides) + + fs.files[chrome_path] = expected_chrome_overrides + actual_chrome_overrides = port.test_expectations_overrides() + self.assertEqual(actual_chrome_overrides, + expected_chromium_overrides + expected_chrome_overrides) if __name__ == '__main__': diff --git a/Tools/Scripts/webkitpy/layout_tests/port/gtk.py b/Tools/Scripts/webkitpy/layout_tests/port/gtk.py index a18fdff..0ec9f87 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/gtk.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/gtk.py @@ -57,8 +57,8 @@ class GtkPort(WebKitPort): def _path_to_apache_config_file(self): # FIXME: This needs to detect the distribution and change config files. - return os.path.join(self.layout_tests_dir(), 'http', 'conf', - 'apache2-debian-httpd.conf') + return self._filesystem.join(self.layout_tests_dir(), 'http', 'conf', + 'apache2-debian-httpd.conf') def _shut_down_http_server(self, server_pid): """Shut down the httpd web server. Blocks until it's fully @@ -103,7 +103,7 @@ class GtkPort(WebKitPort): else: config_name = 'apache2-debian-httpd.conf' - return os.path.join(self.layout_tests_dir(), 'http', 'conf', + return self._filesystem.join(self.layout_tests_dir(), 'http', 'conf', config_name) def _path_to_wdiff(self): @@ -113,4 +113,4 @@ class GtkPort(WebKitPort): return '/usr/bin/wdiff' def _is_redhat_based(self): - return os.path.exists(os.path.join('/etc', 'redhat-release')) + return self._filesystem.exists(self._filesystem.join('/etc', 'redhat-release')) diff --git a/Tools/Scripts/webkitpy/layout_tests/port/mac.py b/Tools/Scripts/webkitpy/layout_tests/port/mac.py index 696e339..0622196 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/mac.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/mac.py @@ -76,9 +76,9 @@ class MacPort(WebKitPort): # platforms and moved into base.Port. skipped_files = [] if self._name in ('mac-tiger', 'mac-leopard', 'mac-snowleopard'): - skipped_files.append(os.path.join( + skipped_files.append(self._filesystem.join( self._webkit_baseline_path(self._name), 'Skipped')) - skipped_files.append(os.path.join(self._webkit_baseline_path('mac'), + skipped_files.append(self._filesystem.join(self._webkit_baseline_path('mac'), 'Skipped')) return skipped_files @@ -99,7 +99,7 @@ class MacPort(WebKitPort): return '' def _build_java_test_support(self): - java_tests_path = os.path.join(self.layout_tests_dir(), "java") + java_tests_path = self._filesystem.join(self.layout_tests_dir(), "java") build_java = ["/usr/bin/make", "-C", java_tests_path] if self._executive.run_command(build_java, return_exit_code=True): _log.error("Failed to build Java support files: %s" % build_java) @@ -124,8 +124,8 @@ class MacPort(WebKitPort): ] def _path_to_apache_config_file(self): - return os.path.join(self.layout_tests_dir(), 'http', 'conf', - 'apache2-httpd.conf') + return self._filesystem.join(self.layout_tests_dir(), 'http', 'conf', + 'apache2-httpd.conf') # FIXME: This doesn't have anything to do with WebKit. def _shut_down_http_server(self, server_pid): diff --git a/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py b/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py index c4b36ac..0b03b4c 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py @@ -28,8 +28,6 @@ """Unit testing base class for Port implementations.""" -import os -import tempfile import unittest from webkitpy.tool import mocktool @@ -52,44 +50,39 @@ class PortTestCase(unittest.TestCase): return self.assertTrue(len(port.driver_cmd_line())) - def test_http_server(self): + def disabled_test_http_server(self): port = self.make_port() if not port: return port.start_http_server() port.stop_http_server() - def test_image_diff(self): + def disabled_test_image_diff(self): port = self.make_port() if not port: return - # FIXME: not sure why this shouldn't always be True - #self.assertTrue(port.check_image_diff()) if not port.check_image_diff(): + # The port hasn't been built - don't run the tests. return dir = port.layout_tests_dir() - file1 = os.path.join(dir, 'fast', 'css', 'button_center.png') - fh1 = file(file1) - contents1 = fh1.read() - file2 = os.path.join(dir, 'fast', 'css', - 'remove-shorthand-expected.png') - fh2 = file(file2) - contents2 = fh2.read() - tmpfile = tempfile.mktemp() + file1 = port._filesystem.join(dir, 'fast', 'css', 'button_center.png') + contents1 = port._filesystem.read_binary_file(file1) + file2 = port._filesystem.join(dir, 'fast', 'css', + 'remove-shorthand-expected.png') + contents2 = port._filesystem.read_binary_file(file2) + tmpfd, tmpfile = port._filesystem.open_binary_tempfile('') + tmpfd.close() self.assertFalse(port.diff_image(contents1, contents1)) self.assertTrue(port.diff_image(contents1, contents2)) self.assertTrue(port.diff_image(contents1, contents2, tmpfile)) - fh1.close() - fh2.close() - # FIXME: this may not be being written? - # self.assertTrue(os.path.exists(tmpfile)) - # os.remove(tmpfile) - def test_websocket_server(self): + port._filesystem.remove(tmpfile) + + def disabled_test_websocket_server(self): port = self.make_port() if not port: return diff --git a/Tools/Scripts/webkitpy/layout_tests/port/qt.py b/Tools/Scripts/webkitpy/layout_tests/port/qt.py index af94acc..1695b60 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/qt.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/qt.py @@ -71,8 +71,8 @@ class QtPort(WebKitPort): def _path_to_apache_config_file(self): # FIXME: This needs to detect the distribution and change config files. - return os.path.join(self.layout_tests_dir(), 'http', 'conf', - 'apache2-debian-httpd.conf') + return self._filesystem.join(self.layout_tests_dir(), 'http', 'conf', + 'apache2-debian-httpd.conf') def _shut_down_http_server(self, server_pid): """Shut down the httpd web server. Blocks until it's fully diff --git a/Tools/Scripts/webkitpy/layout_tests/port/test.py b/Tools/Scripts/webkitpy/layout_tests/port/test.py index 935881c..5df5c2d 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/test.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/test.py @@ -30,12 +30,10 @@ """Dummy Port implementation used for testing.""" from __future__ import with_statement -import codecs -import fnmatch -import os -import sys import time +from webkitpy.common.system import filesystem_mock + from webkitpy.layout_tests.layout_package import test_output import base @@ -64,8 +62,7 @@ class TestInstance: # This is an in-memory list of tests, what we want them to produce, and # what we want to claim are the expected results. class TestList: - def __init__(self, port): - self.port = port + def __init__(self): self.tests = {} def add(self, name, **kwargs): @@ -84,66 +81,141 @@ class TestList: return self.tests[item] +def unit_test_list(): + tests = TestList() + tests.add('failures/expected/checksum.html', + actual_checksum='checksum_fail-checksum') + tests.add('failures/expected/crash.html', crash=True) + tests.add('failures/expected/exception.html', exception=True) + tests.add('failures/expected/timeout.html', timeout=True) + tests.add('failures/expected/hang.html', hang=True) + tests.add('failures/expected/missing_text.html', + expected_text=None) + tests.add('failures/expected/image.html', + actual_image='image_fail-png', + expected_image='image-png') + tests.add('failures/expected/image_checksum.html', + actual_checksum='image_checksum_fail-checksum', + actual_image='image_checksum_fail-png') + tests.add('failures/expected/keyboard.html', + keyboard=True) + tests.add('failures/expected/missing_check.html', + expected_checksum=None) + tests.add('failures/expected/missing_image.html', + expected_image=None) + tests.add('failures/expected/missing_text.html', + expected_text=None) + tests.add('failures/expected/newlines_leading.html', + expected_text="\nfoo\n", + actual_text="foo\n") + tests.add('failures/expected/newlines_trailing.html', + expected_text="foo\n\n", + actual_text="foo\n") + tests.add('failures/expected/newlines_with_excess_CR.html', + expected_text="foo\r\r\r\n", + actual_text="foo\n") + tests.add('failures/expected/text.html', + actual_text='text_fail-png') + tests.add('failures/unexpected/crash.html', crash=True) + tests.add('failures/unexpected/text-image-checksum.html', + actual_text='text-image-checksum_fail-txt', + actual_checksum='text-image-checksum_fail-checksum') + tests.add('failures/unexpected/timeout.html', timeout=True) + tests.add('http/tests/passes/text.html') + tests.add('http/tests/ssl/text.html') + tests.add('passes/error.html', error='stuff going to stderr') + tests.add('passes/image.html') + tests.add('passes/platform_image.html') + # Text output files contain "\r\n" on Windows. This may be + # helpfully filtered to "\r\r\n" by our Python/Cygwin tooling. + tests.add('passes/text.html', + expected_text='\nfoo\n\n', + actual_text='\nfoo\r\n\r\r\n') + tests.add('websocket/tests/passes/text.html') + return tests + + +# Here we use a non-standard location for the layout tests, to ensure that +# this works. The path contains a '.' in the name because we've seen bugs +# related to this before. + +LAYOUT_TEST_DIR = '/test.checkout/LayoutTests' + + +# Here we synthesize an in-memory filesystem from the test list +# in order to fully control the test output and to demonstrate that +# we don't need a real filesystem to run the tests. + +def unit_test_filesystem(files=None): + """Return the FileSystem object used by the unit tests.""" + test_list = unit_test_list() + files = files or {} + + def add_file(files, test, suffix, contents): + dirname = test.name[0:test.name.rfind('/')] + base = test.base + path = LAYOUT_TEST_DIR + '/' + dirname + '/' + base + suffix + files[path] = contents + + # Add each test and the expected output, if any. + for test in test_list.tests.values(): + add_file(files, test, '.html', '') + add_file(files, test, '-expected.txt', test.expected_text) + add_file(files, test, '-expected.checksum', test.expected_checksum) + add_file(files, test, '-expected.png', test.expected_image) + + # Add the test_expectations file. + files[LAYOUT_TEST_DIR + '/platform/test/test_expectations.txt'] = """ +WONTFIX : failures/expected/checksum.html = IMAGE +WONTFIX : failures/expected/crash.html = CRASH +// This one actually passes because the checksums will match. +WONTFIX : failures/expected/image.html = PASS +WONTFIX : failures/expected/image_checksum.html = IMAGE +WONTFIX : failures/expected/missing_check.html = MISSING PASS +WONTFIX : failures/expected/missing_image.html = MISSING PASS +WONTFIX : failures/expected/missing_text.html = MISSING PASS +WONTFIX : failures/expected/newlines_leading.html = TEXT +WONTFIX : failures/expected/newlines_trailing.html = TEXT +WONTFIX : failures/expected/newlines_with_excess_CR.html = TEXT +WONTFIX : failures/expected/text.html = TEXT +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 +""" + + fs = filesystem_mock.MockFileSystem(files) + fs._tests = test_list + return fs + + class TestPort(base.Port): """Test implementation of the Port interface.""" def __init__(self, **kwargs): + # FIXME: what happens if we're not passed in the test filesystem + # and the tests don't match what's in the filesystem? + # + # We'll leave as is for now to avoid unnecessary dependencies while + # converting all of the unit tests over to using + # unit_test_filesystem(). If things get out of sync the tests should + # fail in fairly obvious ways. Eventually we want to just do: + # + # assert kwargs['filesystem']._tests + # self._tests = kwargs['filesystem']._tests + + if 'filesystem' not in kwargs or kwargs['filesystem'] is None: + kwargs['filesystem'] = unit_test_filesystem() + self._tests = kwargs['filesystem']._tests + else: + self._tests = unit_test_list() + + kwargs.setdefault('port_name', 'test') base.Port.__init__(self, **kwargs) - tests = TestList(self) - tests.add('failures/expected/checksum.html', - actual_checksum='checksum_fail-checksum') - tests.add('failures/expected/crash.html', crash=True) - tests.add('failures/expected/exception.html', exception=True) - tests.add('failures/expected/timeout.html', timeout=True) - tests.add('failures/expected/hang.html', hang=True) - tests.add('failures/expected/missing_text.html', - expected_text=None) - tests.add('failures/expected/image.html', - actual_image='image_fail-png', - expected_image='image-png') - tests.add('failures/expected/image_checksum.html', - actual_checksum='image_checksum_fail-checksum', - actual_image='image_checksum_fail-png') - tests.add('failures/expected/keyboard.html', - keyboard=True) - tests.add('failures/expected/missing_check.html', - expected_checksum=None) - tests.add('failures/expected/missing_image.html', - expected_image=None) - tests.add('failures/expected/missing_text.html', - expected_text=None) - tests.add('failures/expected/newlines_leading.html', - expected_text="\nfoo\n", - actual_text="foo\n") - tests.add('failures/expected/newlines_trailing.html', - expected_text="foo\n\n", - actual_text="foo\n") - tests.add('failures/expected/newlines_with_excess_CR.html', - expected_text="foo\r\r\r\n", - actual_text="foo\n") - tests.add('failures/expected/text.html', - actual_text='text_fail-png') - tests.add('failures/unexpected/crash.html', crash=True) - tests.add('failures/unexpected/text-image-checksum.html', - actual_text='text-image-checksum_fail-txt', - actual_checksum='text-image-checksum_fail-checksum') - tests.add('failures/unexpected/timeout.html', timeout=True) - tests.add('http/tests/passes/text.html') - tests.add('http/tests/ssl/text.html') - tests.add('passes/error.html', error='stuff going to stderr') - tests.add('passes/image.html') - tests.add('passes/platform_image.html') - # Text output files contain "\r\n" on Windows. This may be - # helpfully filtered to "\r\r\n" by our Python/Cygwin tooling. - tests.add('passes/text.html', - expected_text='\nfoo\n\n', - actual_text='\nfoo\r\n\r\r\n') - tests.add('websocket/tests/passes/text.html') - self._tests = tests def baseline_path(self): - return os.path.join(self.layout_tests_dir(), 'platform', - self.name() + self.version()) + return self._filesystem.join(self.layout_tests_dir(), 'platform', + self.name() + self.version()) def baseline_search_path(self): return [self.baseline_path()] @@ -155,92 +227,12 @@ class TestPort(base.Port): diff_filename=None): diffed = actual_contents != expected_contents if diffed and diff_filename: - with codecs.open(diff_filename, "w", "utf-8") as diff_fh: - diff_fh.write("< %s\n---\n> %s\n" % - (expected_contents, actual_contents)) + self._filesystem.write_text_file(diff_filename, + "< %s\n---\n> %s\n" % (expected_contents, actual_contents)) return diffed - def expected_checksum(self, test): - test = self.relative_test_filename(test) - return self._tests[test].expected_checksum - - def expected_image(self, test): - test = self.relative_test_filename(test) - return self._tests[test].expected_image - - def expected_text(self, test): - test = self.relative_test_filename(test) - text = self._tests[test].expected_text - if not text: - text = '' - return text - - def tests(self, paths): - # Test the idea of port-specific overrides for test lists. Also - # keep in memory to speed up the test harness. - if not paths: - paths = ['*'] - - matched_tests = [] - for p in paths: - if self.path_isdir(p): - matched_tests.extend(fnmatch.filter(self._tests.keys(), p + '*')) - else: - matched_tests.extend(fnmatch.filter(self._tests.keys(), p)) - layout_tests_dir = self.layout_tests_dir() - return set([os.path.join(layout_tests_dir, p) for p in matched_tests]) - - def path_exists(self, path): - # used by test_expectations.py and printing.py - rpath = self.relative_test_filename(path) - if rpath in self._tests: - return True - if self.path_isdir(rpath): - return True - if rpath.endswith('-expected.txt'): - test = rpath.replace('-expected.txt', '.html') - return (test in self._tests and - self._tests[test].expected_text) - if rpath.endswith('-expected.checksum'): - test = rpath.replace('-expected.checksum', '.html') - return (test in self._tests and - self._tests[test].expected_checksum) - if rpath.endswith('-expected.png'): - test = rpath.replace('-expected.png', '.html') - return (test in self._tests and - self._tests[test].expected_image) - return False - def layout_tests_dir(self): - return self.path_from_webkit_base('Tools', 'Scripts', - 'webkitpy', 'layout_tests', 'data') - - def path_isdir(self, path): - # Used by test_expectations.py - # - # We assume that a path is a directory if we have any tests - # that whose prefix matches the path plus a directory modifier - # and not a file extension. - if path[-1] != '/': - path += '/' - - # FIXME: Directories can have a dot in the name. We should - # probably maintain a white list of known cases like CSS2.1 - # and check it here in the future. - if path.find('.') != -1: - # extension separator found, assume this is a file - return False - - # strip out layout tests directory path if found. The tests - # keys are relative to it. - tests_dir = self.layout_tests_dir() - if path.startswith(tests_dir): - path = path[len(tests_dir) + 1:] - - return any([t.startswith(path) for t in self._tests.keys()]) - - def test_dirs(self): - return ['passes', 'failures'] + return LAYOUT_TEST_DIR def name(self): return self._name @@ -269,33 +261,12 @@ class TestPort(base.Port): def stop_websocket_server(self): pass - 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 """ -WONTFIX : failures/expected/checksum.html = IMAGE -WONTFIX : failures/expected/crash.html = CRASH -// This one actually passes because the checksums will match. -WONTFIX : failures/expected/image.html = PASS -WONTFIX : failures/expected/image_checksum.html = IMAGE -WONTFIX : failures/expected/missing_check.html = MISSING PASS -WONTFIX : failures/expected/missing_image.html = MISSING PASS -WONTFIX : failures/expected/missing_text.html = MISSING PASS -WONTFIX : failures/expected/newlines_leading.html = TEXT -WONTFIX : failures/expected/newlines_trailing.html = TEXT -WONTFIX : failures/expected/newlines_with_excess_CR.html = TEXT -WONTFIX : failures/expected/text.html = TEXT -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 -""" - def test_base_platform_names(self): return ('mac', 'win') + def test_expectations(self): + return self._filesystem.read_text_file(LAYOUT_TEST_DIR + '/platform/test/test_expectations.txt') + def test_platform_name(self): return 'mac' diff --git a/Tools/Scripts/webkitpy/layout_tests/port/test_files.py b/Tools/Scripts/webkitpy/layout_tests/port/test_files.py index 2c0a7b6..41d918f 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/test_files.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/test_files.py @@ -34,8 +34,6 @@ list of test files is constrained to those found under the paths passed in, i.e. calling find(["LayoutTests/fast"]) will only return files under that directory.""" -import glob -import os import time from webkitpy.common.system import logutils @@ -58,16 +56,18 @@ def find(port, paths): paths: a list of command line paths relative to the layout_tests_dir() to limit the search to. glob patterns are ok. """ + fs = port._filesystem gather_start_time = time.time() paths_to_walk = set() + # if paths is empty, provide a pre-defined list. if paths: _log.debug("Gathering tests from: %s relative to %s" % (paths, port.layout_tests_dir())) for path in paths: # If there's an * in the name, assume it's a glob pattern. - path = os.path.join(port.layout_tests_dir(), path) + path = fs.join(port.layout_tests_dir(), path) if path.find('*') > -1: - filenames = glob.glob(path) + filenames = fs.glob(path) paths_to_walk.update(filenames) else: paths_to_walk.add(path) @@ -75,30 +75,12 @@ def find(port, paths): _log.debug("Gathering tests from: %s" % port.layout_tests_dir()) paths_to_walk.add(port.layout_tests_dir()) - # Now walk all the paths passed in on the command line and get filenames + # FIXME: I'm not sure there's much point in this being a set. A list would + # probably be faster. test_files = set() for path in paths_to_walk: - if os.path.isfile(path) and _is_test_file(path): - test_files.add(os.path.normpath(path)) - continue - - for root, dirs, files in os.walk(path): - # Don't walk skipped directories or their sub-directories. - if os.path.basename(root) in _skipped_directories: - del dirs[:] - continue - # This copy and for-in is slightly inefficient, but - # the extra walk avoidance consistently shaves .5 seconds - # off of total walk() time on my MacBook Pro. - for directory in dirs[:]: - if directory in _skipped_directories: - dirs.remove(directory) - - for filename in files: - if _is_test_file(filename): - filename = os.path.join(root, filename) - filename = os.path.normpath(filename) - test_files.add(filename) + files = fs.files_under(path, _skipped_directories, _is_test_file) + test_files.update(set(files)) gather_time = time.time() - gather_start_time _log.debug("Test gathering took %f seconds" % gather_time) @@ -106,10 +88,10 @@ def find(port, paths): return test_files -def _has_supported_extension(filename): +def _has_supported_extension(fs, filename): """Return true if filename is one of the file extensions we want to run a test on.""" - extension = os.path.splitext(filename)[1] + extension = fs.splitext(filename)[1] return extension in _supported_file_extensions @@ -122,7 +104,7 @@ def _is_reference_html_file(filename): return False -def _is_test_file(filename): +def _is_test_file(fs, dirname, filename): """Return true if the filename points to a test file.""" - return (_has_supported_extension(filename) and + return (_has_supported_extension(fs, filename) and not _is_reference_html_file(filename)) diff --git a/Tools/Scripts/webkitpy/layout_tests/port/test_files_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/test_files_unittest.py index 83525c8..a68950a 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/test_files_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/test_files_unittest.py @@ -26,7 +26,6 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import os import unittest import base @@ -37,8 +36,8 @@ class TestFilesTest(unittest.TestCase): def test_find_no_paths_specified(self): port = base.Port() layout_tests_dir = port.layout_tests_dir() - port.layout_tests_dir = lambda: os.path.join(layout_tests_dir, - 'fast', 'html') + port.layout_tests_dir = lambda: port._filesystem.join(layout_tests_dir, + 'fast', 'html') tests = test_files.find(port, []) self.assertNotEqual(tests, 0) @@ -64,11 +63,13 @@ class TestFilesTest(unittest.TestCase): self.assertEqual(tests, set([])) def test_is_test_file(self): - self.assertTrue(test_files._is_test_file('foo.html')) - self.assertTrue(test_files._is_test_file('foo.shtml')) - self.assertFalse(test_files._is_test_file('foo.png')) - self.assertFalse(test_files._is_test_file('foo-expected.html')) - self.assertFalse(test_files._is_test_file('foo-expected-mismatch.html')) + port = base.Port() + fs = port._filesystem + self.assertTrue(test_files._is_test_file(fs, '', 'foo.html')) + self.assertTrue(test_files._is_test_file(fs, '', 'foo.shtml')) + self.assertFalse(test_files._is_test_file(fs, '', 'foo.png')) + self.assertFalse(test_files._is_test_file(fs, '', 'foo-expected.html')) + self.assertFalse(test_files._is_test_file(fs, '', 'foo-expected-mismatch.html')) if __name__ == '__main__': diff --git a/Tools/Scripts/webkitpy/layout_tests/port/webkit.py b/Tools/Scripts/webkitpy/layout_tests/port/webkit.py index afdebeb..577acd4 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/webkit.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/webkit.py @@ -31,20 +31,14 @@ """WebKit implementations of the Port interface.""" -from __future__ import with_statement - -import codecs import logging +import operator import os import re -import shutil import signal import sys import time import webbrowser -import operator -import tempfile -import shutil import webkitpy.common.system.ospath as ospath import webkitpy.layout_tests.layout_package.test_output as test_output @@ -72,8 +66,8 @@ class WebKitPort(base.Port): return [self._webkit_baseline_path(self._name)] def path_to_test_expectations_file(self): - return os.path.join(self._webkit_baseline_path(self._name), - 'test_expectations.txt') + return self._filesystem.join(self._webkit_baseline_path(self._name), + 'test_expectations.txt') # Only needed by ports which maintain versioned test expectations (like mac-tiger vs. mac-leopard) def version(self): @@ -85,7 +79,7 @@ class WebKitPort(base.Port): def _check_driver(self): driver_path = self._path_to_driver() - if not os.path.exists(driver_path): + if not self._filesystem.exists(driver_path): _log.error("DumpRenderTree was not found at %s" % driver_path) return False return True @@ -108,7 +102,7 @@ class WebKitPort(base.Port): def check_image_diff(self, override_step=None, logging=True): image_diff_path = self._path_to_image_diff() - if not os.path.exists(image_diff_path): + if not self._filesystem.exists(image_diff_path): _log.error("ImageDiff was not found at %s" % image_diff_path) return False return True @@ -165,8 +159,7 @@ class WebKitPort(base.Port): if m.group(2) == 'passed': result = False elif output and diff_filename: - with open(diff_filename, 'w') as file: - file.write(output) + self._filesystem.write_text_file(diff_filename, output) elif sp.timed_out: _log.error("ImageDiff timed out") elif sp.crashed: @@ -300,25 +293,22 @@ class WebKitPort(base.Port): return tests_to_skip def _skipped_file_paths(self): - return [os.path.join(self._webkit_baseline_path(self._name), - 'Skipped')] + return [self._filesystem.join(self._webkit_baseline_path(self._name), 'Skipped')] def _expectations_from_skipped_files(self): tests_to_skip = [] for filename in self._skipped_file_paths(): - if not os.path.exists(filename): + if not self._filesystem.exists(filename): _log.warn("Failed to open Skipped file: %s" % filename) continue - with codecs.open(filename, "r", "utf-8") as skipped_file: - tests_to_skip.extend(self._tests_from_skipped_file(skipped_file)) + skipped_file = self._filesystem.read_text_file(filename) return tests_to_skip def test_expectations(self): # The WebKit mac port uses a combination of a test_expectations file # and 'Skipped' files. expectations_path = self.path_to_test_expectations_file() - with codecs.open(expectations_path, "r", "utf-8") as file: - return file.read() + self._skips() + return self._filesystem.read_text_file(expectations_path) + self._skips() def _skips(self): # Each Skipped file contains a list of files @@ -373,7 +363,7 @@ class WebKitPort(base.Port): # The Apache binary path can vary depending on OS and distribution # See http://wiki.apache.org/httpd/DistrosDefaultLayout for path in ["/usr/sbin/httpd", "/usr/sbin/apache2"]: - if os.path.exists(path): + if self._filesystem.exists(path): self._cached_apache_path = path break @@ -389,10 +379,10 @@ class WebKitDriver(base.Driver): def __init__(self, port, worker_number): self._worker_number = worker_number self._port = port - self._driver_tempdir = tempfile.mkdtemp(prefix='DumpRenderTree-') + self._driver_tempdir = port._filesystem.mkdtemp(prefix='DumpRenderTree-') def __del__(self): - shutil.rmtree(self._driver_tempdir) + self._port._filesystem.rmtree(str(self._driver_tempdir)) def cmd_line(self): cmd = self._command_wrapper(self._port.get_option('wrapper')) @@ -406,7 +396,7 @@ class WebKitDriver(base.Driver): def start(self): environment = self._port.setup_environ_for_server() environment['DYLD_FRAMEWORK_PATH'] = self._port._build_path() - environment['DUMPRENDERTREE_TEMP'] = self._driver_tempdir + environment['DUMPRENDERTREE_TEMP'] = str(self._driver_tempdir) self._server_process = server_process.ServerProcess(self._port, "DumpRenderTree", self.cmd_line(), environment) diff --git a/Tools/Scripts/webkitpy/layout_tests/port/win.py b/Tools/Scripts/webkitpy/layout_tests/port/win.py index 9e30155..e7d2004 100644 --- a/Tools/Scripts/webkitpy/layout_tests/port/win.py +++ b/Tools/Scripts/webkitpy/layout_tests/port/win.py @@ -29,7 +29,6 @@ """WebKit Win implementation of the Port interface.""" import logging -import os from webkitpy.layout_tests.port.webkit import WebKitPort @@ -60,8 +59,8 @@ class WinPort(WebKitPort): ] def _path_to_apache_config_file(self): - return os.path.join(self.layout_tests_dir(), 'http', 'conf', - 'cygwin-httpd.conf') + return self._filesystem.join(self.layout_tests_dir(), 'http', 'conf', + 'cygwin-httpd.conf') def _shut_down_http_server(self, server_pid): """Shut down the httpd web server. Blocks until it's fully diff --git a/Tools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py b/Tools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py index 4d8b7c9..c852186 100644 --- a/Tools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py +++ b/Tools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py @@ -41,26 +41,18 @@ The script does the following for each platform specified: At the end, the script generates a html that compares old and new baselines. """ -from __future__ import with_statement - -import codecs import copy import logging import optparse -import os import re -import shutil -import subprocess import sys -import tempfile import time import urllib import zipfile +from webkitpy.common.checkout import scm from webkitpy.common.system import path -from webkitpy.common.system import user -from webkitpy.common.system.executive import Executive, ScriptError -import webkitpy.common.checkout.scm as scm +from webkitpy.common.system.executive import ScriptError import port from layout_package import test_expectations @@ -70,16 +62,16 @@ _log = logging.getLogger("webkitpy.layout_tests." BASELINE_SUFFIXES = ['.txt', '.png', '.checksum'] REBASELINE_PLATFORM_ORDER = ['mac', 'win', 'win-xp', 'win-vista', 'linux'] -ARCHIVE_DIR_NAME_DICT = {'win': 'Webkit_Win', +ARCHIVE_DIR_NAME_DICT = {'win': 'Webkit_Win__deps_', 'win-vista': 'webkit-dbg-vista', - 'win-xp': 'Webkit_Win', - 'mac': 'Webkit_Mac10_5', - 'linux': 'webkit-rel-linux64', - 'win-canary': 'webkit-rel-webkit-org', + 'win-xp': 'Webkit_Win__deps_', + 'mac': 'Webkit_Mac10_5__deps_', + 'linux': 'Webkit_Linux__deps_', + 'win-canary': 'Webkit_Win', 'win-vista-canary': 'webkit-dbg-vista', - 'win-xp-canary': 'webkit-rel-webkit-org', - 'mac-canary': 'webkit-rel-mac-webkit-org', - 'linux-canary': 'webkit-rel-linux-webkit-org'} + 'win-xp-canary': 'Webkit_Win', + 'mac-canary': 'Webkit_Mac10_5', + 'linux-canary': 'Webkit_Linux'} def log_dashed_string(text, platform, logging_level=logging.INFO): @@ -100,41 +92,35 @@ def log_dashed_string(text, platform, logging_level=logging.INFO): _log.info(msg) -def setup_html_directory(html_directory): +def setup_html_directory(filesystem, parent_directory): """Setup the directory to store html results. - All html related files are stored in the "rebaseline_html" subdirectory. - - Args: - html_directory: parent directory that stores the rebaselining results. - If None, a temp directory is created. - - Returns: - the directory that stores the html related rebaselining results. + All html related files are stored in the "rebaseline_html" subdirectory of + the parent directory. The path to the created directory is returned. """ - if not html_directory: - html_directory = tempfile.mkdtemp() - elif not os.path.exists(html_directory): - os.mkdir(html_directory) + if not parent_directory: + parent_directory = str(filesystem.mkdtemp()) + else: + filesystem.maybe_make_directory(parent_directory) - html_directory = os.path.join(html_directory, 'rebaseline_html') + html_directory = filesystem.join(parent_directory, 'rebaseline_html') _log.info('Html directory: "%s"', html_directory) - if os.path.exists(html_directory): - shutil.rmtree(html_directory, True) - _log.info('Deleted file at html directory: "%s"', html_directory) + if filesystem.exists(html_directory): + filesystem.rmtree(html_directory) + _log.info('Deleted html directory: "%s"', html_directory) - if not os.path.exists(html_directory): - os.mkdir(html_directory) + filesystem.maybe_make_directory(html_directory) return html_directory -def get_result_file_fullpath(html_directory, baseline_filename, platform, +def get_result_file_fullpath(filesystem, html_directory, baseline_filename, platform, result_type): """Get full path of the baseline result file. Args: + filesystem: wrapper object html_directory: directory that stores the html related files. baseline_filename: name of the baseline file. platform: win, linux or mac @@ -144,9 +130,9 @@ def get_result_file_fullpath(html_directory, baseline_filename, platform, Full path of the baseline file for rebaselining result comparison. """ - base, ext = os.path.splitext(baseline_filename) + base, ext = filesystem.splitext(baseline_filename) result_filename = '%s-%s-%s%s' % (base, platform, result_type, ext) - fullpath = os.path.join(html_directory, result_filename) + fullpath = filesystem.join(html_directory, result_filename) _log.debug(' Result file full path: "%s".', fullpath) return fullpath @@ -168,6 +154,7 @@ class Rebaseliner(object): self._platform = platform self._options = options self._port = running_port + self._filesystem = running_port._filesystem self._target_port = target_port self._rebaseline_port = port.get( self._target_port.test_platform_name_to_name(platform), options) @@ -370,7 +357,7 @@ class Rebaseliner(object): found = False scm_error = False - test_basename = os.path.splitext(test)[0] + test_basename = self._filesystem.splitext(test)[0] for suffix in BASELINE_SUFFIXES: archive_test_name = ('layout-test-results/%s-actual%s' % (test_basename, suffix)) @@ -385,15 +372,14 @@ class Rebaseliner(object): # Extract new baseline from archive and save it to a temp file. data = zip_file.read(archive_test_name) - temp_fd, temp_name = tempfile.mkstemp(suffix) - f = os.fdopen(temp_fd, 'wb') - f.write(data) - f.close() + tempfile, temp_name = self._filesystem.open_binary_tempfile(suffix) + tempfile.write(data) + tempfile.close() expected_filename = '%s-expected%s' % (test_basename, suffix) - expected_fullpath = os.path.join( + expected_fullpath = self._filesystem.join( self._rebaseline_port.baseline_path(), expected_filename) - expected_fullpath = os.path.normpath(expected_fullpath) + expected_fullpath = self._filesystem.normpath(expected_fullpath) _log.debug(' Expected file full path: "%s"', expected_fullpath) @@ -407,16 +393,13 @@ class Rebaseliner(object): test, suffix, self._platform): - os.remove(temp_name) + self._filesystem.remove(temp_name) self._delete_baseline(expected_fullpath) continue - # Create the new baseline directory if it doesn't already - # exist. - self._port.maybe_make_directory( - os.path.dirname(expected_fullpath)) + self._filesystem.maybe_make_directory(self._filesystem.dirname(expected_fullpath)) - shutil.move(temp_name, expected_fullpath) + self._filesystem.move(temp_name, expected_fullpath) if 0 != self._scm.add(expected_fullpath, return_exit_code=True): # FIXME: print detailed diagnose messages @@ -436,7 +419,7 @@ class Rebaseliner(object): test_no += 1 zip_file.close() - os.remove(archive_file) + self._filesystem.remove(archive_file) return self._rebaselined_tests @@ -458,21 +441,17 @@ class Rebaseliner(object): True if the baseline is unnecessary. False otherwise. """ - test_filepath = os.path.join(self._target_port.layout_tests_dir(), + test_filepath = self._filesystem.join(self._target_port.layout_tests_dir(), test) all_baselines = self._rebaseline_port.expected_baselines( test_filepath, suffix, True) for (fallback_dir, fallback_file) in all_baselines: if fallback_dir and fallback_file: - fallback_fullpath = os.path.normpath( - os.path.join(fallback_dir, fallback_file)) + fallback_fullpath = self._filesystem.normpath( + self._filesystem.join(fallback_dir, fallback_file)) if fallback_fullpath.lower() != baseline_path.lower(): - with codecs.open(new_baseline, "r", - None) as file_handle1: - new_output = file_handle1.read() - with codecs.open(fallback_fullpath, "r", - None) as file_handle2: - fallback_output = file_handle2.read() + new_output = self._filesystem.read_binary_file(new_baseline) + fallback_output = self._filesystem.read_binary_file(fallback_fullpath) is_image = baseline_path.lower().endswith('.png') if not self._diff_baselines(new_output, fallback_output, is_image): @@ -507,7 +486,7 @@ class Rebaseliner(object): filename: full path of the file to delete. """ - if not filename or not os.path.isfile(filename): + if not filename or not self._filesystem.isfile(filename): return self._scm.delete(filename) @@ -530,14 +509,12 @@ class Rebaseliner(object): date_suffix = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time())) backup_file = ('%s.orig.%s' % (path, date_suffix)) - if os.path.exists(backup_file): - os.remove(backup_file) + if self._filesystem.exists(backup_file): + self._filesystem.remove(backup_file) _log.info('Saving original file to "%s"', backup_file) - os.rename(path, backup_file) - # FIXME: What encoding are these files? - # Or is new_expectations always a byte array? - with open(path, "w") as file: - file.write(new_expectations) + self._filesystem.move(path, backup_file) + + self._filesystem.write_text_file(path, new_expectations) # self._scm.add(path) else: _log.info('No test was rebaselined so nothing to remove.') @@ -551,15 +528,15 @@ class Rebaseliner(object): baseline_fullpath: full path of the expected baseline file. """ - if not baseline_fullpath or not os.path.exists(baseline_fullpath): + if not baseline_fullpath or not self._filesystem.exists(baseline_fullpath): return # Copy the new baseline to html directory for result comparison. - baseline_filename = os.path.basename(baseline_fullpath) - new_file = get_result_file_fullpath(self._options.html_directory, + baseline_filename = self._filesystem.basename(baseline_fullpath) + new_file = get_result_file_fullpath(self._filesystem, self._options.html_directory, baseline_filename, self._platform, 'new') - shutil.copyfile(baseline_fullpath, new_file) + self._filesystem.copyfile(baseline_fullpath, new_file) _log.info(' Html: copied new baseline file from "%s" to "%s".', baseline_fullpath, new_file) @@ -574,12 +551,13 @@ class Rebaseliner(object): 'NO SUCH FILE OR DIRECTORY')): _log.info(' No base file: "%s"', baseline_fullpath) return - base_file = get_result_file_fullpath(self._options.html_directory, + base_file = get_result_file_fullpath(self._filesystem, self._options.html_directory, baseline_filename, self._platform, 'old') - # We should be using an explicit encoding here. - with open(base_file, "wb") as file: - file.write(output) + if base_file.upper().endswith('.PNG'): + self._filesystem.write_binary_file(base_file, output) + else: + self._filesystem.write_text_file(base_file, output) _log.info(' Html: created old baseline file: "%s".', base_file) @@ -587,11 +565,10 @@ class Rebaseliner(object): if baseline_filename.upper().endswith('.TXT'): output = self._scm.diff_for_file(baseline_fullpath, log=_log) if output: - diff_file = get_result_file_fullpath( + diff_file = get_result_file_fullpath(self._filesystem, self._options.html_directory, baseline_filename, self._platform, 'diff') - with open(diff_file, 'wb') as file: - file.write(output) + self._filesystem.write_text_file(diff_file, output) _log.info(' Html: created baseline diff file: "%s".', diff_file) @@ -642,19 +619,19 @@ class HtmlGenerator(object): '<img style="width: 200" src="%(uri)s" /></a></td>') HTML_TR = '<tr>%s</tr>' - def __init__(self, target_port, options, platforms, rebaselining_tests, - executive): + def __init__(self, port, target_port, options, platforms, rebaselining_tests): self._html_directory = options.html_directory + self._port = port self._target_port = target_port self._platforms = platforms self._rebaselining_tests = rebaselining_tests - self._executive = executive - self._html_file = os.path.join(options.html_directory, - 'rebaseline.html') + self._filesystem = port._filesystem + self._html_file = self._filesystem.join(options.html_directory, + 'rebaseline.html') def abspath_to_uri(self, filename): """Converts an absolute path to a file: URI.""" - return path.abspath_to_uri(filename, self._executive) + return path.abspath_to_uri(filename, self._port._executive) def generate_html(self): """Generate html file for rebaselining result comparison.""" @@ -677,9 +654,7 @@ class HtmlGenerator(object): 'body': html_body}) _log.debug(html) - with codecs.open(self._html_file, "w", "utf-8") as file: - file.write(html) - + self._filesystem.write_text_file(self._html_file, html) _log.info('Baseline comparison html generated at "%s"', self._html_file) @@ -687,7 +662,7 @@ class HtmlGenerator(object): """Launch the rebaselining html in brwoser.""" _log.info('Launching html: "%s"', self._html_file) - user.User().open_url(self._html_file) + self._port._user.open_url(self._html_file) _log.info('Html launched.') def _generate_baseline_links(self, test_basename, suffix, platform): @@ -705,14 +680,14 @@ class HtmlGenerator(object): baseline_filename = '%s-expected%s' % (test_basename, suffix) _log.debug(' baseline filename: "%s"', baseline_filename) - new_file = get_result_file_fullpath(self._html_directory, + new_file = get_result_file_fullpath(self._filesystem, self._html_directory, baseline_filename, platform, 'new') _log.info(' New baseline file: "%s"', new_file) - if not os.path.exists(new_file): + if not self._filesystem.exists(new_file): _log.info(' No new baseline file: "%s"', new_file) return '' - old_file = get_result_file_fullpath(self._html_directory, + old_file = get_result_file_fullpath(self._filesystem, self._html_directory, baseline_filename, platform, 'old') _log.info(' Old baseline file: "%s"', old_file) if suffix == '.png': @@ -721,7 +696,7 @@ class HtmlGenerator(object): html_td_link = self.HTML_TD_LINK links = '' - if os.path.exists(old_file): + if self._filesystem.exists(old_file): links += html_td_link % { 'uri': self.abspath_to_uri(old_file), 'name': baseline_filename} @@ -732,11 +707,11 @@ class HtmlGenerator(object): links += html_td_link % {'uri': self.abspath_to_uri(new_file), 'name': baseline_filename} - diff_file = get_result_file_fullpath(self._html_directory, + diff_file = get_result_file_fullpath(self._filesystem, self._html_directory, baseline_filename, platform, 'diff') _log.info(' Baseline diff file: "%s"', diff_file) - if os.path.exists(diff_file): + if self._filesystem.exists(diff_file): links += html_td_link % {'uri': self.abspath_to_uri(diff_file), 'name': 'Diff'} else: @@ -755,7 +730,7 @@ class HtmlGenerator(object): html that compares baseline results for the test. """ - test_basename = os.path.basename(os.path.splitext(test)[0]) + test_basename = self._filesystem.basename(self._filesystem.splitext(test)[0]) _log.info(' basename: "%s"', test_basename) rows = [] for suffix in BASELINE_SUFFIXES: @@ -776,8 +751,7 @@ class HtmlGenerator(object): rows.append(self.HTML_TR % row) if rows: - test_path = os.path.join(self._target_port.layout_tests_dir(), - test) + test_path = self._filesystem.join(self._target_port.layout_tests_dir(), test) html = self.HTML_TR_TEST % (self.abspath_to_uri(test_path), test) html += self.HTML_TEST_DETAIL % ' '.join(rows) @@ -883,7 +857,7 @@ def parse_options(args): return (options, target_options) -def main(executive=Executive()): +def main(): """Main function to produce new baselines.""" (options, target_options) = parse_options(sys.argv[1:]) @@ -929,7 +903,7 @@ def main(executive=Executive()): if platform in platforms: rebaseline_platforms.append(platform) - options.html_directory = setup_html_directory(options.html_directory) + options.html_directory = setup_html_directory(host_port_obj._filesystem, options.html_directory) rebaselining_tests = set() backup = options.backup @@ -950,11 +924,11 @@ def main(executive=Executive()): _log.info('') log_dashed_string('Rebaselining result comparison started', None) - html_generator = HtmlGenerator(target_port_obj, + html_generator = HtmlGenerator(host_port_obj, + target_port_obj, options, rebaseline_platforms, - rebaselining_tests, - executive=executive) + rebaselining_tests) html_generator.generate_html() if not options.quiet: html_generator.show_html() diff --git a/Tools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py b/Tools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py index 7c55b94..50c0204 100644 --- a/Tools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py @@ -29,15 +29,15 @@ """Unit tests for rebaseline_chromium_webkit_tests.py.""" -import os -import sys import unittest from webkitpy.tool import mocktool -from webkitpy.layout_tests import port -from webkitpy.layout_tests import rebaseline_chromium_webkit_tests +from webkitpy.common.system import filesystem_mock from webkitpy.common.system.executive import Executive, ScriptError +import port +import rebaseline_chromium_webkit_tests + class MockPort(object): def __init__(self, image_diff_exists): @@ -88,9 +88,10 @@ class TestRebaseliner(unittest.TestCase): def make_rebaseliner(self): options = mocktool.MockOptions(configuration=None, html_directory=None) - host_port_obj = port.get('test', options) + filesystem = filesystem_mock.MockFileSystem() + host_port_obj = port.get('test', options, filesystem=filesystem) target_options = options - target_port_obj = port.get('test', target_options) + target_port_obj = port.get('test', target_options, filesystem=filesystem) platform = 'test' return rebaseline_chromium_webkit_tests.Rebaseliner( host_port_obj, target_port_obj, platform, options) @@ -113,44 +114,43 @@ class TestRebaseliner(unittest.TestCase): def test_diff_baselines_txt(self): rebaseliner = self.make_rebaseliner() output = rebaseliner._port.expected_text( - os.path.join(rebaseliner._port.layout_tests_dir(), - 'passes/text.html')) + rebaseliner._port._filesystem.join(rebaseliner._port.layout_tests_dir(), + 'passes/text.html')) self.assertFalse(rebaseliner._diff_baselines(output, output, is_image=False)) def test_diff_baselines_png(self): rebaseliner = self.make_rebaseliner() image = rebaseliner._port.expected_image( - os.path.join(rebaseliner._port.layout_tests_dir(), - 'passes/image.html')) + rebaseliner._port._filesystem.join(rebaseliner._port.layout_tests_dir(), + 'passes/image.html')) self.assertFalse(rebaseliner._diff_baselines(image, image, is_image=True)) class TestHtmlGenerator(unittest.TestCase): - def make_generator(self, tests): - return rebaseline_chromium_webkit_tests.HtmlGenerator( + def make_generator(self, files, tests): + options = mocktool.MockOptions(configuration=None, html_directory='/tmp') + host_port = port.get('test', options, filesystem=filesystem_mock.MockFileSystem(files)) + generator = rebaseline_chromium_webkit_tests.HtmlGenerator( + host_port, target_port=None, - options=mocktool.MockOptions(configuration=None, - html_directory='/tmp'), + options=options, platforms=['mac'], - rebaselining_tests=tests, - executive=Executive()) + rebaselining_tests=tests) + return generator, host_port def test_generate_baseline_links(self): - orig_platform = sys.platform - orig_exists = os.path.exists - - try: - sys.platform = 'darwin' - os.path.exists = lambda x: True - generator = self.make_generator(["foo.txt"]) - links = generator._generate_baseline_links("foo", ".txt", "mac") - expected_links = '<td align=center><a href="file:///tmp/foo-expected-mac-old.txt">foo-expected.txt</a></td><td align=center><a href="file:///tmp/foo-expected-mac-new.txt">foo-expected.txt</a></td><td align=center><a href="file:///tmp/foo-expected-mac-diff.txt">Diff</a></td>' - self.assertEqual(links, expected_links) - finally: - sys.platform = orig_platform - os.path.exists = orig_exists + files = { + "/tmp/foo-expected-mac-old.txt": "", + "/tmp/foo-expected-mac-new.txt": "", + "/tmp/foo-expected-mac-diff.txt": "", + } + tests = ["foo.txt"] + generator, host_port = self.make_generator(files, tests) + links = generator._generate_baseline_links("foo", ".txt", "mac") + expected_links = '<td align=center><a href="file:///tmp/foo-expected-mac-old.txt">foo-expected.txt</a></td><td align=center><a href="file:///tmp/foo-expected-mac-new.txt">foo-expected.txt</a></td><td align=center><a href="file:///tmp/foo-expected-mac-diff.txt">Diff</a></td>' + self.assertEqual(links, expected_links) if __name__ == '__main__': diff --git a/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py b/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py index a141661..17b6e89 100755 --- a/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py +++ b/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py @@ -32,7 +32,6 @@ from __future__ import with_statement -import codecs import errno import logging import optparse @@ -80,7 +79,7 @@ def run(port, options, args, regular_output=sys.stderr, printer.cleanup() return 0 - last_unexpected_results = _gather_unexpected_results(options) + last_unexpected_results = _gather_unexpected_results(port._filesystem, options) if options.print_last_failures: printer.write("\n".join(last_unexpected_results) + "\n") printer.cleanup() @@ -146,7 +145,7 @@ def _set_up_derived_options(port_obj, options): if not options.use_apache: options.use_apache = sys.platform in ('darwin', 'linux2') - if not os.path.isabs(options.results_directory): + if not port_obj._filesystem.isabs(options.results_directory): # This normalizes the path to the build dir. # FIXME: how this happens is not at all obvious; this is a dumb # interface and should be cleaned up. @@ -162,15 +161,16 @@ def _set_up_derived_options(port_obj, options): return warnings -def _gather_unexpected_results(options): +def _gather_unexpected_results(filesystem, options): """Returns the unexpected results from the previous run, if any.""" last_unexpected_results = [] if options.print_last_failures or options.retest_last_failures: - unexpected_results_filename = os.path.join( - options.results_directory, "unexpected_results.json") - with codecs.open(unexpected_results_filename, "r", "utf-8") as file: - results = simplejson.load(file) - last_unexpected_results = results['tests'].keys() + unexpected_results_filename = filesystem.join( + options.results_directory, "unexpected_results.json") + if filesystem.exists(unexpected_results_filename): + content = filesystem.read_text_file(unexpected_results_filename) + results = simplejson.loads(content) + last_unexpected_results = results['tests'].keys() return last_unexpected_results @@ -277,6 +277,8 @@ def parse_args(args=None): default="layout-test-results", help="Output results directory source dir, relative to Debug or " "Release"), + optparse.make_option("--build-directory", + help="Path to the directory under which build files are kept (should not include configuration)"), optparse.make_option("--new-baseline", action="store_true", default=False, help="Save all generated results as new baselines " "into the platform directory, overwriting whatever's " diff --git a/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py b/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py index 2bfac2f..677becd 100644 --- a/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py @@ -30,14 +30,13 @@ """Unit tests for run_webkit_tests.""" +from __future__ import with_statement + import codecs import itertools import logging -import os import Queue -import shutil import sys -import tempfile import thread import time import threading @@ -45,6 +44,7 @@ import unittest from webkitpy.common import array_stream from webkitpy.common.system import outputcapture +from webkitpy.common.system import filesystem_mock from webkitpy.common.system import user from webkitpy.layout_tests import port from webkitpy.layout_tests import run_webkit_tests @@ -88,25 +88,25 @@ def parse_args(extra_args=None, record_results=False, tests_included=False, def passing_run(extra_args=None, port_obj=None, record_results=False, - tests_included=False): + tests_included=False, filesystem=None): options, parsed_args = parse_args(extra_args, record_results, tests_included) if not port_obj: port_obj = port.get(port_name=options.platform, options=options, - user=MockUser()) + user=MockUser(), filesystem=filesystem) res = run_webkit_tests.run(port_obj, options, parsed_args) return res == 0 -def logging_run(extra_args=None, port_obj=None, tests_included=False): +def logging_run(extra_args=None, port_obj=None, record_results=False, tests_included=False, filesystem=None): options, parsed_args = parse_args(extra_args=extra_args, - record_results=False, + record_results=record_results, tests_included=tests_included, print_nothing=False) user = MockUser() if not port_obj: port_obj = port.get(port_name=options.platform, options=options, - user=user) + user=user, filesystem=filesystem) res, buildbot_output, regular_output = run_and_capture(port_obj, options, parsed_args) @@ -127,7 +127,7 @@ def run_and_capture(port_obj, options, parsed_args): return (res, buildbot_output, regular_output) -def get_tests_run(extra_args=None, tests_included=False, flatten_batches=False): +def get_tests_run(extra_args=None, tests_included=False, flatten_batches=False, filesystem=None): extra_args = extra_args or [] if not tests_included: # Not including http tests since they get run out of order (that @@ -163,7 +163,7 @@ def get_tests_run(extra_args=None, tests_included=False, flatten_batches=False): def create_driver(self, worker_number): return RecordingTestDriver(self, worker_number) - recording_port = RecordingTestPort(options=options, user=user) + recording_port = RecordingTestPort(options=options, user=user, filesystem=filesystem) run_and_capture(recording_port, options, parsed_args) if flatten_batches: @@ -239,9 +239,12 @@ class MainTest(unittest.TestCase): ['failures/expected/keyboard.html'], tests_included=True) def test_last_results(self): - passing_run(['--clobber-old-results'], record_results=True) + fs = port.unit_test_filesystem() + # We do a logging run here instead of a passing run in order to + # suppress the output from the json generator. + (res, buildbot_output, regular_output, user) = logging_run(['--clobber-old-results'], record_results=True, filesystem=fs) (res, buildbot_output, regular_output, user) = logging_run( - ['--print-last-failures']) + ['--print-last-failures'], filesystem=fs) self.assertEqual(regular_output.get(), ['\n\n']) self.assertEqual(buildbot_output.get(), []) @@ -315,19 +318,33 @@ class MainTest(unittest.TestCase): tests_run = get_tests_run(['passes/text.html'], tests_included=True, flatten_batches=True) self.assertEquals(['passes/text.html'], tests_run) + def test_single_file_with_prefix(self): + tests_run = get_tests_run(['LayoutTests/passes/text.html'], tests_included=True, flatten_batches=True) + self.assertEquals(['passes/text.html'], tests_run) + + def test_single_skipped_file(self): + tests_run = get_tests_run(['failures/expected/keybaord.html'], tests_included=True, flatten_batches=True) + self.assertEquals([], tests_run) + def test_test_list(self): - filename = tempfile.mktemp() - tmpfile = file(filename, mode='w+') - tmpfile.write('passes/text.html') - tmpfile.close() - tests_run = get_tests_run(['--test-list=%s' % filename], tests_included=True, flatten_batches=True) + fs = port.unit_test_filesystem() + filename = '/tmp/foo.txt' + fs.write_text_file(filename, 'passes/text.html') + tests_run = get_tests_run(['--test-list=%s' % filename], tests_included=True, flatten_batches=True, filesystem=fs) self.assertEquals(['passes/text.html'], tests_run) - os.remove(filename) + fs.remove(filename) res, out, err, user = logging_run(['--test-list=%s' % filename], - tests_included=True) + tests_included=True, filesystem=fs) self.assertEqual(res, -1) self.assertFalse(err.empty()) + def test_test_list_with_prefix(self): + fs = port.unit_test_filesystem() + filename = '/tmp/foo.txt' + fs.write_text_file(filename, 'LayoutTests/passes/text.html') + tests_run = get_tests_run(['--test-list=%s' % filename], tests_included=True, flatten_batches=True, filesystem=fs) + self.assertEquals(['passes/text.html'], tests_run) + def test_unexpected_failures(self): # Run tests including the unexpected failures. self._url_opened = None @@ -393,11 +410,11 @@ class MainTest(unittest.TestCase): # We run a configuration that should fail, to generate output, then # look for what the output results url was. - tmpdir = tempfile.mkdtemp() - res, out, err, user = logging_run(['--results-directory=' + tmpdir], - tests_included=True) - self.assertEqual(user.url, os.path.join(tmpdir, 'results.html')) - shutil.rmtree(tmpdir, ignore_errors=True) + fs = port.unit_test_filesystem() + with fs.mkdtemp() as tmpdir: + res, out, err, user = logging_run(['--results-directory=' + str(tmpdir)], + tests_included=True, filesystem=fs) + self.assertEqual(user.url, fs.join(tmpdir, 'results.html')) def test_results_directory_default(self): # We run a configuration that should fail, to generate output, then @@ -454,18 +471,6 @@ class MainTest(unittest.TestCase): MainTest = skip_if(MainTest, sys.platform == 'cygwin' and compare_version(sys, '2.6')[0] < 0, 'new-run-webkit-tests tests hang on Cygwin Python 2.5.2') - -def _mocked_open(original_open, file_list): - def _wrapper(name, mode, encoding): - if name.find("-expected.") != -1 and mode.find("w") != -1: - # we don't want to actually write new baselines, so stub these out - name.replace('\\', '/') - file_list.append(name) - return original_open(os.devnull, mode, encoding) - return original_open(name, mode, encoding) - return _wrapper - - class RebaselineTest(unittest.TestCase): def assertBaselines(self, file_list, file): "assert that the file_list contains the baselines.""" @@ -476,49 +481,39 @@ class RebaselineTest(unittest.TestCase): # FIXME: Add tests to ensure that we're *not* writing baselines when we're not # supposed to be. - def disabled_test_reset_results(self): - # FIXME: This test is disabled until we can rewrite it to use a - # mock filesystem. - # + def test_reset_results(self): # Test that we update expectations in place. If the expectation # is missing, update the expected generic location. - file_list = [] + fs = port.unit_test_filesystem() passing_run(['--pixel-tests', '--reset-results', 'passes/image.html', 'failures/expected/missing_image.html'], - tests_included=True) + tests_included=True, filesystem=fs) + file_list = fs.written_files.keys() + file_list.remove('/tmp/layout-test-results/tests_run.txt') self.assertEqual(len(file_list), 6) self.assertBaselines(file_list, - "data/passes/image") + "/passes/image") self.assertBaselines(file_list, - "data/failures/expected/missing_image") + "/failures/expected/missing_image") - def disabled_test_new_baseline(self): - # FIXME: This test is disabled until we can rewrite it to use a - # mock filesystem. - # + 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. - file_list = [] - original_open = codecs.open - try: - # Test that we update the platform expectations. If the expectation - # is mssing, then create a new expectation in the platform dir. - file_list = [] - codecs.open = _mocked_open(original_open, file_list) - passing_run(['--pixel-tests', - '--new-baseline', - 'passes/image.html', - 'failures/expected/missing_image.html'], - tests_included=True) - self.assertEqual(len(file_list), 6) - self.assertBaselines(file_list, - "data/platform/test/passes/image") - self.assertBaselines(file_list, - "data/platform/test/failures/expected/missing_image") - finally: - codecs.open = original_open + fs = port.unit_test_filesystem() + passing_run(['--pixel-tests', + '--new-baseline', + 'passes/image.html', + 'failures/expected/missing_image.html'], + tests_included=True, filesystem=fs) + file_list = fs.written_files.keys() + file_list.remove('/tmp/layout-test-results/tests_run.txt') + self.assertEqual(len(file_list), 6) + self.assertBaselines(file_list, + "/platform/test/passes/image") + self.assertBaselines(file_list, + "/platform/test/failures/expected/missing_image") class DryrunTest(unittest.TestCase): @@ -526,15 +521,14 @@ class DryrunTest(unittest.TestCase): # chromium platforms require a chromium checkout, and the mac platform # requires fcntl, so it can't be tested on win32, etc. There is # probably a better way of handling this. - def test_darwin(self): + def disabled_test_darwin(self): if sys.platform != "darwin": return - self.assertTrue(passing_run(['--platform', 'test'])) - self.assertTrue(passing_run(['--platform', 'dryrun', - 'fast/html'])) - self.assertTrue(passing_run(['--platform', 'dryrun-mac', - 'fast/html'])) + self.assertTrue(passing_run(['--platform', 'dryrun', 'fast/html'], + tests_included=True)) + self.assertTrue(passing_run(['--platform', 'dryrun-mac', 'fast/html'], + tests_included=True)) def test_test(self): self.assertTrue(passing_run(['--platform', 'dryrun-test', diff --git a/Tools/Scripts/webkitpy/layout_tests/test_types/image_diff.py b/Tools/Scripts/webkitpy/layout_tests/test_types/image_diff.py index da466c8..44605d2 100644 --- a/Tools/Scripts/webkitpy/layout_tests/test_types/image_diff.py +++ b/Tools/Scripts/webkitpy/layout_tests/test_types/image_diff.py @@ -34,13 +34,8 @@ match, returns FailureImageHashMismatch and outputs both hashes into the layout test results directory. """ -from __future__ import with_statement - -import codecs import errno import logging -import os -import shutil from webkitpy.layout_tests.layout_package import test_failures from webkitpy.layout_tests.test_types import test_type_base diff --git a/Tools/Scripts/webkitpy/layout_tests/test_types/test_type_base.py b/Tools/Scripts/webkitpy/layout_tests/test_types/test_type_base.py index 4b96b3a..ad65016 100644 --- a/Tools/Scripts/webkitpy/layout_tests/test_types/test_type_base.py +++ b/Tools/Scripts/webkitpy/layout_tests/test_types/test_type_base.py @@ -32,13 +32,9 @@ Also defines the TestArguments "struct" to pass them additional arguments. """ -from __future__ import with_statement - -import codecs import cgi import errno import logging -import os.path _log = logging.getLogger("webkitpy.layout_tests.test_types.test_type_base") @@ -86,9 +82,10 @@ class TestTypeBase(object): def _make_output_directory(self, filename): """Creates the output directory (if needed) for a given test filename.""" - output_filename = os.path.join(self._root_output_dir, + fs = self._port._filesystem + output_filename = fs.join(self._root_output_dir, self._port.relative_test_filename(filename)) - self._port.maybe_make_directory(os.path.split(output_filename)[0]) + fs.maybe_make_directory(fs.dirname(output_filename)) def _save_baseline_data(self, filename, data, modifier, encoding, generate_new_baseline=True): @@ -106,21 +103,22 @@ class TestTypeBase(object): baseline, or update the existing one """ + port = self._port + fs = self._port._filesystem if generate_new_baseline: - relative_dir = os.path.dirname( - self._port.relative_test_filename(filename)) - baseline_path = self._port.baseline_path() - output_dir = os.path.join(baseline_path, relative_dir) - output_file = os.path.basename(os.path.splitext(filename)[0] + + relative_dir = fs.dirname(port.relative_test_filename(filename)) + baseline_path = port.baseline_path() + output_dir = fs.join(baseline_path, relative_dir) + output_file = fs.basename(fs.splitext(filename)[0] + self.FILENAME_SUFFIX_EXPECTED + modifier) - self._port.maybe_make_directory(output_dir) - output_path = os.path.join(output_dir, output_file) + fs.maybe_make_directory(output_dir) + output_path = fs.join(output_dir, output_file) _log.debug('writing new baseline result "%s"' % (output_path)) else: - output_path = self._port.expected_filename(filename, modifier) + output_path = port.expected_filename(filename, modifier) _log.debug('resetting baseline result "%s"' % output_path) - self._port.update_baseline(output_path, data, encoding) + port.update_baseline(output_path, data, encoding) def output_filename(self, filename, modifier): """Returns a filename inside the output dir that contains modifier. @@ -136,9 +134,10 @@ class TestTypeBase(object): Return: The absolute windows path to the output filename """ - output_filename = os.path.join(self._root_output_dir, + fs = self._port._filesystem + output_filename = fs.join(self._root_output_dir, self._port.relative_test_filename(filename)) - return os.path.splitext(output_filename)[0] + modifier + return fs.splitext(output_filename)[0] + modifier def compare_output(self, port, filename, test_args, actual_test_output, expected_test_output): @@ -165,11 +164,11 @@ class TestTypeBase(object): def _write_into_file_at_path(self, file_path, contents, encoding): """This method assumes that byte_array is already encoded into the right format.""" - open_mode = 'w' + fs = self._port._filesystem if encoding is None: - open_mode = 'w+b' - with codecs.open(file_path, open_mode, encoding=encoding) as file: - file.write(contents) + fs.write_binary_file(file_path, contents) + return + fs.write_text_file(file_path, contents) def write_output_files(self, filename, file_type, output, expected, encoding, diff --git a/Tools/Scripts/webkitpy/layout_tests/test_types/text_diff.py b/Tools/Scripts/webkitpy/layout_tests/test_types/text_diff.py index ad25262..7b7febe 100644 --- a/Tools/Scripts/webkitpy/layout_tests/test_types/text_diff.py +++ b/Tools/Scripts/webkitpy/layout_tests/test_types/text_diff.py @@ -33,12 +33,8 @@ If the output doesn't match, returns FailureTextMismatch and outputs the diff files into the layout test results directory. """ -from __future__ import with_statement - -import codecs import errno import logging -import os.path from webkitpy.layout_tests.layout_package import test_failures from webkitpy.layout_tests.test_types import test_type_base diff --git a/Tools/Scripts/webkitpy/tool/bot/commitqueuetask.py b/Tools/Scripts/webkitpy/tool/bot/commitqueuetask.py index 3be2556..b22138d 100644 --- a/Tools/Scripts/webkitpy/tool/bot/commitqueuetask.py +++ b/Tools/Scripts/webkitpy/tool/bot/commitqueuetask.py @@ -187,7 +187,9 @@ class CommitQueueTask(object): first_failing_tests = [result.filename for result in first_results] first_results_archive = self._delegate.archive_last_layout_test_results(self._patch) if self._test(): - self._report_flaky_tests(first_results, first_results_archive) + # Only report flaky tests if we were successful at archiving results. + if first_results_archive: + self._report_flaky_tests(first_results, first_results_archive) return True second_results = self._failing_results_from_last_run() diff --git a/Tools/Scripts/webkitpy/tool/bot/commitqueuetask_unittest.py b/Tools/Scripts/webkitpy/tool/bot/commitqueuetask_unittest.py index 26231ae..87d0ab5 100644 --- a/Tools/Scripts/webkitpy/tool/bot/commitqueuetask_unittest.py +++ b/Tools/Scripts/webkitpy/tool/bot/commitqueuetask_unittest.py @@ -209,6 +209,34 @@ command_passed: success_message='Landed patch' patch='197' """ self._run_through_task(commit_queue, expected_stderr) + def test_failed_archive(self): + commit_queue = MockCommitQueue([ + None, + None, + None, + None, + ScriptError("MOCK tests failure"), + ]) + # It's possible delegate to fail to archive layout tests, don't try to report + # flaky tests when that happens. + commit_queue.archive_last_layout_test_results = lambda patch: None + expected_stderr = """run_webkit_patch: ['clean'] +command_passed: success_message='Cleaned working directory' patch='197' +run_webkit_patch: ['update'] +command_passed: success_message='Updated working directory' patch='197' +run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 197] +command_passed: success_message='Applied patch' patch='197' +run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both'] +command_passed: success_message='Built patch' patch='197' +run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive'] +command_failed: failure_message='Patch does not pass tests' script_error='MOCK tests failure' patch='197' +run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive'] +command_passed: success_message='Passed tests' patch='197' +run_webkit_patch: ['land-attachment', '--force-clean', '--ignore-builders', '--non-interactive', '--parent-command=commit-queue', 197] +command_passed: success_message='Landed patch' patch='197' +""" + self._run_through_task(commit_queue, expected_stderr) + _double_flaky_test_counter = 0 def test_double_flaky_test_failure(self): diff --git a/Tools/Scripts/webkitpy/tool/commands/earlywarningsystem.py b/Tools/Scripts/webkitpy/tool/commands/earlywarningsystem.py index 3b53d1a..996ab24 100644 --- a/Tools/Scripts/webkitpy/tool/commands/earlywarningsystem.py +++ b/Tools/Scripts/webkitpy/tool/commands/earlywarningsystem.py @@ -122,6 +122,7 @@ class EflEWS(AbstractEarlyWarningSystem): "leandro@profusion.mobi", "antognolli@profusion.mobi", "lucas.demarchi@profusion.mobi", + "gyuyoung.kim@samsung.com", ] diff --git a/Tools/Scripts/webkitpy/tool/commands/queues.py b/Tools/Scripts/webkitpy/tool/commands/queues.py index 42321cf..9e50dd4 100644 --- a/Tools/Scripts/webkitpy/tool/commands/queues.py +++ b/Tools/Scripts/webkitpy/tool/commands/queues.py @@ -323,10 +323,12 @@ class CommitQueue(AbstractPatchQueue, StepSequenceErrorHandler, CommitQueueTaskD results_name, _ = os.path.splitext(os.path.basename(results_directory)) # Note: We name the zip with the bug_id instead of patch_id to match work_item_log_path(). zip_path = self._tool.workspace.find_unused_filename(self._log_directory(), "%s-%s" % (patch.bug_id(), results_name), "zip") + if not zip_path: + return None archive = self._tool.workspace.create_zip(zip_path, results_directory) # Remove the results directory to prevent http logs, etc. from getting huge between runs. # We could have create_zip remove the original, but this is more explicit. - self._tool.filesystem.remove_tree(results_directory, ignore_errors=True) + self._tool.filesystem.rmtree(results_directory) return archive def refetch_patch(self, patch): diff --git a/Tools/Scripts/webkitpy/tool/main.py b/Tools/Scripts/webkitpy/tool/main.py index 76d5bef..6b07615 100755 --- a/Tools/Scripts/webkitpy/tool/main.py +++ b/Tools/Scripts/webkitpy/tool/main.py @@ -36,6 +36,7 @@ import threading from webkitpy.common.checkout.api import Checkout from webkitpy.common.checkout.scm import default_scm from webkitpy.common.config.ports import WebKitPort +from webkitpy.common.host import Host from webkitpy.common.net.bugzilla import Bugzilla from webkitpy.common.net.buildbot import BuildBot from webkitpy.common.net.irc.ircproxy import IRCProxy @@ -46,7 +47,7 @@ from webkitpy.tool.multicommandtool import MultiCommandTool import webkitpy.tool.commands as commands -class WebKitPatch(MultiCommandTool): +class WebKitPatch(MultiCommandTool, Host): global_options = [ make_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="enable all logging"), make_option("-d", "--directory", action="append", dest="patch_directories", default=[], help="Directory to look at for changed files"), @@ -59,58 +60,14 @@ class WebKitPatch(MultiCommandTool): def __init__(self, path): MultiCommandTool.__init__(self) + Host.__init__(self) self._path = path self.wakeup_event = threading.Event() - # FIXME: All of these shared objects should move off onto a - # separate "Tool" object. WebKitPatch should inherit from - # "Tool" and all these objects should use getters/setters instead of - # manual getter functions (e.g. scm()). - self.bugs = Bugzilla() - self.buildbot = BuildBot() - self.executive = executive.Executive() - self._irc = None - self.filesystem = filesystem.FileSystem() - self.workspace = workspace.Workspace(self.filesystem, self.executive) - self._port = None - self.user = user.User() - self._scm = None - self._checkout = None - self.status_server = StatusServer() - self.port_factory = port.factory - self.platform = platforminfo.PlatformInfo() - - def scm(self): - # Lazily initialize SCM to not error-out before command line parsing (or when running non-scm commands). - if not self._scm: - self._scm = default_scm(self._options.patch_directories) - return self._scm - - def checkout(self): - if not self._checkout: - self._checkout = Checkout(self.scm()) - return self._checkout - - def port(self): - return self._port - - def ensure_irc_connected(self, irc_delegate): - if not self._irc: - self._irc = IRCProxy(irc_delegate) - - def irc(self): - # We don't automatically construct IRCProxy here because constructing - # IRCProxy actually connects to IRC. We want clients to explicitly - # connect to IRC. - return self._irc def path(self): return self._path - def command_completed(self): - if self._irc: - self._irc.disconnect() - def should_show_in_main_help(self, command): if not command.show_in_main_help: return False @@ -120,7 +77,7 @@ class WebKitPatch(MultiCommandTool): # FIXME: This may be unnecessary since we pass global options to all commands during execute() as well. def handle_global_options(self, options): - self._options = options + self._initialize_scm(options.patch_directories) if options.dry_run: self.scm().dryrun = True self.bugs.dryrun = True |