Skip to content
Open
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
12 changes: 11 additions & 1 deletion Doc/library/test.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1607,7 +1607,7 @@ The :mod:`!test.support.import_helper` module provides support for import tests.
byte-compiled files of the module.


.. function:: import_fresh_module(name, fresh=(), blocked=(), deprecated=False)
.. function:: import_fresh_module(name, fresh=(), blocked=(), cleared=(), *, deprecated=False, usefrozen=False)

This function imports and returns a fresh copy of the named Python module
by removing the named module from ``sys.modules`` before doing the import.
Expand All @@ -1621,6 +1621,11 @@ The :mod:`!test.support.import_helper` module provides support for import tests.
in the module cache during the import to ensure that attempts to import
them raise :exc:`ImportError`.

*cleared* is an iterable of module names that are removed from the
sys.modules cache before doing the import but *are not* re-imported.
This is to allow for the case where these module imports may fail as part
of the test due to other modules that are blocked.

The named module and any modules named in the *fresh* and *blocked*
parameters are saved before starting the import and then reinserted into
``sys.modules`` when the fresh import is complete.
Expand All @@ -1631,6 +1636,9 @@ The :mod:`!test.support.import_helper` module provides support for import tests.
This function will raise :exc:`ImportError` if the named module cannot be
imported.

If "usefrozen" is False (the default) then the frozen importer is
disabled (except for essential modules like ``importlib._bootstrap``).

Example use::

# Get copies of the warnings module for testing without affecting the
Expand All @@ -1641,6 +1649,8 @@ The :mod:`!test.support.import_helper` module provides support for import tests.
c_warnings = import_fresh_module('warnings', fresh=['_warnings'])

.. versionadded:: 3.1
.. versionchanged:: next
New optional argument *cleared*.


.. function:: import_module(name, deprecated=False, *, required_on=())
Expand Down
3 changes: 3 additions & 0 deletions Lib/bz2.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
import io
import os

# Force the eager import of _bz2
# See: https://github.com/python/cpython/issues/150167
import _bz2; _bz2
from _bz2 import BZ2Compressor, BZ2Decompressor


Expand Down
12 changes: 9 additions & 3 deletions Lib/test/support/import_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def multi_interp_extensions_check(enabled=True):
_imp._override_multi_interp_extensions_check(old)


def import_fresh_module(name, fresh=(), blocked=(), *,
def import_fresh_module(name, fresh=(), blocked=(), cleared=(), *,
deprecated=False,
usefrozen=False,
):
Expand All @@ -158,7 +158,12 @@ def import_fresh_module(name, fresh=(), blocked=(), *,
in the module cache during the import to ensure that attempts to import
them raise ImportError.

The named module and any modules named in the *fresh* and *blocked*
*cleared* is an iterable of module names that are removed from the
sys.modules cache before doing the import but *are not* re-imported.
This is to allow for the case where these module imports may fail as part
of the test due to other modules that are blocked.

The named module and any modules named in the *fresh*, *blocked* and *cleared*
parameters are saved before starting the import and then reinserted into
sys.modules when the fresh import is complete.

Expand All @@ -178,7 +183,8 @@ def import_fresh_module(name, fresh=(), blocked=(), *,
# as those which just need a blocking entry removed
fresh = list(fresh)
blocked = list(blocked)
names = {name, *fresh, *blocked}
cleared = list(cleared)
names = {name, *fresh, *blocked, *cleared}
orig_modules = _save_and_remove_modules(names)
for modname in blocked:
sys.modules[modname] = None
Expand Down
70 changes: 70 additions & 0 deletions Lib/test/test_shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from test import support
from test.support import os_helper, socket_helper
from test.support.os_helper import TESTFN, FakePath
from test.support.import_helper import import_fresh_module

TESTFN2 = TESTFN + "2"
TESTFN_SRC = TESTFN + "_SRC"
Expand Down Expand Up @@ -2322,6 +2323,75 @@ def _boo(filename, extract_dir, extra):
self.assertEqual(get_unpack_formats(), formats)


@support.requires_bz2()
@support.requires_lzma()
@support.requires_zlib()
@support.requires_zstd()
class TestLazyArchiveDetection(unittest.TestCase):
# Test archive detection under 'all' lazy imports
# see: https://github.com/python/cpython/issues/150167
@support.thread_unsafe("Modifies global import state")
def test_lazy_success(self):
import_state = sys.get_lazy_imports()
try:
sys.set_lazy_imports("all")
lazy_shutil = import_fresh_module(
"shutil",
cleared=(
"bz2",
"lzma",
"zlib",
"compression",
"compression.zstd",
"_bz2",
"_lzma",
"_zstd",
),
)
finally:
sys.set_lazy_imports(import_state)

assert lazy_shutil is not None # type narrowing

formats = {
"bz2": lazy_shutil._BZ2_SUPPORTED,
"lzma": lazy_shutil._LZMA_SUPPORTED,
"zlib": lazy_shutil._ZLIB_SUPPORTED,
"zstd": lazy_shutil._ZSTD_SUPPORTED,
}

expected = {k: True for k in formats}

self.assertEqual(formats, expected)


@support.thread_unsafe("Modifies global import state")
def test_lazy_failure(self):
import_state = sys.get_lazy_imports()
try:
sys.set_lazy_imports("all")
lazy_shutil = import_fresh_module(
"shutil",
blocked=("_bz2", "_lzma", "zlib", "_zstd"),
cleared=("bz2", "lzma", "compression", "compression.zstd"),
)
finally:
sys.set_lazy_imports(import_state)

assert lazy_shutil is not None # type narrowing

formats = {
"bz2": lazy_shutil._BZ2_SUPPORTED,
"lzma": lazy_shutil._LZMA_SUPPORTED,
"zlib": lazy_shutil._ZLIB_SUPPORTED,
"zstd": lazy_shutil._ZSTD_SUPPORTED,
}

expected = {k: False for k in formats}

self.assertEqual(formats, expected)


class TestMisc(BaseTest, unittest.TestCase):

@unittest.skipUnless(hasattr(shutil, 'disk_usage'),
Expand Down
Loading