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