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 5import logging 6import os 7import re 8import shutil 9import tempfile 10import xml.etree.ElementTree as ET 11 12import common 13from autotest_lib.client.bin import test, utils 14from autotest_lib.client.common_lib import error 15from autotest_lib.client.common_lib import file_utils 16from autotest_lib.client.cros import constants 17 18 19class 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 164def 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