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