• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
19import json
20import pprint
21
22from vts.runners.host import signals
23from vts.runners.host import utils
24
25
26class 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
48class 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
192class 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