| OLD | NEW |
| (Empty) |
| 1 # Copyright (C) 2012 Google, Inc. | |
| 2 # Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org) | |
| 3 # | |
| 4 # Redistribution and use in source and binary forms, with or without | |
| 5 # modification, are permitted provided that the following conditions | |
| 6 # are met: | |
| 7 # 1. Redistributions of source code must retain the above copyright | |
| 8 # notice, this list of conditions and the following disclaimer. | |
| 9 # 2. Redistributions in binary form must reproduce the above copyright | |
| 10 # notice, this list of conditions and the following disclaimer in the | |
| 11 # documentation and/or other materials provided with the distribution. | |
| 12 # | |
| 13 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND | |
| 14 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
| 15 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| 16 # DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR | |
| 17 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| 18 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| 19 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| 20 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| 21 # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 22 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 23 | |
| 24 """unit testing code for webkitpy.""" | |
| 25 | |
| 26 import StringIO | |
| 27 import logging | |
| 28 import multiprocessing | |
| 29 import optparse | |
| 30 import os | |
| 31 import sys | |
| 32 import time | |
| 33 import traceback | |
| 34 import unittest | |
| 35 | |
| 36 from webkitpy.common.webkit_finder import WebKitFinder | |
| 37 from webkitpy.common.system.filesystem import FileSystem | |
| 38 from webkitpy.common.system.executive import Executive | |
| 39 from webkitpy.test.finder import Finder | |
| 40 from webkitpy.test.printer import Printer | |
| 41 from webkitpy.test.runner import Runner, unit_test_name | |
| 42 | |
| 43 _log = logging.getLogger(__name__) | |
| 44 | |
| 45 | |
| 46 up = os.path.dirname | |
| 47 webkit_root = up(up(up(up(up(os.path.abspath(__file__)))))) | |
| 48 | |
| 49 | |
| 50 def main(): | |
| 51 filesystem = FileSystem() | |
| 52 wkf = WebKitFinder(filesystem) | |
| 53 tester = Tester(filesystem, wkf) | |
| 54 tester.add_tree(wkf.path_from_webkit_base('Tools', 'Scripts'), 'webkitpy') | |
| 55 | |
| 56 tester.skip(('webkitpy.common.checkout.scm.scm_unittest',), 'are really, rea
lly, slow', 31818) | |
| 57 if sys.platform == 'win32': | |
| 58 tester.skip(('webkitpy.common.checkout', 'webkitpy.common.config', 'webk
itpy.tool', 'webkitpy.w3c', 'webkitpy.layout_tests.layout_package.bot_test_expec
tations'), 'fail horribly on win32', 54526) | |
| 59 | |
| 60 # This only needs to run on Unix, so don't worry about win32 for now. | |
| 61 appengine_sdk_path = '/usr/local/google_appengine' | |
| 62 if os.path.exists(appengine_sdk_path): | |
| 63 if not appengine_sdk_path in sys.path: | |
| 64 sys.path.append(appengine_sdk_path) | |
| 65 import dev_appserver | |
| 66 from google.appengine.dist import use_library | |
| 67 use_library('django', '1.2') | |
| 68 dev_appserver.fix_sys_path() | |
| 69 tester.add_tree(wkf.path_from_webkit_base('Tools', 'TestResultServer')) | |
| 70 else: | |
| 71 _log.info('Skipping TestResultServer tests; the Google AppEngine Python
SDK is not installed.') | |
| 72 | |
| 73 return not tester.run() | |
| 74 | |
| 75 | |
| 76 class Tester(object): | |
| 77 def __init__(self, filesystem=None, webkit_finder=None): | |
| 78 self.filesystem = filesystem or FileSystem() | |
| 79 self.executive = Executive() | |
| 80 self.finder = Finder(self.filesystem) | |
| 81 self.printer = Printer(sys.stderr) | |
| 82 self.webkit_finder = webkit_finder or WebKitFinder(self.filesystem) | |
| 83 self._options = None | |
| 84 | |
| 85 def add_tree(self, top_directory, starting_subdirectory=None): | |
| 86 self.finder.add_tree(top_directory, starting_subdirectory) | |
| 87 | |
| 88 def skip(self, names, reason, bugid): | |
| 89 self.finder.skip(names, reason, bugid) | |
| 90 | |
| 91 def _parse_args(self, argv): | |
| 92 parser = optparse.OptionParser(usage='usage: %prog [options] [args...]') | |
| 93 parser.add_option('-a', '--all', action='store_true', default=False, | |
| 94 help='run all the tests') | |
| 95 parser.add_option('-c', '--coverage', action='store_true', default=False
, | |
| 96 help='generate code coverage info') | |
| 97 parser.add_option('-j', '--child-processes', action='store', type='int',
default=(1 if sys.platform == 'win32' else multiprocessing.cpu_count()), | |
| 98 help='number of tests to run in parallel (default=%def
ault)') | |
| 99 parser.add_option('-p', '--pass-through', action='store_true', default=F
alse, | |
| 100 help='be debugger friendly by passing captured output
through to the system') | |
| 101 parser.add_option('-q', '--quiet', action='store_true', default=False, | |
| 102 help='run quietly (errors, warnings, and progress only
)') | |
| 103 parser.add_option('-t', '--timing', action='store_true', default=False, | |
| 104 help='display per-test execution time (implies --verbo
se)') | |
| 105 parser.add_option('-v', '--verbose', action='count', default=0, | |
| 106 help='verbose output (specify once for individual test
results, twice for debug messages)') | |
| 107 | |
| 108 parser.epilog = ('[args...] is an optional list of modules, test_classes
, or individual tests. ' | |
| 109 'If no args are given, all the tests will be run.') | |
| 110 | |
| 111 return parser.parse_args(argv) | |
| 112 | |
| 113 def run(self): | |
| 114 argv = sys.argv[1:] | |
| 115 self._options, args = self._parse_args(argv) | |
| 116 | |
| 117 # Make sure PYTHONPATH is set up properly. | |
| 118 sys.path = self.finder.additional_paths(sys.path) + sys.path | |
| 119 | |
| 120 # FIXME: coverage needs to be in sys.path for its internal imports to wo
rk. | |
| 121 thirdparty_path = self.webkit_finder.path_from_webkit_base('Tools', 'Scr
ipts', 'webkitpy', 'thirdparty') | |
| 122 if not thirdparty_path in sys.path: | |
| 123 sys.path.append(thirdparty_path) | |
| 124 | |
| 125 self.printer.configure(self._options) | |
| 126 | |
| 127 # Do this after configuring the printer, so that logging works properly. | |
| 128 if self._options.coverage: | |
| 129 argv = ['-j', '1'] + [arg for arg in argv if arg not in ('-c', '--co
verage', '-j', '--child-processes')] | |
| 130 _log.warning('Checking code coverage, so running things serially') | |
| 131 return self._run_under_coverage(argv) | |
| 132 | |
| 133 self.finder.clean_trees() | |
| 134 | |
| 135 names = self.finder.find_names(args, self._options.all) | |
| 136 if not names: | |
| 137 _log.error('No tests to run') | |
| 138 return False | |
| 139 | |
| 140 return self._run_tests(names) | |
| 141 | |
| 142 def _run_under_coverage(self, argv): | |
| 143 # coverage doesn't run properly unless its parent dir is in PYTHONPATH. | |
| 144 # This means we need to add that dir to the environment. Also, the | |
| 145 # report output is best when the paths are relative to the Scripts dir. | |
| 146 dirname = self.filesystem.dirname | |
| 147 script_dir = dirname(dirname(dirname(__file__))) | |
| 148 thirdparty_dir = self.filesystem.join(script_dir, 'webkitpy', 'thirdpart
y') | |
| 149 | |
| 150 env = os.environ.copy() | |
| 151 python_path = env.get('PYTHONPATH', '') | |
| 152 python_path = python_path + os.pathsep + thirdparty_dir | |
| 153 env['PYTHONPATH'] = python_path | |
| 154 | |
| 155 prefix_cmd = [sys.executable, 'webkitpy/thirdparty/coverage'] | |
| 156 exit_code = self.executive.call(prefix_cmd + ['run', __file__] + argv, c
wd=script_dir, env=env) | |
| 157 if not exit_code: | |
| 158 exit_code = self.executive.call(prefix_cmd + ['report', '--omit', 'w
ebkitpy/thirdparty/*,/usr/*,/Library/*'], cwd=script_dir, env=env) | |
| 159 return (exit_code == 0) | |
| 160 | |
| 161 def _run_tests(self, names): | |
| 162 self.printer.write_update("Checking imports ...") | |
| 163 if not self._check_imports(names): | |
| 164 return False | |
| 165 | |
| 166 self.printer.write_update("Finding the individual test methods ...") | |
| 167 loader = unittest.TestLoader() | |
| 168 tests = self._test_names(loader, names) | |
| 169 | |
| 170 self.printer.write_update("Running the tests ...") | |
| 171 self.printer.num_tests = len(tests) | |
| 172 start = time.time() | |
| 173 test_runner = Runner(self.printer, loader, self.webkit_finder) | |
| 174 test_runner.run(tests, self._options.child_processes) | |
| 175 | |
| 176 self.printer.print_result(time.time() - start) | |
| 177 | |
| 178 return not self.printer.num_errors and not self.printer.num_failures | |
| 179 | |
| 180 def _check_imports(self, names): | |
| 181 for name in names: | |
| 182 if self.finder.is_module(name): | |
| 183 # if we failed to load a name and it looks like a module, | |
| 184 # try importing it directly, because loadTestsFromName() | |
| 185 # produces lousy error messages for bad modules. | |
| 186 try: | |
| 187 __import__(name) | |
| 188 except ImportError: | |
| 189 _log.fatal('Failed to import %s:' % name) | |
| 190 self._log_exception() | |
| 191 return False | |
| 192 return True | |
| 193 | |
| 194 def _test_names(self, loader, names): | |
| 195 tests = [] | |
| 196 for name in names: | |
| 197 tests.extend(self._all_test_names(loader.loadTestsFromName(name, Non
e))) | |
| 198 return tests | |
| 199 | |
| 200 def _all_test_names(self, suite): | |
| 201 names = [] | |
| 202 if hasattr(suite, '_tests'): | |
| 203 for t in suite._tests: | |
| 204 names.extend(self._all_test_names(t)) | |
| 205 else: | |
| 206 names.append(unit_test_name(suite)) | |
| 207 return names | |
| 208 | |
| 209 def _log_exception(self): | |
| 210 s = StringIO.StringIO() | |
| 211 traceback.print_exc(file=s) | |
| 212 for l in s.buflist: | |
| 213 _log.error(' ' + l.rstrip()) | |
| 214 | |
| 215 | |
| 216 | |
| 217 if __name__ == '__main__': | |
| 218 sys.exit(main()) | |
| OLD | NEW |