Skip to content

Commit 75120d2

Browse files
authored
bpo-36719: regrtest always detect uncollectable objects (GH-12951)
regrtest now always detects uncollectable objects. Previously, the check was only enabled by --findleaks. The check now also works with -jN/--multiprocess N. --findleaks becomes a deprecated alias to --fail-env-changed.
1 parent 7abb6c0 commit 75120d2

File tree

5 files changed

+66
-69
lines changed

5 files changed

+66
-69
lines changed

Lib/test/libregrtest/cmdline.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,9 @@ def _create_parser():
226226
'(instead of the Python stdlib test suite)')
227227

228228
group = parser.add_argument_group('Special runs')
229-
group.add_argument('-l', '--findleaks', action='store_true',
230-
help='if GC is available detect tests that leak memory')
229+
group.add_argument('-l', '--findleaks', action='store_const', const=2,
230+
default=1,
231+
help='deprecated alias to --fail-env-changed')
231232
group.add_argument('-L', '--runleaks', action='store_true',
232233
help='run the leaks(1) command just before exit.' +
233234
more_details)
@@ -309,7 +310,7 @@ def _parse_args(args, **kwargs):
309310
# Defaults
310311
ns = argparse.Namespace(testdir=None, verbose=0, quiet=False,
311312
exclude=False, single=False, randomize=False, fromfile=None,
312-
findleaks=False, use_resources=None, trace=False, coverdir='coverage',
313+
findleaks=1, use_resources=None, trace=False, coverdir='coverage',
313314
runleaks=False, huntrleaks=False, verbose2=False, print_slow=False,
314315
random_seed=None, use_mp=None, verbose3=False, forever=False,
315316
header=False, failfast=False, match_tests=None, pgo=False)
@@ -330,12 +331,13 @@ def _parse_args(args, **kwargs):
330331
parser.error("unrecognized arguments: %s" % arg)
331332
sys.exit(1)
332333

334+
if ns.findleaks > 1:
335+
# --findleaks implies --fail-env-changed
336+
ns.fail_env_changed = True
333337
if ns.single and ns.fromfile:
334338
parser.error("-s and -f don't go together!")
335339
if ns.use_mp is not None and ns.trace:
336340
parser.error("-T and -j don't go together!")
337-
if ns.use_mp is not None and ns.findleaks:
338-
parser.error("-l and -j don't go together!")
339341
if ns.failfast and not (ns.verbose or ns.verbose3):
340342
parser.error("-G/--failfast needs either -v or -W")
341343
if ns.pgo and (ns.verbose or ns.verbose2 or ns.verbose3):

Lib/test/libregrtest/main.py

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,6 @@
2020
from test.libregrtest.setup import setup_tests
2121
from test.libregrtest.utils import removepy, count, format_duration, printlist
2222
from test import support
23-
try:
24-
import gc
25-
except ImportError:
26-
gc = None
2723

2824

2925
# When tests are run from the Python build directory, it is best practice
@@ -90,9 +86,6 @@ def __init__(self):
9086
# used by --coverage, trace.Trace instance
9187
self.tracer = None
9288

93-
# used by --findleaks, store for gc.garbage
94-
self.found_garbage = []
95-
9689
# used to display the progress bar "[ 3/100]"
9790
self.start_time = time.monotonic()
9891
self.test_count = ''
@@ -173,22 +166,6 @@ def parse_args(self, kwargs):
173166
"faulthandler.dump_traceback_later", file=sys.stderr)
174167
ns.timeout = None
175168

176-
if ns.threshold is not None and gc is None:
177-
print('No GC available, ignore --threshold.', file=sys.stderr)
178-
ns.threshold = None
179-
180-
if ns.findleaks:
181-
if gc is not None:
182-
# Uncomment the line below to report garbage that is not
183-
# freeable by reference counting alone. By default only
184-
# garbage that is not collectable by the GC is reported.
185-
pass
186-
#gc.set_debug(gc.DEBUG_SAVEALL)
187-
else:
188-
print('No GC available, disabling --findleaks',
189-
file=sys.stderr)
190-
ns.findleaks = False
191-
192169
if ns.xmlpath:
193170
support.junit_xml_list = self.testsuite_xml = []
194171

@@ -308,7 +285,7 @@ def rerun_failed_tests(self):
308285
print("Re-running failed tests in verbose mode")
309286
self.rerun = self.bad[:]
310287
for test_name in self.rerun:
311-
print("Re-running test %r in verbose mode" % test_name, flush=True)
288+
print(f"Re-running {test_name} in verbose mode", flush=True)
312289
self.ns.verbose = True
313290
ok = runtest(self.ns, test_name)
314291

@@ -318,10 +295,10 @@ def rerun_failed_tests(self):
318295
if ok.result == INTERRUPTED:
319296
self.interrupted = True
320297
break
321-
else:
322-
if self.bad:
323-
print(count(len(self.bad), 'test'), "failed again:")
324-
printlist(self.bad)
298+
299+
if self.bad:
300+
print(count(len(self.bad), 'test'), "failed again:")
301+
printlist(self.bad)
325302

326303
self.display_result()
327304

@@ -426,16 +403,6 @@ def run_tests_sequential(self):
426403
# be quiet: say nothing if the test passed shortly
427404
previous_test = None
428405

429-
if self.ns.findleaks:
430-
gc.collect()
431-
if gc.garbage:
432-
print("Warning: test created", len(gc.garbage), end=' ')
433-
print("uncollectable object(s).")
434-
# move the uncollectable objects somewhere so we don't see
435-
# them again
436-
self.found_garbage.extend(gc.garbage)
437-
del gc.garbage[:]
438-
439406
# Unload the newly imported modules (best effort finalization)
440407
for module in sys.modules.keys():
441408
if module not in save_modules and module.startswith("test."):

