blob: 85246f302cc51d5d642a83f72d9e2a15a14e2f98 [file] [log] [blame]
# Copyright (c) 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import sys
import threading
import traceback
import unittest
class ThreadWatcherMixIn(object):
def setUp(self):
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]
if new_threads:
details = []
for th in new_threads:
details.append('\nThread %s stacktrace:\n' % th)
try:
details.extend(traceback.format_stack(sys._current_frames()[t.ident]))
except Exception:
if t.ident in sys._current_frames():
raise
# This can happen due to threads starting or stopping concurrently.
details.append(' Thread stopped while acquiring stacktrace.\n')
details = ''.join(details)
self.fail('Found %d running thread(s) after the test.\n%s' % (
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):
"""Base unittest class that fails on leaked threads after the test."""
def setUp(self):
super(TestCase, self).setUp()
ThreadWatcherMixIn.setUp(self)
def tearDown(self):
ThreadWatcherMixIn.tearDown(self)
super(TestCase, self).tearDown()