We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Test result entry should include stacktrace
Test result entry in test summary should include the stacktrace if an exception happened during test.
Right now we only see the message of the exception, not the whole stacktrace.

--- END ISSUE ---

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

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mobly/records.py b/mobly/records.py
--- a/mobly/records.py
+++ b/mobly/records.py
@@ -13,13 +13,14 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-
 """This module is where all the record definitions and record containers live.
 """
 
 import json
 import logging
 import pprint
+import sys
+import traceback
 
 from mobly import signals
 from mobly import utils
@@ -41,6 +42,7 @@
     RECORD_EXTRAS = 'Extras'
     RECORD_EXTRA_ERRORS = 'Extra Errors'
     RECORD_DETAILS = 'Details'
+    RECORD_STACKTRACE = 'Stacktrace'
     TEST_RESULT_PASS = 'PASS'
     TEST_RESULT_FAIL = 'FAIL'
     TEST_RESULT_SKIP = 'SKIP'
@@ -69,6 +71,7 @@
         self.result = None
         self.extras = None
         self.details = None
+        self.stacktrace = None
         self.extra_errors = {}
 
     def test_begin(self):
@@ -93,9 +96,15 @@
             self.result = TestResultEnums.TEST_RESULT_ERROR
         if isinstance(e, signals.TestSignal):
             self.details = e.details
+            _, _, exc_traceback = sys.exc_info()
+            if exc_traceback:
+                self.stacktrace = ''.join(traceback.format_tb(exc_traceback))
             self.extras = e.extras
-        elif e:
+        elif isinstance(e, Exception):
             self.details = str(e)
