Skip to content
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanups not always called when handling KeyboardInterrupt #1003

Open
bbatliner opened this issue Mar 2, 2022 · 0 comments
Open

Cleanups not always called when handling KeyboardInterrupt #1003

bbatliner opened this issue Mar 2, 2022 · 0 comments

Comments

@bbatliner
Copy link

bbatliner commented Mar 2, 2022

From my testing, it seems that if a KeyboardInterrupt occurs during cleanup of a scenario:

behave/behave/runner.py

Lines 226 to 228 in 61781ed

try:
cleanup_func()
except Exception as e: # pylint: disable=broad-except

Then there are two negative consequences:

  1. Not all cleanup functions for the scenario will get called (except Exception does not catch KeyboardInterrupt and aborts the loop)
  2. The testrun layer of the context does not have its @cleanups run.

Problem 1 can be solved by changing Exception to BaseException (simple) so that the loop doesn't terminate, though the cleanup_func can still be interrupted:

diff --git a/behave/runner.py b/behave/runner.py
index 1f32ec1..5e5c4f5 100644
--- a/behave/runner.py
+++ b/behave/runner.py
@@ -225,7 +225,7 @@ class Context(object):
         for cleanup_func in reversed(cleanup_funcs):
             try:
                 cleanup_func()
-            except Exception as e: # pylint: disable=broad-except
+            except BaseException as e: # pylint: disable=broad-except
                 # pylint: disable=protected-access
                 context._root["cleanup_errors"] += 1
                 cleanup_errors.append(sys.exc_info())

OR by writing more complex logic to disable/defer SIGINT while cleanups are running so that they are uninterruptible.

I will need to monkey patch Behave to solve Problem 1 for now.

Problem 2 can be solved in user space with the following after_all check:

def after_all(context):
    while len(context._stack) > 1:
        context._pop()

OR by explicitly _poping the feature layer from the context stack when a KeyboardInterrupt is received (simple):

diff --git a/behave/runner.py b/behave/runner.py
index 1f32ec1..4461d78 100644
--- a/behave/runner.py
+++ b/behave/runner.py
@@ -698,6 +698,9 @@ class ModelRunner(object):
                     self.aborted = True
                     failed_count += 1
                     run_feature = False
+                    # -- Pop the feature layer if interrupted before feature.run() did so
+                    if self.context._stack[0].get("@layer", "") == "feature":
+                        self.context._stack.pop()
 
             # -- ALWAYS: Report run/not-run feature to reporters.
             # REQUIRED-FOR: Summary to keep track of untested features.

OR by rewriting context._push to return a context manager that guarantees that the stack entry will be popped in a finally block (more complex/requires more code change).

I am curious what you think is the best way forward here.

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

No branches or pull requests

2 participants