|
| 1 | +import os.path |
| 2 | +from typing import Optional, List |
| 3 | + |
| 4 | +import pytest |
| 5 | +from mypy import options, build |
| 6 | +from mypy.build import State |
| 7 | +from mypy.modulefinder import BuildSource |
| 8 | +from mypy.nodes import SymbolTableNode, TypeInfo |
| 9 | + |
| 10 | +HERE = os.path.abspath(os.path.dirname(__file__)) |
| 11 | +SAMPLES_DIR = os.path.join(HERE, "samples") |
| 12 | + |
| 13 | + |
| 14 | +@pytest.fixture(scope="session") |
| 15 | +def mypy_cache_dir(tmp_path_factory): |
| 16 | + tdir = tmp_path_factory.mktemp('.mypy_cahe') |
| 17 | + print("Setup cache", str(tdir)) |
| 18 | + return str(tdir) |
| 19 | + |
| 20 | + |
| 21 | +def test_mro_computation_in_forward_reference_to_implementer(mypy_cache_dir: str) -> None: |
| 22 | + sample_name = "forward_reference_to_implementer" |
| 23 | + |
| 24 | + opts = options.Options() |
| 25 | + opts.show_traceback = True |
| 26 | + opts.namespace_packages = True |
| 27 | + opts.cache_dir = mypy_cache_dir |
| 28 | + opts.plugins = ['mypy_zope:plugin'] |
| 29 | + # Config file is needed to load plugins, it doesn't not exist and is not |
| 30 | + # supposed to. |
| 31 | + opts.config_file = 'not_existing_config.ini' |
| 32 | + |
| 33 | + samplefile = os.path.join(SAMPLES_DIR, f"{sample_name}.py") |
| 34 | + base_dir = os.path.dirname(samplefile) |
| 35 | + with open(samplefile) as f: |
| 36 | + source = BuildSource( |
| 37 | + None, |
| 38 | + module=sample_name, |
| 39 | + text=f.read(), |
| 40 | + base_dir=base_dir, |
| 41 | + ) |
| 42 | + result = build.build(sources=[source], options=opts) |
| 43 | + assert result.errors == [] |
| 44 | + |
| 45 | + # Result.graph is a map from module name to state objects. |
| 46 | + state: State = result.graph[sample_name] |
| 47 | + |
| 48 | + # Find Mypy's representation of the Protocol class. |
| 49 | + node: Optional[SymbolTableNode] = None |
| 50 | + for fullname, symbol_table_node, _type_info in state.tree.local_definitions(): |
| 51 | + # Use startswith(...) rather than a direct comparison |
| 52 | + # because the typename includes a line number at the end |
| 53 | + if fullname.startswith(f"{sample_name}.Protocol"): |
| 54 | + node = symbol_table_node |
| 55 | + break |
| 56 | + |
| 57 | + assert node is not None, f"Failed to find `Protocol` class in mypy's state for {samplefile}" |
| 58 | + |
| 59 | + mro: List[TypeInfo] = node.node.mro |
| 60 | + # Expected: [ |
| 61 | + # <TypeInfo forward_reference_to_implementer.Protocol@21>, |
| 62 | + # <TypeInfo builtins.object>, |
| 63 | + # <TypeInfo forward_reference_to_implementer.IProtocol>, |
| 64 | + # ] |
| 65 | + assert len(mro) == 3 |
| 66 | + assert mro[0].fullname.startswith(f"{sample_name}.Protocol") |
| 67 | + assert mro[1].fullname == "builtins.object" |
| 68 | + assert mro[2].fullname == f"{sample_name}.IProtocol" |
0 commit comments