+            _, _, exc_traceback = sys.exc_info()
+            if exc_traceback:
+                self.stacktrace = ''.join(traceback.format_tb(exc_traceback))
 
     def test_pass(self, e=None):
         """To mark the test as passed in this record.
@@ -174,6 +183,7 @@
         d[TestResultEnums.RECORD_EXTRAS] = self.extras
         d[TestResultEnums.RECORD_DETAILS] = self.details
         d[TestResultEnums.RECORD_EXTRA_ERRORS] = self.extra_errors
+        d[TestResultEnums.RECORD_STACKTRACE] = self.stacktrace
         return d
 
     def json_str(self):

{"golden_diff": "diff --git a/mobly/records.py b/mobly/records.py\n--- a/mobly/records.py\n+++ b/mobly/records.py\n@@ -13,13 +13,14 @@\n # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n # See the License for the specific language governing permissions and\n # limitations under the License.\n-\n \"\"\"This module is where all the record definitions and record containers live.\n \"\"\"\n \n import json\n import logging\n import pprint\n+import sys\n+import traceback\n \n from mobly import signals\n from mobly import utils\n@@ -41,6 +42,7 @@\n     RECORD_EXTRAS = 'Extras'\n     RECORD_EXTRA_ERRORS = 'Extra Errors'\n     RECORD_DETAILS = 'Details'\n+    RECORD_STACKTRACE = 'Stacktrace'\n     TEST_RESULT_PASS = 'PASS'\n     TEST_RESULT_FAIL = 'FAIL'\n     TEST_RESULT_SKIP = 'SKIP'\n@@ -69,6 +71,7 @@\n         self.result = None\n         self.extras = None\n         self.details = None\n+        self.stacktrace = None\n         self.extra_errors = {}\n \n     def test_begin(self):\n@@ -93,9 +96,15 @@\n             self.result = TestResultEnums.TEST_RESULT_ERROR\n         if isinstance(e, signals.TestSignal):\n             self.details = e.details\n+            _, _, exc_traceback = sys.exc_info()\n+            if exc_traceback:\n+                self.stacktrace = ''.join(traceback.format_tb(exc_traceback))\n             self.extras = e.extras\n-        elif e:\n+        elif isinstance(e, Exception):\n             self.details = str(e)\n+            _, _, exc_traceback = sys.exc_info()\n+            if exc_traceback:\n+                self.stacktrace = ''.join(traceback.format_tb(exc_traceback))\n \n     def test_pass(self, e=None):\n         \"\"\"To mark the test as passed in this record.\n@@ -174,6 +183,7 @@\n         d[TestResultEnums.RECORD_EXTRAS] = self.extras\n         d[TestResultEnums.RECORD_DETAILS] = self.details\n         d[TestResultEnums.RECORD_EXTRA_ERRORS] = self.extra_errors\n+        d[TestResultEnums.RECORD_STACKTRACE] = self.stacktrace\n         return d\n \n     def json_str(self):\n", "issue": "Test result entry should include stacktrace\nTest result entry in test summary should include the stacktrace if an exception happened during test.\r\nRight now we only see the message of the exception, not the whole stacktrace.\n", "before_files": [{"content": "#!/usr/bin/env python3.4\n#\n# Copyright 2016 Google Inc.\n# \n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n# \n#     http://www.apache.org/licenses/LICENSE-2.0\n# \n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"This module is where all the record definitions and record containers live.\n\"\"\"\n\nimport json\nimport logging\nimport pprint\n\nfrom mobly import signals\nfrom mobly import utils\n\n\nclass TestResultEnums(object):\n    \"\"\"Enums used for TestResultRecord class.\n\n    Includes the tokens to mark test result with, and the string names for each\n    field in TestResultRecord.\n    \"\"\"\n\n    RECORD_NAME = 'Test Name'\n    RECORD_CLASS = 'Test Class'\n    RECORD_BEGIN_TIME = 'Begin Time'\n    RECORD_END_TIME = 'End Time'\n    RECORD_RESULT = 'Result'\n    RECORD_UID = 'UID'\n    RECORD_EXTRAS = 'Extras'\n    RECORD_EXTRA_ERRORS = 'Extra Errors'\n    RECORD_DETAILS = 'Details'\n    TEST_RESULT_PASS = 'PASS'\n    TEST_RESULT_FAIL = 'FAIL'\n    TEST_RESULT_SKIP = 'SKIP'\n    TEST_RESULT_ERROR = 'ERROR'\n\n\nclass TestResultRecord(object):\n    \"\"\"A record that holds the information of a test case execution.\n\n    Attributes:\n        test_name: A string representing the name of the test case.\n        begin_time: Epoch timestamp of when the test case started.\n        end_time: Epoch timestamp of when the test case ended.\n        self.uid: Unique identifier of a test case.\n        self.result: Test result, PASS/FAIL/SKIP.\n        self.extras: User defined extra information of the test result.\n        self.details: A string explaining the details of the test case.\n    \"\"\"\n\n    def __init__(self, t_name, t_class=None):\n        self.test_name = t_name\n        self.test_class = t_class\n        self.begin_time = None\n        self.end_time = None\n        self.uid = None\n        self.result = None\n        self.extras = None\n        self.details = None\n        self.extra_errors = {}\n\n    def test_begin(self):\n        \"\"\"Call this when the test case it records begins execution.\n\n        Sets the begin_time of this record.\n        \"\"\"\n        self.begin_time = utils.get_current_epoch_time()\n\n    def _test_end(self, result, e):\n        \"\"\"Class internal function to signal the end of a test case execution.\n\n        Args:\n            result: One of the TEST_RESULT enums in TestResultEnums.\n            e: A test termination signal (usually an exception object). It can\n                be any exception instance or of any subclass of\n                mobly.signals.TestSignal.\n        \"\"\"\n        self.end_time = utils.get_current_epoch_time()\n        self.result = result\n        if self.extra_errors:\n            self.result = TestResultEnums.TEST_RESULT_ERROR\n        if isinstance(e, signals.TestSignal):\n            self.details = e.details\n            self.extras = e.extras\n        elif e:\n            self.details = str(e)\n\n    def test_pass(self, e=None):\n        \"\"\"To mark the test as passed in this record.\n\n        Args:\n            e: An instance of mobly.signals.TestPass.\n        \"\"\"\n        self._test_end(TestResultEnums.TEST_RESULT_PASS, e)\n\n    def test_fail(self, e=None):\n        \"\"\"To mark the test as failed in this record.\n\n        Only test_fail does instance check because we want 'assert xxx' to also\n        fail the test same way assert_true does.\n\n        Args:\n            e: An exception object. It can be an instance of AssertionError or\n                mobly.base_test.TestFailure.\n        \"\"\"\n        self._test_end(TestResultEnums.TEST_RESULT_FAIL, e)\n\n    def test_skip(self, e=None):\n        \"\"\"To mark the test as skipped in this record.\n\n        Args:\n            e: An instance of mobly.signals.TestSkip.\n        \"\"\"\n        self._test_end(TestResultEnums.TEST_RESULT_SKIP, e)\n\n    def test_error(self, e=None):\n        \"\"\"To mark the test as error in this record.\n\n        Args:\n            e: An exception object.\n        \"\"\"\n        self._test_end(TestResultEnums.TEST_RESULT_ERROR, e)\n\n    def add_error(self, tag, e):\n        \"\"\"Add extra error happened during a test mark the test result as\n        ERROR.\n\n        If an error is added the test record, the record's result is equivalent\n        to the case where an uncaught exception happened.\n\n        Args:\n            tag: A string describing where this error came from, e.g. 'on_pass'.\n            e: An exception object.\n        \"\"\"\n        self.result = TestResultEnums.TEST_RESULT_ERROR\n        self.extra_errors[tag] = str(e)\n\n    def __str__(self):\n        d = self.to_dict()\n        l = ['%s = %s' % (k, v) for k, v in d.items()]\n        s = ', '.join(l)\n        return s\n\n    def __repr__(self):\n        \"\"\"This returns a short string representation of the test record.\"\"\"\n        t = utils.epoch_to_human_time(self.begin_time)\n        return '%s %s %s' % (t, self.test_name, self.result)\n\n    def to_dict(self):\n        \"\"\"Gets a dictionary representating the content of this class.\n\n        Returns:\n            A dictionary representating the content of this class.\n        \"\"\"\n        d = {}\n        d[TestResultEnums.RECORD_NAME] = self.test_name\n        d[TestResultEnums.RECORD_CLASS] = self.test_class\n        d[TestResultEnums.RECORD_BEGIN_TIME] = self.begin_time\n        d[TestResultEnums.RECORD_END_TIME] = self.end_time\n        d[TestResultEnums.RECORD_RESULT] = self.result\n        d[TestResultEnums.RECORD_UID] = self.uid\n        d[TestResultEnums.RECORD_EXTRAS] = self.extras\n        d[TestResultEnums.RECORD_DETAILS] = self.details\n        d[TestResultEnums.RECORD_EXTRA_ERRORS] = self.extra_errors\n        return d\n\n    def json_str(self):\n        \"\"\"Converts this test record to a string in json format.\n\n        Format of the json string is:\n            {\n                'Test Name': <test name>,\n                'Begin Time': <epoch timestamp>,\n                'Details': <details>,\n                ...\n            }\n\n        Returns:\n            A json-format string representing the test record.\n        \"\"\"\n        return json.dumps(self.to_dict())\n\n\nclass TestResult(object):\n    \"\"\"A class that contains metrics of a test run.\n\n    This class is essentially a container of TestResultRecord objects.\n\n    Attributes:\n        self.requested: A list of strings, each is the name of a test requested\n            by user.\n        self.failed: A list of records for tests failed.\n        self.executed: A list of records for tests that were actually executed.\n        self.passed: A list of records for tests passed.\n        self.skipped: A list of records for tests skipped.\n        self.error: A list of records for tests with error result token.\n    \"\"\"\n\n    def __init__(self):\n        self.requested = []\n        self.failed = []\n        self.executed = []\n        self.passed = []\n        self.skipped = []\n        self.error = []\n        self.controller_info = {}\n\n    def __add__(self, r):\n        \"\"\"Overrides '+' operator for TestResult class.\n\n        The add operator merges two TestResult objects by concatenating all of\n        their lists together.\n\n        Args:\n            r: another instance of TestResult to be added\n\n        Returns:\n            A TestResult instance that's the sum of two TestResult instances.\n        \"\"\"\n        if not isinstance(r, TestResult):\n            raise TypeError('Operand %s of type %s is not a TestResult.' %\n                            (r, type(r)))\n        sum_result = TestResult()\n        for name in sum_result.__dict__:\n            r_value = getattr(r, name)\n            l_value = getattr(self, name)\n            if isinstance(r_value, list):\n                setattr(sum_result, name, l_value + r_value)\n            elif isinstance(r_value, dict):\n                # '+' operator for TestResult is only valid when multiple\n                # TestResult objs were created in the same test run, which means\n                # the controller info would be the same across all of them.\n                # TODO(angli): have a better way to validate this situation.\n                setattr(sum_result, name, l_value)\n        return sum_result\n\n    def add_record(self, record):\n        \"\"\"Adds a test record to test result.\n\n        A record is considered executed once it's added to the test result.\n\n        Args:\n            record: A test record object to add.\n        \"\"\"\n        self.executed.append(record)\n        if record.result == TestResultEnums.TEST_RESULT_FAIL:\n            self.failed.append(record)\n        elif record.result == TestResultEnums.TEST_RESULT_SKIP:\n            self.skipped.append(record)\n        elif record.result == TestResultEnums.TEST_RESULT_PASS:\n            self.passed.append(record)\n        else:\n            self.error.append(record)\n\n    def add_controller_info(self, name, info):\n        try:\n            json.dumps(info)\n        except TypeError:\n            logging.warning('Controller info for %s is not JSON serializable!'\n                            ' Coercing it to string.' % name)\n            self.controller_info[name] = str(info)\n            return\n        self.controller_info[name] = info\n\n    def fail_class(self, test_record):\n        \"\"\"Add a record to indicate a test class setup has failed and no test\n        in the class was executed.\n\n        Args:\n            test_record: A TestResultRecord object for the test class.\n        \"\"\"\n        self.executed.append(test_record)\n        self.failed.append(test_record)\n\n    @property\n    def is_all_pass(self):\n        \"\"\"True if no tests failed or threw errors, False otherwise.\"\"\"\n        num_of_failures = len(self.failed) + len(self.error)\n        if num_of_failures == 0:\n            return True\n        return False\n\n    def json_str(self):\n        \"\"\"Converts this test result to a string in json format.\n\n        Format of the json string is:\n            {\n                'Results': [\n                    {<executed test record 1>},\n                    {<executed test record 2>},\n                    ...\n                ],\n                'Summary': <summary dict>\n            }\n\n        Returns:\n            A json-format string representing the test results.\n        \"\"\"\n        d = {}\n        d['ControllerInfo'] = self.controller_info\n        d['Results'] = [record.to_dict() for record in self.executed]\n        d['Summary'] = self.summary_dict()\n        json_str = json.dumps(d, indent=4, sort_keys=True)\n        return json_str\n\n    def summary_str(self):\n        \"\"\"Gets a string that summarizes the stats of this test result.\n\n        The summary rovides the counts of how many test cases fall into each\n        category, like 'Passed', 'Failed' etc.\n\n        Format of the string is:\n            Requested <int>, Executed <int>, ...\n\n        Returns:\n            A summary string of this test result.\n        \"\"\"\n        l = ['%s %d' % (k, v) for k, v in self.summary_dict().items()]\n        # Sort the list so the order is the same every time.\n        msg = ', '.join(sorted(l))\n        return msg\n\n    def summary_dict(self):\n        \"\"\"Gets a dictionary that summarizes the stats of this test result.\n\n        The summary rovides the counts of how many test cases fall into each\n        category, like 'Passed', 'Failed' etc.\n\n        Returns:\n            A dictionary with the stats of this test result.\n        \"\"\"\n        d = {}\n        d['Requested'] = len(self.requested)\n        d['Executed'] = len(self.executed)\n        d['Passed'] = len(self.passed)\n        d['Failed'] = len(self.failed)\n        d['Skipped'] = len(self.skipped)\n        d['Error'] = len(self.error)\n        return d\n", "path": "mobly/records.py"}], "after_files": [{"content": "#!/usr/bin/env python3.4\n#\n# Copyright 2016 Google Inc.\n# \n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n# \n#     http://www.apache.org/licenses/LICENSE-2.0\n# \n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"This module is where all the record definitions and record containers live.\n\"\"\"\n\nimport json\nimport logging\nimport pprint\nimport sys\nimport traceback\n\nfrom mobly import signals\nfrom mobly import utils\n\n\nclass TestResultEnums(object):\n    \"\"\"Enums used for TestResultRecord class.\n\n    Includes the tokens to mark test result with, and the string names for each\n    field in TestResultRecord.\n    \"\"\"\n\n    RECORD_NAME = 'Test Name'\n    RECORD_CLASS = 'Test Class'\n    RECORD_BEGIN_TIME = 'Begin Time'\n    RECORD_END_TIME = 'End Time'\n    RECORD_RESULT = 'Result'\n    RECORD_UID = 'UID'\n    RECORD_EXTRAS = 'Extras'\n    RECORD_EXTRA_ERRORS = 'Extra Errors'\n    RECORD_DETAILS = 'Details'\n    RECORD_STACKTRACE = 'Stacktrace'\n    TEST_RESULT_PASS = 'PASS'\n    TEST_RESULT_FAIL = 'FAIL'\n    TEST_RESULT_SKIP = 'SKIP'\n    TEST_RESULT_ERROR = 'ERROR'\n\n\nclass TestResultRecord(object):\n    \"\"\"A record that holds the information of a test case execution.\n\n    Attributes:\n        test_name: A string representing the name of the test case.\n        begin_time: Epoch timestamp of when the test case started.\n        end_time: Epoch timestamp of when the test case ended.\n        self.uid: Unique identifier of a test case.\n        self.result: Test result, PASS/FAIL/SKIP.\n        self.extras: User defined extra information of the test result.\n        self.details: A string explaining the details of the test case.\n    \"\"\"\n\n    def __init__(self, t_name, t_class=None):\n        self.test_name = t_name\n        self.test_class = t_class\n        self.begin_time = None\n        self.end_time = None\n        self.uid = None\n        self.result = None\n        self.extras = None\n        self.details = None\n        self.stacktrace = None\n        self.extra_errors = {}\n\n    def test_begin(self):\n        \"\"\"Call this when the test case it records begins execution.\n\n        Sets the begin_time of this record.\n        \"\"\"\n        self.begin_time = utils.get_current_epoch_time()\n\n    def _test_end(self, result, e):\n        \"\"\"Class internal function to signal the end of a test case execution.\n\n        Args:\n            result: One of the TEST_RESULT enums in TestResultEnums.\n            e: A test termination signal (usually an exception object). It can\n                be any exception instance or of any subclass of\n                mobly.signals.TestSignal.\n        \"\"\"\n        self.end_time = utils.get_current_epoch_time()\n        self.result = result\n        if self.extra_errors:\n            self.result = TestResultEnums.TEST_RESULT_ERROR\n        if isinstance(e, signals.TestSignal):\n            self.details = e.details\n            _, _, exc_traceback = sys.exc_info()\n            if exc_traceback:\n                self.stacktrace = ''.join(traceback.format_tb(exc_traceback))\n            self.extras = e.extras\n        elif isinstance(e, Exception):\n            self.details = str(e)\n            _, _, exc_traceback = sys.exc_info()\n            if exc_traceback:\n                self.stacktrace = ''.join(traceback.format_tb(exc_traceback))\n\n    def test_pass(self, e=None):\n        \"\"\"To mark the test as passed in this record.\n\n        Args:\n            e: An instance of mobly.signals.TestPass.\n        \"\"\"\n        self._test_end(TestResultEnums.TEST_RESULT_PASS, e)\n\n    def test_fail(self, e=None):\n        \"\"\"To mark the test as failed in this record.\n\n        Only test_fail does instance check because we want 'assert xxx' to also\n        fail the test same way assert_true does.\n\n        Args:\n            e: An exception object. It can be an instance of AssertionError or\n                mobly.base_test.TestFailure.\n        \"\"\"\n        self._test_end(TestResultEnums.TEST_RESULT_FAIL, e)\n\n    def test_skip(self, e=None):\n        \"\"\"To mark the test as skipped in this record.\n\n        Args:\n            e: An instance of mobly.signals.TestSkip.\n        \"\"\"\n        self._test_end(TestResultEnums.TEST_RESULT_SKIP, e)\n\n    def test_error(self, e=None):\n        \"\"\"To mark the test as error in this record.\n\n        Args:\n            e: An exception object.\n        \"\"\"\n        self._test_end(TestResultEnums.TEST_RESULT_ERROR, e)\n\n    def add_error(self, tag, e):\n        \"\"\"Add extra error happened during a test mark the test result as\n        ERROR.\n\n        If an error is added the test record, the record's result is equivalent\n        to the case where an uncaught exception happened.\n\n        Args:\n            tag: A string describing where this error came from, e.g. 'on_pass'.\n            e: An exception object.\n        \"\"\"\n        self.result = TestResultEnums.TEST_RESULT_ERROR\n        self.extra_errors[tag] = str(e)\n\n    def __str__(self):\n        d = self.to_dict()\n        l = ['%s = %s' % (k, v) for k, v in d.items()]\n        s = ', '.join(l)\n        return s\n\n    def __repr__(self):\n        \"\"\"This returns a short string representation of the test record.\"\"\"\n        t = utils.epoch_to_human_time(self.begin_time)\n        return '%s %s %s' % (t, self.test_name, self.result)\n\n    def to_dict(self):\n        \"\"\"Gets a dictionary representating the content of this class.\n\n        Returns:\n            A dictionary representating the content of this class.\n        \"\"\"\n        d = {}\n        d[TestResultEnums.RECORD_NAME] = self.test_name\n        d[TestResultEnums.RECORD_CLASS] = self.test_class\n        d[TestResultEnums.RECORD_BEGIN_TIME] = self.begin_time\n        d[TestResultEnums.RECORD_END_TIME] = self.end_time\n        d[TestResultEnums.RECORD_RESULT] = self.result\n        d[TestResultEnums.RECORD_UID] = self.uid\n        d[TestResultEnums.RECORD_EXTRAS] = self.extras\n        d[TestResultEnums.RECORD_DETAILS] = self.details\n        d[TestResultEnums.RECORD_EXTRA_ERRORS] = self.extra_errors\n        d[TestResultEnums.RECORD_STACKTRACE] = self.stacktrace\n        return d\n\n    def json_str(self):\n        \"\"\"Converts this test record to a string in json format.\n\n        Format of the json string is:\n            {\n                'Test Name': <test name>,\n                'Begin Time': <epoch timestamp>,\n                'Details': <details>,\n                ...\n            }\n\n        Returns:\n            A json-format string representing the test record.\n        \"\"\"\n        return json.dumps(self.to_dict())\n\n\nclass TestResult(object):\n    \"\"\"A class that contains metrics of a test run.\n\n    This class is essentially a container of TestResultRecord objects.\n\n    Attributes:\n        self.requested: A list of strings, each is the name of a test requested\n            by user.\n        self.failed: A list of records for tests failed.\n        self.executed: A list of records for tests that were actually executed.\n        self.passed: A list of records for tests passed.\n        self.skipped: A list of records for tests skipped.\n        self.error: A list of records for tests with error result token.\n    \"\"\"\n\n    def __init__(self):\n        self.requested = []\n        self.failed = []\n        self.executed = []\n        self.passed = []\n        self.skipped = []\n        self.error = []\n        self.controller_info = {}\n\n    def __add__(self, r):\n        \"\"\"Overrides '+' operator for TestResult class.\n\n        The add operator merges two TestResult objects by concatenating all of\n        their lists together.\n\n        Args:\n            r: another instance of TestResult to be added\n\n        Returns:\n            A TestResult instance that's the sum of two TestResult instances.\n        \"\"\"\n        if not isinstance(r, TestResult):\n            raise TypeError('Operand %s of type %s is not a TestResult.' %\n                            (r, type(r)))\n        sum_result = TestResult()\n        for name in sum_result.__dict__:\n            r_value = getattr(r, name)\n            l_value = getattr(self, name)\n            if isinstance(r_value, list):\n                setattr(sum_result, name, l_value + r_value)\n            elif isinstance(r_value, dict):\n                # '+' operator for TestResult is only valid when multiple\n                # TestResult objs were created in the same test run, which means\n                # the controller info would be the same across all of them.\n                # TODO(angli): have a better way to validate this situation.\n                setattr(sum_result, name, l_value)\n        return sum_result\n\n    def add_record(self, record):\n        \"\"\"Adds a test record to test result.\n\n        A record is considered executed once it's added to the test result.\n\n        Args:\n            record: A test record object to add.\n        \"\"\"\n        self.executed.append(record)\n        if record.result == TestResultEnums.TEST_RESULT_FAIL:\n            self.failed.append(record)\n        elif record.result == TestResultEnums.TEST_RESULT_SKIP:\n            self.skipped.append(record)\n        elif record.result == TestResultEnums.TEST_RESULT_PASS:\n            self.passed.append(record)\n        else:\n            self.error.append(record)\n\n    def add_controller_info(self, name, info):\n        try:\n            json.dumps(info)\n        except TypeError:\n            logging.warning('Controller info for %s is not JSON serializable!'\n                            ' Coercing it to string.' % name)\n            self.controller_info[name] = str(info)\n            return\n        self.controller_info[name] = info\n\n    def fail_class(self, test_record):\n        \"\"\"Add a record to indicate a test class setup has failed and no test\n        in the class was executed.\n\n        Args:\n            test_record: A TestResultRecord object for the test class.\n        \"\"\"\n        self.executed.append(test_record)\n        self.failed.append(test_record)\n\n    @property\n    def is_all_pass(self):\n        \"\"\"True if no tests failed or threw errors, False otherwise.\"\"\"\n        num_of_failures = len(self.failed) + len(self.error)\n        if num_of_failures == 0:\n            return True\n        return False\n\n    def json_str(self):\n        \"\"\"Converts this test result to a string in json format.\n\n        Format of the json string is:\n            {\n                'Results': [\n                    {<executed test record 1>},\n                    {<executed test record 2>},\n                    ...\n                ],\n                'Summary': <summary dict>\n            }\n\n        Returns:\n            A json-format string representing the test results.\n        \"\"\"\n        d = {}\n        d['ControllerInfo'] = self.controller_info\n        d['Results'] = [record.to_dict() for record in self.executed]\n        d['Summary'] = self.summary_dict()\n        json_str = json.dumps(d, indent=4, sort_keys=True)\n        return json_str\n\n    def summary_str(self):\n        \"\"\"Gets a string that summarizes the stats of this test result.\n\n        The summary rovides the counts of how many test cases fall into each\n        category, like 'Passed', 'Failed' etc.\n\n        Format of the string is:\n            Requested <int>, Executed <int>, ...\n\n        Returns:\n            A summary string of this test result.\n        \"\"\"\n        l = ['%s %d' % (k, v) for k, v in self.summary_dict().items()]\n        # Sort the list so the order is the same every time.\n        msg = ', '.join(sorted(l))\n        return msg\n\n    def summary_dict(self):\n        \"\"\"Gets a dictionary that summarizes the stats of this test result.\n\n        The summary rovides the counts of how many test cases fall into each\n        category, like 'Passed', 'Failed' etc.\n\n        Returns:\n            A dictionary with the stats of this test result.\n        \"\"\"\n        d = {}\n        d['Requested'] = len(self.requested)\n        d['Executed'] = len(self.executed)\n        d['Passed'] = len(self.passed)\n        d['Failed'] = len(self.failed)\n        d['Skipped'] = len(self.skipped)\n        d['Error'] = len(self.error)\n        return d\n", "path": "mobly/records.py"}]}