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-36829: Add test.support.catch_unraisable_exception() by vstinner · Pull Request #13490 · python/cpython

Conversation

@vstinner

@vstinner vstinner commented May 22, 2019 •
edited by bedevere-bot
Loading

Uh oh!

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

Copy link
Copy Markdown
Member
  • Copy test_exceptions.test_unraisable() to
    test_sys.UnraisableHookTest().
  • test_exceptions.test_unraisable() uses catch_unraisable_exception();
    simplify the test. test_sys now checks the exact output.
  • Use catch_unraisable_exception() in test_coroutines,
    test_exceptions, test_generators.

https://bugs.python.org/issue36829

Sorry, something went wrong.

Uh oh!

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

All reactions
@vstinner
bpo-36829: Add test.support.catch_unraisable_exception()
1fc116b
* Copy test_exceptions.test_unraisable() to
  test_sys.UnraisableHookTest().
* test_exceptions.test_unraisable() uses catch_unraisable_exception();
  simplify the test. test_sys now checks the exact output.
* Use catch_unraisable_exception() in test_coroutines,
  test_exceptions, test_generators.
@the-knights-who-say-ni the-knights-who-say-ni added the CLA signed label May 22, 2019
@bedevere-bot bedevere-bot added the awaiting core review label May 22, 2019
@vstinner vstinner mentioned this pull request May 22, 2019
bpo-36829: Add sys.unraisablehook() #13187
Merged
@vstinner

vstinner commented May 22, 2019

Copy link
Copy Markdown
Member Author

I chose to leave test_io unchanged on purpose: I have a local branch which fix also https://bugs.python.org/issue36918 in Lib/_pyio.py and use this new catch_unraisable_exception() function. Once this PR will be merged, I will write a second PR based on it.

All reactions

Sorry, something went wrong.

Uh oh!

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

vstinner
vstinner commented May 22, 2019
View reviewed changes
Comment thread Lib/test/test_coroutines.py

self.assertEqual(repr(cm.unraisable.object), coro_repr)
self.assertEqual(cm.unraisable.exc_type, ZeroDivisionError)
self.assertIn("was never awaited", stream.getvalue())

@vstinner vstinner May 22, 2019

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.

It's a little bit strange to have to catch half of the error message from stderr, and the other half from sys.unraisablehook. My PR #13488 will allow to inject the error message in the unraisable exception to catch both at the same time.

Sorry, something went wrong.

Uh oh!

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

All reactions
@vstinner vstinner mentioned this pull request May 22, 2019
bpo-36829: Add _PyErr_WriteUnraisableMsg() #13488
Merged
graingert
graingert reviewed May 22, 2019
View reviewed changes
Comment thread Lib/test/support/__init__.py Outdated

# check the expected unraisable exception
...
finally:

@graingert graingert May 22, 2019 •
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.

this try/with/finally is somewhat tricky, how about:

try:
    with support.throw_unraisable_exceptions():
        ...
except Exception as e:
    ... # the exception is now here

eg:

@contextlib.contextmanager
def throw_unraisable_exceptions():
    unraisable = None
    old_hook = sys.unraisablehook

    def hook(exc):
        nonlocal unraisable
        unraisable = exc

    sys.unraisablehook = hook
    try:
        yield
        if unraisable is not None:
            raise unraisable
    finally:
        unraisable = None
        sys.unraisablehook = old_hook

Sorry, something went wrong.

Uh oh!

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

All reactions

@graingert graingert May 22, 2019 •
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.

or recommend usage like this:

    with support.catch_unraisable_exceptions() as cm:
        ...
        cm.unraisable  # only available here
    assert cm.unraisable is None  # now it's gone

by updating __exit__:

   def __exit__(self, *exc_info):
        self.unraisable = None  # clear unraisable here
        sys.unraisablehook = self._old_hook

Sorry, something went wrong.

Uh oh!

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

All reactions

@vstinner vstinner May 22, 2019

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.

Good idea :-) I implemented your __exit__ idea to avoid the need for try/finally.

throw_unraisable_exceptions() might be useful, but modified tests needs to access the 'obj' attribute of the unraisable hook. Later, they might also want to get access to the 'err_msg' attribute: #13488

Sorry, something went wrong.

Uh oh!

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

All reactions

@graingert graingert May 22, 2019

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.

There's still a sharp edge here:

how about making cm.unraisable throw if accessed outside the context instead of being None, eg:

def __exit__(self, *exc_info):
    del self.unraisable
    sys.unraisablehook = self._old_hook

Sorry, something went wrong.

Uh oh!

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

All reactions

@graingert graingert May 22, 2019 •
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.

and if you want to access additional unraisablehook info using throw_unraisable_exceptions:

class UnraisableException(Exception):
    def __init__(self, unraisable):
        self.unraisable = unraisable
        super().__init__(self, unraisable)


@contextlib.contextmanager
def throw_unraisable_exceptions():
    unraisable = None
    old_hook = sys.unraisablehook

    def hook(unraisable_):
        nonlocal unraisable
        unraisable = unraisable_

    sys.unraisablehook = hook
    try:
        yield
        if unraisable is not None:
            raise UnraisableException(unraisable) from unraisable.exc_value
    finally:
        sys.unraisablehook = old_hook

then you can use it with:

try:
    with throw_unraisable_exceptions():
        ...
except UnraisableException as e:
    print(repr(e.__cause__))
    err_msg = e.unraisable.err_msg

Sorry, something went wrong.

Uh oh!

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

All reactions

@vstinner vstinner May 24, 2019

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 wrote PR #13554 to implement your "del self.unraisable" idea.

Sorry, something went wrong.

Uh oh!

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

All reactions
vstinner added 2 commits May 22, 2019 18:28
@vstinner
catch_unraisable_exception.__exit__ clears unraisable
1ffe98d
Avoid the need for try/finally: __exit__ clears unraisable to break
the reference cycle.
@vstinner
Cleanup
5c1bf39
@vstinner
vstinner merged commit e4d300e into python:master May 22, 2019
@vstinner
vstinner deleted the catch_unraisable_exception branch May 22, 2019 21:44
@bedevere-bot bedevere-bot removed the awaiting core review label May 22, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@graingert graingert graingert left review comments

Uh oh!

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

Assignees

No one assigned

Labels

None yet

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.

4 participants

@vstinner @graingert @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.