Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork32.3k
gh-134079: AddaddCleanup
,enterContext
anddoCleanups
tounittest.subTest
and tests#134318
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
base:main
Are you sure you want to change the base?
Uh oh!
There was an error while loading.Please reload this page.
Conversation
python-cla-botbot commentedMay 20, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Most changes to Pythonrequire a NEWS entry. Add one using theblurb_it web app or theblurb command-line tool. If this change has little impact on Python users, wait for a maintainer to apply the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
cc@encukou
Lib/unittest/case.py Outdated
@@ -1626,3 +1629,46 @@ def shortDescription(self): | |||
def __str__(self): | |||
return "{} {}".format(self.test_case, self._subDescription()) | |||
class _SubTestCleanupHelper(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
class_SubTestCleanupHelper(): | |
class_SubTestCleanupHelper: |
Lib/unittest/case.py Outdated
def _callCleanup(self, function, /, *args, **kwargs): | ||
function(*args, **kwargs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
def_callCleanup(self,function,/,*args,**kwargs): | |
function(*args,**kwargs) | |
@staticmethod | |
def_callCleanup(function,/,*args,**kwargs): | |
function(*args,**kwargs) |
I don't think we need to hold a reference toself
so a staticmethod can be used but OTOH, I don't know if there is a world where we want to be able to subclass this one in the future.
if hasattr(TestSubTestCleanups, '_active_instance'): | ||
del TestSubTestCleanups._active_instance | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
ifhasattr(TestSubTestCleanups,'_active_instance'): | |
delTestSubTestCleanups._active_instance | |
ifhasattr(TestSubTestCleanups,'_active_instance'): | |
delTestSubTestCleanups._active_instance | |
ArunRawat404May 21, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Can you explain this one i don't see any difference
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
We use 2 blank lines to separate toplevel classes/functions and 1 line for methods.
] | ||
self.assertEqual(self.events, expected_events) | ||
def tearDown(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Can you put the tearDown() after the setUp() instead? it'll be easier to maintain and read.
encukou left a comment• edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Thanks! This looks great so far. Do you want to write the documentation as well?
def test_addCleanup_operation_and_LIFO_order(self): | ||
class MyTests(unittest.TestCase): | ||
def runTest(inner_self): | ||
events = TestSubTestCleanups._get_events() | ||
recorder = TestSubTestCleanups._active_instance._record_event | ||
with inner_self.subTest() as sub: | ||
events.append("subtest_body_start") | ||
sub.addCleanup(recorder, "cleanup_2_args", "arg") | ||
sub.addCleanup(recorder, "cleanup_1") | ||
events.append("subtest_body_end") | ||
MyTests().run() | ||
expected_events = [ | ||
"subtest_body_start", | ||
"subtest_body_end", | ||
"cleanup_1", | ||
"cleanup_2_args", | ||
] | ||
self.assertEqual(self.events, expected_events) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
There should be no need for_active_events_list
and_get_events
and_record_event
.
deftest_addCleanup_operation_and_LIFO_order(self): | |
classMyTests(unittest.TestCase): | |
defrunTest(inner_self): | |
events=TestSubTestCleanups._get_events() | |
recorder=TestSubTestCleanups._active_instance._record_event | |
withinner_self.subTest()assub: | |
events.append("subtest_body_start") | |
sub.addCleanup(recorder,"cleanup_2_args","arg") | |
sub.addCleanup(recorder,"cleanup_1") | |
events.append("subtest_body_end") | |
MyTests().run() | |
expected_events= [ | |
"subtest_body_start", | |
"subtest_body_end", | |
"cleanup_1", | |
"cleanup_2_args", | |
] | |
self.assertEqual(self.events,expected_events) | |
deftest_addCleanup_operation_and_LIFO_order(self): | |
events= [] | |
classMyTests(unittest.TestCase): | |
defrunTest(inner_self): | |
defrecord(event_name,*args,**kwargs): | |
events.append((event_name,args,kwargs)) | |
withinner_self.subTest()assub: | |
events.append("subtest_body_start") | |
sub.addCleanup(record,"cleanup_2_args","pos",kw="kwd") | |
sub.addCleanup(record,"cleanup_1") | |
events.append("subtest_body_end") | |
MyTests().run() | |
expected_events= [ | |
"subtest_body_start", | |
"subtest_body_end", | |
("cleanup_1", (), {}), | |
("cleanup_2_args", ("pos", ), {"kw":"kwd"}), | |
] | |
self.assertEqual(events,expected_events) |
class _SubTestCleanupHelper: | ||
""" | ||
Helper class to manage cleanups and context managers inside subTest blocks, | ||
without exposing full TestCase functionality. | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Nitpick: PerhapsContext
would be a better name rather thanHelper
:
class_SubTestCleanupHelper: | |
""" | |
HelperclasstomanagecleanupsandcontextmanagersinsidesubTestblocks, | |
withoutexposingfullTestCasefunctionality. | |
""" | |
class_SubTestContext: | |
""" | |
HelperclasstomanagecleanupsandcontextmanagersinsidesubTestblocks, | |
withoutexposingfullTestCasefunctionality. | |
""" |
If successful, also adds its __exit__ method as a cleanup | ||
function and returns the result of the __enter__ method. | ||
""" | ||
return _enter_context(cm, self.addCleanup) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Could you add a flag argument to_enter_context
to prevent adding the "enterAsyncContext" suggestion?
An async version of this is probably better left to a separate PR.
outcome = self._outcome or _Outcome() | ||
while self._cleanups: | ||
function, args, kwargs = self._cleanups.pop() | ||
if hasattr(outcome, 'testPartExecutor'): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Is there any way the outcome would not havetestPartExecutor
?
function, args, kwargs = self._cleanups.pop() | ||
if hasattr(outcome, 'testPartExecutor'): | ||
with outcome.testPartExecutor(self._subtest, subTest=True): | ||
self._callCleanup(function, *args, **kwargs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Can this befunction(*args, **kwargs)
? I see no need for an extra method.
Summary
gh-134079: Add
addCleanup
,enterContext
anddoCleanups
tounittest.subTest
and add corresponding testsDescription
This PR implements support for
addCleanup
,enterContext
, anddoCleanups
inunittest.subTest()
contexts, as discussed inissue-134079. This enables users to register cleanups or use context managers inside a withself.subTest():
block, similar to how it's done inTestCase
Tests
Added tests in
test_runner.py
to validate:enterContext()
behavior on success/failureI'm still learning the internals of CPython and writing tests, so please don't hesitate to suggest improvements. I'd really appreciate any feedback.