We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Properly report skipped test classes
Now that we have a way to reliably get all the tests in a class, including the generated tests, the test report for an aborted class should include entries for all the tests requested, instead of only one entry for the class.

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

{"golden_diff": "diff --git a/mobly/records.py b/mobly/records.py\n--- a/mobly/records.py\n+++ b/mobly/records.py\n@@ -262,11 +262,12 @@\n         Args:\n             record: A test record object to add.\n         \"\"\"\n+        if record.result == TestResultEnums.TEST_RESULT_SKIP:\n+            self.skipped.append(record)\n+            return\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@@ -283,14 +284,32 @@\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+        \"\"\"Add a record to indicate a test class has failed before any test\n+        could execute.\n+\n+        This is only called before any test is actually executed. So it only\n+        adds an error entry that describes why the class failed to the tally\n+        and does not affect the total number of tests requrested or exedcuted.\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+        self.error.append(test_record)\n+\n+    def is_test_executed(self, test_name):\n+        \"\"\"Checks if a specific test has been executed.\n+\n+        Args:\n+            test_name: string, the name of the test to check.\n+\n+        Returns:\n+            True if the test has been executed according to the test result,\n+            False otherwise.\n+        \"\"\"\n+        for record in self.executed:\n+            if record.test_name == test_name:\n+                return True\n+        return False\n \n     @property\n     def is_all_pass(self):\n", "issue": "Properly report skipped test classes\nNow that we have a way to reliably get all the tests in a class, including the generated tests, the test report for an aborted class should include entries for all the tests requested, instead of only one entry for the class.\n", "before_files": [{"content": "# 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 execution.\n\n    Attributes:\n        test_name: A string representing the name of the test method.\n        begin_time: Epoch timestamp of when the test started.\n        end_time: Epoch timestamp of when the test ended.\n        self.uid: Unique identifier of a test.\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.\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 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 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 provides the counts of how many tests 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 provides the counts of how many tests 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": "# 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 execution.\n\n    Attributes:\n        test_name: A string representing the name of the test method.\n        begin_time: Epoch timestamp of when the test started.\n        end_time: Epoch timestamp of when the test ended.\n        self.uid: Unique identifier of a test.\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.\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 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 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        if record.result == TestResultEnums.TEST_RESULT_SKIP:\n            self.skipped.append(record)\n            return\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_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 has failed before any test\n        could execute.\n\n        This is only called before any test is actually executed. So it only\n        adds an error entry that describes why the class failed to the tally\n        and does not affect the total number of tests requrested or exedcuted.\n\n        Args:\n            test_record: A TestResultRecord object for the test class.\n        \"\"\"\n        self.error.append(test_record)\n\n    def is_test_executed(self, test_name):\n        \"\"\"Checks if a specific test has been executed.\n\n        Args:\n            test_name: string, the name of the test to check.\n\n        Returns:\n            True if the test has been executed according to the test result,\n            False otherwise.\n        \"\"\"\n        for record in self.executed:\n            if record.test_name == test_name:\n                return True\n        return False\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 provides the counts of how many tests 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 provides the counts of how many tests 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"}]}