GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — bpo-30856: Update TestResult early, without buffering in _Outcome by serhiy-storchaka · Pull Request #28180 · python/cpython

Conversation

@serhiy-storchaka

@serhiy-storchaka serhiy-storchaka commented Sep 5, 2021 •
edited by bedevere-bot
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

TestResult methods addFailure(), addError(), addSkip() and
addSubTest() are now called immediately after raising an exception
in test or finishing a subtest. Previously they were called only
after finishing the test clean up.

https://bugs.python.org/issue30856

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@serhiy-storchaka
bpo-30856: Update TestResult early, without buffering in _Outcome
ee4b771
TestResult methods addFailure(), addError(), addSkip() and
addSubTest() are now called immediately after raising an exception
in test or finishing a subtest.  Previously they were called only
after finishing the test clean up.
@serhiy-storchaka serhiy-storchaka added the type-feature A feature request or enhancement label Sep 5, 2021
@serhiy-storchaka
serhiy-storchaka requested a review from voidspace September 5, 2021 18:42
@the-knights-who-say-ni the-knights-who-say-ni added the CLA signed label Sep 5, 2021
@bedevere-bot bedevere-bot added the awaiting core review label Sep 5, 2021
@vstinner vstinner mentioned this pull request Sep 6, 2021
bpo-45115: Enable optimiaztions on Windows debug build #28181
Closed
@serhiy-storchaka
Merge branch 'main' into unittest-result-immediate-update
96480af
ambv
ambv reviewed Sep 17, 2021
View reviewed changes
Comment thread Lib/unittest/case.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/unittest/case.py
self.success = False
self.errors.append((test_case, exc_info))
if subTest:
self.result.addSubTest(test_case.test_case, test_case, exc_info)

@ambv ambv Sep 17, 2021 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

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.

_addSkip checks whether the TestResult given has an addSkip method. Are we sure that it will always have an addSubTest method? Sub-test support was added later (BPO-16997) than test skipping support (available since unittest.py was split into a package in bed7d04).

In fact, _Outcome specifically tests for this with its result_supports_subtests attribute. I would add a check here for this just to be safe. I know that TestCase will exit early if _Outcome has result_supports_subtests set to False but this is a weird circular dependency. _Outcome should make this check on its own for consistency.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@serhiy-storchaka serhiy-storchaka Sep 17, 2021

Copy link
Copy Markdown
Member Author

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.

Yes, I am sure. testPartExecutor() with subTest=True is only called in subTest() if _outcome.result_supports_subtests is true.

Adding redundant test would make the code less clear. If add it, the reader should think about yet one possible special case: if subTest is true, but result doesn't have addSubTest. In what circumstances is it possible? Is it covered by tests? The answer is: no, it is not possible, and no tests are needed.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@ambv ambv Sep 17, 2021

Copy link
Copy Markdown
Contributor

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.

I disagree. The subTest method argument to the executor means "this test part is executing as a sub-test". It has nothing to do with whether the TestResult object used by _Outcome supports addSubTest. Those are two different things:

  • TestResult is provided by the framework you're using;
  • "executing as a sub-test" is signified by user code putting a self.subTest context manager in a user test method.

If what you said was true, then we wouldn't have to test for skip support either, right? After all, if SkipTest is raised then it means unittest supports it, no? But we check for it anyway.

Lastly, putting an additional if statement here that uses the already existing self.result_supports_subtests attribute of that same class feels the correct thing to do because it's _Outcome's own state. As it stands now, you made it the dependency of the caller of outcome.testPartExecutor to ensure that outcome.result_supports_subtests is True which is weird.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@serhiy-storchaka serhiy-storchaka Sep 17, 2021

Copy link
Copy Markdown
Member Author

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.

The subTest method argument to the executor means "this test part is executing as a sub-test". It has nothing to do with whether the TestResult object used by _Outcome supports addSubTest.

The executor is only used for subtests if the TestResult object supports addSubTest. If it does not -- there is no subtests, and the subTest() context manager is null. No addSubTest -- no subtests.

result_supports_subtests is not _Outcome's own state. It is only used outside of _Outcome. Actually, its only purpose is caching the result of hasattr(result, "addSubTest"), because it is usually tested several times per test. It is only for microoptimization.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@ambv ambv Sep 17, 2021 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

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.

You're describing the current state of how the code works but that is fragile. None of this is explicitly documented. If there ever is a new caller to the executor, it might not check result_supports_subtests on its own and in that way break.

result_supports_subtests is not _Outcome's own state. It is only used outside of _Outcome. Actually, its only purpose is caching the result of hasattr(result, "addSubTest"), because it is usually tested several times per test. It is only for microoptimization.

The attribute is certainly available for other users of an _Outcome object to look at but saying it's not its state is kind of weird when it's defined in its own __init__. Not using it directly but relying on it being checked by an external user is an anti-pattern, I cannot put it any clearer.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@serhiy-storchaka serhiy-storchaka Sep 17, 2021

Copy link
Copy Markdown
Member Author

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.

What is the worst consequence if not add any check or assert?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@ambv ambv Sep 17, 2021

Copy link
Copy Markdown
Contributor

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.

As I mentioned in the original comment, I would consider mimicking what _addSkip is doing a good defensive solution: warn about incompatibility but continue running.

