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