From 8fb0e7be9187e2ed9c10cc9170ecbe480ef564f4 Mon Sep 17 00:00:00 2001 From: Om Singhal Date: Mon, 5 Oct 2026 16:35:47 -0400 Subject: [PATCH] Run -c code as the __main__ module run_code() evaluated the code with an empty globals dict, so __name__ resolved to the builtins module name and "if __name__ == '__main__'" blocks never ran. Classes defined in the code also could not be pickled. Run the code in a temporary __main__ module, the same way run_path() does for a file target. --- src/debugpy/server/cli.py | 5 ++++- tests/debugpy/server/test_cli.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/debugpy/server/cli.py b/src/debugpy/server/cli.py index 67b922a1..7e935a33 100644 --- a/src/debugpy/server/cli.py +++ b/src/debugpy/server/cli.py @@ -428,7 +428,10 @@ def run_code(): log.describe_environment("Pre-launch environment:") log.info("Running code:\n\n{0}", options.target) - eval(code, {}) + # Like "python -c", run the code as the __main__ module, so that __name__ is + # "__main__" and the classes and functions it defines can be pickled. + with runpy._TempModule("__main__") as main_module: + eval(code, main_module.module.__dict__) else: log.error("No target to run.") diff --git a/tests/debugpy/server/test_cli.py b/tests/debugpy/server/test_cli.py index fa400ec7..43226729 100644 --- a/tests/debugpy/server/test_cli.py +++ b/tests/debugpy/server/test_cli.py @@ -408,3 +408,35 @@ def test_pep_768_remote_exec_called_with_backslash_path(): finally: for attr, value in original_options.items(): setattr(cli.options, attr, value) + + +def test_run_code_runs_as_main(monkeypatch): + """run_code() must run the -c code as the __main__ module, like "python -c" does, + so that __name__ == "__main__" checks pass and the classes it defines can be + pickled. It must also put the original __main__ module back afterwards.""" + import types + from debugpy.server import cli + + # The code records what it sees as attributes of this module. + result = types.ModuleType("debugpy_run_code_result") + monkeypatch.setitem(sys.modules, "debugpy_run_code_result", result) + monkeypatch.setattr(sys, "path", list(sys.path)) + monkeypatch.setattr(cli, "start_debugging", lambda argv_0: None) + monkeypatch.setattr( + cli.options, + "target", + "import pickle, sys\n" + "import debugpy_run_code_result as result\n" + "class Point: pass\n" + "result.name = __name__\n" + "result.is_main = sys.modules['__main__'].__dict__ is globals()\n" + "result.unpickled = type(pickle.loads(pickle.dumps(Point()))) is Point\n", + ) + + original_main = sys.modules["__main__"] + cli.run_code() + + assert result.name == "__main__" + assert result.is_main + assert result.unpickled + assert sys.modules["__main__"] is original_main