To be quite frank, we either need both checks or none of them. It's unclear to me what kind of TestResult-like classes in modern usage actually lack subtest and skip support. Do you know?

But, again, if the code assumes that skip support can't be guaranteed in TestResult objects, then neither subtest support can. And so, recovering from that seems the sensible thing to do.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@serhiy-storchaka serhiy-storchaka Sep 17, 2021

Copy link
Copy Markdown
Member Author

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 are circling. There is already a check for addSubTest. It is just earlier in code, because the absence of addSubTest affects more things. Here, addSubTest is only called if the subTest parameter is true, and it can be true only if result_supports_subtests is true, which is set to the result of the check.

What is the worst consequence if not add the additional (redundant) check which you want to add?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@ambv ambv Sep 17, 2021

Copy link
Copy Markdown
Contributor

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.

I think out disagreement is about the fact that, in my opinion, the current state of things is crossing boundaries of responsibility when calling a method on Object A is only safe because you, the caller, previously have to check that an attribute on Object A is True.

Functionally everything works today because, as you're saying, TestCase.subTest has a check for result_supports_subtests. But that doesn't mean it's good factoring.

In any case, I suspect the likelihood of TestResult-like objects that don't subclass actual TestResult is pretty low in 2021 so this is all low stakes. I obviously failed to convince you so carry on.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@serhiy-storchaka serhiy-storchaka Sep 18, 2021

Copy link
Copy Markdown
Member Author

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.

I do not think there are some boundaries. _Outcome is not some class with internal state and public interface. It is just a collection of variables and one function. Its only purpose is to reduce code repetition. All its state is open for code which uses it.

It does not only work today. I am steel seeking for scenario in which the additional check would help.

As for supporting TestResult-like objects that don't subclass actual TestResult, I think it is a time to start deprecating this. But it does not have any relation to this code.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@serhiy-storchaka @ambv
Update Lib/unittest/case.py
f2d5ffc
Co-authored-by: Łukasz Langa <[email protected]>
@ambv

ambv commented Sep 17, 2021

Copy link
Copy Markdown
Contributor

BTW, the original introduction of _Outcome was in BPO-10611 and I checked that this PR (GH-28180) preserves the fixed behavior that Michael wanted in that issue. 👍🏻

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@serhiy-storchaka

serhiy-storchaka commented Sep 19, 2021

Copy link
Copy Markdown
Member Author

I am merging it now. We can continue the discussion if you show a case in which additional checks would be helpful.

ambv reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@serhiy-storchaka
serhiy-storchaka merged commit 664448d into python:main Sep 19, 2021
@bedevere-bot bedevere-bot removed the awaiting core review label Sep 19, 2021
@serhiy-storchaka
serhiy-storchaka deleted the unittest-result-immediate-update branch September 19, 2021 12:24
niyas-sait pushed a commit to niyas-sait/cpython that referenced this pull request Sep 21, 2021
@serhiy-storchaka @niyas-sait
bpo-30856: Update TestResult early, without buffering in _Outcome (py…
15442f8
…thonGH-28180)

TestResult methods addFailure(), addError(), addSkip() and
addSubTest() are now called immediately after raising an exception
in test or finishing a subtest.  Previously they were called only
after finishing the test clean up.
yilei added a commit to yilei/abseil-py that referenced this pull request Jul 18, 2022
@yilei @copybara-github
Make TempFileCleanup.SUCCESS work with Python 3.11 after python/cpy…
f2a901e
…thon#28180.

After the change, the errors from the test case were no longer buffered in `_Outcome`. Instead, we need to take a note of errors on the `TestResult` before, then compare errors after.

PiperOrigin-RevId: 461070558
Change-Id: I7d9f769cd46143a886bc5edeb224804acd8dbaf9
@jiasli jiasli mentioned this pull request Oct 19, 2023
Live test broken with Python 3.11 Azure/azure-cli#27641
Open
@jiasli

jiasli commented Oct 19, 2023

Copy link
Copy Markdown
Contributor

This change makes it no longer possible to get test errors during the execution of a cleanup function added by unittest.case.TestCase.addCleanup.

The errors are now immediately sent to result by calling addError on the result object:

cpython/Lib/unittest/case.py

Line 74 in 26a0282

_addError(self.result, test_case, exc_info)

cpython/Lib/unittest/case.py

Lines 94 to 99 in 26a0282

def _addError(result, test, exc_info):
if result is not None and exc_info is not None:
if issubclass(exc_info[0], test.failureException):
result.addFailure(test, exc_info)
else:
result.addError(test, exc_info)

The actually implementation of result may not necessarily be a subclass unittest.result.TestResult. For example, pytest passes _pytest.unittest.TestCaseFunction as result. There is no consistent way of retrieving test errors from result, as the implementation of result is test-runner-dependent.

So the question is: After this change, how can a cleanup function get test errors and act accordingly?

Thanks for your help in advance.

ladaflac reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@ambv ambv ambv left review comments
@voidspace voidspace Awaiting requested review from voidspace

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

type-feature A feature request or enhancement

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

5 participants

@serhiy-storchaka @ambv @jiasli @the-knights-who-say-ni @bedevere-bot
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.