Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 218 additions & 0 deletions Lib/test/test_lazy_import/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2109,5 +2109,223 @@ def test_set_bad_filter(self):
self.assertRaises(ValueError, _testcapi.PyImport_SetLazyImportsFilter, 42)



class DictOperationsWithLazyTests(unittest.TestCase):
"""Tests for dict operations with lazy import values."""

def tearDown(self):
for key in list(sys.modules.keys()):
if key.startswith('test.test_lazy_import.data'):
del sys.modules[key]
sys.set_lazy_imports_filter(None)
sys.set_lazy_imports("normal")

def test_dict_copy_preserves_lazy(self):
"""dict.copy() should preserve lazy import proxy objects."""
sys.set_lazy_imports("all")
from test.test_lazy_import.data.metasyntactic import names
d = names.__dict__.copy()
self.assertIsInstance(d["Foo"], types.LazyImportType)
self.assertEqual(d["Metasyntactic"], "Metasyntactic")

def test_dict_or_preserves_lazy(self):
"""dict | should keep the winning side's lazy/resolved status."""
sys.set_lazy_imports("all")
from test.test_lazy_import.data.metasyntactic import names
lazy = names.__dict__.copy()
resolved = {"Foo": "resolved"}
self.assertEqual((lazy | resolved)["Foo"], "resolved")
self.assertIsInstance((resolved | lazy)["Foo"], types.LazyImportType)

def test_dict_update_preserves_lazy(self):
"""dict.update() should transfer lazy import proxy objects."""
sys.set_lazy_imports("all")
from test.test_lazy_import.data.metasyntactic import names
target = {}
target.update(names.__dict__)
self.assertIsInstance(target["Foo"], types.LazyImportType)


class SubmoduleLazinessTests(unittest.TestCase):
"""Tests that module-level lazy imports remain lazy until accessed."""

def tearDown(self):
for key in list(sys.modules.keys()):
if key.startswith('test.test_lazy_import.data'):
del sys.modules[key]
sys.set_lazy_imports_filter(None)
sys.set_lazy_imports("normal")

def test_unaccessed_imports_stay_lazy(self):
"""Imports in 'all' mode should stay lazy until accessed."""
sys.set_lazy_imports("all")
from test.test_lazy_import.data.metasyntactic import names
self.assertIsInstance(names.__dict__["Foo"], types.LazyImportType)
self.assertNotIn(
"test.test_lazy_import.data.metasyntactic.foo", sys.modules
)
_ = names.Foo
self.assertEqual(names.Foo, "Foo")
self.assertIn(
"test.test_lazy_import.data.metasyntactic.foo", sys.modules
)
self.assertIsInstance(names.__dict__["Ack"], types.LazyImportType)
self.assertNotIn(
"test.test_lazy_import.data.metasyntactic.foo.ack", sys.modules
)


class AttributeSideEffectTests(unittest.TestCase):
"""Tests that submodule imports don't overwrite parent attributes."""

def tearDown(self):
for key in list(sys.modules.keys()):
if key.startswith('test.test_lazy_import.data'):
del sys.modules[key]
sys.set_lazy_imports_filter(None)
sys.set_lazy_imports("normal")

def test_version_submodule_does_not_overwrite(self):
"""A __version__ submodule should not overwrite the parent's
__version__ attribute imported in __init__.py."""
import test.test_lazy_import.data.versioned as versioned
self.assertEqual(versioned.__version__, "1.0")
self.assertEqual(
versioned.__copyright__,
"Copyright (c) 2001-2022 Python Software Foundation.",
)


class ModuleVariableNameCollisionTests(unittest.TestCase):
"""Tests for name collision between a submodule and a variable."""

def tearDown(self):
for key in list(sys.modules.keys()):
if key.startswith('test.test_lazy_import.data'):
del sys.modules[key]
sys.set_lazy_imports_filter(None)
sys.set_lazy_imports("normal")

def test_variable_after_import_wins(self):
"""Variable assigned after import should overwrite the submodule."""
from test.test_lazy_import.data import module_same_name_var_order1
self.assertEqual(module_same_name_var_order1.bar, "Blah")

def test_import_after_variable_wins(self):
"""Import after variable assignment should overwrite the variable."""
from test.test_lazy_import.data import module_same_name_var_order2
bar_mod = sys.modules[
"test.test_lazy_import.data.module_same_name_var_order2.bar"
]
self.assertIs(module_same_name_var_order2.bar, bar_mod)


class DeletedModuleReimportTests(unittest.TestCase):
"""Tests for reimporting after module deletion from sys.modules."""

def tearDown(self):
for key in list(sys.modules.keys()):
if key.startswith('test.test_lazy_import.data'):
del sys.modules[key]
sys.set_lazy_imports_filter(None)
sys.set_lazy_imports("normal")

