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