Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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

Open
ArunRawat404 wants to merge4 commits intopython:main
base:main
Choose a base branch
Loading
fromArunRawat404:fix-issue-134079

Conversation

ArunRawat404
Copy link

Summary

gh-134079: AddaddCleanup,enterContext anddoCleanups tounittest.subTest and add corresponding tests

Description

This PR implements support foraddCleanup,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 intest_runner.py to validate:

  • Cleanup order (LIFO)
  • Cleanup on failure
  • enterContext() behavior on success/failure
  • Exception safety in context manager entry and exit

I'm still learning the internals of CPython and writing tests, so please don't hesitate to suggest improvements. I'd really appreciate any feedback.

@python-cla-bot
Copy link

python-cla-botbot commentedMay 20, 2025
edited
Loading

All commit authors signed the Contributor License Agreement.

CLA signed

@bedevere-app
Copy link

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 theskip news label instead.

Copy link
Member

@picnixzpicnixz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

@@ -1626,3 +1629,46 @@ def shortDescription(self):

def __str__(self):
return "{} {}".format(self.test_case, self._subDescription())


class _SubTestCleanupHelper():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Suggested change
class_SubTestCleanupHelper():
class_SubTestCleanupHelper:

Comment on lines 1645 to 1646
def _callCleanup(self, function, /, *args, **kwargs):
function(*args, **kwargs)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Suggested change
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.

Comment on lines 1652 to 1654
if hasattr(TestSubTestCleanups, '_active_instance'):
del TestSubTestCleanups._active_instance

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Suggested change
ifhasattr(TestSubTestCleanups,'_active_instance'):
delTestSubTestCleanups._active_instance
ifhasattr(TestSubTestCleanups,'_active_instance'):
delTestSubTestCleanups._active_instance

Copy link
Author

@ArunRawat404ArunRawat404May 21, 2025
edited
Loading

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

Copy link
Member

@picnixzpicnixzMay 22, 2025
edited
Loading

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):
Copy link
Member

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.

ArunRawat404 reacted with thumbs up emoji
Copy link
Member

@encukouencukou left a comment
edited
Loading

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?

Comment on lines +1503 to +1522
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)
Copy link
Member

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.

Suggested change
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)

Comment on lines +1634 to +1638
class _SubTestCleanupHelper:
"""
Helper class to manage cleanups and context managers inside subTest blocks,
without exposing full TestCase functionality.
"""
Copy link
Member

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:

Suggested change
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)
Copy link
Member

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'):
Copy link
Member

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)
Copy link
Member

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.

@serhiy-storchakaserhiy-storchaka self-requested a reviewJune 4, 2025 10:58
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@encukouencukouencukou left review comments

@picnixzpicnixzpicnixz left review comments

@serhiy-storchakaserhiy-storchakaAwaiting requested review from serhiy-storchaka

Assignees
No one assigned
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

3 participants
@ArunRawat404@encukou@picnixz

[8]ページ先頭

©2009-2025 Movatter.jp