From 1a42ece0c76166b1dead10decb0e54af084b4eb2 Mon Sep 17 00:00:00 2001 From: Nick Coghlan Date: Sat, 25 Aug 2007 10:50:41 +0000 Subject: [PATCH] Fix bug 1764407 - the -i switch now does the right thing when using the -m switch --- Lib/runpy.py | 91 ++++++++++++++++++++++++--------------- Lib/test/test_cmd_line.py | 40 ++++++++++++----- Lib/test/test_runpy.py | 28 ++++++------ Modules/main.c | 9 ++-- 4 files changed, 102 insertions(+), 66 deletions(-) diff --git a/Lib/runpy.py b/Lib/runpy.py index d2f18d37e36..4e8d563a8a8 100755 --- a/Lib/runpy.py +++ b/Lib/runpy.py @@ -21,8 +21,9 @@ __all__ = [ ] -def _run_code(code, run_globals, init_globals, - mod_name, mod_fname, mod_loader): +def _run_code(code, run_globals, init_globals=None, + mod_name=None, mod_fname=None, + mod_loader=None): """Helper for _run_module_code""" if init_globals is not None: run_globals.update(init_globals) @@ -34,35 +35,30 @@ def _run_code(code, run_globals, init_globals, def _run_module_code(code, init_globals=None, mod_name=None, mod_fname=None, - mod_loader=None, alter_sys=False): + mod_loader=None): """Helper for run_module""" # Set up the top level namespace dictionary - if alter_sys: - # Modify sys.argv[0] and sys.module[mod_name] - temp_module = imp.new_module(mod_name) - mod_globals = temp_module.__dict__ - saved_argv0 = sys.argv[0] - restore_module = mod_name in sys.modules - if restore_module: - saved_module = sys.modules[mod_name] - sys.argv[0] = mod_fname - sys.modules[mod_name] = temp_module - try: - _run_code(code, mod_globals, init_globals, - mod_name, mod_fname, mod_loader) - finally: - sys.argv[0] = saved_argv0 + temp_module = imp.new_module(mod_name) + mod_globals = temp_module.__dict__ + # Modify sys.argv[0] and sys.module[mod_name] + saved_argv0 = sys.argv[0] + restore_module = mod_name in sys.modules + if restore_module: + saved_module = sys.modules[mod_name] + sys.argv[0] = mod_fname + sys.modules[mod_name] = temp_module + try: + _run_code(code, mod_globals, init_globals, + mod_name, mod_fname, mod_loader) + finally: + sys.argv[0] = saved_argv0 if restore_module: sys.modules[mod_name] = saved_module else: del sys.modules[mod_name] - # Copy the globals of the temporary module, as they - # may be cleared when the temporary module goes away - return mod_globals.copy() - else: - # Leave the sys module alone - return _run_code(code, {}, init_globals, - mod_name, mod_fname, mod_loader) + # Copy the globals of the temporary module, as they + # may be cleared when the temporary module goes away + return mod_globals.copy() # This helper is needed due to a missing component in the PEP 302 @@ -75,13 +71,8 @@ def _get_filename(loader, mod_name): else: return get_filename(mod_name) - -def run_module(mod_name, init_globals=None, - run_name=None, alter_sys=False): - """Execute a module's code without importing it - - Returns the resulting top level namespace dictionary - """ +# Helper to get the loader, code and filename for a module +def _get_module_details(mod_name): loader = get_loader(mod_name) if loader is None: raise ImportError("No module named %s" % mod_name) @@ -92,10 +83,40 @@ def run_module(mod_name, init_globals=None, if code is None: raise ImportError("No code object available for %s" % mod_name) filename = _get_filename(loader, mod_name) + return loader, code, filename + + +# XXX ncoghlan: Should this be documented and made public? +def _run_module_as_main(mod_name, set_argv0=True): + """Runs the designated module in the __main__ namespace + + These __*__ magic variables will be overwritten: + __file__ + __loader__ + """ + loader, code, fname = _get_module_details(mod_name) + main_globals = sys.modules["__main__"].__dict__ + if set_argv0: + sys.argv[0] = fname + return _run_code(code, main_globals, None, + "__main__", fname, loader) + +def run_module(mod_name, init_globals=None, + run_name=None, alter_sys=False): + """Execute a module's code without importing it + + Returns the resulting top level namespace dictionary + """ + loader, code, fname = _get_module_details(mod_name) if run_name is None: run_name = mod_name - return _run_module_code(code, init_globals, run_name, - filename, loader, alter_sys) + if alter_sys: + return _run_module_code(code, init_globals, run_name, + fname, loader) + else: + # Leave the sys module alone + return _run_code(code, {}, init_globals, + run_name, fname, loader) if __name__ == "__main__": @@ -104,4 +125,4 @@ if __name__ == "__main__": print >> sys.stderr, "No module specified for execution" else: del sys.argv[0] # Make the requested module sys.argv[0] - run_module(sys.argv[0], run_name="__main__", alter_sys=True) + _run_module_as_main(sys.argv[0]) diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index d3f07c7f134..9fcd219c0cc 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -3,18 +3,25 @@ import test.test_support, unittest import sys import subprocess +def _spawn_python(*args): + cmd_line = [sys.executable] + cmd_line.extend(args) + return subprocess.Popen(cmd_line, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + +def _kill_python(p): + p.stdin.close() + data = p.stdout.read() + p.stdout.close() + # try to cleanup the child so we don't appear to leak when running + # with regrtest -R. This should be a no-op on Windows. + subprocess._cleanup() + return data + class CmdLineTest(unittest.TestCase): - def start_python(self, cmd_line): - cmd = '"%s" %s' % (sys.executable, cmd_line) - p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - p.stdin.close() - data = p.stdout.read() - p.stdout.close() - # try to cleanup the child so we don't appear to leak when running - # with regrtest -R. This should be a no-op on Windows. - subprocess._cleanup() - return data + def start_python(self, *args): + p = _spawn_python(*args) + return _kill_python(p) def exit_code(self, *args): cmd_line = [sys.executable] @@ -72,6 +79,17 @@ class CmdLineTest(unittest.TestCase): self.exit_code('-m', 'timeit', '-n', '1'), 0) + def test_run_module_bug1764407(self): + # -m and -i need to play well together + # Runs the timeit module and checks the __main__ + # namespace has been populated appropriately + p = _spawn_python('-i', '-m', 'timeit', '-n', '1') + p.stdin.write('Timer\n') + p.stdin.write('exit()\n') + data = _kill_python(p) + self.assertTrue(data.startswith('1 loop')) + self.assertTrue('__main__.Timer' in data) + def test_run_code(self): # Test expected operation of the '-c' switch # Switch needs an argument diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py index 9de98118341..dfefee982f2 100644 --- a/Lib/test/test_runpy.py +++ b/Lib/test/test_runpy.py @@ -5,7 +5,7 @@ import os.path import sys import tempfile from test.test_support import verbose, run_unittest, forget -from runpy import _run_module_code, run_module +from runpy import _run_code, _run_module_code, _run_module_as_main, run_module # Set up the test code and expected results @@ -26,10 +26,19 @@ class RunModuleCodeTest(unittest.TestCase): " module_in_sys_modules = globals() is sys.modules[__name__].__dict__\n" "# Check nested operation\n" "import runpy\n" - "nested = runpy._run_module_code('x=1\\n', mod_name='',\n" - " alter_sys=True)\n" + "nested = runpy._run_module_code('x=1\\n', mod_name='')\n" ) + def test_run_code(self): + saved_argv0 = sys.argv[0] + d = _run_code(self.test_source, {}) + self.failUnless(d["result"] == self.expected_result) + self.failUnless(d["__name__"] is None) + self.failUnless(d["__file__"] is None) + self.failUnless(d["__loader__"] is None) + self.failUnless(d["run_argv0"] is saved_argv0) + self.failUnless("run_name" not in d) + self.failUnless(sys.argv[0] is saved_argv0) def test_run_module_code(self): initial = object() @@ -42,8 +51,7 @@ class RunModuleCodeTest(unittest.TestCase): d1, name, file, - loader, - True) + loader) self.failUnless("result" not in d1) self.failUnless(d2["initial"] is initial) self.failUnless(d2["result"] == self.expected_result) @@ -57,16 +65,6 @@ class RunModuleCodeTest(unittest.TestCase): self.failUnless(sys.argv[0] is saved_argv0) self.failUnless(name not in sys.modules) - def test_run_module_code_defaults(self): - saved_argv0 = sys.argv[0] - d = _run_module_code(self.test_source) - self.failUnless(d["result"] == self.expected_result) - self.failUnless(d["__name__"] is None) - self.failUnless(d["__file__"] is None) - self.failUnless(d["__loader__"] is None) - self.failUnless(d["run_argv0"] is saved_argv0) - self.failUnless("run_name" not in d) - self.failUnless(sys.argv[0] is saved_argv0) class RunModuleTest(unittest.TestCase): diff --git a/Modules/main.c b/Modules/main.c index 7777db1a961..59b0e9f2ed5 100644 --- a/Modules/main.c +++ b/Modules/main.c @@ -149,17 +149,16 @@ static int RunModule(char *module) fprintf(stderr, "Could not import runpy module\n"); return -1; } - runmodule = PyObject_GetAttrString(runpy, "run_module"); + runmodule = PyObject_GetAttrString(runpy, "_run_module_as_main"); if (runmodule == NULL) { - fprintf(stderr, "Could not access runpy.run_module\n"); + fprintf(stderr, "Could not access runpy._run_module_as_main\n"); Py_DECREF(runpy); return -1; } - runargs = Py_BuildValue("sOsO", module, - Py_None, "__main__", Py_True); + runargs = Py_BuildValue("(s)", module); if (runargs == NULL) { fprintf(stderr, - "Could not create arguments for runpy.run_module\n"); + "Could not create arguments for runpy._run_module_as_main\n"); Py_DECREF(runpy); Py_DECREF(runmodule); return -1;