Skip to content

Commit

Permalink
Make -P (pdb) work better with exceptions triggered from events
Browse files Browse the repository at this point in the history
Previously, if an exception was raised from an event listener, and the
`-P` option was specified, the debugger would be started not for the
original error but for the `ExtensionError` wrapping it that was
raised by `EventManager.emit`.  That made it difficult to debug the
error.

With this change, when `-P` is specified, wrapping of errors in
`ExtensionError` is disabled, which allows pdb to debug the original
error.
  • Loading branch information
jbms committed Jul 4, 2022
1 parent 7e76f2c commit 1af2f35
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 3 deletions.
4 changes: 3 additions & 1 deletion sphinx/application.py
Expand Up @@ -131,7 +131,8 @@ def __init__(self, srcdir: str, confdir: Optional[str], outdir: str, doctreedir:
buildername: str, confoverrides: Dict = None,
status: Optional[IO] = sys.stdout, warning: Optional[IO] = sys.stderr,
freshenv: bool = False, warningiserror: bool = False, tags: List[str] = None,
verbosity: int = 0, parallel: int = 0, keep_going: bool = False) -> None:
verbosity: int = 0, parallel: int = 0, keep_going: bool = False,
pdb: bool = False) -> None:
self.phase = BuildPhase.INITIALIZATION
self.verbosity = verbosity
self.extensions: Dict[str, Extension] = {}
Expand Down Expand Up @@ -173,6 +174,7 @@ def __init__(self, srcdir: str, confdir: Optional[str], outdir: str, doctreedir:
self.warningiserror = False
else:
self.warningiserror = warningiserror
self.pdb = pdb
logging.setup(self, self._status, self._warning)

self.events = EventManager(self)
Expand Down
3 changes: 2 additions & 1 deletion sphinx/cmd/build.py
Expand Up @@ -272,7 +272,8 @@ def build_main(argv: List[str] = sys.argv[1:]) -> int:
app = Sphinx(args.sourcedir, args.confdir, args.outputdir,
args.doctreedir, args.builder, confoverrides, status,
warning, args.freshenv, args.warningiserror,
args.tags, args.verbosity, args.jobs, args.keep_going)
args.tags, args.verbosity, args.jobs, args.keep_going,
args.pdb)
app.build(args.force_all, filenames)
return app.statuscode
except (Exception, KeyboardInterrupt) as exc:
Expand Down
3 changes: 3 additions & 0 deletions sphinx/events.py
Expand Up @@ -98,6 +98,9 @@ def emit(self, name: str, *args: Any,
except SphinxError:
raise
except Exception as exc:
if self.app.pdb:
# Just pass through the error, so that it can be debugged.
raise
modname = safe_getattr(listener.handler, '__module__', None)
raise ExtensionError(__("Handler %r for event %r threw an exception") %
(listener.handler, name), exc, modname=modname) from exc
Expand Down
23 changes: 22 additions & 1 deletion tests/test_events.py
Expand Up @@ -19,11 +19,16 @@ def test_event_priority():
assert result == [3, 1, 2, 5, 4]


class FakeApp:
def __init__(self, pdb: bool=False):
self.pdb = pdb


def test_event_allowed_exceptions():
def raise_error(app):
raise RuntimeError

events = EventManager(object()) # pass an dummy object as an app
events = EventManager(FakeApp()) # pass an dummy object as an app
events.connect('builder-inited', raise_error, priority=500)

# all errors are converted to ExtensionError
Expand All @@ -33,3 +38,19 @@ def raise_error(app):
# Allow RuntimeError (pass-through)
with pytest.raises(RuntimeError):
events.emit('builder-inited', allowed_exceptions=(RuntimeError,))


def test_event_pdb():
def raise_error(app):
raise RuntimeError

events = EventManager(FakeApp(pdb=True)) # pass an dummy object as an app
events.connect('builder-inited', raise_error, priority=500)

# errors aren't converted
with pytest.raises(RuntimeError):
events.emit('builder-inited')

# Allow RuntimeError (pass-through)
with pytest.raises(RuntimeError):
events.emit('builder-inited', allowed_exceptions=(RuntimeError,))

0 comments on commit 1af2f35

Please sign in to comment.