summaryrefslogtreecommitdiff
path: root/numpy/testing/tests/test_utils.py
diff options
context:
space:
mode:
authorCharles Harris <charlesr.harris@gmail.com>2018-04-21 21:05:58 -0600
committerGitHub <noreply@github.com>2018-04-21 21:05:58 -0600
commitb5c1bcf1e8ef6e9c11bb4138a15286e648fcbce0 (patch)
tree62cd661d640647bfd210682cf0ce8d553ef69d09 /numpy/testing/tests/test_utils.py
parente0b5e8740efe6d42c909c1374494e614592c65ab (diff)
parent3ff0c5c82b8abc4c94b1801a13f488778631f38a (diff)
downloadnumpy-b5c1bcf1e8ef6e9c11bb4138a15286e648fcbce0.tar.gz
Merge pull request #10891 from eric-wieser/assert-no-cycles
TST: Extract a helper function to test for reference cycles
Diffstat (limited to 'numpy/testing/tests/test_utils.py')
-rw-r--r--numpy/testing/tests/test_utils.py76
1 files changed, 75 insertions, 1 deletions
diff --git a/numpy/testing/tests/test_utils.py b/numpy/testing/tests/test_utils.py
index 35f81d8a7..0592e62f8 100644
--- a/numpy/testing/tests/test_utils.py
+++ b/numpy/testing/tests/test_utils.py
@@ -6,6 +6,7 @@ import os
import itertools
import textwrap
import pytest
+import weakref
import numpy as np
from numpy.testing import (
@@ -14,7 +15,7 @@ from numpy.testing import (
assert_raises, assert_warns, assert_no_warnings, assert_allclose,
assert_approx_equal, assert_array_almost_equal_nulp, assert_array_max_ulp,
clear_and_catch_warnings, suppress_warnings, assert_string_equal, assert_,
- tempdir, temppath,
+ tempdir, temppath, assert_no_gc_cycles, HAS_REFCOUNT
)
@@ -1360,3 +1361,76 @@ def test_clear_and_catch_warnings_inherit():
warnings.simplefilter('ignore')
warnings.warn('Some warning')
assert_equal(my_mod.__warningregistry__, {})
+
+
+@pytest.mark.skipif(not HAS_REFCOUNT, reason="Python lacks refcounts")
+class TestAssertNoGcCycles(object):
+ """ Test assert_no_gc_cycles """
+ def test_passes(self):
+ def no_cycle():
+ b = []
+ b.append([])
+ return b
+
+ with assert_no_gc_cycles():
+ no_cycle()
+
+ assert_no_gc_cycles(no_cycle)
+
+
+ def test_asserts(self):
+ def make_cycle():
+ a = []
+ a.append(a)
+ a.append(a)
+ return a
+
+ with assert_raises(AssertionError):
+ with assert_no_gc_cycles():
+ make_cycle()
+
+ with assert_raises(AssertionError):
+ assert_no_gc_cycles(make_cycle)
+
+
+ def test_fails(self):
+ """
+ Test that in cases where the garbage cannot be collected, we raise an
+ error, instead of hanging forever trying to clear it.
+ """
+
+ class ReferenceCycleInDel(object):
+ """
+ An object that not only contains a reference cycle, but creates new
+ cycles whenever it's garbage-collected and its __del__ runs
+ """
+ make_cycle = True
+
+ def __init__(self):
+ self.cycle = self
+
+ def __del__(self):
+ # break the current cycle so that `self` can be freed
+ self.cycle = None
+
+ if ReferenceCycleInDel.make_cycle:
+ # but create a new one so that the garbage collector has more
+ # work to do.
+ ReferenceCycleInDel()
+
+ try:
+ w = weakref.ref(ReferenceCycleInDel())
+ try:
+ with assert_raises(RuntimeError):
+ # this will be unable to get a baseline empty garbage
+ assert_no_gc_cycles(lambda: None)
+ except AssertionError:
+ # the above test is only necessary if the GC actually tried to free
+ # our object anyway, which python 2.7 does not.
+ if w() is not None:
+ pytest.skip("GC does not call __del__ on cyclic objects")
+ raise
+
+ finally:
+ # make sure that we stop creating reference cycles
+ ReferenceCycleInDel.make_cycle = False