From 85e94943b14da71b6f288f26325a0b2fdc23e9d0 Mon Sep 17 00:00:00 2001
From: Sachin Saharan <saharan@deshaw.com>
Date: Thu, 3 Oct 2024 18:08:20 +0530
Subject: [PATCH] Addressed review comments

- Removed f-strings from logger's info and warning
- Use sys.last_exc for python 3.12+
- Added some examples in the doc of saveframe function and the script
---
 bin/saveframe                    |  44 ++-
 lib/python/pyflyby/_saveframe.py | 124 ++++++---
 setup.py                         |   1 +
 tests/test_saveframe.py          | 464 +++++++++++++------------------
 4 files changed, 314 insertions(+), 319 deletions(-)

diff --git a/bin/saveframe b/bin/saveframe
index a6c07efb..903abf26 100755
--- a/bin/saveframe
+++ b/bin/saveframe
@@ -55,11 +55,43 @@ NOTE:
 
 Example Usage:
 
-    $ saveframe <script_or_command_to_run>
+    Let's say your script / command is raising an error with the following traceback:
+
+    File "dir/__init__.py", line 6, in init_func1
+        func1()
+    File "dir/mod1.py", line 14, in func1
+        func2()
+    File "dir/mod1.py", line 9, in func2
+        obj.func2()
+    File "dir/pkg1/mod2.py", line 10, in func2
+        func3()
+    File "dir/pkg1/pkg2/mod3.py", line 6, in func3
+        raise ValueError("Error is raised")
+    ValueError: Error is raised
+
+    => To save the last frame (the error frame) in file '/path/to/file', use:
     $ saveframe --filename=/path/to/file <script_or_command_to_run>
