Commit f518b140 authored by Jason Madden's avatar Jason Madden

Improve the output of checking the refcounts to get a sense of what might have leaked.

parent 47643cca
...@@ -88,6 +88,30 @@ def wrap_refcount(method): ...@@ -88,6 +88,30 @@ def wrap_refcount(method):
if not os.getenv('GEVENTTEST_LEAKCHECK'): if not os.getenv('GEVENTTEST_LEAKCHECK'):
return method return method
# Some builtin things that we ignore
IGNORED_TYPES = (tuple, dict)
def type_hist():
import collections
d = collections.defaultdict(int)
for x in gc.get_objects():
k = type(x)
if k in IGNORED_TYPES:
continue
d[k] += 1
return d
def report_diff(a, b):
diff_lines = []
for k, v in sorted(a.items()):
if b[k] != v:
diff_lines.append("%s: %s != %s" % (k, v, b[k]))
if not diff_lines:
return None
diff = '\n'.join(diff_lines)
return diff
@wraps(method) @wraps(method)
def wrapped(self, *args, **kwargs): def wrapped(self, *args, **kwargs):
gc.collect() gc.collect()
...@@ -98,16 +122,29 @@ def wrap_refcount(method): ...@@ -98,16 +122,29 @@ def wrap_refcount(method):
gc.disable() gc.disable()
try: try:
while True: while True:
d = len(gc.get_objects())
# Grab current snapshot
hist_before = type_hist()
d = sum(hist_before.values())
self.setUp() self.setUp()
method(self, *args, **kwargs) method(self, *args, **kwargs)
self.tearDown() self.tearDown()
# Grab post snapshot
if 'urlparse' in sys.modules: if 'urlparse' in sys.modules:
sys.modules['urlparse'].clear_cache() sys.modules['urlparse'].clear_cache()
if 'urllib.parse' in sys.modules: if 'urllib.parse' in sys.modules:
sys.modules['urllib.parse'].clear_cache() sys.modules['urllib.parse'].clear_cache()
d = len(gc.get_objects()) - d hist_after = type_hist()
d = sum(hist_after.values()) - d
deltas.append(d) deltas.append(d)
# Reset and check for cycles
gc.collect()
if gc.garbage:
raise AssertionError("Generated uncollectable garbage")
# the following configurations are classified as "no leak" # the following configurations are classified as "no leak"
# [0, 0] # [0, 0]
# [x, 0, 0] # [x, 0, 0]
...@@ -122,7 +159,8 @@ def wrap_refcount(method): ...@@ -122,7 +159,8 @@ def wrap_refcount(method):
elif len(deltas) >= 4 and sum(deltas[-4:]) == 0: elif len(deltas) >= 4 and sum(deltas[-4:]) == 0:
break break
elif len(deltas) >= 3 and deltas[-1] > 0 and deltas[-1] == deltas[-2] and deltas[-2] == deltas[-3]: elif len(deltas) >= 3 and deltas[-1] > 0 and deltas[-1] == deltas[-2] and deltas[-2] == deltas[-3]:
raise AssertionError('refcount increased by %r' % (deltas, )) diff = report_diff(hist_before, hist_after)
raise AssertionError('refcount increased by %r\n%s' % (deltas, diff))
# OK, we don't know for sure yet. Let's search for more # OK, we don't know for sure yet. Let's search for more
if sum(deltas[-3:]) <= 0 or sum(deltas[-4:]) <= 0 or deltas[-4:].count(0) >= 2: if sum(deltas[-3:]) <= 0 or sum(deltas[-4:]) <= 0 or deltas[-4:].count(0) >= 2:
# this is suspicious, so give a few more runs # this is suspicious, so give a few more runs
...@@ -130,7 +168,7 @@ def wrap_refcount(method): ...@@ -130,7 +168,7 @@ def wrap_refcount(method):
else: else:
limit = 7 limit = 7
if len(deltas) >= limit: if len(deltas) >= limit:
raise AssertionError('refcount increased by %r' % (deltas, )) raise AssertionError('refcount increased by %r\n%s' % (deltas, report_diff(hist_before, hist_after)))
finally: finally:
gc.enable() gc.enable()
self.skipTearDown = True self.skipTearDown = True
......
...@@ -25,7 +25,6 @@ class Test(greentest.TestCase): ...@@ -25,7 +25,6 @@ class Test(greentest.TestCase):
def test(self): def test(self):
server = backdoor.BackdoorServer(('127.0.0.1', 0)) server = backdoor.BackdoorServer(('127.0.0.1', 0))
server.start()
def connect(): def connect():
conn = create_connection(('127.0.0.1', server.server_port)) conn = create_connection(('127.0.0.1', server.server_port))
...@@ -34,9 +33,12 @@ class Test(greentest.TestCase): ...@@ -34,9 +33,12 @@ class Test(greentest.TestCase):
line = conn.makefile().readline() line = conn.makefile().readline()
assert line.strip() == '4', repr(line) assert line.strip() == '4', repr(line)
jobs = [gevent.spawn(connect) for _ in xrange(10)] server.start()
gevent.joinall(jobs) try:
server.close() jobs = [gevent.spawn(connect) for _ in xrange(10)]
gevent.joinall(jobs, raise_error=True)
finally:
server.close()
#self.assertEqual(conn.recv(1), '') #self.assertEqual(conn.recv(1), '')
def test_quit(self): def test_quit(self):
......
...@@ -51,11 +51,12 @@ class TestTCP(greentest.TestCase): ...@@ -51,11 +51,12 @@ class TestTCP(greentest.TestCase):
self.port = listener.getsockname()[1] self.port = listener.getsockname()[1]
def cleanup(self): def cleanup(self):
try: if hasattr(self, 'listener'):
self.listener.close() try:
except: self.listener.close()
pass except:
del self.listener pass
del self.listener
def create_connection(self): def create_connection(self):
sock = socket.socket() sock = socket.socket()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment