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 |