diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst index 3d10c9d68b3..304f7c91d3c 100644 --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -95,8 +95,9 @@ source. file is not available. If this option is given, the first element of :data:`sys.argv` will be the - full path to the module file. As with the :option:`-c` option, the current - directory will be added to the start of :data:`sys.path`. + full path to the module file (while the module file is being located, the + first element will be set to ``"-m"``). As with the :option:`-c` option, + the current directory will be added to the start of :data:`sys.path`. Many standard library modules contain code that is invoked on their execution as a script. An example is the :mod:`timeit` module:: diff --git a/Lib/test/script_helper.py b/Lib/test/script_helper.py index 28d5e9d13be..7a29ec66bfc 100644 --- a/Lib/test/script_helper.py +++ b/Lib/test/script_helper.py @@ -86,9 +86,9 @@ def make_zip_script(zip_dir, zip_basename, script_name, name_in_zip=None): # zip_file.close() return zip_name, os.path.join(zip_name, name_in_zip) -def make_pkg(pkg_dir): +def make_pkg(pkg_dir, init_source=''): os.mkdir(pkg_dir) - make_script(pkg_dir, '__init__', '') + make_script(pkg_dir, '__init__', init_source) def make_zip_pkg(zip_dir, zip_basename, pkg_name, script_basename, source, depth=1, compiled=False): diff --git a/Lib/test/support.py b/Lib/test/support.py index c69dd94010d..8a8e410da94 100644 --- a/Lib/test/support.py +++ b/Lib/test/support.py @@ -419,21 +419,32 @@ elif sys.platform != 'darwin': SAVEDCWD = os.getcwd() @contextlib.contextmanager -def temp_cwd(name='tempcwd', quiet=False): +def temp_cwd(name='tempcwd', quiet=False, path=None): """ - Context manager that creates a temporary directory and set it as CWD. + Context manager that temporarily changes the CWD. - The new CWD is created in the current directory and it's named *name*. - If *quiet* is False (default) and it's not possible to create or change - the CWD, an error is raised. If it's True, only a warning is raised - and the original CWD is used. + An existing path may be provided as *path*, in which case this + function makes no changes to the file system. + + Otherwise, the new CWD is created in the current directory and it's + named *name*. If *quiet* is False (default) and it's not possible to + create or change the CWD, an error is raised. If it's True, only a + warning is raised and the original CWD is used. """ saved_dir = os.getcwd() is_temporary = False + if path is None: + path = name + try: + os.mkdir(name) + is_temporary = True + except OSError: + if not quiet: + raise + warnings.warn('tests may fail, unable to create temp CWD ' + name, + RuntimeWarning, stacklevel=3) try: - os.mkdir(name) - os.chdir(name) - is_temporary = True + os.chdir(path) except OSError: if not quiet: raise diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index 66889981537..46d23950c3a 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -1,5 +1,5 @@ # Tests invocation of the interpreter with various command line arguments -# All tests are executed with environment variables ignored +# Most tests are executed with environment variables ignored # See test_cmd_line_script.py for testing of script execution import test.support, unittest @@ -7,10 +7,6 @@ import os import sys from test.script_helper import spawn_python, kill_python, python_exit_code -# XXX (ncoghlan): there are assorted gratuitous inconsistencies between the -# support code in the Py3k version and the 2.x version that unnecessarily -# complicate test suite merges. See issue 7331 - # spawn_python normally enforces use of -E to avoid environmental effects # but one test checks PYTHONPATH behaviour explicitly # XXX (ncoghlan): Give script_helper.spawn_python an option to switch diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py index 3f4dd6d9193..d19316b2d8b 100644 --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -5,12 +5,12 @@ import os import os.path import py_compile -import test.support +from test import support from test.script_helper import ( make_pkg, make_script, make_zip_pkg, make_zip_script, run_python, temp_dir) -verbose = test.support.verbose +verbose = support.verbose test_source = """\ # Script may be run with optimisation enabled, so don't rely on assert @@ -36,6 +36,10 @@ print('__package__==%r' % __package__) import sys assertIdentical(globals(), sys.modules[__name__].__dict__) print('sys.argv[0]==%r' % sys.argv[0]) +print('sys.path[0]==%r' % sys.path[0]) +# Check the working directory +import os +print('cwd==%r' % os.getcwd()) """ def _make_test_script(script_dir, script_basename, source=test_source): @@ -65,32 +69,44 @@ def _make_launch_script(script_dir, script_basename, module_name, path=None): return make_script(script_dir, script_basename, source) class CmdLineTest(unittest.TestCase): - def _check_script(self, script_name, expected_file, - expected_argv0, expected_package, - *cmd_line_switches): - run_args = cmd_line_switches + (script_name,) - exit_code, data = run_python(*run_args) - if verbose: + def _check_output(self, script_name, exit_code, data, + expected_file, expected_argv0, + expected_path0, expected_package): + if verbose > 1: print("Output from test script %r:" % script_name) print(data) self.assertEqual(exit_code, 0) printed_file = '__file__==%r' % expected_file - printed_argv0 = 'sys.argv[0]==%r' % expected_argv0 printed_package = '__package__==%r' % expected_package - if verbose: + printed_argv0 = 'sys.argv[0]==%r' % expected_argv0 + printed_path0 = 'sys.path[0]==%r' % expected_path0 + printed_cwd = 'cwd==%r' % os.getcwd() + if verbose > 1: print('Expected output:') print(printed_file) print(printed_package) print(printed_argv0) + print(printed_cwd) self.assertIn(printed_file.encode('utf-8'), data) self.assertIn(printed_package.encode('utf-8'), data) self.assertIn(printed_argv0.encode('utf-8'), data) + self.assertIn(printed_path0.encode('utf-8'), data) + self.assertIn(printed_cwd.encode('utf-8'), data) + + def _check_script(self, script_name, expected_file, + expected_argv0, expected_path0, + expected_package, + *cmd_line_switches): + run_args = cmd_line_switches + (script_name,) + exit_code, data = run_python(*run_args) + self._check_output(script_name, exit_code, data, expected_file, + expected_argv0, expected_path0, expected_package) def _check_import_error(self, script_name, expected_msg, *cmd_line_switches): run_args = cmd_line_switches + (script_name,) exit_code, data = run_python(*run_args) - if verbose: + if verbose > 1: print('Output from test script %r:' % script_name) print(data) print('Expected output: %r' % expected_msg) @@ -99,28 +115,32 @@ class CmdLineTest(unittest.TestCase): def test_basic_script(self): with temp_dir() as script_dir: script_name = _make_test_script(script_dir, 'script') - self._check_script(script_name, script_name, script_name, None) + self._check_script(script_name, script_name, script_name, + script_dir, None) def test_script_compiled(self): with temp_dir() as script_dir: script_name = _make_test_script(script_dir, 'script') - compiled_name = py_compile.compile(script_name, doraise=True) + py_compile.compile(script_name, doraise=True) os.remove(script_name) - self._check_script(compiled_name, compiled_name, - compiled_name, None) + pyc_file = support.make_legacy_pyc(script_name) + self._check_script(pyc_file, pyc_file, + pyc_file, script_dir, None) def test_directory(self): with temp_dir() as script_dir: script_name = _make_test_script(script_dir, '__main__') - self._check_script(script_dir, script_name, script_dir, '') + self._check_script(script_dir, script_name, script_dir, + script_dir, '') def test_directory_compiled(self): with temp_dir() as script_dir: script_name = _make_test_script(script_dir, '__main__') - compiled_name = py_compile.compile(script_name, doraise=True) + py_compile.compile(script_name, doraise=True) os.remove(script_name) - pyc_file = test.support.make_legacy_pyc(script_name) - self._check_script(script_dir, pyc_file, script_dir, '') + pyc_file = support.make_legacy_pyc(script_name) + self._check_script(script_dir, pyc_file, script_dir, + script_dir, '') def test_directory_error(self): with temp_dir() as script_dir: @@ -131,14 +151,14 @@ class CmdLineTest(unittest.TestCase): with temp_dir() as script_dir: script_name = _make_test_script(script_dir, '__main__') zip_name, run_name = make_zip_script(script_dir, 'test_zip', script_name) - self._check_script(zip_name, run_name, zip_name, '') + self._check_script(zip_name, run_name, zip_name, zip_name, '') def test_zipfile_compiled(self): with temp_dir() as script_dir: script_name = _make_test_script(script_dir, '__main__') compiled_name = py_compile.compile(script_name, doraise=True) zip_name, run_name = make_zip_script(script_dir, 'test_zip', compiled_name) - self._check_script(zip_name, run_name, zip_name, '') + self._check_script(zip_name, run_name, zip_name, zip_name, '') def test_zipfile_error(self): with temp_dir() as script_dir: @@ -153,19 +173,19 @@ class CmdLineTest(unittest.TestCase): make_pkg(pkg_dir) script_name = _make_test_script(pkg_dir, 'script') launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg.script') - self._check_script(launch_name, script_name, script_name, 'test_pkg') + self._check_script(launch_name, script_name, script_name, script_dir, 'test_pkg') def test_module_in_package_in_zipfile(self): with temp_dir() as script_dir: zip_name, run_name = _make_test_zip_pkg(script_dir, 'test_zip', 'test_pkg', 'script') launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg.script', zip_name) - self._check_script(launch_name, run_name, run_name, 'test_pkg') + self._check_script(launch_name, run_name, run_name, zip_name, 'test_pkg') def test_module_in_subpackage_in_zipfile(self): with temp_dir() as script_dir: zip_name, run_name = _make_test_zip_pkg(script_dir, 'test_zip', 'test_pkg', 'script', depth=2) launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg.test_pkg.script', zip_name) - self._check_script(launch_name, run_name, run_name, 'test_pkg.test_pkg') + self._check_script(launch_name, run_name, run_name, zip_name, 'test_pkg.test_pkg') def test_package(self): with temp_dir() as script_dir: @@ -174,7 +194,7 @@ class CmdLineTest(unittest.TestCase): script_name = _make_test_script(pkg_dir, '__main__') launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg') self._check_script(launch_name, script_name, - script_name, 'test_pkg') + script_name, script_dir, 'test_pkg') def test_package_compiled(self): with temp_dir() as script_dir: @@ -183,10 +203,10 @@ class CmdLineTest(unittest.TestCase): script_name = _make_test_script(pkg_dir, '__main__') compiled_name = py_compile.compile(script_name, doraise=True) os.remove(script_name) - pyc_file = test.support.make_legacy_pyc(script_name) + pyc_file = support.make_legacy_pyc(script_name) launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg') self._check_script(launch_name, pyc_file, - pyc_file, 'test_pkg') + pyc_file, script_dir, 'test_pkg') def test_package_error(self): with temp_dir() as script_dir: @@ -209,10 +229,53 @@ class CmdLineTest(unittest.TestCase): launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg') self._check_import_error(launch_name, msg) + def test_issue8202(self): + # Make sure package __init__ modules see "-m" in sys.argv0 while + # searching for the module to execute + with temp_dir() as script_dir: + with support.temp_cwd(path=script_dir): + pkg_dir = os.path.join(script_dir, 'test_pkg') + make_pkg(pkg_dir, "import sys; print('init_argv0==%r' % sys.argv[0])") + script_name = _make_test_script(pkg_dir, 'script') + exit_code, data = run_python('-m', 'test_pkg.script') + if verbose > 1: + print(data) + self.assertEqual(exit_code, 0) + expected = "init_argv0==%r" % '-m' + self.assertIn(expected.encode('utf-8'), data) + self._check_output(script_name, exit_code, data, + script_name, script_name, '', 'test_pkg') + + def test_issue8202_dash_c_file_ignored(self): + # Make sure a "-c" file in the current directory + # does not alter the value of sys.path[0] + with temp_dir() as script_dir: + with support.temp_cwd(path=script_dir): + with open("-c", "w") as f: + f.write("data") + exit_code, data = run_python('-c', + 'import sys; print("sys.path[0]==%r" % sys.path[0])') + if verbose > 1: + print(data) + self.assertEqual(exit_code, 0) + expected = "sys.path[0]==%r" % '' + self.assertIn(expected.encode('utf-8'), data) + + def test_issue8202_dash_m_file_ignored(self): + # Make sure a "-m" file in the current directory + # does not alter the value of sys.path[0] + with temp_dir() as script_dir: + script_name = _make_test_script(script_dir, 'other') + with support.temp_cwd(path=script_dir): + with open("-m", "w") as f: + f.write("data") + exit_code, data = run_python('-m', 'other') + self._check_output(script_name, exit_code, data, + script_name, script_name, '', '') def test_main(): - test.support.run_unittest(CmdLineTest) - test.support.reap_children() + support.run_unittest(CmdLineTest) + support.reap_children() if __name__ == '__main__': test_main() diff --git a/Misc/NEWS b/Misc/NEWS index a2d41a0f1c1..4b52c2b62de 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,9 @@ What's New in Python 3.2 Alpha 2? Core and Builtins ----------------- +- Issue #8202: sys.argv[0] is now set to '-m' instead of '-c' when searching + for the module file to be executed with the -m command line option. + - Issue #9599: Create PySys_FormatStdout() and PySys_FormatStderr() functions to write a message formatted by PyUnicode_FromFormatV() to sys.stdout and sys.stderr. diff --git a/Modules/main.c b/Modules/main.c index f9d0c48a2a7..3e7e065fb84 100644 --- a/Modules/main.c +++ b/Modules/main.c @@ -604,10 +604,9 @@ Py_Main(int argc, wchar_t **argv) } if (module != NULL) { - /* Backup _PyOS_optind and force sys.argv[0] = '-c' - so that PySys_SetArgv correctly sets sys.path[0] to ''*/ + /* Backup _PyOS_optind and force sys.argv[0] = '-m'*/ _PyOS_optind--; - argv[_PyOS_optind] = L"-c"; + argv[_PyOS_optind] = L"-m"; } PySys_SetArgv(argc-_PyOS_optind, argv+_PyOS_optind); diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 7bf2c4c3b5f..013f5f1a9b4 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1723,6 +1723,10 @@ _wrealpath(const wchar_t *path, wchar_t *resolved_path) } #endif +#define _HAVE_SCRIPT_ARGUMENT(argc, argv) \ + (argc > 0 && argv0 != NULL && \ + wcscmp(argv0, L"-c") != 0 && wcscmp(argv0, L"-m") != 0) + void PySys_SetArgvEx(int argc, wchar_t **argv, int updatepath) { @@ -1747,7 +1751,7 @@ PySys_SetArgvEx(int argc, wchar_t **argv, int updatepath) wchar_t link[MAXPATHLEN+1]; wchar_t argv0copy[2*MAXPATHLEN+1]; int nr = 0; - if (argc > 0 && argv0 != NULL && wcscmp(argv0, L"-c") != 0) + if (_HAVE_SCRIPT_ARGUMENT(argc, argv)) nr = _Py_wreadlink(argv0, link, MAXPATHLEN); if (nr > 0) { /* It's a symlink */ @@ -1772,7 +1776,7 @@ PySys_SetArgvEx(int argc, wchar_t **argv, int updatepath) } #endif /* HAVE_READLINK */ #if SEP == '\\' /* Special case for MS filename syntax */ - if (argc > 0 && argv0 != NULL && wcscmp(argv0, L"-c") != 0) { + if (_HAVE_SCRIPT_ARGUMENT(argc, argv)) { wchar_t *q; #if defined(MS_WINDOWS) && !defined(MS_WINCE) /* This code here replaces the first element in argv with the full @@ -1798,7 +1802,7 @@ PySys_SetArgvEx(int argc, wchar_t **argv, int updatepath) } } #else /* All other filename syntaxes */ - if (argc > 0 && argv0 != NULL && wcscmp(argv0, L"-c") != 0) { + if (_HAVE_SCRIPT_ARGUMENT(argc, argv)) { #if defined(HAVE_REALPATH) if (_wrealpath(argv0, fullpath)) { argv0 = fullpath;