-    $ saveframe --frames=frames_to_save <script_or_command_to_run>
-    $ saveframe --variables=local_variables_to_include <script_or_command_to_run>
-    $ saveframe --exclude_variables=local_variables_to_exclude <script_or_command_to_run>
+
+    => To save a specific frame like `File "dir/mod1.py", line 9, in func2`, use:
+    $ saveframe --filename=/path/to/file --frames=mod1.py:9:func2 <script_or_command_to_run>
+
+    => To save the last 3 frames from the bottom, use:
+    $ saveframe --frames=3 <script_or_command_to_run>
+
+    => To save all the frames from 'mod1.py' and 'mod2.py' files, use:
+    $ saveframe --filename=/path/to/file --frames=mod1.py::,mod2.py:: <script_or_command_to_run>
+
+    => To save a range of frames from 'mod1.py' to 'mod3.py', use:
+    $ saveframe --frames=mod1.py::..mod3.py:: <script_or_command_to_run>
+
+    => To save a range of frames from '__init__.py' till the last frame, use:
+    $ saveframe --frames=__init__.py::.. <script_or_command_to_run>
+
+    => To only save local variables 'var1' and 'var2' from the frames, use:
+    $ saveframe --frames=frames_to_save --variables=var1,var2 <script_or_command_to_run>
+
+    => To exclude local variables 'var1' and 'var2' from the frames, use:
+    $ saveframe --frames=frames_to_save --exclude_variables=var1,var2 <script_or_command_to_run>
 
 For interactive use cases, checkout pyflyby.saveframe function.
 """
@@ -248,11 +280,11 @@ def main():
     # KeyboardInterrupt rather than BaseException, since we don't want to
     # catch SystemExit.
     try:
-        _SAVEFRAME_LOGGER.info(f"Executing the program: {command_string!a}")
+        _SAVEFRAME_LOGGER.info("Executing the program: %a", command_string)
         run_program(command)
     except (Exception, KeyboardInterrupt) as err:
         _SAVEFRAME_LOGGER.info(
-            f"Saving frames and metadata info for the exception: {err!a}")
+            "Saving frames and metadata info for the exception: %a", err)
         # Save the frames and metadata info to the file.
         _save_frames_and_exception_info_to_file(
             filename=filename, frames=frames, variables=variables,
diff --git a/lib/python/pyflyby/_saveframe.py b/lib/python/pyflyby/_saveframe.py
index 56a13f07..4c7fd77a 100644
--- a/lib/python/pyflyby/_saveframe.py
+++ b/lib/python/pyflyby/_saveframe.py
@@ -141,7 +141,7 @@ def _get_exception_info(exception_obj):
                 type(exception_obj), exception_obj, exception_obj.__traceback__))
     except Exception as err:
         _SAVEFRAME_LOGGER.warning(
-            f"Error while formatting the traceback. Error: {err!a}")
+            "Error while formatting the traceback. Error: %a", err)
         tb = "Traceback couldn't be formatted"
     exception_info = ExceptionInfo(
         exception_string=str(exception_obj),
@@ -207,9 +207,9 @@ def _get_frame_local_variables_data(frame, variables, exclude_variables):
                 all_local_variables[variable], protocol=PICKLE_PROTOCOL)
         except Exception as err:
             _SAVEFRAME_LOGGER.warning(
-                f"Cannot pickle variable: {variable!a} for frame: "
-                f"{_get_frame_repr(frame)}. Error: {err!a}. Skipping this "
-                f"variable and continuing.")
+                "Cannot pickle variable: %a for frame: %s. Error: %a. Skipping "
+                "this variable and continuing.",
+                variable, _get_frame_repr(frame), err)
         else:
             local_variables_to_save[variable] = pickled_value
     return local_variables_to_save
@@ -280,13 +280,13 @@ def _get_frame_module_name(frame):
         frame_module = inspect.getmodule(frame)
         if frame_module is not None:
             return frame_module.__name__
-        _SAVEFRAME_LOGGER.info(f"No module found for the frame: "
-                              f"{_get_frame_repr(frame)}")
+        _SAVEFRAME_LOGGER.info(
+            "No module found for the frame: %s", _get_frame_repr(frame))
         return "Module name not found"
     except Exception as err:
         _SAVEFRAME_LOGGER.warning(
-            f"Module name couldn't be found for the frame: "
-            f"{_get_frame_repr(frame)}. Error: {err!a}")
+            "Module name couldn't be found for the frame: %s. Error: %a",
+            _get_frame_repr(frame), err)
         return "Module name not found"
 
 
@@ -329,8 +329,8 @@ def _get_frame_metadata(frame_idx, frame_obj):
                 frame_function_object, protocol=PICKLE_PROTOCOL)
     except Exception as err:
         _SAVEFRAME_LOGGER.info(
-            f"Cannot pickle the function object for the frame: "
-            f"{_get_frame_repr(frame_obj)}. Error: {err!a}")
+            "Cannot pickle the function object for the frame: %s. Error: %a",
+            _get_frame_repr(frame_obj), err)
         pickled_function = "Function object not pickleable"
     # Object that stores all the frame's metadata.
     frame_metadata = FrameMetadata(
@@ -407,9 +407,9 @@ def _get_frames_to_save(frames, all_frames):
     elif frame_type == FrameFormat.NUM:
         if len(all_frames) < frames:
             _SAVEFRAME_LOGGER.info(
-                f"Number of frames to dump are {frames}, but there are only "
-                f"{len(all_frames)} frames in the error stack. So dumping "
-                f"all the frames.")
+                "Number of frames to dump are %s, but there are only %s frames "
+                "in the error stack. So dumping all the frames.",
+                frames, len(all_frames))
             frames = len(all_frames)
         return [(idx+1, all_frames[idx]) for idx in range(frames)]
     elif frame_type == FrameFormat.LIST:
@@ -538,11 +538,12 @@ def _save_frames_and_exception_info_to_file(
     all_frames = _get_all_frames_from_exception_obj(exception_obj)
     # Take out the frame objects we want to save as per 'frames'.
     frames_to_save = _get_frames_to_save(frames, all_frames)
-    _SAVEFRAME_LOGGER.info(f"Number of frames that'll be saved: {len(frames_to_save)}")
+    _SAVEFRAME_LOGGER.info(
+        "Number of frames that'll be saved: %s", len(frames_to_save))
 
     for frame_idx, frame_obj in frames_to_save:
         _SAVEFRAME_LOGGER.info(
-            f"Getting required info for the frame: {_get_frame_repr(frame_obj)}")
+            "Getting required info for the frame: %s", _get_frame_repr(frame_obj))
         frames_and_exception_info[frame_idx] = _get_frame_metadata(
             frame_idx, frame_obj).__dict__
         frames_and_exception_info[frame_idx]['variables'] = (
@@ -550,7 +551,7 @@ def _save_frames_and_exception_info_to_file(
 
     _SAVEFRAME_LOGGER.info("Getting exception metadata info.")
     frames_and_exception_info.update(_get_exception_info(exception_obj).__dict__)
-    _SAVEFRAME_LOGGER.info(f"Saving the complete data in the file: {filename!a}")
+    _SAVEFRAME_LOGGER.info("Saving the complete data in the file: %a", filename)
     with _open_file(filename, 'wb') as f:
         pickle.dump(frames_and_exception_info, f, protocol=PICKLE_PROTOCOL)
     _SAVEFRAME_LOGGER.info("Done!!")
@@ -594,10 +595,11 @@ def _validate_filename(filename, utility):
     if filename is None:
         filename = os.path.abspath(DEFAULT_FILENAME)
         _SAVEFRAME_LOGGER.info(
-            f"Filename is not passed explicitly using the "
-            f"{'`filename` parameter' if utility == 'function' else '--filename argument'}. "
-            f"The frame info will be saved in the file: {filename!a}.")
-    _SAVEFRAME_LOGGER.info(f"Validating filename: {filename!a}")
+            "Filename is not passed explicitly using the %s. The frame info will "
+            "be saved in the file: %a.",
+            '`filename` parameter' if utility == 'function' else '--filename argument',
+            filename)
+    _SAVEFRAME_LOGGER.info("Validating filename: %a", filename)
     # Resolve any symlinks.
     filename = os.path.realpath(filename)
     if os.path.islink(filename):
@@ -607,13 +609,11 @@ def _validate_filename(filename, utility):
                          f"pass a different filename.")
     if os.path.exists(filename):
         _SAVEFRAME_LOGGER.info(
-            f"File {filename!a} already exists. This run will "
-            f"overwrite the file.")
+            "File %a already exists. This run will overwrite the file.", filename)
     parent_dir = os.path.dirname(filename)
     # Check if the parent directory and the ancestors are world traversable.
     # Log a warning if not. Raise an error if the parent or any ancestor
     # directory doesn't exist.
-    is_parent_and_ancestors_world_traversable = True
     try:
         is_parent_and_ancestors_world_traversable = (
             _is_dir_and_ancestors_world_traversable(directory=parent_dir))
@@ -624,15 +624,15 @@ def _validate_filename(filename, utility):
                f"{filename!a}. Error: {err!a}")
         raise type(err)(msg) from None
     except OSError as err:
+        is_parent_and_ancestors_world_traversable = False
         _SAVEFRAME_LOGGER.warning(
-            f"Error while trying to determine if the parent directory: "
-            f"{parent_dir!a} and the ancestors are world traversable. "
-            f"Error: {err!a}")
+            "Error while trying to determine if the parent directory: %a and "
+            "the ancestors are world traversable. Error: %a", parent_dir, err)
     if not is_parent_and_ancestors_world_traversable:
         _SAVEFRAME_LOGGER.warning(
-            f"The parent directory {parent_dir!a} or an ancestor is not world "
-            f"traversable (i.e., the execute bit of one of the ancestors is 0). "
-            f"The filename {filename!a} might not be accessible by others.")
+            "The parent directory %a or an ancestor is not world traversable "
+            "(i.e., the execute bit of one of the ancestors is 0). The filename "
+            "%a might not be accessible by others.", parent_dir, filename)
     return filename
 
 
@@ -674,11 +674,11 @@ def _validate_frames(frames, utility):
     """
     if frames is None:
         _SAVEFRAME_LOGGER.info(
-            f"{'`frames` parameter' if utility == 'function' else '--frames argument'} "
-            f"is not passed explicitly. The first frame from the bottom will be "
-            f"saved by default.")
+            "%s is not passed explicitly. The first frame from the bottom will be "
+            "saved by default.",
+            '`frames` parameter' if utility == 'function' else '--frames argument')
         return None, None
