| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/python | |
| 2 | |
| 3 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
| 4 # Use of this source code is governed by a BSD-style license that can be | |
| 5 # found in the LICENSE file. | |
| 6 | |
| 7 import logging | |
| 8 import optparse | |
| 9 import os | |
| 10 import sys | |
| 11 import types | |
| 12 import unittest | |
| 13 | |
| 14 from chromedriver_launcher import ChromeDriverLauncher | |
| 15 import chromedriver_paths | |
| 16 from gtest_text_test_runner import GTestTextTestRunner | |
| 17 | |
| 18 # Add the PYTHON_BINDINGS first so that our 'test' module is found instead of | |
| 19 # Python's. | |
| 20 sys.path = [chromedriver_paths.PYTHON_BINDINGS] + sys.path | |
| 21 | |
| 22 from selenium.webdriver.remote.webdriver import WebDriver | |
| 23 | |
| 24 | |
| 25 # Implementation inspired from unittest.main() | |
| 26 class Main(object): | |
| 27 """Main program for running WebDriver tests.""" | |
| 28 | |
| 29 _options, _args = None, None | |
| 30 TESTS_FILENAME = 'WEBDRIVER_TESTS' | |
| 31 _platform_map = { | |
| 32 'win32': 'win', | |
| 33 'darwin': 'mac', | |
| 34 'linux2': 'linux', | |
| 35 'linux3': 'linux', | |
| 36 } | |
| 37 TEST_PREFIX = 'selenium.test.selenium.webdriver.common.' | |
| 38 | |
| 39 def __init__(self): | |
| 40 self._tests_path = os.path.join(os.path.dirname(__file__), | |
| 41 self.TESTS_FILENAME) | |
| 42 self._ParseArgs() | |
| 43 self._Run() | |
| 44 | |
| 45 def _ParseArgs(self): | |
| 46 """Parse command line args.""" | |
| 47 parser = optparse.OptionParser() | |
| 48 parser.add_option( | |
| 49 '-v', '--verbose', action='store_true', default=False, | |
| 50 help='Output verbosely.') | |
| 51 parser.add_option( | |
| 52 '', '--log-file', type='string', default=None, | |
| 53 help='Provide a path to a file to which the logger will log') | |
| 54 parser.add_option( | |
| 55 '', '--driver-exe', type='string', default=None, | |
| 56 help='Path to the ChromeDriver executable.') | |
| 57 | |
| 58 self._options, self._args = parser.parse_args() | |
| 59 | |
| 60 # Setup logging - start with defaults | |
| 61 level = logging.WARNING | |
| 62 format = None | |
| 63 | |
| 64 if self._options.verbose: | |
| 65 level=logging.DEBUG | |
| 66 format='%(asctime)s %(levelname)-8s %(message)s' | |
| 67 | |
| 68 logging.basicConfig(level=level, format=format, | |
| 69 filename=self._options.log_file) | |
| 70 | |
| 71 @staticmethod | |
| 72 def _IsTestClass(obj): | |
| 73 """Returns whether |obj| is a unittest.TestCase.""" | |
| 74 return isinstance(obj, (type, types.ClassType)) and \ | |
| 75 issubclass(obj, unittest.TestCase) | |
| 76 | |
| 77 @staticmethod | |
| 78 def _GetModuleFromName(test_name): | |
| 79 """Return the module from the given test name. | |
| 80 | |
| 81 Args: | |
| 82 test_name: dot-separated string for a module, a test case or a test | |
| 83 method | |
| 84 Examples: omnibox (a module) | |
| 85 omnibox.OmniboxTest (a test case) | |
| 86 omnibox.OmniboxTest.testA (a test method) | |
| 87 | |
| 88 Returns: | |
| 89 tuple with first item corresponding to the module and second item | |
| 90 corresponding to the parts of the name that did not specify the module | |
| 91 Example: _GetModuleFromName('my_module.MyClass.testThis') returns | |
| 92 (my_module, ['MyClass', 'testThis']) | |
| 93 """ | |
| 94 parts = test_name.split('.') | |
| 95 parts_copy = parts[:] | |
| 96 while parts_copy: | |
| 97 try: | |
| 98 module = __import__('.'.join(parts_copy)) | |
| 99 break | |
| 100 except ImportError: | |
| 101 del parts_copy[-1] | |
| 102 if not parts_copy: raise | |
| 103 | |
| 104 for comp in parts[1:]: | |
| 105 if type(getattr(module, comp)) is not types.ModuleType: | |
| 106 break | |
| 107 module = getattr(module, comp) | |
| 108 return (module, parts[len(parts_copy):]) | |
| 109 | |
| 110 @staticmethod | |
| 111 def _GetTestClassFromName(test_name): | |
| 112 """Return the class for this test or None.""" | |
| 113 (obj, parts) = Main._GetModuleFromName(test_name) | |
| 114 for comp in parts: | |
| 115 if not Main._IsTestClass(getattr(obj, comp)): | |
| 116 break | |
| 117 obj = getattr(obj, comp) | |
| 118 if Main._IsTestClass(obj): | |
| 119 return obj | |
| 120 return None | |
| 121 | |
| 122 @staticmethod | |
| 123 def _SetTestClassAttributes(test_name, attribute_name, value): | |
| 124 """Sets attributes for all the test classes found from |test_name|. | |
| 125 | |
| 126 Args: | |
| 127 test_name: name of the test | |
| 128 attribute_name: name of the attribute to set | |
| 129 value: value to set the attribute to | |
| 130 """ | |
| 131 class_obj = Main._GetTestClassFromName(test_name) | |
| 132 if class_obj is not None: | |
| 133 class_objs = [class_obj] | |
| 134 else: | |
| 135 class_objs = [] | |
| 136 module, = Main._GetModuleFromName(class_obj) | |
| 137 for name in dir(module): | |
| 138 item = getattr(module, name) | |
| 139 if type(item) is type.TypeType: | |
| 140 class_objs += [item] | |
| 141 for c in class_objs: | |
| 142 setattr(c, attribute_name, value) | |
| 143 | |
| 144 @staticmethod | |
| 145 def _GetTestsFromName(name): | |
| 146 """Get a list of all test names from the given string. | |
| 147 | |
| 148 Args: | |
| 149 name: dot-separated string for a module, a test case or a test method. | |
| 150 Examples: omnibox (a module) | |
| 151 omnibox.OmniboxTest (a test case) | |
| 152 omnibox.OmniboxTest.testA (a test method) | |
| 153 | |
| 154 Returns: | |
| 155 [omnibox.OmniboxTest.testA, omnibox.OmniboxTest.testB, ...] | |
| 156 """ | |
| 157 def _GetTestsFromTestCase(class_obj): | |
| 158 """Return all test method names from given class object.""" | |
| 159 return [class_obj.__name__ + '.' + x for x in dir(class_obj) if | |
| 160 x.startswith('test')] | |
| 161 | |
| 162 def _GetTestsFromModule(module): | |
| 163 """Return all test method names from the given module object.""" | |
| 164 tests = [] | |
| 165 for name in dir(module): | |
| 166 obj = getattr(module, name) | |
| 167 if Main._IsTestClass(obj): | |
| 168 tests.extend([module.__name__ + '.' + x for x in | |
| 169 _GetTestsFromTestCase(obj)]) | |
| 170 return tests | |
| 171 (obj, parts) = Main._GetModuleFromName(name) | |
| 172 for comp in parts: | |
| 173 obj = getattr(obj, comp) | |
| 174 | |
| 175 if type(obj) == types.ModuleType: | |
| 176 return _GetTestsFromModule(obj) | |
| 177 elif Main._IsTestClass(obj): | |
| 178 return [module.__name__ + '.' + x for x in _GetTestsFromTestCase(obj)] | |
| 179 elif type(obj) == types.UnboundMethodType: | |
| 180 return [name] | |
| 181 else: | |
| 182 logging.warn('No tests in "%s"' % name) | |
| 183 return [] | |
| 184 | |
| 185 def _HasTestCases(self, module_string): | |
| 186 """Determines if we have any test case classes in the module | |
| 187 identified by |module_string|.""" | |
| 188 module = __import__(module_string) | |
| 189 for name in dir(module): | |
| 190 obj = getattr(module, name) | |
| 191 if Main._IsTestClass(obj): | |
| 192 return True | |
| 193 return False | |
| 194 | |
| 195 def _GetTestNames(self, args): | |
| 196 """Returns a suite of tests loaded from the given args. | |
| 197 | |
| 198 The given args can be either a module (ex: module1) or a testcase | |
| 199 (ex: module2.MyTestCase) or a test (ex: module1.MyTestCase.testX) | |
| 200 If empty, the tests in the already imported modules are loaded. | |
| 201 | |
| 202 Args: | |
| 203 args: [module1, module2, module3.testcase, module4.testcase.testX] | |
| 204 These modules or test cases or tests should be importable | |
| 205 """ | |
| 206 if not args: # Load tests ourselves | |
| 207 logging.debug("Reading %s", self._tests_path) | |
| 208 if not os.path.exists(self._tests_path): | |
| 209 logging.warn("%s missing. Cannot load tests." % self._tests_path) | |
| 210 else: | |
| 211 args = self._GetTestNamesFrom(self._tests_path) | |
| 212 return args | |
| 213 | |
| 214 @staticmethod | |
| 215 def _EvalDataFrom(filename): | |
| 216 """Return eval of python code from given file. | |
| 217 | |
| 218 The datastructure used in the file will be preserved. | |
| 219 """ | |
| 220 data_file = os.path.join(filename) | |
| 221 contents = open(data_file).read() | |
| 222 try: | |
| 223 ret = eval(contents, {'__builtins__': None}, None) | |
| 224 except: | |
| 225 print >>sys.stderr, '%s is an invalid data file.' % data_file | |
| 226 raise | |
| 227 return ret | |
| 228 | |
| 229 def _GetTestNamesFrom(self, filename): | |
| 230 modules = self._EvalDataFrom(filename) | |
| 231 all_names = modules.get('all', []) + \ | |
| 232 modules.get(self._platform_map[sys.platform], []) | |
| 233 args = [] | |
| 234 excluded = [] | |
| 235 # Find all excluded tests. Excluded tests begin with '-'. | |
| 236 for name in all_names: | |
| 237 if name.startswith('-'): # Exclude | |
| 238 excluded.extend(self._GetTestsFromName(self.TEST_PREFIX + name[1:])) | |
| 239 else: | |
| 240 args.extend(self._GetTestsFromName(self.TEST_PREFIX + name)) | |
| 241 for name in excluded: | |
| 242 args.remove(name) | |
| 243 if excluded: | |
| 244 logging.debug('Excluded %d test(s): %s' % (len(excluded), excluded)) | |
| 245 return args | |
| 246 | |
| 247 def _FakePytestHack(self): | |
| 248 """Adds a fake 'pytest' module to the system modules. | |
| 249 | |
| 250 A single test in text_handling_tests.py depends on the pytest module for | |
| 251 its test skipping capabilities. Without pytest, we can not run any tests | |
| 252 in the text_handling_tests.py module. | |
| 253 | |
| 254 We are not sure we want to add pytest to chrome's third party dependencies, | |
| 255 so for now create a fake pytest module so that we can at least import and | |
| 256 run all the tests that do not depend on it. Those depending on it are | |
| 257 disabled. | |
| 258 """ | |
| 259 import imp | |
| 260 sys.modules['pytest'] = imp.new_module('pytest') | |
| 261 sys.modules['pytest'].mark = imp.new_module('mark') | |
| 262 sys.modules['pytest'].mark.ignore_chrome = lambda x: x | |
| 263 | |
| 264 def _Run(self): | |
| 265 """Run the tests.""" | |
| 266 # TODO(kkania): Remove this hack. | |
| 267 self._FakePytestHack() | |
| 268 | |
| 269 test_names = self._GetTestNames(self._args) | |
| 270 | |
| 271 # The tests expect to run with preset 'driver' and 'webserver' class | |
| 272 # properties. | |
| 273 launcher = ChromeDriverLauncher(self._options.driver_exe, | |
| 274 chromedriver_paths.WEBDRIVER_TEST_DATA) | |
| 275 driver = WebDriver(launcher.GetURL(), {}) | |
| 276 # The tests expect a webserver. Since ChromeDriver also operates as one, | |
| 277 # just pass this dummy class with the right info. | |
| 278 class DummyWebserver: | |
| 279 pass | |
| 280 webserver = DummyWebserver() | |
| 281 webserver.port = launcher.GetPort() | |
| 282 for test in test_names: | |
| 283 Main._SetTestClassAttributes(test, 'driver', driver) | |
| 284 Main._SetTestClassAttributes(test, 'webserver', webserver) | |
| 285 | |
| 286 # Load and run the tests. | |
| 287 logging.debug('Loading tests from %s', test_names) | |
| 288 loaded_tests = unittest.defaultTestLoader.loadTestsFromNames(test_names) | |
| 289 test_suite = unittest.TestSuite() | |
| 290 test_suite.addTests(loaded_tests) | |
| 291 verbosity = 1 | |
| 292 if self._options.verbose: | |
| 293 verbosity = 2 | |
| 294 result = GTestTextTestRunner(verbosity=verbosity).run(test_suite) | |
| 295 launcher.Kill() | |
| 296 sys.exit(not result.wasSuccessful()) | |
| 297 | |
| 298 | |
| 299 if __name__ == '__main__': | |
| 300 Main() | |
| OLD | NEW |