15c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# Copyright (C) 2012 Google, Inc. 25c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org) 35c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# 45c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# Redistribution and use in source and binary forms, with or without 55c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# modification, are permitted provided that the following conditions 65c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# are met: 75c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# 1. Redistributions of source code must retain the above copyright 85c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# notice, this list of conditions and the following disclaimer. 95c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# 2. Redistributions in binary form must reproduce the above copyright 105c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# notice, this list of conditions and the following disclaimer in the 115c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# documentation and/or other materials provided with the distribution. 125c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# 135c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND 145c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 155c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 165c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR 175c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 185c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 195c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 205c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 215c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 225c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 235c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 245c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)"""this module is responsible for finding python tests.""" 255c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 265c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)import logging 275c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)import re 285c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 295c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 305c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)_log = logging.getLogger(__name__) 315c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 325c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 335c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)class _DirectoryTree(object): 345c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) def __init__(self, filesystem, top_directory, starting_subdirectory): 355c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) self.filesystem = filesystem 365c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) self.top_directory = filesystem.realpath(top_directory) 375c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) self.search_directory = self.top_directory 385c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) self.top_package = '' 395c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) if starting_subdirectory: 405c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) self.top_package = starting_subdirectory.replace(filesystem.sep, '.') + '.' 415c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) self.search_directory = filesystem.join(self.top_directory, starting_subdirectory) 425c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 435c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) def find_modules(self, suffixes, sub_directory=None): 445c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) if sub_directory: 455c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) search_directory = self.filesystem.join(self.top_directory, sub_directory) 465c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) else: 475c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) search_directory = self.search_directory 485c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 495c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) def file_filter(filesystem, dirname, basename): 505c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return any(basename.endswith(suffix) for suffix in suffixes) 515c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 525c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) filenames = self.filesystem.files_under(search_directory, file_filter=file_filter) 535c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return [self.to_module(filename) for filename in filenames] 545c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 555c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) def to_module(self, path): 565c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return path.replace(self.top_directory + self.filesystem.sep, '').replace(self.filesystem.sep, '.')[:-3] 575c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 585c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) def subpath(self, path): 595c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) """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.""" 605c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) realpath = self.filesystem.realpath(self.filesystem.join(self.top_directory, path)) 615c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) if realpath.startswith(self.top_directory + self.filesystem.sep): 625c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return realpath.replace(self.top_directory + self.filesystem.sep, '') 635c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return None 645c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 655c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) def clean(self): 665c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) """Delete all .pyc files in the tree that have no matching .py file.""" 675c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) _log.debug("Cleaning orphaned *.pyc files from: %s" % self.search_directory) 685c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) filenames = self.filesystem.files_under(self.search_directory) 695c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) for filename in filenames: 705c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) if filename.endswith(".pyc") and filename[:-1] not in filenames: 715c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) _log.info("Deleting orphan *.pyc file: %s" % filename) 725c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) self.filesystem.remove(filename) 735c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 745c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 755c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)class Finder(object): 765c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) def __init__(self, filesystem): 775c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) self.filesystem = filesystem 785c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) self.trees = [] 795c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) self._names_to_skip = [] 805c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 815c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) def add_tree(self, top_directory, starting_subdirectory=None): 825c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) self.trees.append(_DirectoryTree(self.filesystem, top_directory, starting_subdirectory)) 835c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 845c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) def skip(self, names, reason, bugid): 855c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) self._names_to_skip.append(tuple([names, reason, bugid])) 865c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 875c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) def additional_paths(self, paths): 885c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return [tree.top_directory for tree in self.trees if tree.top_directory not in paths] 895c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 905c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) def clean_trees(self): 915c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) for tree in self.trees: 925c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) tree.clean() 935c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 945c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) def is_module(self, name): 955c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) relpath = name.replace('.', self.filesystem.sep) + '.py' 965c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return any(self.filesystem.exists(self.filesystem.join(tree.top_directory, relpath)) for tree in self.trees) 975c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 985c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) def is_dotted_name(self, name): 995c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return re.match(r'[a-zA-Z.][a-zA-Z0-9_.]*', name) 1005c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 1015c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) def to_module(self, path): 1025c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) for tree in self.trees: 1035c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) if path.startswith(tree.top_directory): 1045c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return tree.to_module(path) 1055c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return None 1065c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 1075c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) def find_names(self, args, find_all): 1085c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) suffixes = ['_unittest.py', '_integrationtest.py'] 1095c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) if args: 1105c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) names = [] 1115c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) for arg in args: 1125c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) names.extend(self._find_names_for_arg(arg, suffixes)) 1135c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return names 1145c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 1155c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return self._default_names(suffixes, find_all) 1165c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 1175c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) def _find_names_for_arg(self, arg, suffixes): 1185c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) realpath = self.filesystem.realpath(arg) 1195c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) if self.filesystem.exists(realpath): 1205c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) names = self._find_in_trees(realpath, suffixes) 1215c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) if not names: 1225c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) _log.error("%s is not in one of the test trees." % arg) 1235c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return names 1245c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 1255c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) # See if it's a python package in a tree (or a relative path from the top of a tree). 1265c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) names = self._find_in_trees(arg.replace('.', self.filesystem.sep), suffixes) 1275c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) if names: 1285c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return names 1295c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 1305c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) if self.is_dotted_name(arg): 1315c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) # The name may not exist, but that's okay; we'll find out later. 1325c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return [arg] 1335c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 1345c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) _log.error("%s is not a python name or an existing file or directory." % arg) 1355c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return [] 1365c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 1375c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) def _find_in_trees(self, path, suffixes): 1385c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) for tree in self.trees: 1395c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) relpath = tree.subpath(path) 1405c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) if not relpath: 1415c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) continue 1425c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) if self.filesystem.isfile(path): 1435c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return [tree.to_module(path)] 1445c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) else: 1455c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return tree.find_modules(suffixes, path) 1465c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return [] 1475c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 1485c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) def _default_names(self, suffixes, find_all): 1495c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) modules = [] 1505c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) for tree in self.trees: 1515c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) modules.extend(tree.find_modules(suffixes)) 1525c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) modules.sort() 1535c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 1545c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) for module in modules: 1555c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) _log.debug("Found: %s" % module) 1565c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 1575c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) if not find_all: 1585c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) for (names, reason, bugid) in self._names_to_skip: 1595c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) self._exclude(modules, names, reason, bugid) 1605c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 1615c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return modules 1625c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 1635c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) def _exclude(self, modules, module_prefixes, reason, bugid): 1645c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) _log.info('Skipping tests in the following modules or packages because they %s:' % reason) 1655c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) for prefix in module_prefixes: 1665c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) _log.info(' %s' % prefix) 1675c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) modules_to_exclude = filter(lambda m: m.startswith(prefix), modules) 1685c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) for m in modules_to_exclude: 1695c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) if len(modules_to_exclude) > 1: 1705c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) _log.debug(' %s' % m) 1715c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) modules.remove(m) 1725c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) _log.info(' (https://bugs.webkit.org/show_bug.cgi?id=%d; use --all to include)' % bugid) 1735c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) _log.info('') 174