| #!/usr/bin/env python |
| # Copyright (c) 2009 Google Inc. All rights reserved. |
| # Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org) |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are |
| # met: |
| # |
| # * Redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer. |
| # * Redistributions in binary form must reproduce the above |
| # copyright notice, this list of conditions and the following disclaimer |
| # in the documentation and/or other materials provided with the |
| # distribution. |
| # * Neither the name of Google Inc. nor the names of its |
| # contributors may be used to endorse or promote products derived from |
| # this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| import logging |
| import os |
| import sys |
| |
| # Do not import anything from webkitpy prior to cleaning webkitpy of |
| # orphaned *.pyc files. This ensures that no orphaned *.pyc files are |
| # accidentally imported during the course of this script. |
| # |
| # Also, do not import or execute any Python code incompatible with |
| # Python 2.4 until after execution of the init() method below. |
| |
| |
| _log = logging.getLogger("test-webkitpy") |
| |
| |
| # Verbose logging is useful for debugging test-webkitpy code that runs |
| # before the actual unit tests -- things like autoinstall downloading and |
| # unit-test auto-detection logic. This is different from verbose logging |
| # of the unit tests themselves (i.e. the unittest module's --verbose flag). |
| def configure_logging(is_verbose_logging): |
| """Configure the root logger. |
| |
| Configure the root logger not to log any messages from webkitpy -- |
| except for messages from the autoinstall module. Also set the |
| logging level as described below. |
| |
| Args: |
| is_verbose_logging: A boolean value of whether logging should be |
| verbose. If this parameter is true, the logging |
| level for the handler on the root logger is set to |
| logging.DEBUG. Otherwise, it is set to logging.INFO. |
| |
| """ |
| # Don't use the Python ternary operator here so that this method will |
| # work with Python 2.4. |
| if is_verbose_logging: |
| logging_level = logging.DEBUG |
| else: |
| logging_level = logging.INFO |
| |
| handler = logging.StreamHandler(sys.stderr) |
| # We constrain the level on the handler rather than on the root |
| # logger itself. This is probably better because the handler is |
| # configured and known only to this module, whereas the root logger |
| # is an object shared (and potentially modified) by many modules. |
| # Modifying the handler, then, is less intrusive and less likely to |
| # interfere with modifications made by other modules (e.g. in unit |
| # tests). |
| handler.setLevel(logging_level) |
| formatter = logging.Formatter("%(name)s: %(levelname)-8s %(message)s") |
| handler.setFormatter(formatter) |
| |
| logger = logging.getLogger() |
| logger.addHandler(handler) |
| logger.setLevel(logging.NOTSET) |
| |
| # Filter out most webkitpy messages. |
| # |
| # Messages can be selectively re-enabled for this script by updating |
| # this method accordingly. |
| def filter(record): |
| """Filter out autoinstall and non-third-party webkitpy messages.""" |
| # FIXME: Figure out a way not to use strings here, for example by |
| # using syntax like webkitpy.test.__name__. We want to be |
| # sure not to import any non-Python 2.4 code, though, until |
| # after the version-checking code has executed. |
| if (record.name.startswith("webkitpy.common.system.autoinstall") or |
| record.name.startswith("webkitpy.test")): |
| return True |
| if record.name.startswith("webkitpy"): |
| return False |
| return True |
| |
| testing_filter = logging.Filter() |
| testing_filter.filter = filter |
| |
| # Display a message so developers are not mystified as to why |
| # logging does not work in the unit tests. |
| _log.info("Suppressing most webkitpy logging while running unit tests.") |
| handler.addFilter(testing_filter) |
| |
| |
| def _clean_pyc_files(dir_to_clean, paths_not_to_log): |
| """Delete from a directory all .pyc files that have no .py file. |
| |
| Args: |
| dir_to_clean: The path to the directory to clean. |
| paths_not_to_log: A list of paths to .pyc files whose deletions should |
| not be logged. This list should normally include |
| only test .pyc files. |
| |
| """ |
| _log.debug("Cleaning orphaned *.pyc files from: %s" % dir_to_clean) |
| |
| # Normalize paths not to log. |
| paths_not_to_log = [os.path.abspath(path) for path in paths_not_to_log] |
| |
| for dir_path, dir_names, file_names in os.walk(dir_to_clean): |
| for file_name in file_names: |
| if file_name.endswith(".pyc") and file_name[:-1] not in file_names: |
| file_path = os.path.join(dir_path, file_name) |
| if os.path.abspath(file_path) not in paths_not_to_log: |
| _log.info("Deleting orphan *.pyc file: %s" % file_path) |
| os.remove(file_path) |
| |
| |
| # As a substitute for a unit test, this method tests _clean_pyc_files() |
| # in addition to calling it. We chose not to use the unittest module |
| # because _clean_pyc_files() is called only once and is not used elsewhere. |
| def _clean_packages_with_test(external_package_paths): |
| webkitpy_dir = os.path.join(os.path.dirname(__file__), "webkitpy") |
| package_paths = [webkitpy_dir] + external_package_paths |
| |
| # The test .pyc file is-- |
| # webkitpy/python24/TEMP_test-webkitpy_test_pyc_file.pyc. |
| test_path = os.path.join(webkitpy_dir, "python24", |
| "TEMP_test-webkitpy_test_pyc_file.pyc") |
| |
| test_file = open(test_path, "w") |
| try: |
| test_file.write("Test .pyc file generated by test-webkitpy.") |
| finally: |
| test_file.close() |
| |
| # Confirm that the test file exists so that when we check that it does |
| # not exist, the result is meaningful. |
| if not os.path.exists(test_path): |
| raise Exception("Test .pyc file not created: %s" % test_path) |
| |
| for path in package_paths: |
| _clean_pyc_files(path, [test_path]) |
| |
| if os.path.exists(test_path): |
| raise Exception("Test .pyc file not deleted: %s" % test_path) |
| |
| |
| def init(command_args, external_package_paths): |
| """Execute code prior to importing from webkitpy.unittests. |
| |
| Args: |
| command_args: The list of command-line arguments -- usually |
| sys.argv[1:]. |
| |
| """ |
| verbose_logging_flag = "--verbose-logging" |
| is_verbose_logging = verbose_logging_flag in command_args |
| if is_verbose_logging: |
| # Remove the flag so it doesn't cause unittest.main() to error out. |
| # |
| # FIXME: Get documentation for the --verbose-logging flag to show |
| # up in the usage instructions, which are currently generated |
| # by unittest.main(). It's possible that this will require |
| # re-implementing the option parser for unittest.main() |
| # since there may not be an easy way to modify its existing |
| # option parser. |
| sys.argv.remove(verbose_logging_flag) |
| |
| configure_logging(is_verbose_logging) |
| _log.debug("Verbose WebKit logging enabled.") |
| |
| # We clean orphaned *.pyc files from the packages prior to importing from |
| # them to make sure that no import statements falsely succeed. |
| # This helps to check that import statements have been updated correctly |
| # after any file moves. Otherwise, incorrect import statements can |
| # be masked. |
| # |
| # For example, if webkitpy/python24/versioning.py were moved to a |
| # different location without changing any import statements, and if |
| # the corresponding .pyc file were left behind without deleting it, |
| # then "import webkitpy.python24.versioning" would continue to succeed |
| # even though it would fail for someone checking out a fresh copy |
| # of the source tree. This is because of a Python feature: |
| # |
| # "It is possible to have a file called spam.pyc (or spam.pyo when -O |
| # is used) without a file spam.py for the same module. This can be used |
| # to distribute a library of Python code in a form that is moderately |
| # hard to reverse engineer." |
| # |
| # ( http://docs.python.org/tutorial/modules.html#compiled-python-files ) |
| # |
| # Deleting the orphaned .pyc file prior to importing, however, would |
| # cause an ImportError to occur on import as desired. |
| _clean_packages_with_test(external_package_paths) |
| |
| import webkitpy.python24.versioning as versioning |
| |
| versioning.check_version(log=_log) |
| |
| (comparison, current_version, minimum_version) = \ |
| versioning.compare_version() |
| |
| if comparison > 0: |
| # Then the current version is later than the minimum version. |
| message = ("You are testing webkitpy with a Python version (%s) " |
| "higher than the minimum version (%s) it was meant " |
| "to support." % (current_version, minimum_version)) |
| _log.warn(message) |
| |
| |
| def _path_from_webkit_root(*components): |
| webkit_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) |
| return os.path.join(webkit_root, *components) |
| |
| |
| def _test_import(module_path): |
| try: |
| sys.path.append(os.path.dirname(module_path)) |
| module_name = os.path.basename(module_path) |
| __import__(module_name) |
| return True |
| except Exception, e: |
| message = "Skipping tests in %s due to failure (%s)." % (module_path, e) |
| if module_name.endswith("QueueStatusServer"): |
| message += " This module is optional. The failure is likely due to a missing Google AppEngine install. (http://code.google.com/appengine/downloads.html)" |
| _log.warn(message) |
| return False |
| |
| if __name__ == "__main__": |
| # FIXME: We should probably test each package separately to avoid naming conflicts. |
| external_package_paths = [ |
| _path_from_webkit_root('Source', 'WebKit2', 'Scripts', 'webkit2'), |
| _path_from_webkit_root('Tools', 'QueueStatusServer'), |
| ] |
| init(sys.argv[1:], external_package_paths) |
| |
| # We import the unit test code after init() to ensure that any |
| # Python version warnings are displayed in case an error occurs |
| # while interpreting webkitpy.unittests. This also allows |
| # logging to be configured prior to importing -- for example to |
| # enable the display of autoinstall logging.log messages while |
| # running the unit tests. |
| from webkitpy.test.main import Tester |
| |
| external_package_paths = filter(_test_import, external_package_paths) |
| |
| Tester().run_tests(sys.argv, external_package_paths) |