-    _SAVEFRAME_LOGGER.info(f"Validating frames: {frames!a}")
+    _SAVEFRAME_LOGGER.info("Validating frames: %a", frames)
     try:
         # Handle frames as an integer.
         return int(frames), FrameFormat.NUM
@@ -774,7 +774,7 @@ def _validate_variables(variables, utility):
     """
     if variables is None:
         return
-    _SAVEFRAME_LOGGER.info(f"Validating variables: {variables!a}")
+    _SAVEFRAME_LOGGER.info("Validating variables: %a", variables)
     if isinstance(variables, str) and ',' in variables and utility == 'function':
         raise ValueError(
             f"Error while validating variables: {variables!a}. If you want to "
@@ -792,8 +792,8 @@ def _validate_variables(variables, utility):
                               if not _is_variable_name_valid(variable)]
     if invalid_variable_names:
         _SAVEFRAME_LOGGER.warning(
-            f"Invalid variable names: {invalid_variable_names}. Skipping these "
-            f"variables and continuing.")
+            "Invalid variable names: %s. Skipping these variables and continuing.",
+            invalid_variable_names)
         # Filter out invalid variables.
         all_variables = tuple(variable for variable in all_variables
                               if variable not in invalid_variable_names)
@@ -839,10 +839,11 @@ def _validate_saveframe_arguments(
     exclude_variables = _validate_variables(exclude_variables, utility)
     if not (variables or exclude_variables):
         _SAVEFRAME_LOGGER.info(
-            f"Neither {'`variables`' if utility == 'function' else '--variables'} "
-            f"nor {'`exclude_variables`' if utility == 'function' else '--exclude_variables'} "
-            f"{'parameter' if utility == 'function' else 'argument'} is passed. "
-            f"All the local variables from the frames will be saved.")
+            "Neither %s nor %s %s is passed. All the local variables from the "
+            "frames will be saved.",
+            '`variables`' if utility == 'function' else '--variables',
+            '`exclude_variables`' if utility == 'function' else '--exclude_variables',
+            'parameter' if utility == 'function' else 'argument')
 
     return filename, frames, variables, exclude_variables
 
@@ -928,6 +929,44 @@ def saveframe(filename=None, frames=None, variables=None, exclude_variables=None
       >> ipdb> saveframe(filename=/path/to/file, frames=frames_to_save,
       .. variables=local_variables_to_include, exclude_variables=local_variables_to_exclude)
 
+      # Let's say your code is raising an error with the following traceback:
+
+      File "dir/__init__.py", line 6, in init_func1
+          func1()
+      File "dir/mod1.py", line 14, in func1
+          func2()
+      File "dir/mod1.py", line 9, in func2
+          obj.func2()
+      File "dir/pkg1/mod2.py", line 10, in func2
+          func3()
+      File "dir/pkg1/pkg2/mod3.py", line 6, in func3
+          raise ValueError("Error is raised")
+      ValueError: Error is raised
+
+      # To save the last frame (the error frame) in file '/path/to/file', use:
+      >> saveframe(filename='/path/to/file')
+
+      # To save a specific frame like `File "dir/mod1.py", line 9, in func2`, use:
+      >> saveframe(filename='/path/to/file', frames='mod1.py:9:func2')
+
+      # To save the last 3 frames from the bottom, use:
+      >> saveframe(frames=3)
+
+      # To save all the frames from 'mod1.py' and 'mod2.py' files, use:
+      >> saveframe(filename='/path/to/file', frames=['mod1.py::', 'mod2.py::'])
+
+      # To save a range of frames from 'mod1.py' to 'mod3.py', use:
+      >> saveframe(frames='mod1.py::..mod3.py::')
+
+      # To save a range of frames from '__init__.py' till the last frame, use:
+      >> saveframe(frames='__init__.py::..')
+
+      # To only save local variables 'var1' and 'var2' from the frames, use:
+      >> saveframe(frames=<frames_to_save>, variables=['var1', 'var2'])
+
+      # To exclude local variables 'var1' and 'var2' from the frames, use:
+      >> saveframe(frames=<frames_to_save>, exclude_variables=['var1', 'var2'])
+
     For non-interactive use cases (e.g., a failing script or command), checkout
     `pyflyby/bin/saveframe` script.
 
@@ -1005,7 +1044,8 @@ def saveframe(filename=None, frames=None, variables=None, exclude_variables=None
     :return:
       The file path in which the frame info is saved.
     """
