Home | History | Annotate | Download | only in unittest
      1 # Copyright 2012 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 
      5 import logging
      6 import unittest
      7 
      8 from telemetry import decorators
      9 from telemetry.core import browser_finder
     10 from telemetry.core import browser_options
     11 from telemetry.core import command_line
     12 from telemetry.core import discover
     13 from telemetry.unittest import json_results
     14 from telemetry.unittest import progress_reporter
     15 
     16 
     17 class Config(object):
     18   def __init__(self, top_level_dir, test_dirs, progress_reporters):
     19     self._top_level_dir = top_level_dir
     20     self._test_dirs = tuple(test_dirs)
     21     self._progress_reporters = tuple(progress_reporters)
     22 
     23   @property
     24   def top_level_dir(self):
     25     return self._top_level_dir
     26 
     27   @property
     28   def test_dirs(self):
     29     return self._test_dirs
     30 
     31   @property
     32   def progress_reporters(self):
     33     return self._progress_reporters
     34 
     35 
     36 def Discover(start_dir, top_level_dir=None, pattern='test*.py'):
     37   loader = unittest.defaultTestLoader
     38   loader.suiteClass = progress_reporter.TestSuite
     39 
     40   test_suites = []
     41   modules = discover.DiscoverModules(start_dir, top_level_dir, pattern)
     42   for module in modules:
     43     if hasattr(module, 'suite'):
     44       suite = module.suite()
     45     else:
     46       suite = loader.loadTestsFromModule(module)
     47     if suite.countTestCases():
     48       test_suites.append(suite)
     49   return test_suites
     50 
     51 
     52 def FilterSuite(suite, predicate):
     53   new_suite = suite.__class__()
     54   for test in suite:
     55     if isinstance(test, unittest.TestSuite):
     56       subsuite = FilterSuite(test, predicate)
     57       if subsuite.countTestCases():
     58         new_suite.addTest(subsuite)
     59     else:
     60       assert isinstance(test, unittest.TestCase)
     61       if predicate(test):
     62         new_suite.addTest(test)
     63 
     64   return new_suite
     65 
     66 
     67 def DiscoverTests(search_dirs, top_level_dir, possible_browser,
     68                   selected_tests=None, selected_tests_are_exact=False,
     69                   run_disabled_tests=False):
     70   def IsTestSelected(test):
     71     if selected_tests:
     72       found = False
     73       for name in selected_tests:
     74         if selected_tests_are_exact:
     75           if name == test.id():
     76             found = True
     77         else:
     78           if name in test.id():
     79             found = True
     80       if not found:
     81         return False
     82     if run_disabled_tests:
     83       return True
     84     # pylint: disable=W0212
     85     if not hasattr(test, '_testMethodName'):
     86       return True
     87     method = getattr(test, test._testMethodName)
     88     return decorators.IsEnabled(method, possible_browser)
     89 
     90   wrapper_suite = progress_reporter.TestSuite()
     91   for search_dir in search_dirs:
     92     wrapper_suite.addTests(Discover(search_dir, top_level_dir, '*_unittest.py'))
     93   return FilterSuite(wrapper_suite, IsTestSelected)
     94 
     95 
     96 def RestoreLoggingLevel(func):
     97   def _LoggingRestoreWrapper(*args, **kwargs):
     98     # Cache the current logging level, this needs to be done before calling
     99     # parser.parse_args, which changes logging level based on verbosity
    100     # setting.
    101     logging_level = logging.getLogger().getEffectiveLevel()
    102     try:
    103       return func(*args, **kwargs)
    104     finally:
    105       # Restore logging level, which may be changed in parser.parse_args.
    106       logging.getLogger().setLevel(logging_level)
    107 
    108   return _LoggingRestoreWrapper
    109 
    110 
    111 config = None
    112 
    113 
    114 class RunTestsCommand(command_line.OptparseCommand):
    115   """Run unit tests"""
    116 
    117   usage = '[test_name ...] [<options>]'
    118 
    119   @classmethod
    120   def CreateParser(cls):
    121     options = browser_options.BrowserFinderOptions()
    122     options.browser_type = 'any'
    123     parser = options.CreateParser('%%prog %s' % cls.usage)
    124     return parser
    125 
    126   @classmethod
    127   def AddCommandLineArgs(cls, parser):
    128     parser.add_option('--repeat-count', type='int', default=1,
    129                       help='Repeats each a provided number of times.')
    130     parser.add_option('-d', '--also-run-disabled-tests',
    131                       dest='run_disabled_tests',
    132                       action='store_true', default=False,
    133                       help='Ignore @Disabled and @Enabled restrictions.')
    134     parser.add_option('--retry-limit', type='int',
    135                       help='Retry each failure up to N times'
    136                            ' to de-flake things.')
    137     parser.add_option('--exact-test-filter', action='store_true', default=False,
    138                       help='Treat test filter as exact matches (default is '
    139                            'substring matches).')
    140     json_results.AddOptions(parser)
    141 
    142   @classmethod
    143   def ProcessCommandLineArgs(cls, parser, args):
    144     if args.verbosity == 0:
    145       logging.getLogger().setLevel(logging.WARN)
    146 
    147     # We retry failures by default unless we're running a list of tests
    148     # explicitly.
    149     if args.retry_limit is None and not args.positional_args:
    150       args.retry_limit = 3
    151 
    152     try:
    153       possible_browser = browser_finder.FindBrowser(args)
    154     except browser_finder.BrowserFinderException, ex:
    155       parser.error(ex)
    156 
    157     if not possible_browser:
    158       parser.error('No browser found of type %s. Cannot run tests.\n'
    159                    'Re-run with --browser=list to see '
    160                    'available browser types.' % args.browser_type)
    161 
    162     json_results.ValidateArgs(parser, args)
    163 
    164   def Run(self, args):
    165     possible_browser = browser_finder.FindBrowser(args)
    166 
    167     test_suite, result = self.RunOneSuite(possible_browser, args)
    168 
    169     results = [result]
    170 
    171     failed_tests = json_results.FailedTestNames(test_suite, result)
    172     retry_limit = args.retry_limit
    173 
    174     while retry_limit and failed_tests:
    175       args.positional_args = failed_tests
    176       args.exact_test_filter = True
    177 
    178       _, result = self.RunOneSuite(possible_browser, args)
    179       results.append(result)
    180 
    181       failed_tests = json_results.FailedTestNames(test_suite, result)
    182       retry_limit -= 1
    183 
    184     full_results = json_results.FullResults(args, test_suite, results)
    185     json_results.WriteFullResultsIfNecessary(args, full_results)
    186 
    187     err_occurred, err_str = json_results.UploadFullResultsIfNecessary(
    188         args, full_results)
    189     if err_occurred:
    190       for line in err_str.splitlines():
    191         logging.error(line)
    192       return 1
    193 
    194     return json_results.ExitCodeFromFullResults(full_results)
    195 
    196   def RunOneSuite(self, possible_browser, args):
    197     test_suite = DiscoverTests(config.test_dirs, config.top_level_dir,
    198                                possible_browser, args.positional_args,
    199                                args.exact_test_filter, args.run_disabled_tests)
    200     runner = progress_reporter.TestRunner()
    201     result = runner.run(test_suite, config.progress_reporters,
    202                         args.repeat_count, args)
    203     return test_suite, result
    204 
    205   @classmethod
    206   @RestoreLoggingLevel
    207   def main(cls, args=None):
    208     return super(RunTestsCommand, cls).main(args)
    209