Lib/test/libregrtest/runtest.py

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import collections
22
import faulthandler
33
import functools
4+
import gc
45
import importlib
56
import io
67
import os
78
import sys
89
import time
910
import traceback
1011
import unittest
12+
1113
from test import support
1214
from test.libregrtest.refleak import dash_R, clear_caches
1315
from test.libregrtest.save_env import saved_test_environment
@@ -59,7 +61,7 @@
5961

6062

6163
# used by --findleaks, store for gc.garbage
62-
found_garbage = []
64+
FOUND_GARBAGE = []
6365

6466

6567
def format_test_result(result):
@@ -182,11 +184,6 @@ def runtest(ns, test_name):
182184
return TestResult(test_name, FAILED, 0.0, None)
183185

184186

185-
def post_test_cleanup():
186-
support.gc_collect()
187-
support.reap_children()
188-
189-
190187
def _test_module(the_module):
191188
loader = unittest.TestLoader()
192189
tests = loader.loadTestsFromModule(the_module)
@@ -224,21 +221,19 @@ def _runtest_inner2(ns, test_name):
224221
finally:
225222
cleanup_test_droppings(test_name, ns.verbose)
226223

227-
if ns.findleaks:
228-
import gc
229-
support.gc_collect()
230-
if gc.garbage:
231-
import gc
232-
gc.garbage = [1]
233-
print_warning(f"{test_name} created {len(gc.garbage)} "
234-
f"uncollectable object(s).")
235-
# move the uncollectable objects somewhere,
236-
# so we don't see them again
237-
found_garbage.extend(gc.garbage)
238-
gc.garbage.clear()
239-
support.environment_altered = True
224+
support.gc_collect()
225+
226+
if gc.garbage:
227+
support.environment_altered = True
228+
print_warning(f"{test_name} created {len(gc.garbage)} "
229+
f"uncollectable object(s).")
240230

241-
post_test_cleanup()
231+
# move the uncollectable objects somewhere,
232+
# so we don't see them again
233+
FOUND_GARBAGE.extend(gc.garbage)
234+
gc.garbage.clear()
235+
236+
support.reap_children()
242237

243238
return refleak
244239

Lib/test/test_regrtest.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,8 @@
2626
ROOT_DIR = os.path.abspath(os.path.normpath(ROOT_DIR))
2727

2828
TEST_INTERRUPTED = textwrap.dedent("""
29-
from signal import SIGINT
29+
from signal import SIGINT, raise_signal
3030
try:
31-
from signal import raise_signal
3231
raise_signal(SIGINT)
3332
except ImportError:
3433
import os
@@ -255,9 +254,7 @@ def test_multiprocess(self):
255254
self.checkError([opt], 'expected one argument')
256255
self.checkError([opt, 'foo'], 'invalid int value')
257256
self.checkError([opt, '2', '-T'], "don't go together")
258-
self.checkError([opt, '2', '-l'], "don't go together")
259257
self.checkError([opt, '0', '-T'], "don't go together")
260-
self.checkError([opt, '0', '-l'], "don't go together")
261258

262259
def test_coverage(self):
263260
for opt in '-T', '--coverage':
@@ -454,8 +451,8 @@ def list_regex(line_format, tests):
454451
regex = list_regex('%s re-run test%s', rerun)
455452
self.check_line(output, regex)
456453
self.check_line(output, "Re-running failed tests in verbose mode")
457-
for name in rerun:
458-
regex = "Re-running test %r in verbose mode" % name
454+
for test_name in rerun:
455+
regex = f"Re-running {test_name} in verbose mode"
459456
self.check_line(output, regex)
460457

461458
if no_test_ran:
@@ -1070,6 +1067,38 @@ def test_other_bug(self):
10701067
self.check_executed_tests(output, [testname, testname2],
10711068
no_test_ran=[testname])
10721069

1070+
@support.cpython_only
1071+
def test_findleaks(self):
1072+
code = textwrap.dedent(r"""
1073+
import _testcapi
1074+
import gc
1075+
import unittest
1076+
1077+
@_testcapi.with_tp_del
1078+
class Garbage:
1079+
def __tp_del__(self):
1080+
pass
1081+
1082+
class Tests(unittest.TestCase):
1083+
def test_garbage(self):
1084+
# create an uncollectable object
1085+
obj = Garbage()
1086+
obj.ref_cycle = obj
1087+
obj = None
1088+
""")
1089+
testname = self.create_test(code=code)
1090+
1091+
output = self.run_tests("--fail-env-changed", testname, exitcode=3)
1092+
self.check_executed_tests(output, [testname],
1093+
env_changed=[testname],
1094+
fail_env_changed=True)
1095+
1096+
# --findleaks is now basically an alias to --fail-env-changed
1097+
output = self.run_tests("--findleaks", testname, exitcode=3)
1098+
self.check_executed_tests(output, [testname],
1099+
env_changed=[testname],
1100+
fail_env_changed=True)
1101+
10731102

10741103
class TestUtils(unittest.TestCase):
10751104
def test_format_duration(self):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
regrtest now always detects uncollectable objects. Previously, the check was
2+
only enabled by ``--findleaks``. The check now also works with
3+
``-jN/--multiprocess N``. ``--findleaks`` becomes a deprecated alias to
4+
``--fail-env-changed``.

0 commit comments

Comments
 (0)