1# Copyright 2016 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import inspect 6import logging 7import re 8import unittest 9 10from py_utils import cloud_storage 11from telemetry.internal.browser import browser_finder 12from telemetry.testing import browser_test_context 13from telemetry.util import wpr_modes 14 15 16DEFAULT_LOG_FORMAT = ( 17 '(%(levelname)s) %(asctime)s %(module)s.%(funcName)s:%(lineno)d ' 18 '%(message)s') 19 20 21class SeriallyExecutedBrowserTestCase(unittest.TestCase): 22 def __init__(self, methodName): 23 super(SeriallyExecutedBrowserTestCase, self).__init__(methodName) 24 self._private_methodname = methodName 25 26 def shortName(self): 27 """Returns the method name this test runs, without the package prefix.""" 28 return self._private_methodname 29 30 @classmethod 31 def Name(cls): 32 return cls.__name__ 33 34 @classmethod 35 def AddCommandlineArgs(cls, parser): 36 pass 37 38 @classmethod 39 def SetUpProcess(cls): 40 """ Set up testing logic before running the test case. 41 This is guaranteed to be called only once for all the tests before the test 42 suite runs. 43 """ 44 finder_options = browser_test_context.GetCopy().finder_options 45 cls._finder_options = finder_options 46 47 # Set up logging based on the verbosity passed from the parent to 48 # the child process. 49 if finder_options.verbosity >= 2: 50 logging.getLogger().setLevel(logging.DEBUG) 51 elif finder_options.verbosity: 52 logging.getLogger().setLevel(logging.INFO) 53 else: 54 logging.getLogger().setLevel(logging.WARNING) 55 logging.basicConfig(format=DEFAULT_LOG_FORMAT) 56 57 cls.platform = None 58 cls.browser = None 59 cls._browser_to_create = None 60 cls._browser_options = None 61 62 @classmethod 63 def SetBrowserOptions(cls, browser_options): 64 """Sets the browser option for the browser to create. 65 66 Args: 67 browser_options: Browser options object for the browser we want to test. 68 """ 69 cls._browser_options = browser_options 70 cls._browser_to_create = browser_finder.FindBrowser(browser_options) 71 if not cls.platform: 72 cls.platform = cls._browser_to_create.platform 73 cls.platform.network_controller.InitializeIfNeeded() 74 else: 75 assert cls.platform == cls._browser_to_create.platform, ( 76 'All browser launches within same test suite must use browsers on ' 77 'the same platform') 78 79 @classmethod 80 def StartWPRServer(cls, archive_path=None, archive_bucket=None): 81 """Start a webpage replay server. 82 83 Args: 84 archive_path: Path to the WPR file. If there is a corresponding sha1 file, 85 this archive will be automatically downloaded from Google Storage. 86 archive_bucket: The bucket to look for the WPR archive. 87 """ 88 assert cls._browser_options, ( 89 'Browser options must be set with |SetBrowserOptions| prior to ' 90 'starting WPR') 91 assert not cls.browser, 'WPR must be started prior to browser being started' 92 93 cloud_storage.GetIfChanged(archive_path, archive_bucket) 94 cls.platform.network_controller.Open(wpr_modes.WPR_REPLAY, []) 95 cls.platform.network_controller.StartReplay(archive_path=archive_path) 96 97 @classmethod 98 def StopWPRServer(cls): 99 cls.platform.network_controller.StopReplay() 100 101 @classmethod 102 def StartBrowser(cls): 103 assert cls._browser_options, ( 104 'Browser options must be set with |SetBrowserOptions| prior to ' 105 'starting WPR') 106 assert not cls.browser, 'Browser is started. Must close it first' 107 108 cls.browser = cls._browser_to_create.Create(cls._browser_options) 109 110 @classmethod 111 def StopBrowser(cls): 112 assert cls.browser, 'Browser is not started' 113 cls.browser.Close() 114 cls.browser = None 115 116 @classmethod 117 def TearDownProcess(cls): 118 """ Tear down the testing logic after running the test cases. 119 This is guaranteed to be called only once for all the tests after the test 120 suite finishes running. 121 """ 122 123 if cls.platform: 124 cls.platform.StopAllLocalServers() 125 cls.platform.network_controller.Close() 126 if cls.browser: 127 cls.StopBrowser() 128 129 @classmethod 130 def SetStaticServerDirs(cls, dirs_path): 131 assert cls.platform 132 assert isinstance(dirs_path, list) 133 cls.platform.SetHTTPServerDirectories(dirs_path) 134 135 @classmethod 136 def UrlOfStaticFilePath(cls, file_path): 137 return cls.platform.http_server.UrlOf(file_path) 138 139 140def LoadAllTestsInModule(module): 141 """ Load all tests & generated browser tests in a given module. 142 143 This is supposed to be invoke in load_tests() method of your test modules that 144 use browser_test_runner framework to discover & generate the tests to be 145 picked up by the test runner. Here is the example of how your test module 146 should looks like: 147 148 ################## my_awesome_browser_tests.py ################ 149 import sys 150 151 from telemetry.testing import serially_executed_browser_test_case 152 ... 153 154 class TestSimpleBrowser( 155 serially_executed_browser_test_case.SeriallyExecutedBrowserTestCase): 156 ... 157 ... 158 159 def load_tests(loader, tests, pattern): 160 return serially_executed_browser_test_case.LoadAllTestsInModule( 161 sys.modules[__name__]) 162 ################################################################# 163 164 Args: 165 module: the module which contains test cases classes. 166 167 Returns: 168 an instance of unittest.TestSuite, which contains all the tests & generated 169 test cases to be run. 170 """ 171 suite = unittest.TestSuite() 172 test_context = browser_test_context.GetCopy() 173 if not test_context: 174 return suite 175 for _, obj in inspect.getmembers(module): 176 if (inspect.isclass(obj) and 177 issubclass(obj, SeriallyExecutedBrowserTestCase)): 178 # We bail out early if this class doesn't match the targeted 179 # test_class in test_context to avoid calling GenerateTestCases 180 # for tests that we don't intend to run. This is to avoid possible errors 181 # in GenerateTestCases as the test class may define custom options in 182 # the finder_options object, and hence would raise error if they can't 183 # find their custom options in finder_options object. 184 if test_context.test_class != obj: 185 continue 186 for test in GenerateTestCases( 187 test_class=obj, finder_options=test_context.finder_options): 188 if test.id() in test_context.test_case_ids_to_run: 189 suite.addTest(test) 190 return suite 191 192 193def _GenerateTestMethod(based_method, args): 194 return lambda self: based_method(self, *args) 195 196 197_TEST_GENERATOR_PREFIX = 'GenerateTestCases_' 198_INVALID_TEST_NAME_RE = re.compile(r'[^a-zA-Z0-9_]') 199 200def _ValidateTestMethodname(test_name): 201 assert not bool(_INVALID_TEST_NAME_RE.search(test_name)) 202 203 204def GenerateTestCases(test_class, finder_options): 205 test_cases = [] 206 for name, method in inspect.getmembers( 207 test_class, predicate=inspect.ismethod): 208 if name.startswith('test'): 209 # Do not allow method names starting with "test" in these 210 # subclasses, to avoid collisions with Python's unit test runner. 211 raise Exception('Name collision with Python\'s unittest runner: %s' % 212 name) 213 elif name.startswith('Test'): 214 # Pass these through for the time being. We may want to rethink 215 # how they are handled in the future. 216 test_cases.append(test_class(name)) 217 elif name.startswith(_TEST_GENERATOR_PREFIX): 218 based_method_name = name[len(_TEST_GENERATOR_PREFIX):] 219 assert hasattr(test_class, based_method_name), ( 220 '%s is specified but based method %s does not exist' % 221 (name, based_method_name)) 222 based_method = getattr(test_class, based_method_name) 223 for generated_test_name, args in method(finder_options): 224 _ValidateTestMethodname(generated_test_name) 225 setattr(test_class, generated_test_name, _GenerateTestMethod( 226 based_method, args)) 227 test_cases.append(test_class(generated_test_name)) 228 return test_cases 229