Home | History | Annotate | Download | only in server
      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 
      7 import logging, os
      8 from autotest_lib.server import autotest, hosts, host_attributes
      9 from autotest_lib.server import site_server_job_utils
     10 from autotest_lib.client.common_lib import gtest_parser
     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
     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