Home | History | Annotate | Download | only in cros
      1 # Copyright (c) 2014 The Chromium OS 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 os
      7 import re
      8 import shutil
      9 import tempfile
     10 import xml.etree.ElementTree as ET
     11 
     12 import common
     13 from autotest_lib.client.bin import test, utils
     14 from autotest_lib.client.common_lib import error
     15 from autotest_lib.client.common_lib import file_utils
     16 from autotest_lib.client.cros import constants
     17 
     18 
     19 class ChromeBinaryTest(test.test):
     20     """
     21     Base class for tests to run chrome test binaries without signing in and
     22     running Chrome.
     23     """
     24 
     25     CHROME_TEST_DEP = 'chrome_test'
     26     CHROME_SANDBOX = '/opt/google/chrome/chrome-sandbox'
     27     COMPONENT_LIB = '/opt/google/chrome/lib'
     28     home_dir = None
     29     cr_source_dir = None
     30     test_binary_dir = None
     31 
     32     def setup(self):
     33         """
     34         Sets up a test.
     35         """
     36         self.job.setup_dep([self.CHROME_TEST_DEP])
     37 
     38     def initialize(self):
     39         """
     40         Initializes members after setup().
     41         """
     42         test_dep_dir = os.path.join(self.autodir, 'deps', self.CHROME_TEST_DEP)
     43         self.job.install_pkg(self.CHROME_TEST_DEP, 'dep', test_dep_dir)
     44 
     45         self.cr_source_dir = '%s/test_src' % test_dep_dir
     46         self.test_binary_dir = '%s/out/Release' % self.cr_source_dir
     47         # If chrome is a component build then need to create a symlink such
     48         # that the _unittest binaries can find the chrome component libraries.
     49         Release_lib = os.path.join(self.test_binary_dir, 'lib')
     50         if os.path.isdir(self.COMPONENT_LIB):
     51             logging.info('Detected component build. This assumes binary '
     52                          'compatibility between chrome and *unittest.')
     53             if not os.path.islink(Release_lib):
     54                 os.symlink(self.COMPONENT_LIB, Release_lib)
     55         self.home_dir = tempfile.mkdtemp()
     56 
     57     def cleanup(self):
     58         """
     59         Cleans up working directory after run.
     60         """
     61         if self.home_dir:
     62             shutil.rmtree(self.home_dir, ignore_errors=True)
     63 
     64     def get_chrome_binary_path(self, binary_to_run):
     65         """
     66         Gets test binary's full path.
     67 
     68         @returns full path of the test binary to run.
     69         """
     70         return os.path.join(self.test_binary_dir, binary_to_run)
     71 
     72     def parse_fail_reason(self, err, gtest_xml):
     73         """
     74         Parses reason of failure from CmdError and gtest result.
     75 
     76         @param err: CmdError raised from utils.system().
     77         @param gtest_xml: filename of gtest result xml.
     78         @returns reason string
     79         """
     80         reasons = {}
     81 
     82         # Parse gtest result.
     83         if os.path.exists(gtest_xml):
     84             tree = ET.parse(gtest_xml)
     85             root = tree.getroot()
     86             for suite in root.findall('testsuite'):
     87                 for case in suite.findall('testcase'):
     88                     failure = case.find('failure')
     89                     if failure is None:
     90                         continue
     91                     testname = '%s.%s' % (suite.get('name'), case.get('name'))
     92                     reasons[testname] = failure.attrib['message']
     93 
     94         # Parse messages from chrome's test_launcher.
     95         # This provides some information not available from gtest, like timeout.
     96         for line in err.result_obj.stdout.splitlines():
     97             m = re.match(r'\[\d+/\d+\] (\S+) \(([A-Z ]+)\)$', line)
     98             if not m:
     99                 continue
    100             testname, reason = m.group(1, 2)
    101             # Existing reason from gtest has more detail, don't overwrite.
    102             if testname not in reasons:
    103                 reasons[testname] = reason
    104 
    105         if reasons:
    106             message = '%d failures' % len(reasons)
    107             for testname, reason in sorted(reasons.items()):
    108                 message += '; <%s>: %s' % (testname, reason.replace('\n', '; '))
    109             return message
    110 
    111         return 'Unable to parse fail reason: ' + str(err)
    112 
    113     def run_chrome_test_binary(self,
    114                                binary_to_run,
    115                                extra_params='',
    116                                prefix='',
    117                                as_chronos=True,
    118                                timeout=None):
    119         """
    120         Runs chrome test binary.
    121 
    122         @param binary_to_run: The name of the browser test binary.
    123         @param extra_params: Arguments for the browser test binary.
    124         @param prefix: Prefix to the command that invokes the test binary.
    125         @param as_chronos: Boolean indicating if the tests should run in a
    126             chronos shell.
    127         @param timeout: timeout in seconds
    128 
    129         @raises: error.TestFail if there is error running the command.
    130         @raises: CmdTimeoutError: the command timed out and |timeout| is
    131             specified and not None.
    132         """
    133         gtest_xml = tempfile.mktemp(prefix='gtest_xml', suffix='.xml')
    134         binary_path = self.get_chrome_binary_path(binary_to_run)
    135         env_vars = ' '.join([
    136             'HOME=' + self.home_dir,
    137             'CR_SOURCE_ROOT=' + self.cr_source_dir,
    138             'CHROME_DEVEL_SANDBOX=' + self.CHROME_SANDBOX,
    139             'GTEST_OUTPUT=xml:' + gtest_xml,
    140             ])
    141         cmd = ' '.join([env_vars, prefix, binary_path, extra_params])
    142 
    143         try:
    144             if as_chronos:
    145                 utils.system("su chronos -c '%s'" % cmd,
    146                              timeout=timeout)
    147             else:
    148                 utils.system(cmd, timeout=timeout)
    149         except error.CmdError as e:
    150             return_code = e.result_obj.exit_status
    151             if return_code == 126:
    152                 path_permission = '; '.join(
    153                     file_utils.recursive_path_permission(binary_path))
    154                 fail_reason = ('Cannot execute command %s. Permissions: %s' %
    155                                (binary_path, path_permission))
    156             elif return_code == 127:
    157                 fail_reason = ('Command not found: %s' % binary_path)
    158             else:
    159                 fail_reason = self.parse_fail_reason(e, gtest_xml)
    160 
    161             raise error.TestFail(fail_reason)
    162 
    163 
    164 def nuke_chrome(func):
    165     """
    166     Decorator to nuke the Chrome browser processes.
    167     """
    168 
    169     def wrapper(*args, **kargs):
    170         """
    171         Nukes Chrome browser processes before invoking func().
    172 
    173         Also, restarts Chrome after func() returns.
    174         """
    175         open(constants.DISABLE_BROWSER_RESTART_MAGIC_FILE, 'w').close()
    176         try:
    177             try:
    178                 utils.nuke_process_by_name(name=constants.BROWSER,
    179                                            with_prejudice=True)
    180             except error.AutoservPidAlreadyDeadError:
    181                 pass
    182             return func(*args, **kargs)
    183         finally:
    184             # Allow chrome to be restarted again later.
    185             os.unlink(constants.DISABLE_BROWSER_RESTART_MAGIC_FILE)
    186 
    187     return wrapper
    188