# Copyright (c) 2011 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """A utility class used to run a gtest suite parsing individual tests.""" import logging, os from autotest_lib.server import autotest, hosts, host_attributes from autotest_lib.server import site_server_job_utils from autotest_lib.client.common_lib import gtest_parser class gtest_runner(object): """Run a gtest test suite and evaluate the individual tests.""" def __init__(self): """Creates an instance of gtest_runner to run tests on a remote host.""" self._results_dir = '' self._gtest = None self._host = None def run(self, gtest_entry, machine, work_dir='.'): """Run the gtest suite on a remote host, then parse the results. Like machine_worker, gtest_runner honors include/exclude attributes on the test item and will only run the test if the supplied host meets the test requirements. Note: This method takes a test and a machine as arguments, not a list of tests and a list of machines like the parallel and distribute methods do. Args: gtest_entry: Test tuple from control file. See documentation in site_server_job_utils.test_item class. machine: Name (IP) if remote host to run tests on. work_dir: Local directory to run tests in. """ self._gtest = site_server_job_utils.test_item(*gtest_entry) self._host = hosts.create_host(machine) self._results_dir = work_dir client_autotest = autotest.Autotest(self._host) client_attributes = host_attributes.host_attributes(machine) attribute_set = set(client_attributes.get_attributes()) if self._gtest.validate(attribute_set): logging.info('%s %s Running %s', self._host, [a for a in attribute_set], self._gtest) try: self._gtest.run_test(client_autotest, self._results_dir) finally: self.parse() else: self.record_failed_test(self._gtest.test_name, 'No machines found for: ' + self._gtest) def parse(self): """Parse the gtest output recording individual test results. Uses gtest_parser to pull the test results out of the gtest log file. Then creates entries in status.log file for each test. """ # Find gtest log files from the autotest client run. log_path = os.path.join( self._results_dir, self._gtest.tagged_test_name, 'debug', self._gtest.tagged_test_name + '.DEBUG') if not os.path.exists(log_path): logging.error('gtest log file "%s" is missing.', log_path) return parser = gtest_parser.gtest_parser() # Read the log file line-by-line, passing each line into the parser. with open(log_path, 'r') as log_file: for log_line in log_file: parser.ProcessLogLine(log_line) logging.info('gtest_runner found %d tests.', parser.TotalTests()) # Record each failed test. for failed in parser.FailedTests(): fail_description = parser.FailureDescription(failed) if fail_description: self.record_failed_test(failed, fail_description[0].strip(), ''.join(fail_description)) else: self.record_failed_test(failed, 'NO ERROR LINES FOUND.') # Finally record each successful test. for passed in parser.PassedTests(): self.record_passed_test(passed) def record_failed_test(self, failed_test, message, error_lines=None): """Insert a failure record into status.log for this test. Args: failed_test: Name of test that failed. message: Reason test failed, will be put in status.log file. error_lines: Additional failure info, will be put in ERROR log. """ # Create a test name subdirectory to hold the test status.log file. test_dir = os.path.join(self._results_dir, failed_test) if not os.path.exists(test_dir): try: os.makedirs(test_dir) except OSError: logging.exception('Failed to created test directory: %s', test_dir) # Record failure into the global job and test specific status files. self._host.record('START', failed_test, failed_test) self._host.record('INFO', failed_test, 'FAILED: ' + failed_test) self._host.record('END FAIL', failed_test, failed_test, message) # If we have additional information on the failure, create an error log # file for this test in the location a normal autotest would have left # it so the frontend knows where to find it. if error_lines is not None: fail_log_dir = os.path.join(test_dir, 'debug') fail_log_path = os.path.join(fail_log_dir, failed_test + '.ERROR') if not os.path.exists(fail_log_dir): try: os.makedirs(fail_log_dir) except OSError: logging.exception('Failed to created log directory: %s', fail_log_dir) return try: with open(fail_log_path, 'w') as fail_log: fail_log.write(error_lines) except IOError: logging.exception('Failed to open log file: %s', fail_log_path) def record_passed_test(self, passed_test): """Insert a failure record into status.log for this test. Args: passed_test: Name of test that passed. """ self._host.record('START', None, passed_test) self._host.record('INFO', None, 'PASSED: ' + passed_test) self._host.record('END GOOD', None, passed_test)