diff --git a/Lib/test/test_py3kwarn.py b/Lib/test/test_py3kwarn.py index 0aa981089606775..a7b5a6ee8ca78ec 100644 --- a/Lib/test/test_py3kwarn.py +++ b/Lib/test/test_py3kwarn.py @@ -1,11 +1,11 @@ import unittest import sys +import os import contextlib from test.test_support import check_py3k_warnings, CleanImport, run_unittest -from test.script_helper import assert_python_ok import warnings import base64 -from test import test_support +from test import support, test_support, script_helper if not sys.py3kwarning: raise unittest.SkipTest('%s must be run with the -3 flag' % __name__) @@ -61,6 +61,56 @@ def assertWarning(self, _, warning, expected_message): def assertNoWarning(self, _, recorder): self.assertEqual(len(recorder.warnings), 0) + def assertWarningWithFix(self, _, warning, expected_msg, expected_fix): + self.assertTrue(hasattr(warning, 'fix')) + self.assertEqual('{}: {}'.format(warning.message, warning.fix), '{}: {}'.format(expected_msg, expected_fix)) + + def test_implicit_relative_import(self): + expected_msg = ("implicit relative import 'importee' resolved to 'testpkg.subpkg.importee'; " + "in 3.x imports are absolute by default, so this may resolve differently") + expected_fix = "use 'import testpkg.subpkg.importee' if the parent package is intended" + with self.check_py3k_warnings_with_fix() as w, test_support.temp_dir() as test_dir: + try: + sys.path.append(test_dir) + + pkg_dir = os.path.join(test_dir, 'testpkg') + subpkg_dir = os.path.join(pkg_dir, 'subpkg') + script_helper.make_pkg(pkg_dir) + script_helper.make_pkg(subpkg_dir) + script_helper.make_script(subpkg_dir, 'importee', '') + script_helper.make_script(subpkg_dir, 'importer', 'import importee') + + import testpkg.subpkg.importer + self.assertWarningWithFix(None, w, expected_msg, expected_fix) + finally: + sys.path.remove(test_dir) + + def test_implicit_relative_import_from(self): + expected_msg = ("implicit relative import from 'importee' resolved to 'testpkg2.subpkg.importee'; " + "in 3.x imports are absolute by default, so this may resolve differently") + expected_fix = "use 'from testpkg2.subpkg.importee import ...' if the parent package is intended" + with self.check_py3k_warnings_with_fix() as w, test_support.temp_dir() as test_dir: + try: + sys.path.append(test_dir) + + pkg_dir = os.path.join(test_dir, 'testpkg2') + subpkg_dir = os.path.join(pkg_dir, 'subpkg') + script_helper.make_pkg(pkg_dir) + script_helper.make_pkg(subpkg_dir) + script_helper.make_script(subpkg_dir, 'importee', 'foo = 0') + script_helper.make_script(subpkg_dir, 'importer', 'from importee import foo') + + import testpkg2.subpkg.importer + self.assertWarningWithFix(None, w, expected_msg, expected_fix) + finally: + sys.path.remove(test_dir) + + def test_absolute_import_no_warning(self): + pass + + def test_absolute_import_from_no_warning(self): + pass + def test_backquote(self): expected = 'backquote not supported in 3.x; use repr()' with check_py3k_warnings((expected, SyntaxWarning)): diff --git a/Python/import.c b/Python/import.c index b79354b37a40645..7f6731952e6760c 100644 --- a/Python/import.c +++ b/Python/import.c @@ -2247,6 +2247,11 @@ import_module_level(char *name, PyObject *globals, PyObject *locals, Py_ssize_t buflen = 0; PyObject *parent, *head, *next, *tail; + char parentstr[MAXPATHLEN + 1]; + char *orig_name = name; + int is_from_form = 0; + int might_warn_implicit; + if (strchr(name, '/') != NULL #ifdef MS_WINDOWS || strchr(name, '\\') != NULL @@ -2265,6 +2270,14 @@ import_module_level(char *name, PyObject *globals, PyObject *locals, if (parent == NULL) goto error_exit; + might_warn_implicit = level < 0 && + parent != Py_None && + strchr(name, '.') == NULL && + buflen + strlen(name) + 1 <= MAXPATHLEN; + if (might_warn_implicit) { + memcpy(parentstr, buf, buflen + 1); + } + Py_INCREF(parent); head = load_next(parent, level < 0 ? Py_None : parent, &name, buf, &buflen); @@ -2303,6 +2316,35 @@ import_module_level(char *name, PyObject *globals, PyObject *locals, } if (!b) fromlist = NULL; + is_from_form = b; + } + + if (might_warn_implicit) { + char potential_resolved[MAXPATHLEN + 2]; + sprintf(potential_resolved, "%s.%s", parentstr, orig_name); + + if (strcmp(potential_resolved, buf) == 0) { + char msg[524]; + char fix[260]; + if (is_from_form) { + sprintf(msg, + "implicit relative import from '%.200s' resolved to '%.200s'; " + "in 3.x imports are absolute by default, so this may resolve differently", + orig_name, buf); + sprintf(fix, + "use 'from %.200s import ...' if the parent package is intended", buf); + } else { + sprintf(msg, + "implicit relative import '%.200s' resolved to '%.200s'; " + "in 3.x imports are absolute by default, so this may resolve differently", + orig_name, buf); + sprintf(fix, + "use 'import %.200s' if the parent package is intended", buf); + } + if (PyErr_WarnPy3k_WithFix(msg, fix, 1) < 0) { + goto error_exit; + } + } } if (fromlist == NULL) {