|
26 | 26 | """tests for the astroid builder and rebuilder module"""
|
27 | 27 |
|
28 | 28 | import collections
|
| 29 | +import importlib |
29 | 30 | import os
|
| 31 | +import pathlib |
| 32 | +import py_compile |
30 | 33 | import socket
|
31 | 34 | import sys
|
| 35 | +import tempfile |
32 | 36 | import unittest
|
33 | 37 |
|
34 | 38 | import pytest
|
@@ -790,5 +794,67 @@ def test_parse_module_with_invalid_type_comments_does_not_crash():
|
790 | 794 | assert isinstance(node, nodes.Module)
|
791 | 795 |
|
792 | 796 |
|
| 797 | +class HermeticInterpreterTest(unittest.TestCase): |
| 798 | + """Modeled on https://github.com/PyCQA/astroid/pull/1207#issuecomment-951455588""" |
| 799 | + |
| 800 | + @classmethod |
| 801 | + def setUpClass(cls): |
| 802 | + """Simulate a hermetic interpreter environment having no code on the filesystem.""" |
| 803 | + with tempfile.TemporaryDirectory() as tmp_dir: |
| 804 | + sys.path.append(tmp_dir) |
| 805 | + |
| 806 | + # Write a python file and compile it to .pyc |
| 807 | + # To make this test have even more value, we would need to come up with some |
| 808 | + # code that gets inferred differently when we get its "partial representation". |
| 809 | + # This code is too simple for that. But we can't use builtins either, because we would |
| 810 | + # have to delete builtins from the filesystem. But even if we engineered that, |
| 811 | + # the difference might evaporate over time as inference changes. |
| 812 | + cls.code_snippet = "def func(): return 42" |
| 813 | + with tempfile.NamedTemporaryFile( |
| 814 | + mode="w", dir=tmp_dir, suffix=".py", delete=False |
| 815 | + ) as tmp: |
| 816 | + tmp.write(cls.code_snippet) |
| 817 | + pyc_file = py_compile.compile(tmp.name) |
| 818 | + cls.pyc_name = tmp.name.replace(".py", ".pyc") |
| 819 | + os.remove(tmp.name) |
| 820 | + os.rename(pyc_file, cls.pyc_name) |
| 821 | + |
| 822 | + # Import the module |
| 823 | + cls.imported_module_path = pathlib.Path(cls.pyc_name) |
| 824 | + cls.imported_module = importlib.import_module(cls.imported_module_path.stem) |
| 825 | + |
| 826 | + # Delete source code from module object, filesystem, and path |
| 827 | + del cls.imported_module.__file__ |
| 828 | + os.remove(cls.imported_module_path) |
| 829 | + sys.path.remove(tmp_dir) |
| 830 | + |
| 831 | + def test_build_from_live_module_without_source_file(self) -> None: |
| 832 | + """Assert that inspect_build() is not called. |
| 833 | + See comment in module_build() before the call to inspect_build(): |
| 834 | + "get a partial representation by introspection" |
| 835 | +
|
| 836 | + This "partial representation" was presumably causing unexpected behavior. |
| 837 | + """ |
| 838 | + # Sanity check |
| 839 | + self.assertIsNone( |
| 840 | + self.imported_module.__loader__.get_source(self.imported_module_path.stem) |
| 841 | + ) |
| 842 | + with self.assertRaises(AttributeError): |
| 843 | + _ = self.imported_module.__file__ |
| 844 | + |
| 845 | + my_builder = builder.AstroidBuilder() |
| 846 | + with unittest.mock.patch.object( |
| 847 | + self.imported_module.__loader__, |
| 848 | + "get_source", |
| 849 | + return_value=self.code_snippet, |
| 850 | + ): |
| 851 | + with unittest.mock.patch.object( |
| 852 | + my_builder, "inspect_build", side_effect=AssertionError |
| 853 | + ): |
| 854 | + my_builder.module_build( |
| 855 | + self.imported_module, modname=self.imported_module_path.stem |
| 856 | + ) |
| 857 | + |
| 858 | + |
793 | 859 | if __name__ == "__main__":
|
794 | 860 | unittest.main()
|
0 commit comments