diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py index 499da1e04efea8..43c66765dd9779 100644 --- a/Lib/importlib/_bootstrap.py +++ b/Lib/importlib/_bootstrap.py @@ -1307,6 +1307,14 @@ def _sanity_check(name, package, level): def _find_and_load_unlocked(name, import_): path = None + sys.audit( + "import", + name, + path, + getattr(sys, "path", None), + getattr(sys, "meta_path", None), + getattr(sys, "path_hooks", None) + ) parent = name.rpartition('.')[0] parent_spec = None if parent: diff --git a/Lib/test/audit-tests.py b/Lib/test/audit-tests.py index 6884ac0dbe6ff0..a893932169a089 100644 --- a/Lib/test/audit-tests.py +++ b/Lib/test/audit-tests.py @@ -8,6 +8,8 @@ import contextlib import os import sys +import unittest.mock +from test.support import swap_item class TestHook: @@ -672,6 +674,84 @@ def hook(event, args): assertEqual(event_script_path, tmp_file.name) assertEqual(remote_event_script_path, tmp_file.name) +def test_import_module(): + import importlib + + with TestHook() as hook: + importlib.import_module("importlib") # already imported, won't get logged + importlib.import_module("email") # standard library module + importlib.import_module("pythoninfo") # random module + importlib.import_module(".audit_test_data.submodule", "test") # relative import + importlib.import_module("test.audit_test_data.submodule2") # absolute import + importlib.import_module("_testcapi") # extension module + + actual = [a for e, a in hook.seen if e == "import"] + assertSequenceEqual( + [ + ("email", None, sys.path, sys.meta_path, sys.path_hooks), + ("pythoninfo", None, sys.path, sys.meta_path, sys.path_hooks), + ("test.audit_test_data.submodule", None, sys.path, sys.meta_path, sys.path_hooks), + ("test.audit_test_data", None, sys.path, sys.meta_path, sys.path_hooks), + ("test.audit_test_data.submodule2", None, sys.path, sys.meta_path, sys.path_hooks), + ("_testcapi", None, sys.path, sys.meta_path, sys.path_hooks), + ("_testcapi", unittest.mock.ANY, None, None, None) + ], + actual, + ) + +def test_builtin__import__(): + import importlib # noqa: F401 + + with TestHook() as hook: + __import__("importlib") + __import__("email") + __import__("pythoninfo") + __import__("audit_test_data.submodule", level=1, globals={"__package__": "test"}) + __import__("test.audit_test_data.submodule2") + __import__("_testcapi") + + actual = [a for e, a in hook.seen if e == "import"] + assertSequenceEqual( + [ + ("email", None, sys.path, sys.meta_path, sys.path_hooks), + ("pythoninfo", None, sys.path, sys.meta_path, sys.path_hooks), + ("test.audit_test_data.submodule", None, sys.path, sys.meta_path, sys.path_hooks), + ("test.audit_test_data", None, sys.path, sys.meta_path, sys.path_hooks), + ("test.audit_test_data.submodule2", None, sys.path, sys.meta_path, sys.path_hooks), + ("_testcapi", None, sys.path, sys.meta_path, sys.path_hooks), + ("_testcapi", unittest.mock.ANY, None, None, None) + ], + actual, + ) + +def test_import_statement(): + import importlib # noqa: F401 + # Set __package__ so relative imports work + with swap_item(globals(), "__package__", "test"): + with TestHook() as hook: + import importlib # noqa: F401 + import email # noqa: F401 + import pythoninfo # noqa: F401 + from .audit_test_data import submodule # noqa: F401 + import test.audit_test_data.submodule2 # noqa: F401 + import _testcapi # noqa: F401 + + actual = [a for e, a in hook.seen if e == "import"] + # Import statement ordering is different because the package is + # loaded first and then the submodule + assertSequenceEqual( + [ + ("email", None, sys.path, sys.meta_path, sys.path_hooks), + ("pythoninfo", None, sys.path, sys.meta_path, sys.path_hooks), + ("test.audit_test_data", None, sys.path, sys.meta_path, sys.path_hooks), + ("test.audit_test_data.submodule", None, sys.path, sys.meta_path, sys.path_hooks), + ("test.audit_test_data.submodule2", None, sys.path, sys.meta_path, sys.path_hooks), + ("_testcapi", None, sys.path, sys.meta_path, sys.path_hooks), + ("_testcapi", unittest.mock.ANY, None, None, None) + ], + actual, + ) + if __name__ == "__main__": from test.support import suppress_msvcrt_asserts diff --git a/Lib/test/audit_test_data/__init__.py b/Lib/test/audit_test_data/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/Lib/test/audit_test_data/submodule.py b/Lib/test/audit_test_data/submodule.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/Lib/test/audit_test_data/submodule2.py b/Lib/test/audit_test_data/submodule2.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/Lib/test/test_audit.py b/Lib/test/test_audit.py index 077765fcda210a..db4e1eb9999c1f 100644 --- a/Lib/test/test_audit.py +++ b/Lib/test/test_audit.py @@ -331,5 +331,14 @@ def test_sys_remote_exec(self): if returncode: self.fail(stderr) + def test_import_module(self): + self.do_test("test_import_module") + + def test_builtin__import__(self): + self.do_test("test_builtin__import__") + + def test_import_statement(self): + self.do_test("test_import_statement") + if __name__ == "__main__": unittest.main() diff --git a/Makefile.pre.in b/Makefile.pre.in index 9ce6ec65f142d8..de5f59ea73d951 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2590,6 +2590,7 @@ TESTSUBDIRS= idlelib/idle_test \ test/test_ast \ test/test_ast/data \ test/archivetestdata \ + test/audit_test_data \ test/audiodata \ test/certdata \ test/certdata/capath \ diff --git a/Python/import.c b/Python/import.c index 9dee20ecb63c91..d01c4d478283ff 100644 --- a/Python/import.c +++ b/Python/import.c @@ -3681,33 +3681,6 @@ import_find_and_load(PyThreadState *tstate, PyObject *abs_name) PyTime_t t1 = 0, accumulated_copy = accumulated; - PyObject *sys_path, *sys_meta_path, *sys_path_hooks; - if (PySys_GetOptionalAttrString("path", &sys_path) < 0) { - return NULL; - } - if (PySys_GetOptionalAttrString("meta_path", &sys_meta_path) < 0) { - Py_XDECREF(sys_path); - return NULL; - } - if (PySys_GetOptionalAttrString("path_hooks", &sys_path_hooks) < 0) { - Py_XDECREF(sys_meta_path); - Py_XDECREF(sys_path); - return NULL; - } - if (_PySys_Audit(tstate, "import", "OOOOO", - abs_name, Py_None, sys_path ? sys_path : Py_None, - sys_meta_path ? sys_meta_path : Py_None, - sys_path_hooks ? sys_path_hooks : Py_None) < 0) { - Py_XDECREF(sys_path_hooks); - Py_XDECREF(sys_meta_path); - Py_XDECREF(sys_path); - return NULL; - } - Py_XDECREF(sys_path_hooks); - Py_XDECREF(sys_meta_path); - Py_XDECREF(sys_path); - - /* XOptions is initialized after first some imports. * So we can't have negative cache before completed initialization. * Anyway, importlib._find_and_load is much slower than