def test_reimport_creates_new_module(self):
"""Deleting and reimporting should create a new module object."""
import test.test_lazy_import.data.metasyntactic.foo
import test.test_lazy_import.data.metasyntactic.foo.bar.baz

first_bar = test.test_lazy_import.data.metasyntactic.foo.bar

del sys.modules[
"test.test_lazy_import.data.metasyntactic.foo.bar"
]

import test.test_lazy_import.data.metasyntactic.foo.bar.thud

second_bar = test.test_lazy_import.data.metasyntactic.foo.bar

self.assertIsNot(first_bar, second_bar)
self.assertIn("baz", dir(first_bar))
self.assertNotIn("thud", dir(first_bar))
self.assertIn("thud", dir(second_bar))
self.assertNotIn("baz", dir(second_bar))


@support.requires_subprocess()
class CircularImportLazyTests(unittest.TestCase):
"""Tests that lazy imports can break circular import patterns."""

def test_succeeds_with_lazy(self):
"""Same-level circular imports should succeed with lazy mode."""
proc = assert_python_ok(
"-X", "lazy_imports=all", "-c",
"import test.test_lazy_import.data.circular_import_pkg.main;"
"print('OK')",
)
self.assertIn(b"OK", proc.out)

def test_fails_without_lazy(self):
"""Same-level circular imports should fail without lazy mode."""
result = subprocess.run(
[sys.executable, "-X", "lazy_imports=none", "-c",
"import test.test_lazy_import.data.circular_import_pkg.main"],
capture_output=True, text=True,
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("ImportError", result.stderr)


@support.requires_subprocess()
class DictMutationDuringLoadTests(unittest.TestCase):
"""Tests that module dict mutation during loading doesn't crash."""

def test_dict_mutation_during_import(self):
"""Iterating a module dict while lazy imports resolve should not
crash even if the dict is mutated during iteration."""
with tempfile.TemporaryDirectory() as tmpdir:
pkg = os.path.join(tmpdir, "dcwl_pkg")
os.makedirs(pkg)

with open(os.path.join(pkg, "__init__.py"), "w") as f:
f.write(textwrap.dedent("""\
from .elements import elements_function

def __go(lcls):
global __all__
__all__ = sorted(
name
for name, obj in lcls.items()
if not name.startswith("_")
)

__go(locals())
"""))

with open(os.path.join(pkg, "elements.py"), "w") as f:
f.write(textwrap.dedent("""\
from .elements_sub import elements_sub_function

def elements_function():
pass
"""))

with open(os.path.join(pkg, "elements_sub.py"), "w") as f:
f.write("def elements_sub_function(): pass\n")

env = os.environ.copy()
env["PYTHONPATH"] = tmpdir
result = subprocess.run(
[sys.executable, "-X", "lazy_imports=all",
"-c", "import dcwl_pkg; print('OK')"],
capture_output=True, text=True, env=env,
)
self.assertEqual(
result.returncode, 0,
f"stdout: {result.stdout}, stderr: {result.stderr}",
)
self.assertIn("OK", result.stdout)


if __name__ == '__main__':
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

2 changes: 2 additions & 0 deletions Lib/test/test_lazy_import/data/circular_import_pkg/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .x import X2
X2()
7 changes: 7 additions & 0 deletions Lib/test/test_lazy_import/data/circular_import_pkg/x.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
def X1():
return "X"

from .y import Y1

def X2():
return Y1()
7 changes: 7 additions & 0 deletions Lib/test/test_lazy_import/data/circular_import_pkg/y.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
def Y1():
return "Y"

from .x import X2

def Y2():
return X2()
1 change: 1 addition & 0 deletions Lib/test/test_lazy_import/data/metasyntactic/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Foo = "Foo"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Ack = "Ack"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Bar = "Bar"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Baz = "Baz"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Qux = "Qux"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Thud = "Thud"
9 changes: 9 additions & 0 deletions Lib/test/test_lazy_import/data/metasyntactic/names.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from .foo import Foo
from .foo.ack import Ack
from .foo.bar import Bar
from .foo.bar.baz import Baz
from .foo.bar.thud import Thud
from .waldo import Waldo
from .waldo.fred import Fred
from .plugh import Plugh
Metasyntactic = "Metasyntactic"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Plugh = "Plugh"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Waldo = "Waldo"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fred = "Fred"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .bar import Bar
bar = "Blah"
Bar
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Bar = "Bar"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
bar = "Blah"
from .bar import Bar
Bar
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Bar = "Bar"
2 changes: 2 additions & 0 deletions Lib/test/test_lazy_import/data/versioned/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .__version__ import __version__
from .__version__ import __copyright__
2 changes: 2 additions & 0 deletions Lib/test/test_lazy_import/data/versioned/__version__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__version__ = "1.0"
__copyright__ = "Copyright (c) 2001-2022 Python Software Foundation."
Loading