From 62bb2d7cc03afb9a25adf6e58cbd4c8d528f777a Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 15 Feb 2022 14:36:33 -0500 Subject: [PATCH 1/3] Add regression test for changes to support hermetic interpreters Test for changes made in 665574122cb6302fa5342c6f3570c674fcaf0a44. --- tests/unittest_builder.py | 61 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/tests/unittest_builder.py b/tests/unittest_builder.py index eb7fbdc77f..9f887feaa1 100644 --- a/tests/unittest_builder.py +++ b/tests/unittest_builder.py @@ -26,9 +26,13 @@ """tests for the astroid builder and rebuilder module""" import collections +import importlib import os +import pathlib +import py_compile import socket import sys +import tempfile import unittest import pytest @@ -790,5 +794,62 @@ def test_parse_module_with_invalid_type_comments_does_not_crash(): assert isinstance(node, nodes.Module) +class HermeticInterpreterTest(unittest.TestCase): + """Modeled on https://github.com/PyCQA/astroid/pull/1207#issuecomment-951455588""" + + @classmethod + def setUpClass(cls): + """Simulate a hermetic interpreter environment having no code on the filesystem.""" + with tempfile.TemporaryDirectory() as tmp_dir: + sys.path.append(tmp_dir) + + # Write a python file and compile it to .pyc + cls.code_snippet = "def func(): return 42" + with tempfile.NamedTemporaryFile( + mode="w", dir=tmp_dir, suffix=".py", delete=False + ) as tmp: + tmp.write(cls.code_snippet) + pyc_file = py_compile.compile(tmp.name) + cls.pyc_name = tmp.name.replace(".py", ".pyc") + os.remove(tmp.name) + os.rename(pyc_file, cls.pyc_name) + + # Import the module + cls.imported_module_path = pathlib.Path(cls.pyc_name) + cls.imported_module = importlib.import_module(cls.imported_module_path.stem) + + # Delete source code from module object, filesystem, and path + del cls.imported_module.__file__ + os.remove(cls.imported_module_path) + sys.path.remove(tmp_dir) + + def test_build_from_live_module_without_source_file(self): + """Assert that inspect_build() is not called. + See comment in module_build() before the call to inspect_build(): + "get a partial representation by introspection" + + This "partial representation" was presumably causing unexpected behavior. + """ + # Sanity check + self.assertIsNone( + self.imported_module.__loader__.get_source(self.imported_module_path.stem) + ) + with self.assertRaises(AttributeError): + _ = self.imported_module.__file__ + + my_builder = builder.AstroidBuilder() + with unittest.mock.patch.object( + self.imported_module.__loader__, + "get_source", + return_value=self.code_snippet, + ): + with unittest.mock.patch.object( + my_builder, "inspect_build", side_effect=AssertionError + ): + my_builder.module_build( + self.imported_module, modname=self.imported_module_path.stem + ) + + if __name__ == "__main__": unittest.main() From ed622790cd70dfe3e4cecf75f03a971e9ea78ce1 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Wed, 16 Feb 2022 12:59:46 +0100 Subject: [PATCH 2/3] Apply suggestions from code review --- tests/unittest_builder.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/unittest_builder.py b/tests/unittest_builder.py index 9f887feaa1..b14945fd25 100644 --- a/tests/unittest_builder.py +++ b/tests/unittest_builder.py @@ -804,6 +804,11 @@ def setUpClass(cls): sys.path.append(tmp_dir) # Write a python file and compile it to .pyc + # To make this test have even more value, we would need to come up with some + # code that gets inferred differently when we get its "partial representation". + # This code is too simple for that. But we can't use builtins either, because we would + # have to delete builtins from the filesystem. But even if we engineered that, + # the difference might evaporate over time as inference changes. cls.code_snippet = "def func(): return 42" with tempfile.NamedTemporaryFile( mode="w", dir=tmp_dir, suffix=".py", delete=False @@ -823,7 +828,7 @@ def setUpClass(cls): os.remove(cls.imported_module_path) sys.path.remove(tmp_dir) - def test_build_from_live_module_without_source_file(self): + def test_build_from_live_module_without_source_file(self) -> None: """Assert that inspect_build() is not called. See comment in module_build() before the call to inspect_build(): "get a partial representation by introspection" From 5a994cd8d4245c2a73acc5dcc1018202abe99907 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 16 Feb 2022 12:01:05 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/unittest_builder.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unittest_builder.py b/tests/unittest_builder.py index b14945fd25..b7e3739cc7 100644 --- a/tests/unittest_builder.py +++ b/tests/unittest_builder.py @@ -804,10 +804,10 @@ def setUpClass(cls): sys.path.append(tmp_dir) # Write a python file and compile it to .pyc - # To make this test have even more value, we would need to come up with some + # To make this test have even more value, we would need to come up with some # code that gets inferred differently when we get its "partial representation". - # This code is too simple for that. But we can't use builtins either, because we would - # have to delete builtins from the filesystem. But even if we engineered that, + # This code is too simple for that. But we can't use builtins either, because we would + # have to delete builtins from the filesystem. But even if we engineered that, # the difference might evaporate over time as inference changes. cls.code_snippet = "def func(): return 42" with tempfile.NamedTemporaryFile(