summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_import
diff options
context:
space:
mode:
authorShantanu <12621235+hauntsaninja@users.noreply.github.com>2024-04-23 01:24:21 (GMT)
committerGitHub <noreply@github.com>2024-04-23 01:24:21 (GMT)
commit8e86579caef59fad0c54ac698d589f23a7951c55 (patch)
treee844412f935160dbe8b0b487b4f4fc03eaec1581 /Lib/test/test_import
parentc9829eec0883a8991ea4d319d965e123a3cf6c20 (diff)
downloadcpython-8e86579caef59fad0c54ac698d589f23a7951c55.zip
cpython-8e86579caef59fad0c54ac698d589f23a7951c55.tar.gz
cpython-8e86579caef59fad0c54ac698d589f23a7951c55.tar.bz2
gh-95754: Better error when script shadows a standard library or third party module (#113769)
Diffstat (limited to 'Lib/test/test_import')
-rw-r--r--Lib/test/test_import/__init__.py221
1 files changed, 221 insertions, 0 deletions
diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py
index 469d1fb..947a7b1 100644
--- a/Lib/test/test_import/__init__.py
+++ b/Lib/test/test_import/__init__.py
@@ -804,6 +804,227 @@ class ImportTests(unittest.TestCase):
self.assertIn("Frozen object named 'x' is invalid",
str(cm.exception))
+ def test_script_shadowing_stdlib(self):
+ with os_helper.temp_dir() as tmp:
+ with open(os.path.join(tmp, "fractions.py"), "w", encoding='utf-8') as f:
+ f.write("import fractions\nfractions.Fraction")
+
+ expected_error = (
+ rb"AttributeError: module 'fractions' has no attribute 'Fraction' "
+ rb"\(consider renaming '.*fractions.py' since it has the "
+ rb"same name as the standard library module named 'fractions' "
+ rb"and the import system gives it precedence\)"
+ )
+
+ popen = script_helper.spawn_python(os.path.join(tmp, "fractions.py"), cwd=tmp)
+ stdout, stderr = popen.communicate()
+ self.assertRegex(stdout, expected_error)
+
+ popen = script_helper.spawn_python('-m', 'fractions', cwd=tmp)
+ stdout, stderr = popen.communicate()
+ self.assertRegex(stdout, expected_error)
+
+ popen = script_helper.spawn_python('-c', 'import fractions', cwd=tmp)
+ stdout, stderr = popen.communicate()
+ self.assertRegex(stdout, expected_error)
+
+ # and there's no error at all when using -P
+ popen = script_helper.spawn_python('-P', 'fractions.py', cwd=tmp)
+ stdout, stderr = popen.communicate()
+ self.assertEqual(stdout, b'')
+
+ tmp_child = os.path.join(tmp, "child")
+ os.mkdir(tmp_child)
+
+ # test the logic with different cwd
+ popen = script_helper.spawn_python(os.path.join(tmp, "fractions.py"), cwd=tmp_child)
+ stdout, stderr = popen.communicate()
+ self.assertRegex(stdout, expected_error)
+
+ popen = script_helper.spawn_python('-m', 'fractions', cwd=tmp_child)
+ stdout, stderr = popen.communicate()
+ self.assertEqual(stdout, b'') # no error
+
+ popen = script_helper.spawn_python('-c', 'import fractions', cwd=tmp_child)
+ stdout, stderr = popen.communicate()
+ self.assertEqual(stdout, b'') # no error
+
+ def test_package_shadowing_stdlib_module(self):
+ with os_helper.temp_dir() as tmp:
+ os.mkdir(os.path.join(tmp, "fractions"))
+ with open(os.path.join(tmp, "fractions", "__init__.py"), "w", encoding='utf-8') as f:
+ f.write("shadowing_module = True")
+ with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
+ f.write("""
+import fractions
+fractions.shadowing_module
+fractions.Fraction
+""")
+
+ expected_error = (
+ rb"AttributeError: module 'fractions' has no attribute 'Fraction' "
+ rb"\(consider renaming '.*fractions.__init__.py' since it has the "
+ rb"same name as the standard library module named 'fractions' "
+ rb"and the import system gives it precedence\)"
+ )
+
+ popen = script_helper.spawn_python(os.path.join(tmp, "main.py"), cwd=tmp)
+ stdout, stderr = popen.communicate()
+ self.assertRegex(stdout, expected_error)
+
+ popen = script_helper.spawn_python('-m', 'main', cwd=tmp)
+ stdout, stderr = popen.communicate()
+ self.assertRegex(stdout, expected_error)
+
+ # and there's no shadowing at all when using -P
+ popen = script_helper.spawn_python('-P', 'main.py', cwd=tmp)
+ stdout, stderr = popen.communicate()
+ self.assertRegex(stdout, b"module 'fractions' has no attribute 'shadowing_module'")
+
+ def test_script_shadowing_third_party(self):
+ with os_helper.temp_dir() as tmp:
+ with open(os.path.join(tmp, "numpy.py"), "w", encoding='utf-8') as f:
+ f.write("import numpy\nnumpy.array")
+
+ expected_error = (
+ rb"AttributeError: module 'numpy' has no attribute 'array' "
+ rb"\(consider renaming '.*numpy.py' if it has the "
+ rb"same name as a third-party module you intended to import\)\s+\Z"
+ )
+
+ popen = script_helper.spawn_python(os.path.join(tmp, "numpy.py"))
+ stdout, stderr = popen.communicate()
+ self.assertRegex(stdout, expected_error)
+
+ popen = script_helper.spawn_python('-m', 'numpy', cwd=tmp)
+ stdout, stderr = popen.communicate()
+ self.assertRegex(stdout, expected_error)
+
+ popen = script_helper.spawn_python('-c', 'import numpy', cwd=tmp)
+ stdout, stderr = popen.communicate()
+ self.assertRegex(stdout, expected_error)
+
+ def test_script_maybe_not_shadowing_third_party(self):
+ with os_helper.temp_dir() as tmp:
+ with open(os.path.join(tmp, "numpy.py"), "w", encoding='utf-8') as f:
+ f.write("this_script_does_not_attempt_to_import_numpy = True")
+
+ expected_error = (
+ rb"AttributeError: module 'numpy' has no attribute 'attr'\s+\Z"
+ )
+
+ popen = script_helper.spawn_python('-c', 'import numpy; numpy.attr', cwd=tmp)
+ stdout, stderr = popen.communicate()
+ self.assertRegex(stdout, expected_error)
+
+ def test_script_shadowing_stdlib_edge_cases(self):
+ with os_helper.temp_dir() as tmp:
+ with open(os.path.join(tmp, "fractions.py"), "w", encoding='utf-8') as f:
+ f.write("shadowing_module = True")
+ with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
+ f.write("""
+import fractions
+fractions.shadowing_module
+class substr(str):
+ __hash__ = None
+fractions.__name__ = substr('fractions')
+try:
+ fractions.Fraction
+except TypeError as e:
+ print(str(e))
+""")
+
+ popen = script_helper.spawn_python("main.py", cwd=tmp)
+ stdout, stderr = popen.communicate()
+ self.assertEqual(stdout.rstrip(), b"unhashable type: 'substr'")
+
+ with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
+ f.write("""
+import fractions
+fractions.shadowing_module
+
+import sys
+sys.stdlib_module_names = None
+try:
+ fractions.Fraction
+except AttributeError as e:
+ print(str(e))
+
+del sys.stdlib_module_names
+try:
+ fractions.Fraction
+except AttributeError as e:
+ print(str(e))
+
+sys.path = [0]
+try:
+ fractions.Fraction
+except AttributeError as e:
+ print(str(e))
+""")
+
+ popen = script_helper.spawn_python("main.py", cwd=tmp)
+ stdout, stderr = popen.communicate()
+ self.assertEqual(
+ stdout.splitlines(),
+ [
+ b"module 'fractions' has no attribute 'Fraction'",
+ b"module 'fractions' has no attribute 'Fraction'",
+ b"module 'fractions' has no attribute 'Fraction'",
+ ],
+ )
+
+ with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
+ f.write("""
+import fractions
+fractions.shadowing_module
+del fractions.__spec__.origin
+try:
+ fractions.Fraction
+except AttributeError as e:
+ print(str(e))
+
+fractions.__spec__.origin = 0
+try:
+ fractions.Fraction
+except AttributeError as e:
+ print(str(e))
+""")
+
+ popen = script_helper.spawn_python("main.py", cwd=tmp)
+ stdout, stderr = popen.communicate()
+ self.assertEqual(
+ stdout.splitlines(),
+ [
+ b"module 'fractions' has no attribute 'Fraction'",
+ b"module 'fractions' has no attribute 'Fraction'"
+ ],
+ )
+
+ def test_script_shadowing_stdlib_sys_path_modification(self):
+ with os_helper.temp_dir() as tmp:
+ with open(os.path.join(tmp, "fractions.py"), "w", encoding='utf-8') as f:
+ f.write("shadowing_module = True")
+
+ expected_error = (
+ rb"AttributeError: module 'fractions' has no attribute 'Fraction' "
+ rb"\(consider renaming '.*fractions.py' since it has the "
+ rb"same name as the standard library module named 'fractions' "
+ rb"and the import system gives it precedence\)"
+ )
+
+ with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
+ f.write("""
+import sys
+sys.path.insert(0, "this_folder_does_not_exist")
+import fractions
+fractions.Fraction
+""")
+
+ popen = script_helper.spawn_python("main.py", cwd=tmp)
+ stdout, stderr = popen.communicate()
+ self.assertRegex(stdout, expected_error)
+
@skip_if_dont_write_bytecode
class FilePermissionTests(unittest.TestCase):