From 5a3993e9dd9165016d5c732f574b19850e55ead3 Mon Sep 17 00:00:00 2001 From: Hielke Walinga Date: Fri, 29 Nov 2024 19:44:01 +0100 Subject: [PATCH 1/2] Make the Debugger a singleton and do not reset the stopframe to None during postmortem. This fixes #607. Making the Debugger a singleton should be enough to fix #607. HOWEVER, pytest def postmortem() calls .reset() which resets the stopframe. Somehow, this causes the debugger to stop somewhere inside internal debugger source code, instead of in the code the postmortem should be done. So, this also includes an override of the .reset() method and detects when the debugger is already running and does not reset the stopframe. See also #67. --- pudb/debugger.py | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/pudb/debugger.py b/pudb/debugger.py index 8a9c07ad..95f19665 100644 --- a/pudb/debugger.py +++ b/pudb/debugger.py @@ -183,15 +183,21 @@ # {{{ debugger interface -class Debugger(bdb.Bdb): +class Singleton(type): + _instance = None + + def __call__(cls, *args, **kwargs): + if not cls._instance: + cls._instance = super(Singleton, cls).__call__(*args, **kwargs) + return cls._instance + + +class Debugger(bdb.Bdb, metaclass=Singleton): _current_debugger = [] def __init__(self, stdin=None, stdout=None, term_size=None, steal_output=False, _continue_at_start=False, tty_file=None, **kwargs): - if Debugger._current_debugger: - raise ValueError("a Debugger instance already exists") - # Pass remaining kwargs to python debugger framework bdb.Bdb.__init__(self, **kwargs) self.ui = DebuggerUI(self, stdin=stdin, stdout=stdout, term_size=term_size) @@ -214,6 +220,30 @@ def __init__(self, stdin=None, stdout=None, term_size=None, steal_output=False, # Okay, now we have a debugger self._current_debugger.append(self) + def reset(self): + """Set values of attributes as ready to start debugging. + + Override from Bdb.reset() + + When pytest starts a postmortem analysis, but the debugger is already active, + calling .reset() in src/_pytest/debugging.py::post_mortem + causes the self.stopframe to be set to None. + This pauses the debugger somewhere in the source code of the debugger. See #67 + + We detect using _current_debugger that this is the case and do not set the + stopframe to None then. + + Related #607, #52 + """ + import linecache + linecache.checkcache() + self.botframe = None + if not self._current_debugger: + self.stopframe = None + self.returnframe = None + self.quitting = False + self.stoplineno = 0 + def __del__(self): # according to https://stackoverflow.com/a/1481512/1054322, the garbage # collector cannot be relied on to call this, so we call it explicitly From 9f1a22a1134572b3a3ddb4eafb4be5420ba91c9b Mon Sep 17 00:00:00 2001 From: Hielke Walinga Date: Fri, 29 Nov 2024 20:16:24 +0100 Subject: [PATCH 2/2] Have more precise control on when to no reset the stopframe. --- pudb/debugger.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pudb/debugger.py b/pudb/debugger.py index 95f19665..a39e5bf9 100644 --- a/pudb/debugger.py +++ b/pudb/debugger.py @@ -189,11 +189,14 @@ class Singleton(type): def __call__(cls, *args, **kwargs): if not cls._instance: cls._instance = super(Singleton, cls).__call__(*args, **kwargs) + else: + cls._pytest_postmortem = True return cls._instance class Debugger(bdb.Bdb, metaclass=Singleton): _current_debugger = [] + _pytest_postmortem = False def __init__(self, stdin=None, stdout=None, term_size=None, steal_output=False, _continue_at_start=False, tty_file=None, **kwargs): @@ -230,7 +233,7 @@ def reset(self): causes the self.stopframe to be set to None. This pauses the debugger somewhere in the source code of the debugger. See #67 - We detect using _current_debugger that this is the case and do not set the + We detect using _pytest_postmortem that this is the case and do not set the stopframe to None then. Related #607, #52 @@ -238,8 +241,10 @@ def reset(self): import linecache linecache.checkcache() self.botframe = None - if not self._current_debugger: + if not self._pytest_postmortem: self.stopframe = None + else: + self._pytest_postmortem = False self.returnframe = None self.quitting = False self.stoplineno = 0