|  | # Copyright (C) 2012 Google, Inc. | 
|  | # Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org) | 
|  | # | 
|  | # Redistribution and use in source and binary forms, with or without | 
|  | # modification, are permitted provided that the following conditions | 
|  | # are met: | 
|  | # 1.  Redistributions of source code must retain the above copyright | 
|  | #     notice, this list of conditions and the following disclaimer. | 
|  | # 2.  Redistributions in binary form must reproduce the above copyright | 
|  | #     notice, this list of conditions and the following disclaimer in the | 
|  | #     documentation and/or other materials provided with the distribution. | 
|  | # | 
|  | # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND | 
|  | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | 
|  | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 
|  | # DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR | 
|  | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | 
|  | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | 
|  | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | 
|  | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | 
|  | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
|  | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
|  |  | 
|  | """this module is responsible for finding python tests.""" | 
|  |  | 
|  | import logging | 
|  | import re | 
|  |  | 
|  |  | 
|  | _log = logging.getLogger(__name__) | 
|  |  | 
|  |  | 
|  | class _DirectoryTree(object): | 
|  | def __init__(self, filesystem, top_directory, starting_subdirectory): | 
|  | self.filesystem = filesystem | 
|  | self.top_directory = filesystem.realpath(top_directory) | 
|  | self.search_directory = self.top_directory | 
|  | self.top_package = '' | 
|  | if starting_subdirectory: | 
|  | self.top_package = starting_subdirectory.replace(filesystem.sep, '.') + '.' | 
|  | self.search_directory = filesystem.join(self.top_directory, starting_subdirectory) | 
|  |  | 
|  | def find_modules(self, suffixes, sub_directory=None): | 
|  | if sub_directory: | 
|  | search_directory = self.filesystem.join(self.top_directory, sub_directory) | 
|  | else: | 
|  | search_directory = self.search_directory | 
|  |  | 
|  | def file_filter(filesystem, dirname, basename): | 
|  | return any(basename.endswith(suffix) for suffix in suffixes) | 
|  |  | 
|  | filenames = self.filesystem.files_under(search_directory, file_filter=file_filter) | 
|  | return [self.to_module(filename) for filename in filenames] | 
|  |  | 
|  | def to_module(self, path): | 
|  | return path.replace(self.top_directory + self.filesystem.sep, '').replace(self.filesystem.sep, '.')[:-3] | 
|  |  | 
|  | def subpath(self, path): | 
|  | """Returns the relative path from the top of the tree to the path, or None if the path is not under the top of the tree.""" | 
|  | realpath = self.filesystem.realpath(self.filesystem.join(self.top_directory, path)) | 
|  | if realpath.startswith(self.top_directory + self.filesystem.sep): | 
|  | return realpath.replace(self.top_directory + self.filesystem.sep, '') | 
|  | return None | 
|  |  | 
|  | def clean(self): | 
|  | """Delete all .pyc files in the tree that have no matching .py file.""" | 
|  | _log.debug("Cleaning orphaned *.pyc files from: %s" % self.search_directory) | 
|  | filenames = self.filesystem.files_under(self.search_directory) | 
|  | for filename in filenames: | 
|  | if filename.endswith(".pyc") and filename[:-1] not in filenames: | 
|  | _log.info("Deleting orphan *.pyc file: %s" % filename) | 
|  | self.filesystem.remove(filename) | 
|  |  | 
|  |  | 
|  | class Finder(object): | 
|  | def __init__(self, filesystem): | 
|  | self.filesystem = filesystem | 
|  | self.trees = [] | 
|  | self._names_to_skip = [] | 
|  |  | 
|  | def add_tree(self, top_directory, starting_subdirectory=None): | 
|  | self.trees.append(_DirectoryTree(self.filesystem, top_directory, starting_subdirectory)) | 
|  |  | 
|  | def skip(self, names, reason, bugid): | 
|  | self._names_to_skip.append(tuple([names, reason, bugid])) | 
|  |  | 
|  | def additional_paths(self, paths): | 
|  | return [tree.top_directory for tree in self.trees if tree.top_directory not in paths] | 
|  |  | 
|  | def clean_trees(self): | 
|  | for tree in self.trees: | 
|  | tree.clean() | 
|  |  | 
|  | def is_module(self, name): | 
|  | relpath = name.replace('.', self.filesystem.sep) + '.py' | 
|  | return any(self.filesystem.exists(self.filesystem.join(tree.top_directory, relpath)) for tree in self.trees) | 
|  |  | 
|  | def is_dotted_name(self, name): | 
|  | return re.match(r'[a-zA-Z.][a-zA-Z0-9_.]*', name) | 
|  |  | 
|  | def to_module(self, path): | 
|  | for tree in self.trees: | 
|  | if path.startswith(tree.top_directory): | 
|  | return tree.to_module(path) | 
|  | return None | 
|  |  | 
|  | def find_names(self, args, find_all): | 
|  | suffixes = ['_unittest.py', '_integrationtest.py'] | 
|  | if args: | 
|  | names = [] | 
|  | for arg in args: | 
|  | names.extend(self._find_names_for_arg(arg, suffixes)) | 
|  | return names | 
|  |  | 
|  | return self._default_names(suffixes, find_all) | 
|  |  | 
|  | def _find_names_for_arg(self, arg, suffixes): | 
|  | realpath = self.filesystem.realpath(arg) | 
|  | if self.filesystem.exists(realpath): | 
|  | names = self._find_in_trees(realpath, suffixes) | 
|  | if not names: | 
|  | _log.error("%s is not in one of the test trees." % arg) | 
|  | return names | 
|  |  | 
|  | # See if it's a python package in a tree (or a relative path from the top of a tree). | 
|  | names = self._find_in_trees(arg.replace('.', self.filesystem.sep), suffixes) | 
|  | if names: | 
|  | return names | 
|  |  | 
|  | if self.is_dotted_name(arg): | 
|  | # The name may not exist, but that's okay; we'll find out later. | 
|  | return [arg] | 
|  |  | 
|  | _log.error("%s is not a python name or an existing file or directory." % arg) | 
|  | return [] | 
|  |  | 
|  | def _find_in_trees(self, path, suffixes): | 
|  | for tree in self.trees: | 
|  | relpath = tree.subpath(path) | 
|  | if not relpath: | 
|  | continue | 
|  | if self.filesystem.isfile(path): | 
|  | return [tree.to_module(path)] | 
|  | else: | 
|  | return tree.find_modules(suffixes, path) | 
|  | return [] | 
|  |  | 
|  | def _default_names(self, suffixes, find_all): | 
|  | modules = [] | 
|  | for tree in self.trees: | 
|  | modules.extend(tree.find_modules(suffixes)) | 
|  | modules.sort() | 
|  |  | 
|  | for module in modules: | 
|  | _log.debug("Found: %s" % module) | 
|  |  | 
|  | if not find_all: | 
|  | for (names, reason, bugid) in self._names_to_skip: | 
|  | self._exclude(modules, names, reason, bugid) | 
|  |  | 
|  | return modules | 
|  |  | 
|  | def _exclude(self, modules, module_prefixes, reason, bugid): | 
|  | _log.info('Skipping tests in the following modules or packages because they %s:' % reason) | 
|  | for prefix in module_prefixes: | 
|  | _log.info('    %s' % prefix) | 
|  | modules_to_exclude = filter(lambda m: m.startswith(prefix), modules) | 
|  | for m in modules_to_exclude: | 
|  | if len(modules_to_exclude) > 1: | 
|  | _log.debug('        %s' % m) | 
|  | modules.remove(m) | 
|  | _log.info('    (https://bugs.webkit.org/show_bug.cgi?id=%d; use --all to include)' % bugid) | 
|  | _log.info('') |