• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# Copyright (c) 2009 Google Inc. All rights reserved.
3# Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org)
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9#     * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11#     * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15#     * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31import logging
32import os
33import sys
34
35# Do not import anything from webkitpy prior to cleaning webkitpy of
36# orphaned *.pyc files.  This ensures that no orphaned *.pyc files are
37# accidentally imported during the course of this script.
38#
39# Also, do not import or execute any Python code incompatible with
40# Python 2.4 until after execution of the init() method below.
41
42
43_log = logging.getLogger("test-webkitpy")
44
45
46# Verbose logging is useful for debugging test-webkitpy code that runs
47# before the actual unit tests -- things like autoinstall downloading and
48# unit-test auto-detection logic.  This is different from verbose logging
49# of the unit tests themselves (i.e. the unittest module's --verbose flag).
50def configure_logging(is_verbose_logging):
51    """Configure the root logger.
52
53    Configure the root logger not to log any messages from webkitpy --
54    except for messages from the autoinstall module.  Also set the
55    logging level as described below.
56
57    Args:
58      is_verbose_logging: A boolean value of whether logging should be
59                          verbose.  If this parameter is true, the logging
60                          level for the handler on the root logger is set to
61                          logging.DEBUG.  Otherwise, it is set to logging.INFO.
62
63    """
64    # Don't use the Python ternary operator here so that this method will
65    # work with Python 2.4.
66    if is_verbose_logging:
67        logging_level = logging.DEBUG
68    else:
69        logging_level = logging.INFO
70
71    handler = logging.StreamHandler(sys.stderr)
72    # We constrain the level on the handler rather than on the root
73    # logger itself.  This is probably better because the handler is
74    # configured and known only to this module, whereas the root logger
75    # is an object shared (and potentially modified) by many modules.
76    # Modifying the handler, then, is less intrusive and less likely to
77    # interfere with modifications made by other modules (e.g. in unit
78    # tests).
79    handler.setLevel(logging_level)
80    formatter = logging.Formatter("%(name)s: %(levelname)-8s %(message)s")
81    handler.setFormatter(formatter)
82
83    logger = logging.getLogger()
84    logger.addHandler(handler)
85    logger.setLevel(logging.NOTSET)
86
87    # Filter out most webkitpy messages.
88    #
89    # Messages can be selectively re-enabled for this script by updating
90    # this method accordingly.
91    def filter(record):
92        """Filter out autoinstall and non-third-party webkitpy messages."""
93        # FIXME: Figure out a way not to use strings here, for example by
94        #        using syntax like webkitpy.test.__name__.  We want to be
95        #        sure not to import any non-Python 2.4 code, though, until
96        #        after the version-checking code has executed.
97        if (record.name.startswith("webkitpy.common.system.autoinstall") or
98            record.name.startswith("webkitpy.test")):
99            return True
100        if record.name.startswith("webkitpy"):
101            return False
102        return True
103
104    testing_filter = logging.Filter()
105    testing_filter.filter = filter
106
107    # Display a message so developers are not mystified as to why
108    # logging does not work in the unit tests.
109    _log.info("Suppressing most webkitpy logging while running unit tests.")
110    handler.addFilter(testing_filter)
111
112
113def _clean_pyc_files(dir_to_clean, paths_not_to_log):
114    """Delete from a directory all .pyc files that have no .py file.
115
116    Args:
117      dir_to_clean: The path to the directory to clean.
118      paths_not_to_log: A list of paths to .pyc files whose deletions should
119                        not be logged.  This list should normally include
120                        only test .pyc files.
121
122    """
123    _log.debug("Cleaning orphaned *.pyc files from: %s" % dir_to_clean)
124
125    # Normalize paths not to log.
126    paths_not_to_log = [os.path.abspath(path) for path in paths_not_to_log]
127
128    for dir_path, dir_names, file_names in os.walk(dir_to_clean):
129        for file_name in file_names:
130            if file_name.endswith(".pyc") and file_name[:-1] not in file_names:
131                file_path = os.path.join(dir_path, file_name)
132                if os.path.abspath(file_path) not in paths_not_to_log:
133                    _log.info("Deleting orphan *.pyc file: %s" % file_path)
134                os.remove(file_path)
135
136
137# As a substitute for a unit test, this method tests _clean_pyc_files()
138# in addition to calling it.  We chose not to use the unittest module
139# because _clean_pyc_files() is called only once and is not used elsewhere.
140def _clean_packages_with_test(external_package_paths):
141    webkitpy_dir = os.path.join(os.path.dirname(__file__), "webkitpy")
142    package_paths = [webkitpy_dir] + external_package_paths
143
144    # The test .pyc file is--
145    # webkitpy/python24/TEMP_test-webkitpy_test_pyc_file.pyc.
146    test_path = os.path.join(webkitpy_dir, "python24",
147                             "TEMP_test-webkitpy_test_pyc_file.pyc")
148
149    test_file = open(test_path, "w")
150    try:
151        test_file.write("Test .pyc file generated by test-webkitpy.")
152    finally:
153        test_file.close()
154
155    # Confirm that the test file exists so that when we check that it does
156    # not exist, the result is meaningful.
157    if not os.path.exists(test_path):
158        raise Exception("Test .pyc file not created: %s" % test_path)
159
160    for path in package_paths:
161        _clean_pyc_files(path, [test_path])
162
163    if os.path.exists(test_path):
164        raise Exception("Test .pyc file not deleted: %s" % test_path)
165
166
167def init(command_args, external_package_paths):
168    """Execute code prior to importing from webkitpy.unittests.
169
170    Args:
171        command_args: The list of command-line arguments -- usually
172                      sys.argv[1:].
173
174    """
175    verbose_logging_flag = "--verbose-logging"
176    is_verbose_logging = verbose_logging_flag in command_args
177    if is_verbose_logging:
178        # Remove the flag so it doesn't cause unittest.main() to error out.
179        #
180        # FIXME: Get documentation for the --verbose-logging flag to show
181        #        up in the usage instructions, which are currently generated
182        #        by unittest.main().  It's possible that this will require
183        #        re-implementing the option parser for unittest.main()
184        #        since there may not be an easy way to modify its existing
185        #        option parser.
186        sys.argv.remove(verbose_logging_flag)
187
188    configure_logging(is_verbose_logging)
189    _log.debug("Verbose WebKit logging enabled.")
190
191    # We clean orphaned *.pyc files from the packages prior to importing from
192    # them to make sure that no import statements falsely succeed.
193    # This helps to check that import statements have been updated correctly
194    # after any file moves.  Otherwise, incorrect import statements can
195    # be masked.
196    #
197    # For example, if webkitpy/python24/versioning.py were moved to a
198    # different location without changing any import statements, and if
199    # the corresponding .pyc file were left behind without deleting it,
200    # then "import webkitpy.python24.versioning" would continue to succeed
201    # even though it would fail for someone checking out a fresh copy
202    # of the source tree.  This is because of a Python feature:
203    #
204    # "It is possible to have a file called spam.pyc (or spam.pyo when -O
205    # is used) without a file spam.py for the same module. This can be used
206    # to distribute a library of Python code in a form that is moderately
207    # hard to reverse engineer."
208    #
209    # ( http://docs.python.org/tutorial/modules.html#compiled-python-files )
210    #
211    # Deleting the orphaned .pyc file prior to importing, however, would
212    # cause an ImportError to occur on import as desired.
213    _clean_packages_with_test(external_package_paths)
214
215    import webkitpy.python24.versioning as versioning
216
217    versioning.check_version(log=_log)
218
219    (comparison, current_version, minimum_version) = \
220        versioning.compare_version()
221
222    if comparison > 0:
223        # Then the current version is later than the minimum version.
224        message = ("You are testing webkitpy with a Python version (%s) "
225                   "higher than the minimum version (%s) it was meant "
226                   "to support." % (current_version, minimum_version))
227        _log.warn(message)
228
229
230def _path_from_webkit_root(*components):
231    webkit_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
232    return os.path.join(webkit_root, *components)
233
234
235def _test_import(module_path):
236    try:
237        sys.path.append(os.path.dirname(module_path))
238        module_name = os.path.basename(module_path)
239        __import__(module_name)
240        return True
241    except Exception, e:
242        message = "Skipping tests in %s due to failure (%s)." % (module_path, e)
243        if module_name.endswith("QueueStatusServer"):
244            message += "  This module is optional.  The failure is likely due to a missing Google AppEngine install.  (http://code.google.com/appengine/downloads.html)"
245        _log.warn(message)
246        return False
247
248if __name__ == "__main__":
249    # FIXME: We should probably test each package separately to avoid naming conflicts.
250    external_package_paths = [
251        _path_from_webkit_root('Source', 'WebKit2', 'Scripts', 'webkit2'),
252        _path_from_webkit_root('Tools', 'QueueStatusServer'),
253    ]
254    init(sys.argv[1:], external_package_paths)
255
256    # We import the unit test code after init() to ensure that any
257    # Python version warnings are displayed in case an error occurs
258    # while interpreting webkitpy.unittests.  This also allows
259    # logging to be configured prior to importing -- for example to
260    # enable the display of autoinstall logging.log messages while
261    # running the unit tests.
262    from webkitpy.test.main import Tester
263
264    external_package_paths = filter(_test_import, external_package_paths)
265
266    Tester().run_tests(sys.argv, external_package_paths)
267