Home | History | Annotate | Download | only in telemetry
      1 # Copyright 2013 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 optparse
      7 import os
      8 import shutil
      9 import sys
     10 import zipfile
     11 
     12 from telemetry import decorators
     13 from telemetry.core import browser_finder
     14 from telemetry.core import command_line
     15 from telemetry.core import util
     16 from telemetry.page import page_runner
     17 from telemetry.page import cloud_storage
     18 from telemetry.page import page_set
     19 from telemetry.page import page_test
     20 from telemetry.page import test_expectations
     21 from telemetry.results import page_test_results
     22 
     23 
     24 Disabled = decorators.Disabled
     25 Enabled = decorators.Enabled
     26 
     27 
     28 class Test(command_line.Command):
     29   """Base class for a Telemetry test or benchmark.
     30 
     31   A test packages a PageTest/PageMeasurement and a PageSet together.
     32   """
     33   options = {}
     34 
     35   @classmethod
     36   def Name(cls):
     37     name = cls.__module__.split('.')[-1]
     38     if hasattr(cls, 'tag'):
     39       name += '.' + cls.tag
     40     if hasattr(cls, 'page_set'):
     41       name += '.' + cls.page_set.Name()
     42     return name
     43 
     44   @classmethod
     45   def AddCommandLineArgs(cls, parser):
     46     cls.PageTestClass().AddCommandLineArgs(parser)
     47 
     48     if hasattr(cls, 'AddTestCommandLineArgs'):
     49       group = optparse.OptionGroup(parser, '%s test options' % cls.Name())
     50       cls.AddTestCommandLineArgs(group)
     51       parser.add_option_group(group)
     52 
     53   @classmethod
     54   def SetArgumentDefaults(cls, parser):
     55     cls.PageTestClass().SetArgumentDefaults(parser)
     56     parser.set_defaults(**cls.options)
     57 
     58   @classmethod
     59   def ProcessCommandLineArgs(cls, parser, args):
     60     cls.PageTestClass().ProcessCommandLineArgs(parser, args)
     61 
     62   def CustomizeBrowserOptions(self, options):
     63     """Add browser options that are required by this benchmark."""
     64 
     65   def Run(self, args):
     66     """Run this test with the given options."""
     67     self.CustomizeBrowserOptions(args.browser_options)
     68 
     69     test = self.PageTestClass()()
     70     test.__name__ = self.__class__.__name__
     71 
     72     if hasattr(self, '_disabled_strings'):
     73       test._disabled_strings = self._disabled_strings
     74     if hasattr(self, '_enabled_strings'):
     75       test._enabled_strings = self._enabled_strings
     76 
     77     ps = self.CreatePageSet(args)
     78     expectations = self.CreateExpectations(ps)
     79 
     80     self._DownloadGeneratedProfileArchive(args)
     81 
     82     results = page_test_results.PageTestResults()
     83     try:
     84       results = page_runner.Run(test, ps, expectations, args)
     85     except page_test.TestNotSupportedOnPlatformFailure as failure:
     86       logging.warning(str(failure))
     87 
     88     results.PrintSummary()
     89     return len(results.failures) + len(results.errors)
     90 
     91   def _DownloadGeneratedProfileArchive(self, options):
     92     """Download and extract profile directory archive if one exists."""
     93     archive_name = getattr(self, 'generated_profile_archive', None)
     94 
     95     # If attribute not specified, nothing to do.
     96     if not archive_name:
     97       return
     98 
     99     # If profile dir specified on command line, nothing to do.
    100     if options.browser_options.profile_dir:
    101       logging.warning("Profile directory specified on command line: %s, this"
    102           "overrides the benchmark's default profile directory.",
    103           options.browser_options.profile_dir)
    104       return
    105 
    106     # Download profile directory from cloud storage.
    107     found_browser = browser_finder.FindBrowser(options)
    108     test_data_dir = os.path.join(util.GetChromiumSrcDir(), 'tools', 'perf',
    109         'generated_profiles',
    110         found_browser.target_os)
    111     generated_profile_archive_path = os.path.normpath(
    112         os.path.join(test_data_dir, archive_name))
    113 
    114     try:
    115       cloud_storage.GetIfChanged(generated_profile_archive_path,
    116           cloud_storage.PUBLIC_BUCKET)
    117     except (cloud_storage.CredentialsError,
    118             cloud_storage.PermissionError) as e:
    119       if os.path.exists(generated_profile_archive_path):
    120         # If the profile directory archive exists, assume the user has their
    121         # own local copy simply warn.
    122         logging.warning('Could not download Profile archive: %s',
    123             generated_profile_archive_path)
    124       else:
    125         # If the archive profile directory doesn't exist, this is fatal.
    126         logging.error('Can not run without required profile archive: %s. '
    127                       'If you believe you have credentials, follow the '
    128                       'instructions below.',
    129                       generated_profile_archive_path)
    130         logging.error(e)
    131         sys.exit(-1)
    132 
    133     # Unzip profile directory.
    134     extracted_profile_dir_path = (
    135         os.path.splitext(generated_profile_archive_path)[0])
    136     if not os.path.isfile(generated_profile_archive_path):
    137       raise Exception("Profile directory archive not downloaded: ",
    138           generated_profile_archive_path)
    139     with zipfile.ZipFile(generated_profile_archive_path) as f:
    140       try:
    141         f.extractall(os.path.dirname(generated_profile_archive_path))
    142       except e:
    143         # Cleanup any leftovers from unzipping.
    144         if os.path.exists(extracted_profile_dir_path):
    145           shutil.rmtree(extracted_profile_dir_path)
    146         logging.error("Error extracting profile directory zip file: %s", e)
    147         sys.exit(-1)
    148 
    149     # Run with freshly extracted profile directory.
    150     logging.info("Using profile archive directory: %s",
    151         extracted_profile_dir_path)
    152     options.browser_options.profile_dir = extracted_profile_dir_path
    153 
    154   @classmethod
    155   def PageTestClass(cls):
    156     """Get the PageTest for this Test.
    157 
    158     If the Test has no PageTest, raises NotImplementedError.
    159     """
    160     if not hasattr(cls, 'test'):
    161       raise NotImplementedError('This test has no "test" attribute.')
    162     if not issubclass(cls.test, page_test.PageTest):
    163       raise TypeError('"%s" is not a PageTest.' % cls.test.__name__)
    164     return cls.test
    165 
    166   @classmethod
    167   def PageSetClass(cls):
    168     """Get the PageSet for this Test.
    169 
    170     If the Test has no PageSet, raises NotImplementedError.
    171     """
    172     if not hasattr(cls, 'page_set'):
    173       raise NotImplementedError('This test has no "page_set" attribute.')
    174     if not issubclass(cls.page_set, page_set.PageSet):
    175       raise TypeError('"%s" is not a PageSet.' % cls.page_set.__name__)
    176     return cls.page_set
    177 
    178   @classmethod
    179   def CreatePageSet(cls, options):  # pylint: disable=W0613
    180     """Get the page set this test will run on.
    181 
    182     By default, it will create a page set from the file at this test's
    183     page_set attribute. Override to generate a custom page set.
    184     """
    185     return cls.PageSetClass()()
    186 
    187   @classmethod
    188   def CreateExpectations(cls, ps):  # pylint: disable=W0613
    189     """Get the expectations this test will run with.
    190 
    191     By default, it will create an empty expectations set. Override to generate
    192     custom expectations.
    193     """
    194     if hasattr(cls, 'expectations'):
    195       return cls.expectations
    196     else:
    197       return test_expectations.TestExpectations()
    198 
    199 
    200 def AddCommandLineArgs(parser):
    201   page_runner.AddCommandLineArgs(parser)
    202 
    203 
    204 def ProcessCommandLineArgs(parser, args):
    205   page_runner.ProcessCommandLineArgs(parser, args)
    206