xref: /aosp_15_r20/external/autotest/server/site_gtest_runner.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
3*9c5db199SXin Li# found in the LICENSE file.
4*9c5db199SXin Li
5*9c5db199SXin Li"""A utility class used to run a gtest suite parsing individual tests."""
6*9c5db199SXin Li
7*9c5db199SXin Liimport logging, os
8*9c5db199SXin Lifrom autotest_lib.server import autotest, hosts, host_attributes
9*9c5db199SXin Lifrom autotest_lib.server import site_server_job_utils
10*9c5db199SXin Lifrom autotest_lib.client.common_lib import gtest_parser
11*9c5db199SXin Li
12*9c5db199SXin Li
13*9c5db199SXin Liclass gtest_runner(object):
14*9c5db199SXin Li    """Run a gtest test suite and evaluate the individual tests."""
15*9c5db199SXin Li
16*9c5db199SXin Li    def __init__(self):
17*9c5db199SXin Li        """Creates an instance of gtest_runner to run tests on a remote host."""
18*9c5db199SXin Li        self._results_dir = ''
19*9c5db199SXin Li        self._gtest = None
20*9c5db199SXin Li        self._host = None
21*9c5db199SXin Li
22*9c5db199SXin Li    def run(self, gtest_entry, machine, work_dir='.'):
23*9c5db199SXin Li        """Run the gtest suite on a remote host, then parse the results.
24*9c5db199SXin Li
25*9c5db199SXin Li        Like machine_worker, gtest_runner honors include/exclude attributes on
26*9c5db199SXin Li        the test item and will only run the test if the supplied host meets the
27*9c5db199SXin Li        test requirements.
28*9c5db199SXin Li
29*9c5db199SXin Li        Note: This method takes a test and a machine as arguments, not a list
30*9c5db199SXin Li        of tests and a list of machines like the parallel and distribute
31*9c5db199SXin Li        methods do.
32*9c5db199SXin Li
33*9c5db199SXin Li        Args:
34*9c5db199SXin Li            gtest_entry: Test tuple from control file.  See documentation in
35*9c5db199SXin Li                site_server_job_utils.test_item class.
36*9c5db199SXin Li            machine: Name (IP) if remote host to run tests on.
37*9c5db199SXin Li            work_dir: Local directory to run tests in.
38*9c5db199SXin Li
39*9c5db199SXin Li        """
40*9c5db199SXin Li        self._gtest = site_server_job_utils.test_item(*gtest_entry)
41*9c5db199SXin Li        self._host = hosts.create_host(machine)
42*9c5db199SXin Li        self._results_dir = work_dir
43*9c5db199SXin Li
44*9c5db199SXin Li        client_autotest = autotest.Autotest(self._host)
45*9c5db199SXin Li        client_attributes = host_attributes.host_attributes(machine)
46*9c5db199SXin Li        attribute_set = set(client_attributes.get_attributes())
47*9c5db199SXin Li
48*9c5db199SXin Li        if self._gtest.validate(attribute_set):
49*9c5db199SXin Li            logging.info('%s %s Running %s', self._host,
50*9c5db199SXin Li                         [a for a in attribute_set], self._gtest)
51*9c5db199SXin Li            try:
52*9c5db199SXin Li                self._gtest.run_test(client_autotest, self._results_dir)
53*9c5db199SXin Li            finally:
54*9c5db199SXin Li                self.parse()
55*9c5db199SXin Li        else:
56*9c5db199SXin Li            self.record_failed_test(self._gtest.test_name,
57*9c5db199SXin Li                                    'No machines found for: ' + self._gtest)
58*9c5db199SXin Li
59*9c5db199SXin Li    def parse(self):
60*9c5db199SXin Li        """Parse the gtest output recording individual test results.
61*9c5db199SXin Li
62*9c5db199SXin Li        Uses gtest_parser to pull the test results out of the gtest log file.
63*9c5db199SXin Li        Then creates entries  in status.log file for each test.
64*9c5db199SXin Li        """
65*9c5db199SXin Li        # Find gtest log files from the autotest client run.
66*9c5db199SXin Li        log_path = os.path.join(
67*9c5db199SXin Li            self._results_dir, self._gtest.tagged_test_name,
68*9c5db199SXin Li            'debug', self._gtest.tagged_test_name + '.DEBUG')
69*9c5db199SXin Li        if not os.path.exists(log_path):
70*9c5db199SXin Li            logging.error('gtest log file "%s" is missing.', log_path)
71*9c5db199SXin Li            return
72*9c5db199SXin Li
73*9c5db199SXin Li        parser = gtest_parser.gtest_parser()
74*9c5db199SXin Li
75*9c5db199SXin Li        # Read the log file line-by-line, passing each line into the parser.
76*9c5db199SXin Li        with open(log_path, 'r') as log_file:
77*9c5db199SXin Li            for log_line in log_file:
78*9c5db199SXin Li                parser.ProcessLogLine(log_line)
79*9c5db199SXin Li
80*9c5db199SXin Li        logging.info('gtest_runner found %d tests.', parser.TotalTests())
81*9c5db199SXin Li
82*9c5db199SXin Li        # Record each failed test.
83*9c5db199SXin Li        for failed in parser.FailedTests():
84*9c5db199SXin Li            fail_description = parser.FailureDescription(failed)
85*9c5db199SXin Li            if fail_description:
86*9c5db199SXin Li                self.record_failed_test(failed, fail_description[0].strip(),
87*9c5db199SXin Li                                        ''.join(fail_description))
88*9c5db199SXin Li            else:
89*9c5db199SXin Li                self.record_failed_test(failed, 'NO ERROR LINES FOUND.')
90*9c5db199SXin Li
91*9c5db199SXin Li        # Finally record each successful test.
92*9c5db199SXin Li        for passed in parser.PassedTests():
93*9c5db199SXin Li            self.record_passed_test(passed)
94*9c5db199SXin Li
95*9c5db199SXin Li    def record_failed_test(self, failed_test, message, error_lines=None):
96*9c5db199SXin Li        """Insert a failure record into status.log for this test.
97*9c5db199SXin Li
98*9c5db199SXin Li        Args:
99*9c5db199SXin Li           failed_test: Name of test that failed.
100*9c5db199SXin Li           message: Reason test failed, will be put in status.log file.
101*9c5db199SXin Li           error_lines: Additional failure info, will be put in ERROR log.
102*9c5db199SXin Li        """
103*9c5db199SXin Li        # Create a test name subdirectory to hold the test status.log file.
104*9c5db199SXin Li        test_dir = os.path.join(self._results_dir, failed_test)
105*9c5db199SXin Li        if not os.path.exists(test_dir):
106*9c5db199SXin Li            try:
107*9c5db199SXin Li                os.makedirs(test_dir)
108*9c5db199SXin Li            except OSError:
109*9c5db199SXin Li                logging.exception('Failed to created test directory: %s',
110*9c5db199SXin Li                                  test_dir)
111*9c5db199SXin Li
112*9c5db199SXin Li        # Record failure into the global job and test specific status files.
113*9c5db199SXin Li        self._host.record('START', failed_test, failed_test)
114*9c5db199SXin Li        self._host.record('INFO', failed_test, 'FAILED: ' + failed_test)
115*9c5db199SXin Li        self._host.record('END FAIL', failed_test, failed_test, message)
116*9c5db199SXin Li
117*9c5db199SXin Li        # If we have additional information on the failure, create an error log
118*9c5db199SXin Li        # file for this test in the location a normal autotest would have left
119*9c5db199SXin Li        # it so the frontend knows where to find it.
120*9c5db199SXin Li        if error_lines is not None:
121*9c5db199SXin Li            fail_log_dir = os.path.join(test_dir, 'debug')
122*9c5db199SXin Li            fail_log_path = os.path.join(fail_log_dir, failed_test + '.ERROR')
123*9c5db199SXin Li
124*9c5db199SXin Li            if not os.path.exists(fail_log_dir):
125*9c5db199SXin Li                try:
126*9c5db199SXin Li                    os.makedirs(fail_log_dir)
127*9c5db199SXin Li                except OSError:
128*9c5db199SXin Li                    logging.exception('Failed to created log directory: %s',
129*9c5db199SXin Li                                      fail_log_dir)
130*9c5db199SXin Li                    return
131*9c5db199SXin Li            try:
132*9c5db199SXin Li                with open(fail_log_path, 'w') as fail_log:
133*9c5db199SXin Li                    fail_log.write(error_lines)
134*9c5db199SXin Li            except IOError:
135*9c5db199SXin Li                logging.exception('Failed to open log file: %s', fail_log_path)
136*9c5db199SXin Li
137*9c5db199SXin Li    def record_passed_test(self, passed_test):
138*9c5db199SXin Li        """Insert a failure record into status.log for this test.
139*9c5db199SXin Li
140*9c5db199SXin Li        Args:
141*9c5db199SXin Li            passed_test: Name of test that passed.
142*9c5db199SXin Li        """
143*9c5db199SXin Li        self._host.record('START', None, passed_test)
144*9c5db199SXin Li        self._host.record('INFO', None, 'PASSED: ' + passed_test)
145*9c5db199SXin Li        self._host.record('END GOOD', None, passed_test)
146