Home | History | Annotate | Download | only in host
      1 #
      2 # Copyright (C) 2016 The Android Open Source Project
      3 #
      4 # Licensed under the Apache License, Version 2.0 (the "License");
      5 # you may not use this file except in compliance with the License.
      6 # You may obtain a copy of the License at
      7 #
      8 #      http://www.apache.org/licenses/LICENSE-2.0
      9 #
     10 # Unless required by applicable law or agreed to in writing, software
     11 # distributed under the License is distributed on an "AS IS" BASIS,
     12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 # See the License for the specific language governing permissions and
     14 # limitations under the License.
     15 #
     16 """This module is where all the record definitions and record containers live.
     17 """
     18 
     19 import json
     20 import pprint
     21 
     22 from vts.runners.host import signals
     23 from vts.runners.host import utils
     24 
     25 
     26 class TestResultEnums(object):
     27     """Enums used for TestResultRecord class.
     28 
     29     Includes the tokens to mark test result with, and the string names for each
     30     field in TestResultRecord.
     31     """
     32 
     33     RECORD_NAME = "Test Name"
     34     RECORD_CLASS = "Test Class"
     35     RECORD_BEGIN_TIME = "Begin Time"
     36     RECORD_END_TIME = "End Time"
     37     RECORD_RESULT = "Result"
     38     RECORD_UID = "UID"
     39     RECORD_EXTRAS = "Extras"
     40     RECORD_EXTRA_ERRORS = "Extra Errors"
     41     RECORD_DETAILS = "Details"
     42     TEST_RESULT_PASS = "PASS"
     43     TEST_RESULT_FAIL = "FAIL"
     44     TEST_RESULT_SKIP = "SKIP"
     45     TEST_RESULT_ERROR = "ERROR"
     46 
     47 
     48 class TestResultRecord(object):
     49     """A record that holds the information of a test case execution.
     50 
     51     Attributes:
     52         test_name: A string representing the name of the test case.
     53         begin_time: Epoch timestamp of when the test case started.
     54         end_time: Epoch timestamp of when the test case ended.
     55         self.uid: Unique identifier of a test case.
     56         self.result: Test result, PASS/FAIL/SKIP.
     57         self.extras: User defined extra information of the test result.
     58         self.details: A string explaining the details of the test case.
     59     """
     60 
     61     def __init__(self, t_name, t_class=None):
     62         self.test_name = t_name
     63         self.test_class = t_class
     64         self.begin_time = None
     65         self.end_time = None
     66         self.uid = None
     67         self.result = None
     68         self.extras = None
     69         self.details = None
     70         self.extra_errors = {}
     71 
     72     def testBegin(self):
     73         """Call this when the test case it records begins execution.
     74 
     75         Sets the begin_time of this record.
     76         """
     77         self.begin_time = utils.get_current_epoch_time()
     78 
     79     def _testEnd(self, result, e):
     80         """Class internal function to signal the end of a test case execution.
     81 
     82         Args:
     83             result: One of the TEST_RESULT enums in TestResultEnums.
     84             e: A test termination signal (usually an exception object). It can
     85                 be any exception instance or of any subclass of
     86                 vts.runners.host.signals.TestSignal.
     87         """
     88         self.end_time = utils.get_current_epoch_time()
     89         self.result = result
     90         if isinstance(e, signals.TestSignal):
     91             self.details = e.details
     92             self.extras = e.extras
     93         elif e:
     94             self.details = str(e)
     95 
     96     def testPass(self, e=None):
     97         """To mark the test as passed in this record.
     98 
     99         Args:
    100             e: An instance of vts.runners.host.signals.TestPass.
    101         """
    102         self._testEnd(TestResultEnums.TEST_RESULT_PASS, e)
    103 
    104     def testFail(self, e=None):
    105         """To mark the test as failed in this record.
    106 
    107         Only testFail does instance check because we want "assert xxx" to also
    108         fail the test same way assert_true does.
    109 
    110         Args:
    111             e: An exception object. It can be an instance of AssertionError or
    112                 vts.runners.host.base_test.TestFailure.
    113         """
    114         self._testEnd(TestResultEnums.TEST_RESULT_FAIL, e)
    115 
    116     def testSkip(self, e=None):
    117         """To mark the test as skipped in this record.
    118 
    119         Args:
    120             e: An instance of vts.runners.host.signals.TestSkip.
    121         """
    122         self._testEnd(TestResultEnums.TEST_RESULT_SKIP, e)
    123 
    124     def testError(self, e=None):
    125         """To mark the test as error in this record.
    126 
    127         Args:
    128             e: An exception object.
    129         """
    130         self._testEnd(TestResultEnums.TEST_RESULT_ERROR, e)
    131 
    132     def addError(self, tag, e):
    133         """Add extra error happened during a test mark the test result as
    134         ERROR.
    135 
    136         If an error is added the test record, the record's result is equivalent
    137         to the case where an uncaught exception happened.
    138 
    139         Args:
    140             tag: A string describing where this error came from, e.g. 'on_pass'.
    141             e: An exception object.
    142         """
    143         self.result = TestResultEnums.TEST_RESULT_ERROR
    144         self.extra_errors[tag] = str(e)
    145 
    146     def __str__(self):
    147         d = self.getDict()
    148         l = ["%s = %s" % (k, v) for k, v in d.items()]
    149         s = ', '.join(l)
    150         return s
    151 
    152     def __repr__(self):
    153         """This returns a short string representation of the test record."""
    154         t = utils.epoch_to_human_time(self.begin_time)
    155         return "%s %s %s" % (t, self.test_name, self.result)
    156 
    157     def getDict(self):
    158         """Gets a dictionary representating the content of this class.
    159 
    160         Returns:
    161             A dictionary representating the content of this class.
    162         """
    163         d = {}
    164         d[TestResultEnums.RECORD_NAME] = self.test_name
    165         d[TestResultEnums.RECORD_CLASS] = self.test_class
    166         d[TestResultEnums.RECORD_BEGIN_TIME] = self.begin_time
    167         d[TestResultEnums.RECORD_END_TIME] = self.end_time
    168         d[TestResultEnums.RECORD_RESULT] = self.result
    169         d[TestResultEnums.RECORD_UID] = self.uid
    170         d[TestResultEnums.RECORD_EXTRAS] = self.extras
    171         d[TestResultEnums.RECORD_DETAILS] = self.details
    172         d[TestResultEnums.RECORD_EXTRA_ERRORS] = self.extra_errors
    173         return d
    174 
    175     def jsonString(self):
    176         """Converts this test record to a string in json format.
    177 
    178         Format of the json string is:
    179             {
    180                 'Test Name': <test name>,
    181                 'Begin Time': <epoch timestamp>,
    182                 'Details': <details>,
    183                 ...
    184             }
    185 
    186         Returns:
    187             A json-format string representing the test record.
    188         """
    189         return json.dumps(self.getDict())
    190 
    191 
    192 class TestResult(object):
    193     """A class that contains metrics of a test run.
    194 
    195     This class is essentially a container of TestResultRecord objects.
    196 
    197     Attributes:
    198         self.requested: A list of strings, each is the name of a test requested
    199             by user.
    200         self.failed: A list of records for tests failed.
    201         self.executed: A list of records for tests that were actually executed.
    202         self.passed: A list of records for tests passed.
    203         self.skipped: A list of records for tests skipped.
    204         self.error: A list of records for tests with error result token.
    205     """
    206 
    207     def __init__(self):
    208         self.requested = []
    209         self.failed = []
    210         self.executed = []
    211         self.passed = []
    212         self.skipped = []
    213         self.error = []
    214 
    215     def __add__(self, r):
    216         """Overrides '+' operator for TestResult class.
    217 
    218         The add operator merges two TestResult objects by concatenating all of
    219         their lists together.
    220 
    221         Args:
    222             r: another instance of TestResult to be added
    223 
    224         Returns:
    225             A TestResult instance that's the sum of two TestResult instances.
    226         """
    227         if not isinstance(r, TestResult):
    228             raise TypeError("Operand %s of type %s is not a TestResult." %
    229                             (r, type(r)))
    230         sum_result = TestResult()
    231         for name in sum_result.__dict__:
    232             l_value = list(getattr(self, name))
    233             r_value = list(getattr(r, name))
    234             setattr(sum_result, name, l_value + r_value)
    235         return sum_result
    236 
    237     def addRecord(self, record):
    238         """Adds a test record to test result.
    239 
    240         A record is considered executed once it's added to the test result.
    241 
    242         Args:
    243             record: A test record object to add.
    244         """
    245         self.executed.append(record)
    246         if record.result == TestResultEnums.TEST_RESULT_FAIL:
    247             self.failed.append(record)
    248         elif record.result == TestResultEnums.TEST_RESULT_SKIP:
    249             self.skipped.append(record)
    250         elif record.result == TestResultEnums.TEST_RESULT_PASS:
    251             self.passed.append(record)
    252         else:
    253             self.error.append(record)
    254 
    255     def failClass(self, class_name, e):
    256         """Add a record to indicate a test class setup has failed and no test
    257         in the class was executed.
    258 
    259         Args:
    260             class_name: A string that is the name of the failed test class.
    261             e: An exception object.
    262         """
    263         record = TestResultRecord("setup_class", class_name)
    264         record.testBegin()
    265         record.testFail(e)
    266         self.executed.append(record)
    267         self.failed.append(record)
    268 
    269     def skipClass(self, class_name, reason):
    270         """Add a record to indicate all test cases in the class are skipped.
    271 
    272         Args:
    273             class_name: A string that is the name of the skipped test class.
    274             reason: A string that is the reason for skipping.
    275         """
    276         record = TestResultRecord("unknown", class_name)
    277         record.testBegin()
    278         record.testSkip(signals.TestSkip(reason))
    279         self.executed.append(record)
    280         self.skipped.append(record)
    281 
    282     def jsonString(self):
    283         """Converts this test result to a string in json format.
    284 
    285         Format of the json string is:
    286             {
    287                 "Results": [
    288                     {<executed test record 1>},
    289                     {<executed test record 2>},
    290                     ...
    291                 ],
    292                 "Summary": <summary dict>
    293             }
    294 
    295         Returns:
    296             A json-format string representing the test results.
    297         """
    298         d = {}
    299         executed = [record.getDict() for record in self.executed]
    300         d["Results"] = executed
    301         d["Summary"] = self.summaryDict()
    302         jsonString = json.dumps(d, indent=4, sort_keys=True)
    303         return jsonString
    304 
    305     def summary(self):
    306         """Gets a string that summarizes the stats of this test result.
    307 
    308         The summary rovides the counts of how many test cases fall into each
    309         category, like "Passed", "Failed" etc.
    310 
    311         Format of the string is:
    312             Requested <int>, Executed <int>, ...
    313 
    314         Returns:
    315             A summary string of this test result.
    316         """
    317         l = ["%s %d" % (k, v) for k, v in self.summaryDict().items()]
    318         # Sort the list so the order is the same every time.
    319         msg = ", ".join(sorted(l))
    320         return msg
    321 
    322     def summaryDict(self):
    323         """Gets a dictionary that summarizes the stats of this test result.
    324 
    325         The summary rovides the counts of how many test cases fall into each
    326         category, like "Passed", "Failed" etc.
    327 
    328         Returns:
    329             A dictionary with the stats of this test result.
    330         """
    331         d = {}
    332         d["Requested"] = len(self.requested)
    333         d["Executed"] = len(self.executed)
    334         d["Passed"] = len(self.passed)
    335         d["Failed"] = len(self.failed)
    336         d["Skipped"] = len(self.skipped)
    337         d["Error"] = len(self.error)
    338         return d
    339