Paul Pendlebury | 7c1fdcf | 2011-05-04 12:39:15 -0700 | [diff] [blame] | 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 | |
Lloyd Pique | ee8a2b9 | 2017-09-27 17:14:32 -0700 | [diff] [blame] | 5 | """A utility class used to run a gtest suite parsing individual tests.""" |
Paul Pendlebury | 7c1fdcf | 2011-05-04 12:39:15 -0700 | [diff] [blame] | 6 | |
Lloyd Pique | ee8a2b9 | 2017-09-27 17:14:32 -0700 | [diff] [blame] | 7 | import logging, os |
Paul Pendlebury | 7c1fdcf | 2011-05-04 12:39:15 -0700 | [diff] [blame] | 8 | from autotest_lib.server import autotest, hosts, host_attributes |
Paul Pendlebury | 5759356 | 2011-06-15 10:45:49 -0700 | [diff] [blame] | 9 | from autotest_lib.server import site_server_job_utils |
Lloyd Pique | ee8a2b9 | 2017-09-27 17:14:32 -0700 | [diff] [blame] | 10 | from autotest_lib.client.common_lib import gtest_parser |
Paul Pendlebury | 7c1fdcf | 2011-05-04 12:39:15 -0700 | [diff] [blame] | 11 | |
| 12 | |
| 13 | class 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 |
Nirnimesh | 717b092 | 2011-11-09 12:03:48 -0800 | [diff] [blame] | 35 | site_server_job_utils.test_item class. |
Paul Pendlebury | 7c1fdcf | 2011-05-04 12:39:15 -0700 | [diff] [blame] | 36 | machine: Name (IP) if remote host to run tests on. |
| 37 | work_dir: Local directory to run tests in. |
| 38 | |
| 39 | """ |
Paul Pendlebury | 5759356 | 2011-06-15 10:45:49 -0700 | [diff] [blame] | 40 | self._gtest = site_server_job_utils.test_item(*gtest_entry) |
Paul Pendlebury | 7c1fdcf | 2011-05-04 12:39:15 -0700 | [diff] [blame] | 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) |
Paul Pendlebury | 9af083b | 2011-05-17 13:38:58 -0700 | [diff] [blame] | 51 | try: |
| 52 | self._gtest.run_test(client_autotest, self._results_dir) |
| 53 | finally: |
| 54 | self.parse() |
Paul Pendlebury | 7c1fdcf | 2011-05-04 12:39:15 -0700 | [diff] [blame] | 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 | """ |
Paul Pendlebury | 7c1fdcf | 2011-05-04 12:39:15 -0700 | [diff] [blame] | 65 | # Find gtest log files from the autotest client run. |
Nirnimesh | 717b092 | 2011-11-09 12:03:48 -0800 | [diff] [blame] | 66 | log_path = os.path.join( |
| 67 | self._results_dir, self._gtest.tagged_test_name, |
| 68 | 'debug', self._gtest.tagged_test_name + '.DEBUG') |
Paul Pendlebury | 5759356 | 2011-06-15 10:45:49 -0700 | [diff] [blame] | 69 | if not os.path.exists(log_path): |
| 70 | logging.error('gtest log file "%s" is missing.', log_path) |
| 71 | return |
| 72 | |
Lloyd Pique | ee8a2b9 | 2017-09-27 17:14:32 -0700 | [diff] [blame] | 73 | parser = gtest_parser.gtest_parser() |
Paul Pendlebury | 7c1fdcf | 2011-05-04 12:39:15 -0700 | [diff] [blame] | 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) |
Paul Pendlebury | 5759356 | 2011-06-15 10:45:49 -0700 | [diff] [blame] | 85 | if fail_description: |
| 86 | self.record_failed_test(failed, fail_description[0].strip(), |
| 87 | ''.join(fail_description)) |
Paul Pendlebury | 699d64e | 2011-06-22 12:03:56 -0700 | [diff] [blame] | 88 | else: |
Paul Pendlebury | 5759356 | 2011-06-15 10:45:49 -0700 | [diff] [blame] | 89 | self.record_failed_test(failed, 'NO ERROR LINES FOUND.') |
Paul Pendlebury | 7c1fdcf | 2011-05-04 12:39:15 -0700 | [diff] [blame] | 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 | """ |
Paul Pendlebury | df3124a | 2011-05-12 14:00:14 -0700 | [diff] [blame] | 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) |
Paul Pendlebury | 7c1fdcf | 2011-05-04 12:39:15 -0700 | [diff] [blame] | 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: |
Paul Pendlebury | df3124a | 2011-05-12 14:00:14 -0700 | [diff] [blame] | 121 | fail_log_dir = os.path.join(test_dir, 'debug') |
Paul Pendlebury | 7c1fdcf | 2011-05-04 12:39:15 -0700 | [diff] [blame] | 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) |