@@ -53,7 +53,8 @@ def test_sys_lazy_modules(self):
5353 self .fail ('lazy import failed' )
5454
5555 self .assertFalse ("test.test_lazy_import.data.basic2" in sys .modules )
56- self .assertIn ("test.test_lazy_import.data" , sys .lazy_modules )
56+ # The package is already loaded, so it is not a pending import.
57+ self .assertNotIn ("test.test_lazy_import.data" , sys .lazy_modules )
5758 self .assertIn ("test.test_lazy_import.data.basic2" , sys .lazy_modules )
5859 test .test_lazy_import .data .basic_from_unused .basic2
5960 self .assertNotIn ("test.test_import.data" , sys .lazy_modules )
@@ -1305,6 +1306,262 @@ def test_lazy_module_without_children_is_tracked(self):
13051306 """ )
13061307 assert_python_ok ("-c" , code )
13071308
1309+ def test_already_loaded_module_is_not_tracked (self ):
1310+ """A lazy import of an already loaded module should not be tracked."""
1311+ code = textwrap .dedent ("""
1312+ import sys
1313+
1314+ # Loaded by a regular import.
1315+ import json
1316+ lazy import json as lazy_json
1317+ assert "json" not in sys.lazy_modules, (
1318+ f"expected 'json' not in sys.lazy_modules, got {sys.lazy_modules}"
1319+ )
1320+
1321+ # Loaded by reifying an earlier lazy import.
1322+ lazy import base64
1323+ _ = base64.b64encode
1324+ lazy import base64 as lazy_base64
1325+ assert "base64" not in sys.lazy_modules, (
1326+ f"expected 'base64' not in sys.lazy_modules, got {sys.lazy_modules}"
1327+ )
1328+ """ )
1329+ assert_python_ok ("-c" , code )
1330+
1331+ def test_already_loaded_submodule_is_not_tracked (self ):
1332+ """`lazy from` a loaded submodule should not be tracked either."""
1333+ code = textwrap .dedent ("""
1334+ import sys
1335+ import test.test_lazy_import.data.pkg.b
1336+ lazy from test.test_lazy_import.data.pkg import b
1337+ assert "test.test_lazy_import.data.pkg.b" not in sys.lazy_modules, (
1338+ f"expected 'pkg.b' untracked, got {sys.lazy_modules}"
1339+ )
1340+ """ )
1341+ assert_python_ok ("-c" , code )
1342+
1343+ def test_attribute_entry_removed_on_reification (self ):
1344+ """`lazy from x import attr` should untrack "x.attr" once resolved."""
1345+ code = textwrap .dedent ("""
1346+ import sys
1347+ lazy from test.test_lazy_import.data.basic2 import x
1348+ assert "test.test_lazy_import.data.basic2.x" in sys.lazy_modules, (
1349+ f"expected 'basic2.x' tracked, got {sys.lazy_modules}"
1350+ )
1351+ _ = x
1352+ assert "test.test_lazy_import.data.basic2.x" not in sys.lazy_modules, (
1353+ f"expected 'basic2.x' untracked, got {sys.lazy_modules}"
1354+ )
1355+ """ )
1356+ assert_python_ok ("-c" , code )
1357+
1358+ def test_already_loaded_attribute_is_not_tracked (self ):
1359+ code = textwrap .dedent ("""
1360+ import sys
1361+ import math
1362+
1363+ lazy from math import pi
1364+ assert "math.pi" not in sys.lazy_modules, sys.lazy_modules
1365+ assert pi == math.pi
1366+ """ )
1367+ assert_python_ok ("-c" , code )
1368+
1369+ def test_cached_lazy_attribute_tracks_its_target (self ):
1370+ code = textwrap .dedent ("""
1371+ import sys
1372+ import test.test_lazy_import.data.basic_from_unused
1373+
1374+ holder = "test.test_lazy_import.data.basic_from_unused"
1375+ target = "test.test_lazy_import.data.basic2"
1376+
1377+ namespace = {"__name__": "cached_import_test"}
1378+ exec(f"lazy from {holder} import basic2", namespace)
1379+ assert holder + ".basic2" not in sys.lazy_modules, sys.lazy_modules
1380+ assert target in sys.lazy_modules, sys.lazy_modules
1381+ exec("assert basic2.x == 42", namespace)
1382+ assert target not in sys.lazy_modules, sys.lazy_modules
1383+ """ )
1384+ assert_python_ok ("-c" , code )
1385+
1386+ def test_cached_attribute_keeps_pending_module_tracked (self ):
1387+ code = textwrap .dedent ("""
1388+ import sys
1389+ import test.test_lazy_import.data.pkg as pkg
1390+ pkg.b = 42
1391+
1392+ lazy import test.test_lazy_import.data.pkg.b as pending
1393+ lazy from test.test_lazy_import.data.pkg import b
1394+ name = "test.test_lazy_import.data.pkg.b"
1395+ assert b == 42, b
1396+ assert name not in sys.modules, sys.modules
1397+ assert name in sys.lazy_modules, sys.lazy_modules
1398+ assert pending.foo() == "foo"
1399+ assert name not in sys.lazy_modules, sys.lazy_modules
1400+ """ )
1401+ assert_python_ok ("-c" , code )
1402+
1403+ def test_failed_reification_stays_tracked (self ):
1404+ """A lazy import that fails to resolve must stay tracked."""
1405+ code = textwrap .dedent ("""
1406+ import sys
1407+ lazy import test.test_lazy_import.data.broken_module
1408+ try:
1409+ _ = test.test_lazy_import.data.broken_module
1410+ except ValueError:
1411+ pass
1412+ else:
1413+ raise AssertionError("ValueError was not raised")
1414+ assert "test.test_lazy_import.data.broken_module" in sys.lazy_modules, (
1415+ f"failed reification must stay tracked, got {sys.lazy_modules}"
1416+ )
1417+ """ )
1418+ assert_python_ok ("-c" , code )
1419+
1420+ def test_blocked_module_is_still_tracked (self ):
1421+ """A ``None`` entry in sys.modules must not count as loaded."""
1422+ code = textwrap .dedent ("""
1423+ import sys
1424+ sys.modules['test.test_lazy_import.data.basic2'] = None
1425+ lazy import test.test_lazy_import.data.basic2
1426+ assert "test.test_lazy_import.data.basic2" in sys.lazy_modules, (
1427+ f"blocked module must stay tracked, got {sys.lazy_modules}"
1428+ )
1429+ """ )
1430+ assert_python_ok ("-c" , code )
1431+
1432+ def test_initializing_module_is_still_tracked (self ):
1433+ """A module that is still executing must not count as loaded."""
1434+ code = textwrap .dedent ("""
1435+ import sys
1436+ name = "test.test_lazy_import.data.init_fails"
1437+ try:
1438+ import test.test_lazy_import.data.init_fails
1439+ except ValueError:
1440+ pass
1441+ else:
1442+ raise AssertionError("ValueError was not raised")
1443+ assert name not in sys.modules, "failed import left a module behind"
1444+ assert name in sys.lazy_modules, (
1445+ f"expected {name!r} tracked, got {sys.lazy_modules}"
1446+ )
1447+ """ )
1448+ assert_python_ok ("-c" , code )
1449+
1450+ def test_module_spec_descriptor_is_not_run (self ):
1451+ """Checking whether a module is loaded must not run its descriptors."""
1452+ code = textwrap .dedent ("""
1453+ import sys
1454+ import types
1455+
1456+ class RaisingSpec(types.ModuleType):
1457+ @property
1458+ def __spec__(self):
1459+ raise RuntimeError("__spec__ descriptor was run")
1460+
1461+ sys.modules["raising_spec"] = RaisingSpec("raising_spec")
1462+ lazy import raising_spec
1463+ assert "raising_spec" not in sys.lazy_modules, (
1464+ f"expected 'raising_spec' untracked, got {sys.lazy_modules}"
1465+ )
1466+ """ )
1467+ assert_python_ok ("-c" , code )
1468+
1469+ def test_spec_initializing_descriptor_is_not_run (self ):
1470+ code = textwrap .dedent ("""
1471+ import sys
1472+ import types
1473+
1474+ class Spec:
1475+ @property
1476+ def _initializing(self):
1477+ raise RuntimeError("_initializing descriptor was run")
1478+
1479+ @property
1480+ def __dict__(self):
1481+ raise RuntimeError("__dict__ descriptor was run")
1482+
1483+ class SlottedSpec:
1484+ __slots__ = ()
1485+
1486+ @property
1487+ def _initializing(self):
1488+ raise RuntimeError("_initializing descriptor was run")
1489+
1490+ for spec in (Spec(), SlottedSpec()):
1491+ module = types.ModuleType("custom_spec")
1492+ module.__spec__ = spec
1493+ sys.modules["custom_spec"] = module
1494+ lazy import custom_spec
1495+ """ )
1496+ assert_python_ok ("-c" , code )
1497+
1498+ def test_cached_attribute_does_not_check_spec_twice (self ):
1499+ code = textwrap .dedent ("""
1500+ import sys
1501+ import types
1502+
1503+ class Spec:
1504+ def __init__(self):
1505+ self.calls = 0
1506+
1507+ @property
1508+ def _initializing(self):
1509+ self.calls += 1
1510+ if self.calls == 2:
1511+ raise RuntimeError("_initializing was read twice")
1512+ return False
1513+
1514+ module = types.ModuleType("cached_spec")
1515+ module.__spec__ = Spec()
1516+ module.attr = 1
1517+ sys.modules["cached_spec"] = module
1518+ lazy from cached_spec import attr
1519+ assert attr == 1, attr
1520+ """ )
1521+ assert_python_ok ("-c" , code )
1522+
1523+ def test_spec_initializing_truth_conversion_is_not_run (self ):
1524+ code = textwrap .dedent ("""
1525+ import sys
1526+ import types
1527+ from importlib.machinery import ModuleSpec
1528+
1529+ class Flag:
1530+ def __bool__(self):
1531+ raise RuntimeError("_initializing truth conversion was run")
1532+
1533+ released = memoryview(b"")
1534+ released.release()
1535+ for flag in (Flag(), released):
1536+ sys.lazy_modules.discard("custom_spec")
1537+ module = types.ModuleType("custom_spec")
1538+ module.__spec__ = ModuleSpec("custom_spec", None)
1539+ module.__spec__._initializing = flag
1540+ sys.modules["custom_spec"] = module
1541+ lazy import custom_spec
1542+ assert "custom_spec" in sys.lazy_modules, sys.lazy_modules
1543+ """ )
1544+ assert_python_ok ("-c" , code )
1545+
1546+ def test_pending_submodule_is_still_tracked (self ):
1547+ """`lazy from` a submodule that is not loaded must stay tracked."""
1548+ code = textwrap .dedent ("""
1549+ import sys
1550+ lazy from test.test_lazy_import.data.pkg import b
1551+ assert "test.test_lazy_import.data.pkg.b" in sys.lazy_modules, (
1552+ f"expected 'pkg.b' tracked, got {sys.lazy_modules}"
1553+ )
1554+ import test.test_lazy_import.data.pkg
1555+ assert "test.test_lazy_import.data.pkg.b" not in sys.modules, (
1556+ "loading the package must not load the submodule"
1557+ )
1558+ assert "test.test_lazy_import.data.pkg.b" in sys.lazy_modules, (
1559+ f"loading the package must not untrack the submodule, "
1560+ f"got {sys.lazy_modules}"
1561+ )
1562+ """ )
1563+ assert_python_ok ("-c" , code )
1564+
13081565
13091566@support .requires_subprocess ()
13101567class CommandLineAndEnvVarTests (unittest .TestCase ):
0 commit comments