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