-    if not hasattr(sys, 'last_value'):
+    if not ((sys.version_info < (3, 12) and hasattr(sys, 'last_value')) or
+            (sys.version_info >= (3, 12) and hasattr(sys, 'last_exc'))):
         raise RuntimeError(
             "No exception is raised currently for which to save the frames. "
             "Make sure that an uncaught exception is raised before calling the "
@@ -1027,7 +1067,7 @@ def saveframe(filename=None, frames=None, variables=None, exclude_variables=None
     filename, frames, variables, exclude_variables = _validate_saveframe_arguments(
         filename, frames, variables, exclude_variables)
     _SAVEFRAME_LOGGER.info(
-        f"Saving frames and metadata for the exception: '{exception_obj!a}")
+        "Saving frames and metadata for the exception: %a", exception_obj)
     _save_frames_and_exception_info_to_file(
         filename=filename, frames=frames, variables=variables,
         exclude_variables=exclude_variables, exception_obj=exception_obj)
diff --git a/setup.py b/setup.py
index 9b8c179d..fa6e9959 100755
--- a/setup.py
+++ b/setup.py
@@ -200,6 +200,7 @@ def make_distribution(self):
         'bin/pyflyby-diff',
         'bin/reformat-imports',
         'bin/replace-star-imports',
+        'bin/saveframe',
         'bin/tidy-imports',
         'bin/transform-imports',
     ],
diff --git a/tests/test_saveframe.py b/tests/test_saveframe.py
index cd9ecc12..8993d6e0 100644
--- a/tests/test_saveframe.py
+++ b/tests/test_saveframe.py
@@ -76,6 +76,21 @@ def run_command(command):
     return result.stderr.decode('utf-8').strip().split('\n')
 
 
