Thread Watcher: don't fail when test already failed.

This prevents hiding the likely real root cause of the failure.

Does not depend, but works with https://codereview.chromium.org/2121343004
change to expect_tests.

R=machenbach@chromium.org, sergiyb@chromium.org
BUG=

Review URL: https://codereview.chromium.org/2127943002 .
diff --git a/testing_support/tests/thread_watcher_test.py b/testing_support/tests/thread_watcher_test.py
index 4989f2d..c98be29 100644
--- a/testing_support/tests/thread_watcher_test.py
+++ b/testing_support/tests/thread_watcher_test.py
@@ -28,6 +28,13 @@
     self._stop_event.set()
     self.join()
 
+class _ResultFake(object):
+  def __init__(self, value):
+    self.value = value
+
+  def wasSuccessful(self):
+    return self.value
+
 
 class ThreadWatcherTestCase(thread_watcher.TestCase):
   def setUp(self):
@@ -71,6 +78,38 @@
     self.assertNotIn('  Thread stopped while acquiring stacktrace.\n',
                      error_message)
 
+  def test_extra_threads_unittest_pass(self):
+    # Test succeeded, so thread_watcher must cause failure.
+    self.watcher._resultForDoCleanups = _ResultFake(True)
+    self.watcher.setUp()
+    self._threads.append(_PuppetThread('foo'))
+    self.watcher.tearDown()
+    self.assertEqual(len(self._fail_called), 1)
+
+  def test_extra_threads_unittest_fail(self):
+    # Test failed already, so thread_watcher must ignore result.
+    self.watcher._resultForDoCleanups = _ResultFake(False)
+    self.watcher.setUp()
+    self._threads.append(_PuppetThread('foo'))
+    self.watcher.tearDown()
+    self.assertEqual(self._fail_called, [])
+
+  def test_extra_threads_expect_tests_pass(self):
+    # Test succeeded, so thread_watcher must cause failure.
+    self.watcher._test_failed_with_exception = False
+    self.watcher.setUp()
+    self._threads.append(_PuppetThread('foo'))
+    self.watcher.tearDown()
+    self.assertEqual(len(self._fail_called), 1)
+
+  def test_extra_threads_expect_tests_fail(self):
+    # Test failed already, so thread_watcher must ignore result.
+    self.watcher._test_failed_with_exception = True
+    self.watcher.setUp()
+    self._threads.append(_PuppetThread('foo'))
+    self.watcher.tearDown()
+    self.assertEqual(self._fail_called, [])
+
   def test_fail_get_stacktrace(self):
     self.watcher.setUp()
     self._threads.append(_PuppetThread('foo'))
@@ -108,6 +147,5 @@
     self._threads[-1].stop()
 
 
-
 if __name__ == '__main__':
   unittest.main()
diff --git a/testing_support/thread_watcher.py b/testing_support/thread_watcher.py
index ca5180b..85246f3 100644
--- a/testing_support/thread_watcher.py
+++ b/testing_support/thread_watcher.py
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 
+import logging
 import sys
 import threading
 import traceback
@@ -14,6 +15,12 @@
     self._pre_test_threads = [t.ident for t in threading.enumerate()]
 
   def tearDown(self):
+    # If test failed before this check, don't raise another exception
+    # overwriting the original one, and making it harder to debug.
+    # Besides, that exception might be the reason cleanup didn't happen.
+    if self._test_definitely_failed():
+      return
+
     post_test_threads = threading.enumerate()
     new_threads = [t for t in post_test_threads
                    if t.ident not in self._pre_test_threads]
@@ -31,8 +38,25 @@
       details = ''.join(details)
 
       self.fail('Found %d running thread(s) after the test.\n%s' % (
-        len(new_threads), details))
+                len(new_threads), details))
 
+  def _test_definitely_failed(self):
+    """Returns True only if test has definitely failed already.
+
+    This is necessary to determine whether we should fail the test when finding
+    stray threads. If the test has failed in some other way, we should avoid
+    throwing an exception because it will mask the original error.
+    """
+    # When run using unittest runner,
+    # self._resultForDoCleanups is set to internal unittest object.
+    if hasattr(self, '_resultForDoCleanups') and self._resultForDoCleanups:
+      return not self._resultForDoCleanups.wasSuccessful()
+    # expect_tests runner does so differently (see
+    # https://codereview.chromium.org/2121343004).
+    if hasattr(self, '_test_failed_with_exception'):
+      return self._test_failed_with_exception
+    # Default case - be conservative.
+    return False
 
 
 class TestCase(unittest.TestCase, ThreadWatcherMixIn):