Fix stacktrace logging for when both test body and teardown throw
exceptions.

Actually fulfill the promise of TestRecord.add_error: if extra error
was added to the record, something went wrong, the test result should
be UNKNOWN (error) as opposed to the original result of the test body.

Also remove a duplicate test result log line.

Bug=28942818

Change-Id: Id8a48244732eed3dfa4d6fdb07224b2ea8ca418c
diff --git a/acts/framework/acts/base_test.py b/acts/framework/acts/base_test.py
index 8da846f..570a537 100644
--- a/acts/framework/acts/base_test.py
+++ b/acts/framework/acts/base_test.py
@@ -15,6 +15,7 @@
 # limitations under the License.
 
 import os
+import traceback
 
 from acts import asserts
 from acts import keys
@@ -262,7 +263,6 @@
         test_name = record.test_name
         self.log.exception(record.details)
         begin_time = logger.epoch_to_log_line_timestamp(record.begin_time)
-        self.log.info(RESULT_LINE_TEMPLATE, test_name, record.result)
         self.on_exception(test_name, begin_time)
 
     def on_exception(self, test_name, begin_time):
@@ -327,8 +327,15 @@
                 else:
                     verdict = test_func()
             finally:
-                self._teardown_test(test_name)
+                try:
+                    self._teardown_test(test_name)
+                except signals.TestAbortAll:
+                    raise
+                except Exception as e:
+                    self.log.error(traceback.format_exc())
+                    tr_record.add_error("teardown_test", e)
         except (signals.TestFailure, AssertionError) as e:
+            self.log.error(traceback.format_exc())
             tr_record.test_fail(e)
             self._exec_procedure_func(self._on_fail, tr_record)
         except signals.TestSkip as e:
@@ -348,6 +355,7 @@
             is_generate_trigger = True
             self.results.requested.remove(test_name)
         except Exception as e:
+            self.log.error(traceback.format_exc())
             # Exception happened during test.
             tr_record.test_unknown(e)
             self._exec_procedure_func(self._on_exception, tr_record)
diff --git a/acts/framework/acts/records.py b/acts/framework/acts/records.py
index 0a09b387..dce3faf 100644
--- a/acts/framework/acts/records.py
+++ b/acts/framework/acts/records.py
@@ -87,6 +87,8 @@
         """
         self.end_time = utils.get_current_epoch_time()
         self.result = result
+        if self.extra_errors:
+            self.result = TestResultEnums.TEST_RESULT_UNKNOWN
         if isinstance(e, signals.TestSignal):
             self.details = e.details
             self.extras = e.extras
diff --git a/acts/framework/tests/acts_base_class_test.py b/acts/framework/tests/acts_base_class_test.py
index 12d57b9..a42443b 100755
--- a/acts/framework/tests/acts_base_class_test.py
+++ b/acts/framework/tests/acts_base_class_test.py
@@ -212,12 +212,12 @@
                 pass
         bt_cls = MockBaseTest(self.mock_test_cls_configs)
         bt_cls.run()
-        actual_record = bt_cls.results.failed[0]
+        actual_record = bt_cls.results.unknown[0]
         self.assertEqual(actual_record.test_name, self.mock_test_name)
-        self.assertEqual(actual_record.details, MSG_EXPECTED_EXCEPTION)
+        self.assertIsNone(actual_record.details)
         self.assertIsNone(actual_record.extras)
-        expected_summary = ("Executed 1, Failed 1, Passed 0, Requested 1, "
-                            "Skipped 0, Unknown 0")
+        expected_summary = ("Executed 1, Failed 0, Passed 0, Requested 1, "
+                            "Skipped 0, Unknown 1")
         self.assertEqual(bt_cls.results.summary_str(), expected_summary)
 
     def test_teardown_test_raise_exception(self):
@@ -230,12 +230,50 @@
         bt_cls.run()
         actual_record = bt_cls.results.unknown[0]
         self.assertEqual(actual_record.test_name, self.mock_test_name)
-        self.assertEqual(actual_record.details, MSG_EXPECTED_EXCEPTION)
+        self.assertIsNone(actual_record.details)
         self.assertIsNone(actual_record.extras)
         expected_summary = ("Executed 1, Failed 0, Passed 0, Requested 1, "
                             "Skipped 0, Unknown 1")
         self.assertEqual(bt_cls.results.summary_str(), expected_summary)
 
+    def test_both_teardown_and_test_body_raise_exceptions(self):
+        class MockBaseTest(base_test.BaseTestClass):
+            def teardown_test(self):
+                asserts.assert_true(False, MSG_EXPECTED_EXCEPTION)
+            def test_something(self):
+                raise Exception("Test Body Exception.")
+        bt_cls = MockBaseTest(self.mock_test_cls_configs)
+        bt_cls.run()
+        actual_record = bt_cls.results.unknown[0]
+        self.assertEqual(actual_record.test_name, self.mock_test_name)
+        self.assertEqual(actual_record.details, "Test Body Exception.")
+        self.assertIsNone(actual_record.extras)
+        self.assertEqual(actual_record.extra_errors["teardown_test"],
+                         "Details=This is an expected exception., Extras=None")
+        expected_summary = ("Executed 1, Failed 0, Passed 0, Requested 1, "
+                            "Skipped 0, Unknown 1")
+        self.assertEqual(bt_cls.results.summary_str(), expected_summary)
+
+    def test_explicit_pass_but_teardown_test_raises_an_exception(self):
+        """Test record result should be marked as UNKNOWN as opposed to PASS.
+        """
+        class MockBaseTest(base_test.BaseTestClass):
+            def teardown_test(self):
+                asserts.assert_true(False, MSG_EXPECTED_EXCEPTION)
+            def test_something(self):
+                asserts.explicit_pass("Test Passed!")
+        bt_cls = MockBaseTest(self.mock_test_cls_configs)
+        bt_cls.run()
+        actual_record = bt_cls.results.unknown[0]
+        self.assertEqual(actual_record.test_name, self.mock_test_name)
+        self.assertEqual(actual_record.details, "Test Passed!")
+        self.assertIsNone(actual_record.extras)
+        self.assertEqual(actual_record.extra_errors["teardown_test"],
+                         "Details=This is an expected exception., Extras=None")
+        expected_summary = ("Executed 1, Failed 0, Passed 0, Requested 1, "
+                            "Skipped 0, Unknown 1")
+        self.assertEqual(bt_cls.results.summary_str(), expected_summary)
+
     def test_on_pass_raise_exception(self):
         class MockBaseTest(base_test.BaseTestClass):
             def on_pass(self, test_name, begin_time):