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