1# Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org)
2#
3# Redistribution and use in source and binary forms, with or without
4# modification, are permitted provided that the following conditions
5# are met:
6# 1.  Redistributions of source code must retain the above copyright
7#     notice, this list of conditions and the following disclaimer.
8# 2.  Redistributions in binary form must reproduce the above copyright
9#     notice, this list of conditions and the following disclaimer in the
10#     documentation and/or other materials provided with the distribution.
11#
12# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
13# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
16# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
17# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
18# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
19# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
20# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
21# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22
23"""Contains the entry method for test-webkitpy."""
24
25import logging
26import os
27import sys
28import unittest
29
30import webkitpy
31
32
33_log = logging.getLogger(__name__)
34
35
36class Tester(object):
37
38    """Discovers and runs webkitpy unit tests."""
39
40    def _find_unittest_files(self, webkitpy_dir):
41        """Return a list of paths to all unit-test files."""
42        unittest_paths = []  # Return value.
43
44        for dir_path, dir_names, file_names in os.walk(webkitpy_dir):
45            for file_name in file_names:
46                if not file_name.endswith("_unittest.py"):
47                    continue
48                unittest_path = os.path.join(dir_path, file_name)
49                unittest_paths.append(unittest_path)
50
51        return unittest_paths
52
53    def _modules_from_paths(self, package_root, paths):
54        """Return a list of fully-qualified module names given paths."""
55        package_path = os.path.abspath(package_root)
56        root_package_name = os.path.split(package_path)[1]  # Equals "webkitpy".
57
58        prefix_length = len(package_path)
59
60        modules = []
61        for path in paths:
62            path = os.path.abspath(path)
63            # This gives us, for example: /common/config/ports_unittest.py
64            rel_path = path[prefix_length:]
65            # This gives us, for example: /common/config/ports_unittest
66            rel_path = os.path.splitext(rel_path)[0]
67
68            parts = []
69            while True:
70                (rel_path, tail) = os.path.split(rel_path)
71                if not tail:
72                    break
73                parts.insert(0, tail)
74            # We now have, for example: common.config.ports_unittest
75            # FIXME: This is all a hack around the fact that we always prefix webkitpy includes with "webkitpy."
76            parts.insert(0, root_package_name)  # Put "webkitpy" at the beginning.
77            module = ".".join(parts)
78            modules.append(module)
79
80        return modules
81
82    def _win32_blacklist(self, module_path):
83        # FIXME: Remove this once https://bugs.webkit.org/show_bug.cgi?id=54526 is resolved.
84        if any([module_path.startswith(package) for package in [
85            'webkitpy.tool',
86            'webkitpy.common.checkout',
87            'webkitpy.common.config',
88            ]]):
89            return False
90
91        return module_path not in [
92            # FIXME: This file also requires common.checkout to work
93            'webkitpy.layout_tests.deduplicate_tests_unittest',
94        ]
95
96    def run_tests(self, sys_argv, external_package_paths=None):
97        """Run the unit tests in all *_unittest.py modules in webkitpy.
98
99        This method excludes "webkitpy.common.checkout.scm_unittest" unless
100        the --all option is the second element of sys_argv.
101
102        Args:
103          sys_argv: A reference to sys.argv.
104
105        """
106        if external_package_paths is None:
107            external_package_paths = []
108        else:
109            # FIXME: We should consider moving webkitpy off of using "webkitpy." to prefix
110            # all includes.  If we did that, then this would use path instead of dirname(path).
111            # QueueStatusServer.__init__ has a sys.path import hack due to this code.
112            sys.path.extend(set(os.path.dirname(path) for path in external_package_paths))
113
114        if len(sys_argv) > 1 and not sys_argv[-1].startswith("-"):
115            # Then explicit modules or test names were provided, which
116            # the unittest module is equipped to handle.
117            unittest.main(argv=sys_argv, module=None)
118            # No need to return since unitttest.main() exits.
119
120        # Otherwise, auto-detect all unit tests.
121
122        # FIXME: This should be combined with the external_package_paths code above.
123        webkitpy_dir = os.path.dirname(webkitpy.__file__)
124
125        modules = []
126        for path in [webkitpy_dir] + external_package_paths:
127            modules.extend(self._modules_from_paths(path, self._find_unittest_files(path)))
128        modules.sort()
129
130        # This is a sanity check to ensure that the unit-test discovery
131        # methods are working.
132        if len(modules) < 1:
133            raise Exception("No unit-test modules found.")
134
135        for module in modules:
136            _log.debug("Found: %s" % module)
137
138        # FIXME: This is a hack, but I'm tired of commenting out the test.
139        #        See https://bugs.webkit.org/show_bug.cgi?id=31818
140        if len(sys_argv) > 1 and sys.argv[1] == "--all":
141            sys.argv.remove("--all")
142        else:
143            excluded_module = "webkitpy.common.checkout.scm_unittest"
144            _log.info("Excluding: %s (use --all to include)" % excluded_module)
145            modules.remove(excluded_module)
146
147        if sys.platform == 'win32':
148            modules = filter(self._win32_blacklist, modules)
149
150        sys_argv.extend(modules)
151
152        # We pass None for the module because we do not want the unittest
153        # module to resolve module names relative to a given module.
154        # (This would require importing all of the unittest modules from
155        # this module.)  See the loadTestsFromName() method of the
156        # unittest.TestLoader class for more details on this parameter.
157        unittest.main(argv=sys_argv, module=None)
158