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