|
| 1 | +from collections.abc import Iterator |
| 2 | +import contextlib |
1 | 3 | import importlib |
2 | 4 | import io |
3 | 5 | import itertools |
|
13 | 15 | from pkgutil import ModuleInfo |
14 | 16 | from unittest import TestCase, skipUnless, skipIf, SkipTest |
15 | 17 | from unittest.mock import patch |
16 | | -from test.support import force_not_colorized, make_clean_env, Py_DEBUG |
| 18 | +import warnings |
| 19 | +from test.support import ( |
| 20 | + captured_stdout, |
| 21 | + captured_stderr, |
| 22 | + force_not_colorized, |
| 23 | + make_clean_env, |
| 24 | + Py_DEBUG, |
| 25 | +) |
17 | 26 | from test.support import has_subprocess_support, SHORT_TIMEOUT, STDLIB_DIR |
18 | 27 | from test.support.import_helper import import_module |
19 | 28 | from test.support.os_helper import EnvironmentVarGuard, unlink |
@@ -1512,6 +1521,50 @@ def test_suggestions_and_messages(self) -> None: |
1512 | 1521 | new_imports = sys.modules.keys() - _imported |
1513 | 1522 | self.assertSetEqual(new_imports, expected_imports) |
1514 | 1523 |
|
| 1524 | +class TestModuleCompleterAutomaticImports(TestCase): |
| 1525 | + """Out of TestPyReplModuleCompleter case because it blocks module import.""" |
| 1526 | + |
| 1527 | + @classmethod |
| 1528 | + def setUpClass(cls) -> None: |
| 1529 | + super().setUpClass() |
| 1530 | + cls._audit_events: set[str] | None = None |
| 1531 | + def _hook(name: str, _args: tuple): |
| 1532 | + if cls._audit_events is not None: |
| 1533 | + cls._audit_events.add(name) |
| 1534 | + sys.addaudithook(_hook) |
| 1535 | + |
| 1536 | + @classmethod |
| 1537 | + @contextlib.contextmanager |
| 1538 | + def _capture_audit_events(cls) -> Iterator[set[str]]: |
| 1539 | + cls._audit_events = set() |
| 1540 | + try: |
| 1541 | + yield cls._audit_events |
| 1542 | + finally: |
| 1543 | + cls._audit_events = None |
| 1544 | + |
| 1545 | + def test_no_side_effects(self): |
| 1546 | + from test.test___all__ import AllTest # TODO: extract to a helper? |
| 1547 | + |
| 1548 | + completer = ModuleCompleter() |
| 1549 | + for _, modname in AllTest().walk_modules(completer._stdlib_path, ""): |
| 1550 | + with self.subTest(modname=modname): |
| 1551 | + with (captured_stdout() as out, |
| 1552 | + captured_stderr() as err, |
| 1553 | + self._capture_audit_events() as audit_events, |
| 1554 | + patch("tkinter._tkinter.create") as tk_mock, |
| 1555 | + warnings.catch_warnings(action="ignore"), |
| 1556 | + patch.dict(sys.modules)): |
| 1557 | + completer._maybe_import_module(modname) |
| 1558 | + # Test no module is imported that |
| 1559 | + # 1. prints any text |
| 1560 | + self.assertEqual(out.getvalue(), "") |
| 1561 | + self.assertEqual(err.getvalue(), "") |
| 1562 | + # 2. spawn any subprocess (eg. webbrowser.open) |
| 1563 | + self.assertNotIn("subprocess.Popen", audit_events) |
| 1564 | + # 3. launch a Tk window |
| 1565 | + tk_mock.assert_not_called() |
| 1566 | + |
| 1567 | + |
1515 | 1568 | class TestHardcodedSubmodules(TestCase): |
1516 | 1569 | @patch.dict(sys.modules) |
1517 | 1570 | def test_hardcoded_stdlib_submodules_are_importable(self): |
|
0 commit comments