Home | History | Annotate | Download | only in Scripts
      1 #!/usr/bin/env python
      2 # Copyright (c) 2009 Google Inc. All rights reserved.
      3 # Copyright (C) 2010 Chris Jerdonek (cjerdonek (at] 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 
     31 import logging
     32 import os
     33 import 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).
     50 def 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 
    113 def _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.
    140 def _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 
    167 def 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 
    230 def _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 
    235 def _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 
    248 if __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