+@contextmanager
+def run_code_and_set_exception(code, exception):
+    try:
+        exec(code)
+    except exception as err:
+        if VERSION_INFO < (3, 12):
+            sys.last_value = err
+        else:
+            sys.last_exc = err
+    try:
+        yield
+    finally:
+        delattr(sys, "last_value" if VERSION_INFO < (3, 12) else "last_exc")
+
+
 def frames_metadata_checker(tmpdir, pkg_name, filename):
     """
     Check if the metadata of the frames is correctly written in the ``filename``.
@@ -325,236 +340,194 @@ def func3():
 
 def test_saveframe_invalid_filename_1(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    with pytest.raises(ValueError) as err:
-        saveframe(filename=str(tmpdir / pkg_name))
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        with pytest.raises(ValueError) as err:
+            saveframe(filename=str(tmpdir / pkg_name))
     err_msg = (f"{str(tmpdir / pkg_name)!a} is an already existing directory. "
                f"Please pass a different filename.")
     assert str(err.value) == err_msg
-    delattr(sys, "last_value")
 
 
 def test_saveframe_invalid_filename_2(tmpdir):
     pkg_name = create_pkg(tmpdir)
     filename = str(tmpdir / f"saveframe_dir_{get_random()}" / f"saveframe_{get_random()}.pkl")
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    with pytest.raises(FileNotFoundError) as err:
-        saveframe(filename=filename)
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        with pytest.raises(FileNotFoundError) as err:
+            saveframe(filename=filename)
     err_msg = (f"Error while saving the frames to the file: {filename!a}. Error: "
                f"FileNotFoundError(2, 'No such file or directory')")
     assert str(err.value) == err_msg
-    delattr(sys, "last_value")
 
 
 def test_saveframe_invalid_frames_1(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    with pytest.raises(ValueError) as err:
-        saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
-                  frames="foo.py:")
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        with pytest.raises(ValueError) as err:
+            saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
+                      frames="foo.py:")
     err_msg = ("Error while validating frame: 'foo.py:'. The correct syntax for "
                "a frame is 'file_regex:line_no:function_name' but frame 'foo.py:' "
                "contains 1 ':'.")
     assert str(err.value) == err_msg
-    delattr(sys, "last_value")
 
 
 def test_saveframe_invalid_frames_2(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    with pytest.raises(ValueError) as err:
-        saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
-                  frames=":12:func1")
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        with pytest.raises(ValueError) as err:
+            saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
+                      frames=":12:func1")
     err_msg = ("Error while validating frame: ':12:func1'. The filename / file "
                "regex must be passed in a frame.")
     assert str(err.value) == err_msg
-    delattr(sys, "last_value")
 
 
 def test_saveframe_invalid_frames_3(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    with pytest.raises(ValueError) as err:
-        saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
-                  frames="file.py:12:func1,file1.py::")
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        with pytest.raises(ValueError) as err:
+            saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
+                      frames="file.py:12:func1,file1.py::")
     err_msg = ("Error while validating frames: 'file.py:12:func1,file1.py::'. "
                "If you want to pass multiple frames, pass a list/tuple of frames "
                "like ['file.py:12:func1', 'file1.py::'] rather than a comma "
                "separated string of frames.")
     assert str(err.value) == err_msg
-    delattr(sys, "last_value")
 
 
 def test_saveframe_invalid_frames_4(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    with pytest.raises(ValueError) as err:
-        saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
-                  frames=["file.py:12:func1", "file1.py::,file2.py:34:func2"])
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        with pytest.raises(ValueError) as err:
+            saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
+                      frames=["file.py:12:func1", "file1.py::,file2.py:34:func2"])
     err_msg = (
         "Invalid frame: 'file1.py::,file2.py:34:func2' in frames: ['file.py:12:func1', "
         "'file1.py::,file2.py:34:func2'] as it contains character ','. If you are "
         "trying to pass multiple frames, pass them as separate items in the list.")
     assert str(err.value) == err_msg
-    delattr(sys, "last_value")
 
 
 def test_saveframe_invalid_frames_5(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    with pytest.raises(ValueError) as err:
-        saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
-                  frames="file.py:12:func1..file2.py::..")
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        with pytest.raises(ValueError) as err:
+            saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
+                      frames="file.py:12:func1..file2.py::..")
     err_msg = ("Error while validating frames: 'file.py:12:func1..file2.py::..'. "
                "If you want to pass a range of frames, the correct syntax is "
                "'first_frame..last_frame'")
     assert str(err.value) == err_msg
-    delattr(sys, "last_value")
 
 
 def test_saveframe_invalid_frames_6(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    with pytest.raises(ValueError) as err:
-        saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
-                  frames="file.py:foo:func1")
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        with pytest.raises(ValueError) as err:
+            saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
+                      frames="file.py:foo:func1")
     err_msg = ("Error while validating frame: 'file.py:foo:func1'. The line "
                "number 'foo' can't be converted to an integer.")
     assert str(err.value) == err_msg
-    delattr(sys, "last_value")
 
 
 def test_saveframe_variables_and_exclude_variables(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    with pytest.raises(ValueError) as err:
-        saveframe(variables="foo", exclude_variables="bar")
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        with pytest.raises(ValueError) as err:
+            saveframe(variables="foo", exclude_variables="bar")
     err_msg = "Cannot pass both `variables` and `exclude_variables` parameters."
     assert str(err.value) == err_msg
 
 
 def test_saveframe_invalid_variables_1(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    with pytest.raises(ValueError) as err:
-        saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
-                  variables="var1,var2")
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        with pytest.raises(ValueError) as err:
+            saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
+                      variables="var1,var2")
     err_msg = ("Error while validating variables: 'var1,var2'. If you want to pass "
                "multiple variable names, pass a list/tuple of names like ['var1', "
                "'var2'] rather than a comma separated string of names.")
     assert str(err.value) == err_msg
-    delattr(sys, "last_value")
 
 
 def test_saveframe_invalid_variables_2(tmpdir, caplog):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
-              variables=["var1", "1var2"])
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
+                  variables=["var1", "1var2"])
     log_messages = [record.message for record in caplog.records]
     warning_msg = ("Invalid variable names: ['1var2']. Skipping these variables "
                    "and continuing.")
     assert warning_msg in log_messages
-    delattr(sys, "last_value")
 
 
 def test_saveframe_invalid_variables_3(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    with pytest.raises(TypeError) as err:
-        saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
-                  variables=1)
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        with pytest.raises(TypeError) as err:
+            saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
+                      variables=1)
     err_msg = ("Variables '1' must be of type list, tuple or string (for a single "
                "variable), not '<class 'int'>'")
     assert str(err.value) == err_msg
-    delattr(sys, "last_value")
 
 
 def test_saveframe_invalid_exclude_variables_1(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    with pytest.raises(ValueError) as err:
-        saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
-                  exclude_variables="var1,var2")
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        with pytest.raises(ValueError) as err:
+            saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
+                      exclude_variables="var1,var2")
     err_msg = ("Error while validating variables: 'var1,var2'. If you want to pass "
                "multiple variable names, pass a list/tuple of names like ['var1', "
                "'var2'] rather than a comma separated string of names.")
     assert str(err.value) == err_msg
-    delattr(sys, "last_value")
 
 
 def test_saveframe_invalid_exclude_variables_2(tmpdir, caplog):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
-              exclude_variables=["var1", "1var2"])
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
+                  exclude_variables=["var1", "1var2"])
     log_messages = [record.message for record in caplog.records]
     warning_msg = ("Invalid variable names: ['1var2']. Skipping these variables "
                    "and continuing.")
     assert warning_msg in log_messages
-    delattr(sys, "last_value")
 
 
 def test_saveframe_invalid_exclude_variables_3(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    with pytest.raises(TypeError) as err:
-        saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
-                  exclude_variables=1)
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        with pytest.raises(TypeError) as err:
+            saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
+                      exclude_variables=1)
     err_msg = ("Variables '1' must be of type list, tuple or string (for a single "
                "variable), not '<class 'int'>'")
     assert str(err.value) == err_msg
-    delattr(sys, "last_value")
 
 
 def test_saveframe_no_error_raised(tmpdir):
     if hasattr(sys, "last_value"):
         delattr(sys, "last_value")
+    if hasattr(sys, "last_exc"):
+        delattr(sys, "last_exc")
     pkg_name = create_pkg(tmpdir)
     with pytest.raises(RuntimeError) as err:
         exec(f"from {pkg_name} import init_func2; init_func2()")
@@ -567,118 +540,101 @@ def test_saveframe_no_error_raised(tmpdir):
 
 def test_saveframe_frame_format_1(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    filename = str(tmpdir / f"saveframe_{get_random()}.pkl")
-    # Format: 'filename:line_no:func_name'
-    saveframe(filename=filename, frames="pkg1/mod2.py:10:func2")
-    data = load_pkl(filename)
-    assert set(data.keys()) == exception_info_keys | {2}
-    assert data[2]["filename"] == str(tmpdir / pkg_name / "pkg1" / "mod2.py")
-    assert data[2]["function_name"] == "func2"
-
-    # Format: 'filename::'
-    saveframe(filename=filename, frames="mod1.py::")
-    data = load_pkl(filename)
-    assert set(data.keys()) == exception_info_keys | {3, 4}
-    assert data[3]["filename"] == str(tmpdir / pkg_name / "mod1.py")
-    assert data[3]["function_name"] == "func2"
-
-    # Format: 'filename::func_name'
-    saveframe(filename=filename, frames=f"{pkg_name}/mod1.py::func1")
-    data = load_pkl(filename)
-    assert set(data.keys()) == exception_info_keys | {4}
-    assert data[4]["filename"] == str(tmpdir / pkg_name / "mod1.py")
-    assert data[4]["function_name"] == "func1"
-    delattr(sys, "last_value")
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        filename = str(tmpdir / f"saveframe_{get_random()}.pkl")
+        # Format: 'filename:line_no:func_name'
+        saveframe(filename=filename, frames="pkg1/mod2.py:10:func2")
+        data = load_pkl(filename)
+        assert set(data.keys()) == exception_info_keys | {2}
+        assert data[2]["filename"] == str(tmpdir / pkg_name / "pkg1" / "mod2.py")
+        assert data[2]["function_name"] == "func2"
+
+        # Format: 'filename::'
+        saveframe(filename=filename, frames="mod1.py::")
+        data = load_pkl(filename)
+        assert set(data.keys()) == exception_info_keys | {3, 4}
+        assert data[3]["filename"] == str(tmpdir / pkg_name / "mod1.py")
+        assert data[3]["function_name"] == "func2"
+
+        # Format: 'filename::func_name'
+        saveframe(filename=filename, frames=f"{pkg_name}/mod1.py::func1")
+        data = load_pkl(filename)
+        assert set(data.keys()) == exception_info_keys | {4}
+        assert data[4]["filename"] == str(tmpdir / pkg_name / "mod1.py")
+        assert data[4]["function_name"] == "func1"
 
 
 def test_saveframe_frame_format_2(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    filename = str(tmpdir / f"saveframe_{get_random()}.pkl")
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        filename = str(tmpdir / f"saveframe_{get_random()}.pkl")
 
-    saveframe(filename=filename, frames=["__init__.py::", "pkg1/mod2.py:10:func2"])
-    data = load_pkl(filename)
-    assert set(data.keys()) == exception_info_keys | {2, 5}
-    assert data[2]["filename"] == str(tmpdir / pkg_name / "pkg1" / "mod2.py")
-    assert (
-        data[2]["function_qualname"] ==
-        "func2" if VERSION_INFO < (3, 11) else "mod2_cls.func2")
+        saveframe(filename=filename, frames=["__init__.py::", "pkg1/mod2.py:10:func2"])
+        data = load_pkl(filename)
+        assert set(data.keys()) == exception_info_keys | {2, 5}
+        assert data[2]["filename"] == str(tmpdir / pkg_name / "pkg1" / "mod2.py")
+        assert (
+            data[2]["function_qualname"] ==
+            "func2" if VERSION_INFO < (3, 11) else "mod2_cls.func2")
 
-    assert data[5]["filename"] == str(tmpdir / pkg_name / "__init__.py")
-    assert data[5]["function_qualname"] == "init_func1"
+        assert data[5]["filename"] == str(tmpdir / pkg_name / "__init__.py")
+        assert data[5]["function_qualname"] == "init_func1"
 
-    saveframe(filename=filename, frames=["pkg1/pkg2/mod3.py:6:", "mod1::"])
-    data = load_pkl(filename)
-    assert set(data.keys()) == exception_info_keys | {1, 3, 4}
-    assert data[1]["filename"] == str(tmpdir / pkg_name / "pkg1" / "pkg2" / "mod3.py")
-    assert data[1]["function_qualname"] == "func3"
-    delattr(sys, "last_value")
+        saveframe(filename=filename, frames=["pkg1/pkg2/mod3.py:6:", "mod1::"])
+        data = load_pkl(filename)
+        assert set(data.keys()) == exception_info_keys | {1, 3, 4}
+        assert data[1]["filename"] == str(tmpdir / pkg_name / "pkg1" / "pkg2" / "mod3.py")
+        assert data[1]["function_qualname"] == "func3"
 
 
 def test_saveframe_frame_format_3(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    filename = str(tmpdir / f"saveframe_{get_random()}.pkl")
-    saveframe(filename=filename, frames=3)
-    data = load_pkl(filename)
-    assert set(data.keys()) == exception_info_keys | {1, 2, 3}
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        filename = str(tmpdir / f"saveframe_{get_random()}.pkl")
+        saveframe(filename=filename, frames=3)
+        data = load_pkl(filename)
+        assert set(data.keys()) == exception_info_keys | {1, 2, 3}
 
-    saveframe(filename=filename, frames=5)
-    data = load_pkl(filename)
-    assert set(data.keys()) == exception_info_keys | {1, 2, 3, 4, 5}
-    delattr(sys, "last_value")
+        saveframe(filename=filename, frames=5)
+        data = load_pkl(filename)
+        assert set(data.keys()) == exception_info_keys | {1, 2, 3, 4, 5}
 
 
 def test_saveframe_frame_format_4(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    filename = str(tmpdir / f"saveframe_{get_random()}.pkl")
-    saveframe(filename=filename, frames="pkg1/mod2.py::..__init__.py:6:init_func1")
-    data = load_pkl(filename)
-    assert set(data.keys()) == exception_info_keys | {2, 3, 4, 5}
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        filename = str(tmpdir / f"saveframe_{get_random()}.pkl")
+        saveframe(filename=filename, frames="pkg1/mod2.py::..__init__.py:6:init_func1")
+        data = load_pkl(filename)
+        assert set(data.keys()) == exception_info_keys | {2, 3, 4, 5}
 
-    with pytest.raises(ValueError) as err:
-        saveframe(filename=filename,
-                  frames="pkg1/mod3.py::..__init__.py:6:init_func1")
-    err_msg = "No frame in the traceback matched the frame: 'pkg1/mod3.py::'"
-    assert str(err.value) == err_msg
-    delattr(sys, "last_value")
+        with pytest.raises(ValueError) as err:
+            saveframe(filename=filename,
+                      frames="pkg1/mod3.py::..__init__.py:6:init_func1")
+        err_msg = "No frame in the traceback matched the frame: 'pkg1/mod3.py::'"
+        assert str(err.value) == err_msg
 
 
 def test_saveframe_frame_format_5(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    filename = saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
-                         frames=f"{str(tmpdir)}/{pkg_name}/mod1.py::..")
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        filename = saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
+                             frames=f"{str(tmpdir)}/{pkg_name}/mod1.py::..")
     data = load_pkl(filename)
     assert set(data.keys()) == exception_info_keys | {1, 2, 3, 4}
-    delattr(sys, "last_value")
 
 
 def test_saveframe_variables(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    filename = saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
-                         frames=5, variables=['var1', 'var2'])
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        filename = saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
+                             frames=5, variables=['var1', 'var2'])
     data = load_pkl(filename)
     assert set(data.keys()) == exception_info_keys | {1, 2, 3, 4, 5}
 
@@ -687,17 +643,14 @@ def test_saveframe_variables(tmpdir):
     assert set(data[3]["variables"].keys()) == {"var1", "var2"}
     assert set(data[4]["variables"].keys()) == {"var1"}
     assert set(data[5]["variables"].keys()) == {"var1", "var2"}
-    delattr(sys, "last_value")
 
 
 def test_saveframe_exclude_variables(tmpdir, caplog):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    filename = saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
-                         frames=5, exclude_variables=['var1', 'var2'])
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        filename = saveframe(filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
+                             frames=5, exclude_variables=['var1', 'var2'])
     log_messages = [record.message for record in caplog.records]
     data = load_pkl(filename)
     assert set(data.keys()) == exception_info_keys | {1, 2, 3, 4, 5}
@@ -712,18 +665,15 @@ def test_saveframe_exclude_variables(tmpdir, caplog):
         f"Cannot pickle variable: 'var3' for frame: 'File: {str(tmpdir)}/{pkg_name}"
         f"/pkg1/mod2.py, Line: 10, Function: {qualname}'.")
     assert warning_msg in "\n".join(log_messages)
-    delattr(sys, "last_value")
 
 
 def test_saveframe_defaults(tmpdir, caplog):
     pkg_name = create_pkg(tmpdir)
-    with chdir(tmpdir):
-        try:
-            exec(f"from {pkg_name} import init_func1; init_func1()")
-        except ValueError as err:
-            sys.last_value = err
-        filename  = saveframe()
-        log_messages = [record.message for record in caplog.records]
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        with chdir(tmpdir):
+            filename  = saveframe()
+            log_messages = [record.message for record in caplog.records]
     # Test that saveframe.pkl file in the current working directory is used by
     # default.
     info_message = (f"Filename is not passed explicitly using the `filename` "
@@ -738,57 +688,42 @@ def test_saveframe_defaults(tmpdir, caplog):
     # Test that only first frame from the bottom (index = 1) is stored in the
     # data by default.
     assert set(data.keys()) == exception_info_keys | {1}
-    delattr(sys, "last_value")
 
 
 def test_saveframe_frame_metadata(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    filename = saveframe(
-        filename=str(tmpdir / f"saveframe_{get_random()}.pkl"), frames=5)
-
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        filename = saveframe(
+            filename=str(tmpdir / f"saveframe_{get_random()}.pkl"), frames=5)
     frames_metadata_checker(tmpdir, pkg_name, filename)
-    delattr(sys, "last_value")
 
 
 def test_saveframe_local_variables_data(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    filename = saveframe(
-        filename=str(tmpdir / f"saveframe_{get_random()}.pkl"), frames=5)
-
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        filename = saveframe(
+            filename=str(tmpdir / f"saveframe_{get_random()}.pkl"), frames=5)
     frames_local_variables_checker(pkg_name, filename)
-    delattr(sys, "last_value")
 
 
 def test_saveframe_exception_info(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func1; init_func1()")
-    except ValueError as err:
-        sys.last_value = err
-    filename = saveframe(
-        filename=str(tmpdir / f"saveframe_{get_random()}.pkl"), frames=0)
-
+    code = f"from {pkg_name} import init_func1; init_func1()"
+    with run_code_and_set_exception(code, ValueError):
+        filename = saveframe(
+            filename=str(tmpdir / f"saveframe_{get_random()}.pkl"), frames=0)
     exception_info_checker(filename)
-    delattr(sys, "last_value")
 
 
 def test_saveframe_chained_exceptions(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func3; init_func3()")
-    except TypeError as err:
-        sys.last_value = err
-    filename = saveframe(
-        filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
-        frames=['__init__.py::init_func3', f'.*/{pkg_name}/.*::'])
+    code = f"from {pkg_name} import init_func3; init_func3()"
+    with run_code_and_set_exception(code, TypeError):
+        filename = saveframe(
+            filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
+            frames=['__init__.py::init_func3', f'.*/{pkg_name}/.*::'])
     data = load_pkl(filename)
 
     assert 1 in set(data.keys())
@@ -803,46 +738,33 @@ def test_saveframe_chained_exceptions(tmpdir):
     assert data[1]["frame_identifier"] == (
         f"{data[1]['filename']},{data[1]['lineno']},{data[1]['function_name']}")
     assert len(set(data.keys()) - exception_info_keys) == 5
-    delattr(sys, "last_value")
 
 
 def test_keyboard_interrupt_frame_metadata(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func4; init_func4()")
-    except KeyboardInterrupt as err:
-        sys.last_value = err
-    filename = saveframe(
-        filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
-        frames=2)
+    code = f"from {pkg_name} import init_func4; init_func4()"
+    with run_code_and_set_exception(code, KeyboardInterrupt):
+        filename = saveframe(
+            filename=str(tmpdir / f"saveframe_{get_random()}.pkl"), frames=2)
     frames_metadata_checker_for_keyboard_interrupt(tmpdir, pkg_name, filename)
-    delattr(sys, "last_value")
 
 
 def test_keyboard_interrupt_local_variables_data(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func4; init_func4()")
-    except KeyboardInterrupt as err:
-        sys.last_value = err
-    filename = saveframe(
-        filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
-        frames=2)
+    code = f"from {pkg_name} import init_func4; init_func4()"
+    with run_code_and_set_exception(code, KeyboardInterrupt):
+        filename = saveframe(
+            filename=str(tmpdir / f"saveframe_{get_random()}.pkl"), frames=2)
     frames_local_variables_checker_for_keyboard_interrupt(filename)
-    delattr(sys, "last_value")
 
 
 def test_keyboard_interrupt_exception_info(tmpdir):
     pkg_name = create_pkg(tmpdir)
-    try:
-        exec(f"from {pkg_name} import init_func4; init_func4()")
-    except KeyboardInterrupt as err:
-        sys.last_value = err
-    filename = saveframe(
-        filename=str(tmpdir / f"saveframe_{get_random()}.pkl"),
-        frames=0)
+    code = f"from {pkg_name} import init_func4; init_func4()"
+    with run_code_and_set_exception(code, KeyboardInterrupt):
+        filename = saveframe(
+            filename=str(tmpdir / f"saveframe_{get_random()}.pkl"), frames=0)
     exception_info_checker_for_keyboard_interrupt(filename)
-    delattr(sys, "last_value")
 
 
 def test_saveframe_cmdline_no_exception():