from __future__ import annotations

import sys
import time
from multiprocessing import Queue, get_context
from unittest import TestCase, main

import pytest

from mypy.ipc import IPCClient, IPCServer

CONNECTION_NAME = "dmypy-test-ipc"

def server(msg: str, q: Queue[str]) -> None:
    server = IPCServer(CONNECTION_NAME)
    q.put(server.connection_name)
    data = ""
    while not data:
        with server:
            server.write(msg)
            data = server.read()
    server.cleanup()

def server_multi_message_echo(q: Queue[str]) -> None:
    server = IPCServer(CONNECTION_NAME)
    q.put(server.connection_name)
    data = ""
    with server:
        while data != "quit":
            data = server.read()
            server.write(data)
    server.cleanup()

class IPCTests(TestCase):
    def setUp(self) -> None:
        if sys.platform == "linux":
            # The default "fork" start method is potentially unsafe
            self.ctx = get_context("forkserver")
        else:
            self.ctx = get_context("spawn")

    def test_transaction_large(self) -> None:
        queue: Queue[str] = self.ctx.Queue()
        msg = "t" * 200000  # longer than the max read size of 100_000
        p = self.ctx.Process(target=server, args=(msg, queue), daemon=True)
        p.start()
        connection_name = queue.get()
        with IPCClient(connection_name, timeout=1) as client:
            assert client.read() == msg
            client.write("test")
        queue.close()
        queue.join_thread()
        p.join()

    def test_connect_twice(self) -> None:
        queue: Queue[str] = self.ctx.Queue()
        msg = "this is a test message"
        p = self.ctx.Process(target=server, args=(msg, queue), daemon=True)
        p.start()
        connection_name = queue.get()
        with IPCClient(connection_name, timeout=1) as client:
            assert client.read() == msg
            client.write("")  # don't let the server hang up yet, we want to connect again.

        with IPCClient(connection_name, timeout=1) as client:
            assert client.read() == msg
            client.write("test")
        queue.close()
        queue.join_thread()
        p.join()
        assert p.exitcode == 0

    def test_multiple_messages(self) -> None:
        queue: Queue[str] = self.ctx.Queue()
        p = self.ctx.Process(target=server_multi_message_echo, args=(queue,), daemon=True)
        p.start()
        connection_name = queue.get()
        with IPCClient(connection_name, timeout=1) as client:
            # "foo bar" with extra accents on letters.
            # In UTF-8 encoding so we don't confuse editors opening this file.
            fancy_text = b"f\xcc\xb6o\xcc\xb2\xf0\x9d\x91\x9c \xd0\xb2\xe2\xb7\xa1a\xcc\xb6r\xcc\x93\xcd\x98\xcd\x8c"
            client.write(fancy_text.decode("utf-8"))
            assert client.read() == fancy_text.decode("utf-8")

            client.write("Test with spaces")
            client.write("Test write before reading previous")
            time.sleep(0)  # yield to the server to force reading of all messages by server.
            assert client.read() == "Test with spaces"
            assert client.read() == "Test write before reading previous"

            client.write("quit")
            assert client.read() == "quit"
        queue.close()
        queue.join_thread()
        p.join()
        assert p.exitcode == 0

    # Run test_connect_twice a lot, in the hopes of finding issues.
    # This is really slow, so it is skipped, but can be enabled if
    # needed to debug IPC issues.
    @pytest.mark.skip
    def test_connect_alot(self) -> None:
        t0 = time.time()
        for i in range(1000):
            try:
                print(i, "start")
                self.test_connect_twice()
            finally:
                t1 = time.time()
                print(i, t1 - t0)
                sys.stdout.flush()
                t0 = t1

if __name__ == "__main__":
    main()


"""Test cases for AST merge (used for fine-grained incremental checking)"""

from __future__ import annotations

import os
import shutil

from mypy import build
from mypy.build import BuildResult
from mypy.errors import CompileError
from mypy.modulefinder import BuildSource
from mypy.nodes import (
    UNBOUND_IMPORTED,
    Expression,
    MypyFile,
    Node,
    SymbolTable,
    SymbolTableNode,
    TypeInfo,
    TypeVarExpr,
    Var,
)
from mypy.options import Options
from mypy.server.subexpr import get_subexpressions
from mypy.server.update import FineGrainedBuildManager
from mypy.strconv import StrConv
from mypy.test.config import test_temp_dir
from mypy.test.data import DataDrivenTestCase, DataSuite
from mypy.test.helpers import assert_string_arrays_equal, normalize_error_messages, parse_options
from mypy.types import Type, TypeStrVisitor
from mypy.util import IdMapper, short_type

# Which data structures to dump in a test case?
SYMTABLE = "SYMTABLE"
TYPEINFO = " TYPEINFO"
TYPES = "TYPES"
AST = "AST"

class ASTMergeSuite(DataSuite):
    files = ["merge.test"]

    def setup(self) -> None:
        super().setup()
        self.str_conv = StrConv(show_ids=True, options=Options())
        assert self.str_conv.id_mapper is not None
        self.id_mapper: IdMapper = self.str_conv.id_mapper
        self.type_str_conv = TypeStrVisitor(self.id_mapper, options=Options())

    def run_case(self, testcase: DataDrivenTestCase) -> None:
        name = testcase.name
        # We use the test case name to decide which data structures to dump.
        # Dumping everything would result in very verbose test cases.
        if name.endswith("_symtable"):
            kind = SYMTABLE
        elif name.endswith("_typeinfo"):
            kind = TYPEINFO
        elif name.endswith("_types"):
            kind = TYPES
        else:
            kind = AST

        main_src = "\n".join(testcase.input)
        result = self.build(main_src, testcase)
        assert result is not None, "cases where CompileError occurred should not be run"
        result.manager.fscache.flush()
        fine_grained_manager = FineGrainedBuildManager(result)

        a = []
        if result.errors:
            a.extend(result.errors)

        target_path = os.path.join(test_temp_dir, "target.py")
        shutil.copy(os.path.join(test_temp_dir, "target.py.next"), target_path)

        a.extend(self.dump(fine_grained_manager, kind, testcase.test_modules))
        old_subexpr = get_subexpressions(result.manager.modules["target"])

        a.append("==>")

        new_file, new_types = self.build_increment(fine_grained_manager, "target", target_path)
        a.extend(self.dump(fine_grained_manager, kind, testcase.test_modules))

        for expr in old_subexpr:
            if isinstance(expr, TypeVarExpr):
                # These are merged so we can't perform the check.
                continue
            # Verify that old AST nodes are removed from the expression type map.
            assert expr not in new_types

        if testcase.normalize_output:
            a = normalize_error_messages(a)

        assert_string_arrays_equal(
            testcase.output, a, f"Invalid output ({testcase.file}, line {testcase.line})"
        )

    def build(self, source: str, testcase: DataDrivenTestCase) -> BuildResult | None:
        options = parse_options(source, testcase, incremental_step=1)
        options.incremental = True
        options.fine_grained_incremental = True
        options.use_builtins_fixtures = True
        options.export_types = True
        options.show_traceback = True
        options.allow_empty_bodies = True
        options.force_uppercase_builtins = True
        main_path = os.path.join(test_temp_dir, "main")

        self.str_conv.options = options
        self.type_str_conv.options = options
        with open(main_path, "w", encoding="utf8") as f:
            f.write(source)
        try:
            result = build.build(
                sources=[BuildSource(main_path, None, None)],
                options=options,
                alt_lib_path=test_temp_dir,
            )
        except CompileError:
            # TODO: Is it okay to return None?
            return None
        return result

    def build_increment(
        self, manager: FineGrainedBuildManager, module_id: str, path: str
    ) -> tuple[MypyFile, dict[Expression, Type]]:
        manager.flush_cache()
        manager.update([(module_id, path)], [])
        module = manager.manager.modules[module_id]
        type_map = manager.graph[module_id].type_map()
        return module, type_map

    def dump(
        self, manager: FineGrainedBuildManager, kind: str, test_modules: list[str]
    ) -> list[str]:
        modules = {
            name: file for name, file in manager.manager.modules.items() if name in test_modules
        }
        if kind == AST:
            return self.dump_asts(modules)
        elif kind == TYPEINFO:
            return self.dump_typeinfos(modules)
        elif kind == SYMTABLE:
            return self.dump_symbol_tables(modules)
        elif kind == TYPES:
            return self.dump_types(modules, manager)
        assert False, f"Invalid kind {kind}"

    def dump_asts(self, modules: dict[str, MypyFile]) -> list[str]:
        a = []
        for m in sorted(modules):
            s = modules[m].accept(self.str_conv)
            a.extend(s.splitlines())
        return a

    def dump_symbol_tables(self, modules: dict[str, MypyFile]) -> list[str]:
        a = []
        for id in sorted(modules):
            a.extend(self.dump_symbol_table(id, modules[id].names))
        return a

    def dump_symbol_table(self, module_id: str, symtable: SymbolTable) -> list[str]:
        a = [f"{module_id}:"]
        for name in sorted(symtable):
            if name.startswith("__"):
                continue
            a.append(f"    {name}: {self.format_symbol_table_node(symtable[name])}")
        return a

    def format_symbol_table_node(self, node: SymbolTableNode) -> str:
        if node.node is None:
            if node.kind == UNBOUND_IMPORTED:
                return "UNBOUND_IMPORTED"
            return "None"
        if isinstance(node.node, Node):
            s = f"{str(type(node.node).__name__)}<{self.id_mapper.id(node.node)}>"
        else:
            s = f"? ({type(node.node)})"
        if (
            isinstance(node.node, Var)
            and node.node.type
            and not node.node.fullname.startswith("typing.")
        ):
            typestr = self.format_type(node.node.type)
            s += f"({typestr})"
        return s

    def dump_typeinfos(self, modules: dict[str, MypyFile]) -> list[str]:
        a = []
        for id in sorted(modules):
            a.extend(self.dump_typeinfos_recursive(modules[id].names))
        return a

    def dump_typeinfos_recursive(self, names: SymbolTable) -> list[str]:
        a = []
        for name, node in sorted(names.items(), key=lambda x: x[0]):
            if isinstance(node.node, TypeInfo):
                a.extend(self.dump_typeinfo(node.node))
                a.extend(self.dump_typeinfos_recursive(node.node.names))
        return a

    def dump_typeinfo(self, info: TypeInfo) -> list[str]:
        if info.fullname == "enum.Enum":
            # Avoid noise
            return []
        s = info.dump(str_conv=self.str_conv, type_str_conv=self.type_str_conv)
        return s.splitlines()

    def dump_types(
        self, modules: dict[str, MypyFile], manager: FineGrainedBuildManager
    ) -> list[str]:
        a = []
        # To make the results repeatable, we try to generate unique and
        # deterministic sort keys.
        for module_id in sorted(modules):
            all_types = manager.manager.all_types
            # Compute a module type map from the global type map
            tree = manager.graph[module_id].tree
            assert tree is not None
            type_map = {
                node: all_types[node] for node in get_subexpressions(tree) if node in all_types
            }
            if type_map:
                a.append(f"## {module_id}")
                for expr in sorted(
                    type_map,
                    key=lambda n: (
                        n.line,
                        short_type(n),
                        n.str_with_options(self.str_conv.options) + str(type_map[n]),
                    ),
                ):
                    typ = type_map[expr]
                    a.append(f"{short_type(expr)}:{expr.line}: {self.format_type(typ)}")
        return a

    def format_type(self, typ: Type) -> str:
        return typ.accept(self.type_str_conv)


from __future__ import annotations

import os

from mypy.modulefinder import FindModuleCache, ModuleNotFoundReason, SearchPaths
from mypy.options import Options
from mypy.test.config import package_path
from mypy.test.helpers import Suite, assert_equal

data_path = os.path.relpath(os.path.join(package_path, "modulefinder"))

class ModuleFinderSuite(Suite):
    def setUp(self) -> None:
        self.search_paths = SearchPaths(
            python_path=(),
            mypy_path=(
                os.path.join(data_path, "nsx-pkg1"),
                os.path.join(data_path, "nsx-pkg2"),
                os.path.join(data_path, "nsx-pkg3"),
                os.path.join(data_path, "nsy-pkg1"),
                os.path.join(data_path, "nsy-pkg2"),
                os.path.join(data_path, "pkg1"),
                os.path.join(data_path, "pkg2"),
            ),
            package_path=(),
            typeshed_path=(),
        )
        options = Options()
        options.namespace_packages = True
        self.fmc_ns = FindModuleCache(self.search_paths, fscache=None, options=options)

        options = Options()
        options.namespace_packages = False
        self.fmc_nons = FindModuleCache(self.search_paths, fscache=None, options=options)

    def test__no_namespace_packages__nsx(self) -> None:
        """
        If namespace_packages is False, we shouldn't find nsx
        """
        found_module = self.fmc_nons.find_module("nsx")
        assert_equal(ModuleNotFoundReason.NOT_FOUND, found_module)

    def test__no_namespace_packages__nsx_a(self) -> None:
        """
        If namespace_packages is False, we shouldn't find nsx.a.
        """
        found_module = self.fmc_nons.find_module("nsx.a")
        assert_equal(ModuleNotFoundReason.NOT_FOUND, found_module)

    def test__no_namespace_packages__find_a_in_pkg1(self) -> None:
        """
        Find find pkg1/a.py for "a" with namespace_packages False.
        """
        found_module = self.fmc_nons.find_module("a")
        expected = os.path.join(data_path, "pkg1", "a.py")
        assert_equal(expected, found_module)

    def test__no_namespace_packages__find_b_in_pkg2(self) -> None:
        found_module = self.fmc_ns.find_module("b")
        expected = os.path.join(data_path, "pkg2", "b", "__init__.py")
        assert_equal(expected, found_module)

    def test__find_nsx_as_namespace_pkg_in_pkg1(self) -> None:
        """
        There's no __init__.py in any of the nsx dirs, return
        the path to the first one found in mypypath.
        """
        found_module = self.fmc_ns.find_module("nsx")
        expected = os.path.join(data_path, "nsx-pkg1", "nsx")
        assert_equal(expected, found_module)

    def test__find_nsx_a_init_in_pkg1(self) -> None:
        """
        Find nsx-pkg1/nsx/a/__init__.py for "nsx.a" in namespace mode.
        """
        found_module = self.fmc_ns.find_module("nsx.a")
        expected = os.path.join(data_path, "nsx-pkg1", "nsx", "a", "__init__.py")
        assert_equal(expected, found_module)

    def test__find_nsx_b_init_in_pkg2(self) -> None:
        """
        Find nsx-pkg2/nsx/b/__init__.py for "nsx.b" in namespace mode.
        """
        found_module = self.fmc_ns.find_module("nsx.b")
        expected = os.path.join(data_path, "nsx-pkg2", "nsx", "b", "__init__.py")
        assert_equal(expected, found_module)

    def test__find_nsx_c_c_in_pkg3(self) -> None:
        """
        Find nsx-pkg3/nsx/c/c.py for "nsx.c.c" in namespace mode.
        """
        found_module = self.fmc_ns.find_module("nsx.c.c")
        expected = os.path.join(data_path, "nsx-pkg3", "nsx", "c", "c.py")
        assert_equal(expected, found_module)

    def test__find_nsy_a__init_pyi(self) -> None:
        """
        Prefer nsy-pkg1/a/__init__.pyi file over __init__.py.
        """
        found_module = self.fmc_ns.find_module("nsy.a")
        expected = os.path.join(data_path, "nsy-pkg1", "nsy", "a", "__init__.pyi")
        assert_equal(expected, found_module)

    def test__find_nsy_b__init_py(self) -> None:
        """
        There is a nsy-pkg2/nsy/b.pyi, but also a nsy-pkg2/nsy/b/__init__.py.
        We expect to find the latter when looking up "nsy.b" as
        a package is preferred over a module.
        """
        found_module = self.fmc_ns.find_module("nsy.b")
        expected = os.path.join(data_path, "nsy-pkg2", "nsy", "b", "__init__.py")
        assert_equal(expected, found_module)

    def test__find_nsy_c_pyi(self) -> None:
        """
        There is a nsy-pkg2/nsy/c.pyi and nsy-pkg2/nsy/c.py
        We expect to find the former when looking up "nsy.b" as
        .pyi is preferred over .py.
        """
        found_module = self.fmc_ns.find_module("nsy.c")
        expected = os.path.join(data_path, "nsy-pkg2", "nsy", "c.pyi")
        assert_equal(expected, found_module)

    def test__find_a_in_pkg1(self) -> None:
        found_module = self.fmc_ns.find_module("a")
        expected = os.path.join(data_path, "pkg1", "a.py")
        assert_equal(expected, found_module)

    def test__find_b_init_in_pkg2(self) -> None:
        found_module = self.fmc_ns.find_module("b")
        expected = os.path.join(data_path, "pkg2", "b", "__init__.py")
        assert_equal(expected, found_module)

    def test__find_d_nowhere(self) -> None:
        found_module = self.fmc_ns.find_module("d")
        assert_equal(ModuleNotFoundReason.NOT_FOUND, found_module)

class ModuleFinderSitePackagesSuite(Suite):
    def setUp(self) -> None:
        self.package_dir = os.path.relpath(
            os.path.join(package_path, "modulefinder-site-packages")
        )

        package_paths = (
            os.path.join(self.package_dir, "baz"),
            os.path.join(self.package_dir, "..", "not-a-directory"),
            os.path.join(self.package_dir, "..", "modulefinder-src"),
            self.package_dir,
        )

        self.search_paths = SearchPaths(
            python_path=(),
            mypy_path=(os.path.join(data_path, "pkg1"),),
            package_path=tuple(package_paths),
            typeshed_path=(),
        )
        options = Options()
        options.namespace_packages = True
        self.fmc_ns = FindModuleCache(self.search_paths, fscache=None, options=options)

        options = Options()
        options.namespace_packages = False
        self.fmc_nons = FindModuleCache(self.search_paths, fscache=None, options=options)

    def path(self, *parts: str) -> str:
        return os.path.join(self.package_dir, *parts)

    def test__packages_with_ns(self) -> None:
        cases = [
            # Namespace package with py.typed
            ("ns_pkg_typed", self.path("ns_pkg_typed")),
            ("ns_pkg_typed.a", self.path("ns_pkg_typed", "a.py")),
            ("ns_pkg_typed.b", self.path("ns_pkg_typed", "b")),
            ("ns_pkg_typed.b.c", self.path("ns_pkg_typed", "b", "c.py")),
            ("ns_pkg_typed.a.a_var", ModuleNotFoundReason.NOT_FOUND),
            # Namespace package without py.typed
            ("ns_pkg_untyped", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            ("ns_pkg_untyped.a", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            ("ns_pkg_untyped.b", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            ("ns_pkg_untyped.b.c", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            ("ns_pkg_untyped.a.a_var", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            # Namespace package without stub package
            ("ns_pkg_w_stubs", self.path("ns_pkg_w_stubs")),
            ("ns_pkg_w_stubs.typed", self.path("ns_pkg_w_stubs-stubs", "typed", "__init__.pyi")),
            (
                "ns_pkg_w_stubs.typed_inline",
                self.path("ns_pkg_w_stubs", "typed_inline", "__init__.py"),
            ),
            ("ns_pkg_w_stubs.untyped", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            # Regular package with py.typed
            ("pkg_typed", self.path("pkg_typed", "__init__.py")),
            ("pkg_typed.a", self.path("pkg_typed", "a.py")),
            ("pkg_typed.b", self.path("pkg_typed", "b", "__init__.py")),
            ("pkg_typed.b.c", self.path("pkg_typed", "b", "c.py")),
            ("pkg_typed.a.a_var", ModuleNotFoundReason.NOT_FOUND),
            # Regular package without py.typed
            ("pkg_untyped", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            ("pkg_untyped.a", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            ("pkg_untyped.b", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            ("pkg_untyped.b.c", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            ("pkg_untyped.a.a_var", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            # Top-level Python file in site-packages
            ("standalone", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            ("standalone.standalone_var", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            # Packages found by following .pth files
            ("baz_pkg", self.path("baz", "baz_pkg", "__init__.py")),
            ("ns_baz_pkg.a", self.path("baz", "ns_baz_pkg", "a.py")),
            ("neighbor_pkg", self.path("..", "modulefinder-src", "neighbor_pkg", "__init__.py")),
            ("ns_neighbor_pkg.a", self.path("..", "modulefinder-src", "ns_neighbor_pkg", "a.py")),
            # Something that doesn't exist
            ("does_not_exist", ModuleNotFoundReason.NOT_FOUND),
            # A regular package with an installed set of stubs
            ("foo.bar", self.path("foo-stubs", "bar.pyi")),
            # A regular, non-site-packages module
            ("a", os.path.join(data_path, "pkg1", "a.py")),
        ]
        for module, expected in cases:
            template = "Find(" + module + ") got {}; expected {}"

            actual = self.fmc_ns.find_module(module)
            assert_equal(actual, expected, template)

    def test__packages_without_ns(self) -> None:
        cases = [
            # Namespace package with py.typed
            ("ns_pkg_typed", ModuleNotFoundReason.NOT_FOUND),
            ("ns_pkg_typed.a", ModuleNotFoundReason.NOT_FOUND),
            ("ns_pkg_typed.b", ModuleNotFoundReason.NOT_FOUND),
            ("ns_pkg_typed.b.c", ModuleNotFoundReason.NOT_FOUND),
            ("ns_pkg_typed.a.a_var", ModuleNotFoundReason.NOT_FOUND),
            # Namespace package without py.typed
            ("ns_pkg_untyped", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            ("ns_pkg_untyped.a", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            ("ns_pkg_untyped.b", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            ("ns_pkg_untyped.b.c", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            ("ns_pkg_untyped.a.a_var", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            # Namespace package without stub package
            ("ns_pkg_w_stubs", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            ("ns_pkg_w_stubs.typed", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            (
                "ns_pkg_w_stubs.typed_inline",
                self.path("ns_pkg_w_stubs", "typed_inline", "__init__.py"),
            ),
            ("ns_pkg_w_stubs.untyped", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            # Regular package with py.typed
            ("pkg_typed", self.path("pkg_typed", "__init__.py")),
            ("pkg_typed.a", self.path("pkg_typed", "a.py")),
            ("pkg_typed.b", self.path("pkg_typed", "b", "__init__.py")),
            ("pkg_typed.b.c", self.path("pkg_typed", "b", "c.py")),
            ("pkg_typed.a.a_var", ModuleNotFoundReason.NOT_FOUND),
            # Regular package without py.typed
            ("pkg_untyped", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            ("pkg_untyped.a", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            ("pkg_untyped.b", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            ("pkg_untyped.b.c", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            ("pkg_untyped.a.a_var", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            # Top-level Python file in site-packages
            ("standalone", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            ("standalone.standalone_var", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
            # Packages found by following .pth files
            ("baz_pkg", self.path("baz", "baz_pkg", "__init__.py")),
            ("ns_baz_pkg.a", ModuleNotFoundReason.NOT_FOUND),
            ("neighbor_pkg", self.path("..", "modulefinder-src", "neighbor_pkg", "__init__.py")),
            ("ns_neighbor_pkg.a", ModuleNotFoundReason.NOT_FOUND),
            # Something that doesn't exist
            ("does_not_exist", ModuleNotFoundReason.NOT_FOUND),
            # A regular package with an installed set of stubs
            ("foo.bar", self.path("foo-stubs", "bar.pyi")),
            # A regular, non-site-packages module
            ("a", os.path.join(data_path, "pkg1", "a.py")),
        ]
        for module, expected in cases:
            template = "Find(" + module + ") got {}; expected {}"

            actual = self.fmc_nons.find_module(module)
            assert_equal(actual, expected, template)


"""A basic check to make sure that we are using a mypyc-compiled version when expected."""

from __future__ import annotations

import os
from unittest import TestCase

import mypy

class MypycTest(TestCase):
    def test_using_mypyc(self) -> None:
        if os.getenv("TEST_MYPYC", None) == "1":
            assert not mypy.__file__.endswith(".py"), "Expected to find a mypyc-compiled version"


"""Test cases for `--output=json`.

These cannot be run by the usual unit test runner because of the backslashes in
the output, which get normalized to forward slashes by the test suite on Windows.
"""

from __future__ import annotations

import os
import os.path

from mypy import api
from mypy.defaults import PYTHON3_VERSION
from mypy.test.config import test_temp_dir
from mypy.test.data import DataDrivenTestCase, DataSuite

class OutputJSONsuite(DataSuite):
    files = ["outputjson.test"]

    def run_case(self, testcase: DataDrivenTestCase) -> None:
        test_output_json(testcase)

def test_output_json(testcase: DataDrivenTestCase) -> None:
    """Runs Mypy in a subprocess, and ensures that `--output=json` works as intended."""
    mypy_cmdline = ["--output=json"]
    mypy_cmdline.append(f"--python-version={'.'.join(map(str, PYTHON3_VERSION))}")

    # Write the program to a file.
    program_path = os.path.join(test_temp_dir, "main")
    mypy_cmdline.append(program_path)
    with open(program_path, "w", encoding="utf8") as file:
        for s in testcase.input:
            file.write(f"{s}\n")

    output = []
    # Type check the program.
    out, err, returncode = api.run(mypy_cmdline)
    # split lines, remove newlines, and remove directory of test case
    for line in (out + err).rstrip("\n").splitlines():
        if line.startswith(test_temp_dir + os.sep):
            output.append(line[len(test_temp_dir + os.sep) :].rstrip("\r\n"))
        else:
            output.append(line.rstrip("\r\n"))

    if returncode > 1:
        output.append("!!! Mypy crashed !!!")

    # Remove temp file.
    os.remove(program_path)

    # JSON encodes every `\` character into `\\`, so we need to remove `\\` from windows paths
    # and `/` from POSIX paths
    json_os_separator = os.sep.replace("\\", "\\\\")
    normalized_output = [line.replace(test_temp_dir + json_os_separator, "") for line in output]

    assert normalized_output == testcase.output


"""Tests for the mypy parser."""

from __future__ import annotations

import sys

from pytest import skip

from mypy import defaults
from mypy.config_parser import parse_mypy_comments
from mypy.errors import CompileError, Errors
from mypy.options import Options
from mypy.parse import parse
from mypy.test.data import DataDrivenTestCase, DataSuite
from mypy.test.helpers import assert_string_arrays_equal, find_test_files, parse_options
from mypy.util import get_mypy_comments

class ParserSuite(DataSuite):
    required_out_section = True
    base_path = "."
    files = find_test_files(pattern="parse*.test", exclude=["parse-errors.test"])

    if sys.version_info < (3, 10):
        files.remove("parse-python310.test")
    if sys.version_info < (3, 12):
        files.remove("parse-python312.test")

    def run_case(self, testcase: DataDrivenTestCase) -> None:
        test_parser(testcase)

def test_parser(testcase: DataDrivenTestCase) -> None:
    """Perform a single parser test case.

    The argument contains the description of the test case.
    """
    options = Options()
    options.force_uppercase_builtins = True
    options.hide_error_codes = True

    if testcase.file.endswith("python310.test"):
        options.python_version = (3, 10)
    elif testcase.file.endswith("python312.test"):
        options.python_version = (3, 12)
    else:
        options.python_version = defaults.PYTHON3_VERSION

    source = "\n".join(testcase.input)

    # Apply mypy: comments to options.
    comments = get_mypy_comments(source)
    changes, _ = parse_mypy_comments(comments, options)
    options = options.apply_changes(changes)

    try:
        n = parse(
            bytes(source, "ascii"),
            fnam="main",
            module="__main__",
            errors=Errors(options),
            options=options,
            raise_on_error=True,
        )
        a = n.str_with_options(options).split("\n")
    except CompileError as e:
        a = e.messages
    assert_string_arrays_equal(
        testcase.output, a, f"Invalid parser output ({testcase.file}, line {testcase.line})"
    )

# The file name shown in test case output. This is displayed in error
# messages, and must match the file name in the test case descriptions.
INPUT_FILE_NAME = "file"

class ParseErrorSuite(DataSuite):
    required_out_section = True
    base_path = "."
    files = ["parse-errors.test"]

    def run_case(self, testcase: DataDrivenTestCase) -> None:
        test_parse_error(testcase)

def test_parse_error(testcase: DataDrivenTestCase) -> None:
    try:
        options = parse_options("\n".join(testcase.input), testcase, 0)
        if options.python_version != sys.version_info[:2]:
            skip()
        # Compile temporary file. The test file contains non-ASCII characters.
        parse(
            bytes("\n".join(testcase.input), "utf-8"),
            INPUT_FILE_NAME,
            "__main__",
            errors=Errors(options),
            options=options,
            raise_on_error=True,
        )
        raise AssertionError("No errors reported")
    except CompileError as e:
        if e.module_with_blocker is not None:
            assert e.module_with_blocker == "__main__"
        # Verify that there was a compile error and that the error messages
        # are equivalent.
        assert_string_arrays_equal(
            testcase.output,
            e.messages,
            f"Invalid compiler output ({testcase.file}, line {testcase.line})",
        )


from __future__ import annotations

import os
import re
import subprocess
import sys
import tempfile
from contextlib import contextmanager
from typing import Iterator

import filelock

import mypy.api
from mypy.test.config import package_path, pip_lock, pip_timeout, test_temp_dir
from mypy.test.data import DataDrivenTestCase, DataSuite
from mypy.test.helpers import assert_string_arrays_equal, perform_file_operations

# NOTE: options.use_builtins_fixtures should not be set in these
# tests, otherwise mypy will ignore installed third-party packages.

class PEP561Suite(DataSuite):
    files = ["pep561.test"]
    base_path = "."

    def run_case(self, test_case: DataDrivenTestCase) -> None:
        test_pep561(test_case)

@contextmanager
def virtualenv(python_executable: str = sys.executable) -> Iterator[tuple[str, str]]:
    """Context manager that creates a virtualenv in a temporary directory

    Returns the path to the created Python executable
    """
    with tempfile.TemporaryDirectory() as venv_dir:
        proc = subprocess.run(
            [python_executable, "-m", "venv", venv_dir], cwd=os.getcwd(), capture_output=True
        )
        if proc.returncode != 0:
            err = proc.stdout.decode("utf-8") + proc.stderr.decode("utf-8")
            raise Exception("Failed to create venv.\n" + err)
        if sys.platform == "win32":
            yield venv_dir, os.path.abspath(os.path.join(venv_dir, "Scripts", "python"))
        else:
            yield venv_dir, os.path.abspath(os.path.join(venv_dir, "bin", "python"))

def upgrade_pip(python_executable: str) -> None:
    """Install pip>=21.3.1. Required for editable installs with PEP 660."""
    if (
        sys.version_info >= (3, 11)
        or (3, 10, 3) <= sys.version_info < (3, 11)
        or (3, 9, 11) <= sys.version_info < (3, 10)
        or (3, 8, 13) <= sys.version_info < (3, 9)
    ):
        # Skip for more recent Python releases which come with pip>=21.3.1
        # out of the box - for performance reasons.
        return

    install_cmd = [python_executable, "-m", "pip", "install", "pip>=21.3.1"]
    try:
        with filelock.FileLock(pip_lock, timeout=pip_timeout):
            proc = subprocess.run(install_cmd, capture_output=True, env=os.environ)
    except filelock.Timeout as err:
        raise Exception(f"Failed to acquire {pip_lock}") from err
    if proc.returncode != 0:
        raise Exception(proc.stdout.decode("utf-8") + proc.stderr.decode("utf-8"))

def install_package(
    pkg: str, python_executable: str = sys.executable, editable: bool = False
) -> None:
    """Install a package from test-data/packages/pkg/"""
    working_dir = os.path.join(package_path, pkg)
    with tempfile.TemporaryDirectory() as dir:
        install_cmd = [python_executable, "-m", "pip", "install"]
        if editable:
            install_cmd.append("-e")
        install_cmd.append(".")

        # Note that newer versions of pip (21.3+) don't
        # follow this env variable, but this is for compatibility
        env = {"PIP_BUILD": dir}
        # Inherit environment for Windows
        env.update(os.environ)
        try:
            with filelock.FileLock(pip_lock, timeout=pip_timeout):
                proc = subprocess.run(install_cmd, cwd=working_dir, capture_output=True, env=env)
        except filelock.Timeout as err:
            raise Exception(f"Failed to acquire {pip_lock}") from err
    if proc.returncode != 0:
        raise Exception(proc.stdout.decode("utf-8") + proc.stderr.decode("utf-8"))

def test_pep561(testcase: DataDrivenTestCase) -> None:
    """Test running mypy on files that depend on PEP 561 packages."""
    assert testcase.old_cwd is not None, "test was not properly set up"
    python = sys.executable

    assert python is not None, "Should be impossible"
    pkgs, pip_args = parse_pkgs(testcase.input[0])
    mypy_args = parse_mypy_args(testcase.input[1])
    editable = False
    for arg in pip_args:
        if arg == "editable":
            editable = True
        else:
            raise ValueError(f"Unknown pip argument: {arg}")
    assert pkgs, "No packages to install for PEP 561 test?"
    with virtualenv(python) as venv:
        venv_dir, python_executable = venv
        if editable:
            # Editable installs with PEP 660 require pip>=21.3
            upgrade_pip(python_executable)
        for pkg in pkgs:
            install_package(pkg, python_executable, editable)

        cmd_line = list(mypy_args)
        has_program = not ("-p" in cmd_line or "--package" in cmd_line)
        if has_program:
            program = testcase.name + ".py"
            with open(program, "w", encoding="utf-8") as f:
                for s in testcase.input:
                    f.write(f"{s}\n")
            cmd_line.append(program)

        cmd_line.extend(["--no-error-summary", "--hide-error-codes"])
        if python_executable != sys.executable:
            cmd_line.append(f"--python-executable={python_executable}")

        steps = testcase.find_steps()
        if steps != [[]]:
            steps = [[]] + steps

        for i, operations in enumerate(steps):
            perform_file_operations(operations)

            output = []
            # Type check the module
            out, err, returncode = mypy.api.run(cmd_line)

            # split lines, remove newlines, and remove directory of test case
            for line in (out + err).splitlines():
                if line.startswith(test_temp_dir + os.sep):
                    output.append(line[len(test_temp_dir + os.sep) :].rstrip("\r\n"))
                else:
                    # Normalize paths so that the output is the same on Windows and Linux/macOS.
                    line = line.replace(test_temp_dir + os.sep, test_temp_dir + "/")
                    output.append(line.rstrip("\r\n"))
            iter_count = "" if i == 0 else f" on iteration {i + 1}"
            expected = testcase.output if i == 0 else testcase.output2.get(i + 1, [])

            assert_string_arrays_equal(
                expected,
                output,
                f"Invalid output ({testcase.file}, line {testcase.line}){iter_count}",
            )

        if has_program:
            os.remove(program)

def parse_pkgs(comment: str) -> tuple[list[str], list[str]]:
    if not comment.startswith("# pkgs:"):
        return ([], [])
    else:
        pkgs_str, *args = comment[7:].split(";")
        return ([pkg.strip() for pkg in pkgs_str.split(",")], [arg.strip() for arg in args])

def parse_mypy_args(line: str) -> list[str]:
    m = re.match("# flags: (.*)$", line)
    if not m:
        return []  # No args; mypy will spit out an error.
    return m.group(1).split()

def test_mypy_path_is_respected() -> None:
    assert False
    packages = "packages"
    pkg_name = "a"
    with tempfile.TemporaryDirectory() as temp_dir:
        old_dir = os.getcwd()
        os.chdir(temp_dir)
        try:
            # Create the pkg for files to go into
            full_pkg_name = os.path.join(temp_dir, packages, pkg_name)
            os.makedirs(full_pkg_name)

            # Create the empty __init__ file to declare a package
            pkg_init_name = os.path.join(temp_dir, packages, pkg_name, "__init__.py")
            open(pkg_init_name, "w", encoding="utf8").close()

            mypy_config_path = os.path.join(temp_dir, "mypy.ini")
            with open(mypy_config_path, "w") as mypy_file:
                mypy_file.write("[mypy]\n")
                mypy_file.write(f"mypy_path = ./{packages}\n")

            with virtualenv() as venv:
                venv_dir, python_executable = venv

                cmd_line_args = []
                if python_executable != sys.executable:
                    cmd_line_args.append(f"--python-executable={python_executable}")
                cmd_line_args.extend(["--config-file", mypy_config_path, "--package", pkg_name])

                out, err, returncode = mypy.api.run(cmd_line_args)
                assert returncode == 0
        finally:
            os.chdir(old_dir)


"""Test cases for running mypy programs using a Python interpreter.

Each test case type checks a program then runs it using Python. The
output (stdout) of the program is compared to expected output. Type checking
uses full builtins and other stubs.

Note: Currently Python interpreter paths are hard coded.

Note: These test cases are *not* included in the main test suite, as including
      this suite would slow down the main suite too much.
"""

from __future__ import annotations

import os
import os.path
import re
import subprocess
import sys
from tempfile import TemporaryDirectory

from mypy import api
from mypy.defaults import PYTHON3_VERSION
from mypy.test.config import test_temp_dir
from mypy.test.data import DataDrivenTestCase, DataSuite
from mypy.test.helpers import assert_string_arrays_equal, split_lines

# Path to Python 3 interpreter
python3_path = sys.executable
program_re = re.compile(r"\b_program.py\b")

class PythonEvaluationSuite(DataSuite):
    files = ["pythoneval.test", "pythoneval-asyncio.test"]
    cache_dir = TemporaryDirectory()

    def run_case(self, testcase: DataDrivenTestCase) -> None:
        test_python_evaluation(testcase, os.path.join(self.cache_dir.name, ".mypy_cache"))

def test_python_evaluation(testcase: DataDrivenTestCase, cache_dir: str) -> None:
    """Runs Mypy in a subprocess.

    If this passes without errors, executes the script again with a given Python
    version.
    """
    assert testcase.old_cwd is not None, "test was not properly set up"
    # We must enable site packages to get access to installed stubs.
    mypy_cmdline = [
        "--show-traceback",
        "--no-silence-site-packages",
        "--no-error-summary",
        "--hide-error-codes",
        "--allow-empty-bodies",
        "--force-uppercase-builtins",
        "--test-env",  # Speeds up some checks
    ]
    interpreter = python3_path
    mypy_cmdline.append(f"--python-version={'.'.join(map(str, PYTHON3_VERSION))}")

    m = re.search("# flags: (.*)$", "\n".join(testcase.input), re.MULTILINE)
    if m:
        additional_flags = m.group(1).split()
        for flag in additional_flags:
            if flag.startswith("--python-version="):
                targetted_python_version = flag.split("=")[1]
                targetted_major, targetted_minor = targetted_python_version.split(".")
                if (int(targetted_major), int(targetted_minor)) > (
                    sys.version_info.major,
                    sys.version_info.minor,
                ):
                    return
        mypy_cmdline.extend(additional_flags)

    # Write the program to a file.
    program = "_" + testcase.name + ".py"
    program_path = os.path.join(test_temp_dir, program)
    mypy_cmdline.append(program_path)
    with open(program_path, "w", encoding="utf8") as file:
        for s in testcase.input:
            file.write(f"{s}\n")
    mypy_cmdline.append(f"--cache-dir={cache_dir}")
    output = []
    # Type check the program.
    out, err, returncode = api.run(mypy_cmdline)
    # split lines, remove newlines, and remove directory of test case
    for line in (out + err).splitlines():
        if line.startswith(test_temp_dir + os.sep):
            output.append(line[len(test_temp_dir + os.sep) :].rstrip("\r\n"))
        else:
            # Normalize paths so that the output is the same on Windows and Linux/macOS.
            line = line.replace(test_temp_dir + os.sep, test_temp_dir + "/")
            output.append(line.rstrip("\r\n"))
    if returncode > 1 and not testcase.output:
        # Either api.run() doesn't work well in case of a crash, or pytest interferes with it.
        # Tweak output to prevent tests with empty expected output to pass in case of a crash.
        output.append("!!! Mypy crashed !!!")
    if returncode == 0 and not output:
        # Execute the program.
        proc = subprocess.run(
            [interpreter, "-Wignore", program], cwd=test_temp_dir, capture_output=True
        )
        output.extend(split_lines(proc.stdout, proc.stderr))
    # Remove temp file.
    os.remove(program_path)
    for i, line in enumerate(output):
        if os.path.sep + "typeshed" + os.path.sep in line:
            output[i] = line.split(os.path.sep)[-1]
    assert_string_arrays_equal(
        adapt_output(testcase), output, f"Invalid output ({testcase.file}, line {testcase.line})"
    )

def adapt_output(testcase: DataDrivenTestCase) -> list[str]:
    """Translates the generic _program.py into the actual filename."""
    program = "_" + testcase.name + ".py"
    return [program_re.sub(program, line) for line in testcase.output]


"""Test cases for reports generated by mypy."""

from __future__ import annotations

import textwrap

from mypy.report import CoberturaPackage, get_line_rate
from mypy.test.helpers import Suite, assert_equal

try:
    import lxml  # type: ignore[import-untyped]
except ImportError:
    lxml = None

import pytest

class CoberturaReportSuite(Suite):
    @pytest.mark.skipif(lxml is None, reason="Cannot import lxml. Is it installed?")
    def test_get_line_rate(self) -> None:
        assert_equal("1.0", get_line_rate(0, 0))
        assert_equal("0.3333", get_line_rate(1, 3))

    @pytest.mark.skipif(lxml is None, reason="Cannot import lxml. Is it installed?")
    def test_as_xml(self) -> None:
        import lxml.etree as etree  # type: ignore[import-untyped]

        cobertura_package = CoberturaPackage("foobar")
        cobertura_package.covered_lines = 21
        cobertura_package.total_lines = 42

        child_package = CoberturaPackage("raz")
        child_package.covered_lines = 10
        child_package.total_lines = 10
        child_package.classes["class"] = etree.Element("class")

        cobertura_package.packages["raz"] = child_package

        expected_output = textwrap.dedent(
            """\
            <package complexity="1.0" name="foobar" branch-rate="0" line-rate="0.5000">
              <classes/>
              <packages>
                <package complexity="1.0" name="raz" branch-rate="0" line-rate="1.0000">
                  <classes>
                    <class/>
                  </classes>
                </package>
              </packages>
            </package>
        """
        ).encode("ascii")
        assert_equal(
            expected_output, etree.tostring(cobertura_package.as_xml(), pretty_print=True)
        )


"""Semantic analyzer test cases"""

from __future__ import annotations

import sys
from typing import Dict

from mypy import build
from mypy.defaults import PYTHON3_VERSION
from mypy.errors import CompileError
from mypy.modulefinder import BuildSource
from mypy.nodes import TypeInfo
from mypy.options import Options
from mypy.test.config import test_temp_dir
from mypy.test.data import DataDrivenTestCase, DataSuite
from mypy.test.helpers import (
    assert_string_arrays_equal,
    find_test_files,
    normalize_error_messages,
    parse_options,
    testfile_pyversion,
)

# Semantic analyzer test cases: dump parse tree

# Semantic analysis test case description files.
semanal_files = find_test_files(
    pattern="semanal-*.test",
    exclude=[
        "semanal-errors-python310.test",
        "semanal-errors.test",
        "semanal-typeinfo.test",
        "semanal-symtable.test",
    ],
)

if sys.version_info < (3, 10):
    semanal_files.remove("semanal-python310.test")

def get_semanal_options(program_text: str, testcase: DataDrivenTestCase) -> Options:
    options = parse_options(program_text, testcase, 1)
    options.use_builtins_fixtures = True
    options.semantic_analysis_only = True
    options.show_traceback = True
    options.python_version = PYTHON3_VERSION
    options.force_uppercase_builtins = True
    return options

class SemAnalSuite(DataSuite):
    files = semanal_files
    native_sep = True

    def run_case(self, testcase: DataDrivenTestCase) -> None:
        test_semanal(testcase)

def test_semanal(testcase: DataDrivenTestCase) -> None:
    """Perform a semantic analysis test case.

    The testcase argument contains a description of the test case
    (inputs and output).
    """

    try:
        src = "\n".join(testcase.input)
        options = get_semanal_options(src, testcase)
        options.python_version = testfile_pyversion(testcase.file)
        result = build.build(
            sources=[BuildSource("main", None, src)], options=options, alt_lib_path=test_temp_dir
        )
        a = result.errors
        if a:
            raise CompileError(a)
        # Include string representations of the source files in the actual
        # output.
        for module in sorted(result.files.keys()):
            if module in testcase.test_modules:
                a += result.files[module].str_with_options(options).split("\n")
    except CompileError as e:
        a = e.messages
    if testcase.normalize_output:
        a = normalize_error_messages(a)
    assert_string_arrays_equal(
        testcase.output,
        a,
        f"Invalid semantic analyzer output ({testcase.file}, line {testcase.line})",
    )

# Semantic analyzer error test cases

class SemAnalErrorSuite(DataSuite):
    files = ["semanal-errors.test"]
    if sys.version_info >= (3, 10):
        semanal_files.append("semanal-errors-python310.test")

    def run_case(self, testcase: DataDrivenTestCase) -> None:
        test_semanal_error(testcase)

def test_semanal_error(testcase: DataDrivenTestCase) -> None:
    """Perform a test case."""

    try:
        src = "\n".join(testcase.input)
        res = build.build(
            sources=[BuildSource("main", None, src)],
            options=get_semanal_options(src, testcase),
            alt_lib_path=test_temp_dir,
        )
        a = res.errors
    except CompileError as e:
        # Verify that there was a compile error and that the error messages
        # are equivalent.
        a = e.messages
    if testcase.normalize_output:
        a = normalize_error_messages(a)
    assert_string_arrays_equal(
        testcase.output, a, f"Invalid compiler output ({testcase.file}, line {testcase.line})"
    )

# SymbolNode table export test cases

class SemAnalSymtableSuite(DataSuite):
    required_out_section = True
    files = ["semanal-symtable.test"]

    def run_case(self, testcase: DataDrivenTestCase) -> None:
        """Perform a test case."""
        try:
            # Build test case input.
            src = "\n".join(testcase.input)
            result = build.build(
                sources=[BuildSource("main", None, src)],
                options=get_semanal_options(src, testcase),
                alt_lib_path=test_temp_dir,
            )
            # The output is the symbol table converted into a string.
            a = result.errors
            if a:
                raise CompileError(a)
            for module in sorted(result.files.keys()):
                if module in testcase.test_modules:
                    a.append(f"{module}:")
                    for s in str(result.files[module].names).split("\n"):
                        a.append("  " + s)
        except CompileError as e:
            a = e.messages
        assert_string_arrays_equal(
            testcase.output,
            a,
            f"Invalid semantic analyzer output ({testcase.file}, line {testcase.line})",
        )

# Type info export test cases
class SemAnalTypeInfoSuite(DataSuite):
    required_out_section = True
    files = ["semanal-typeinfo.test"]

    def run_case(self, testcase: DataDrivenTestCase) -> None:
        """Perform a test case."""
        try:
            # Build test case input.
            src = "\n".join(testcase.input)
            result = build.build(
                sources=[BuildSource("main", None, src)],
                options=get_semanal_options(src, testcase),
                alt_lib_path=test_temp_dir,
            )
            a = result.errors
            if a:
                raise CompileError(a)

            # Collect all TypeInfos in top-level modules.
            typeinfos = TypeInfoMap()
            for module, file in result.files.items():
                if module in testcase.test_modules:
                    for n in file.names.values():
                        if isinstance(n.node, TypeInfo):
                            assert n.fullname
                            if any(n.fullname.startswith(m + ".") for m in testcase.test_modules):
                                typeinfos[n.fullname] = n.node

            # The output is the symbol table converted into a string.
            a = str(typeinfos).split("\n")
        except CompileError as e:
            a = e.messages
        assert_string_arrays_equal(
            testcase.output,
            a,
            f"Invalid semantic analyzer output ({testcase.file}, line {testcase.line})",
        )

class TypeInfoMap(Dict[str, TypeInfo]):
    def __str__(self) -> str:
        a: list[str] = ["TypeInfoMap("]
        for x, y in sorted(self.items()):
            ti = ("\n" + "  ").join(str(y).split("\n"))
            a.append(f"  {x} : {ti}")
        a[-1] += ")"
        return "\n".join(a)


"""Test cases for the constraint solver used in type inference."""

from __future__ import annotations

from mypy.constraints import SUBTYPE_OF, SUPERTYPE_OF, Constraint
from mypy.solve import Bounds, Graph, solve_constraints, transitive_closure
from mypy.test.helpers import Suite, assert_equal
from mypy.test.typefixture import TypeFixture
from mypy.types import Type, TypeVarId, TypeVarLikeType, TypeVarType

class SolveSuite(Suite):
    def setUp(self) -> None:
        self.fx = TypeFixture()

    def test_empty_input(self) -> None:
        self.assert_solve([], [], [])

    def test_simple_supertype_constraints(self) -> None:
        self.assert_solve([self.fx.t], [self.supc(self.fx.t, self.fx.a)], [self.fx.a])
        self.assert_solve(
            [self.fx.t],
            [self.supc(self.fx.t, self.fx.a), self.supc(self.fx.t, self.fx.b)],
            [self.fx.a],
        )

    def test_simple_subtype_constraints(self) -> None:
        self.assert_solve([self.fx.t], [self.subc(self.fx.t, self.fx.a)], [self.fx.a])
        self.assert_solve(
            [self.fx.t],
            [self.subc(self.fx.t, self.fx.a), self.subc(self.fx.t, self.fx.b)],
            [self.fx.b],
        )

    def test_both_kinds_of_constraints(self) -> None:
        self.assert_solve(
            [self.fx.t],
            [self.supc(self.fx.t, self.fx.b), self.subc(self.fx.t, self.fx.a)],
            [self.fx.b],
        )

    def test_unsatisfiable_constraints(self) -> None:
        # The constraints are impossible to satisfy.
        self.assert_solve(
            [self.fx.t], [self.supc(self.fx.t, self.fx.a), self.subc(self.fx.t, self.fx.b)], [None]
        )

    def test_exactly_specified_result(self) -> None:
        self.assert_solve(
            [self.fx.t],
            [self.supc(self.fx.t, self.fx.b), self.subc(self.fx.t, self.fx.b)],
            [self.fx.b],
        )

    def test_multiple_variables(self) -> None:
        self.assert_solve(
            [self.fx.t, self.fx.s],
            [
                self.supc(self.fx.t, self.fx.b),
                self.supc(self.fx.s, self.fx.c),
                self.subc(self.fx.t, self.fx.a),
            ],
            [self.fx.b, self.fx.c],
        )

    def test_no_constraints_for_var(self) -> None:
        self.assert_solve([self.fx.t], [], [self.fx.uninhabited])
        self.assert_solve([self.fx.t, self.fx.s], [], [self.fx.uninhabited, self.fx.uninhabited])
        self.assert_solve(
            [self.fx.t, self.fx.s],
            [self.supc(self.fx.s, self.fx.a)],
            [self.fx.uninhabited, self.fx.a],
        )

    def test_simple_constraints_with_dynamic_type(self) -> None:
        self.assert_solve([self.fx.t], [self.supc(self.fx.t, self.fx.anyt)], [self.fx.anyt])
        self.assert_solve(
            [self.fx.t],
            [self.supc(self.fx.t, self.fx.anyt), self.supc(self.fx.t, self.fx.anyt)],
            [self.fx.anyt],
        )
        self.assert_solve(
            [self.fx.t],
            [self.supc(self.fx.t, self.fx.anyt), self.supc(self.fx.t, self.fx.a)],
            [self.fx.anyt],
        )

        self.assert_solve([self.fx.t], [self.subc(self.fx.t, self.fx.anyt)], [self.fx.anyt])
        self.assert_solve(
            [self.fx.t],
            [self.subc(self.fx.t, self.fx.anyt), self.subc(self.fx.t, self.fx.anyt)],
            [self.fx.anyt],
        )
        # self.assert_solve([self.fx.t],
        #                   [self.subc(self.fx.t, self.fx.anyt),
        #                    self.subc(self.fx.t, self.fx.a)],
        #                   [self.fx.anyt])
        # TODO: figure out what this should be after changes to meet(any, X)

    def test_both_normal_and_any_types_in_results(self) -> None:
        # If one of the bounds is any, we promote the other bound to
        # any as well, since otherwise the type range does not make sense.
        self.assert_solve(
            [self.fx.t],
            [self.supc(self.fx.t, self.fx.a), self.subc(self.fx.t, self.fx.anyt)],
            [self.fx.anyt],
        )

        self.assert_solve(
            [self.fx.t],
            [self.supc(self.fx.t, self.fx.anyt), self.subc(self.fx.t, self.fx.a)],
            [self.fx.anyt],
        )

    def test_poly_no_constraints(self) -> None:
        self.assert_solve(
            [self.fx.t, self.fx.u],
            [],
            [self.fx.uninhabited, self.fx.uninhabited],
            allow_polymorphic=True,
        )

    def test_poly_trivial_free(self) -> None:
        self.assert_solve(
            [self.fx.t, self.fx.u],
            [self.subc(self.fx.t, self.fx.a)],
            [self.fx.a, self.fx.u],
            [self.fx.u],
            allow_polymorphic=True,
        )

    def test_poly_free_pair(self) -> None:
        self.assert_solve(
            [self.fx.t, self.fx.u],
            [self.subc(self.fx.t, self.fx.u)],
            [self.fx.t, self.fx.t],
            [self.fx.t],
            allow_polymorphic=True,
        )

    def test_poly_free_pair_with_bounds(self) -> None:
        t_prime = self.fx.t.copy_modified(upper_bound=self.fx.b)
        self.assert_solve(
            [self.fx.t, self.fx.ub],
            [self.subc(self.fx.t, self.fx.ub)],
            [t_prime, t_prime],
            [t_prime],
            allow_polymorphic=True,
        )

    def test_poly_free_pair_with_bounds_uninhabited(self) -> None:
        self.assert_solve(
            [self.fx.ub, self.fx.uc],
            [self.subc(self.fx.ub, self.fx.uc)],
            [self.fx.uninhabited, self.fx.uninhabited],
            [],
            allow_polymorphic=True,
        )

    def test_poly_bounded_chain(self) -> None:
        # B <: T <: U <: S <: A
        self.assert_solve(
            [self.fx.t, self.fx.u, self.fx.s],
            [
                self.supc(self.fx.t, self.fx.b),
                self.subc(self.fx.t, self.fx.u),
                self.subc(self.fx.u, self.fx.s),
                self.subc(self.fx.s, self.fx.a),
            ],
            [self.fx.b, self.fx.b, self.fx.b],
            allow_polymorphic=True,
        )

    def test_poly_reverse_overlapping_chain(self) -> None:
        # A :> T <: S :> B
        self.assert_solve(
            [self.fx.t, self.fx.s],
            [
                self.subc(self.fx.t, self.fx.s),
                self.subc(self.fx.t, self.fx.a),
                self.supc(self.fx.s, self.fx.b),
            ],
            [self.fx.a, self.fx.a],
            allow_polymorphic=True,
        )

    def test_poly_reverse_split_chain(self) -> None:
        # B :> T <: S :> A
        self.assert_solve(
            [self.fx.t, self.fx.s],
            [
                self.subc(self.fx.t, self.fx.s),
                self.subc(self.fx.t, self.fx.b),
                self.supc(self.fx.s, self.fx.a),
            ],
            [self.fx.b, self.fx.a],
            allow_polymorphic=True,
        )

    def test_poly_unsolvable_chain(self) -> None:
        # A <: T <: U <: S <: B
        self.assert_solve(
            [self.fx.t, self.fx.u, self.fx.s],
            [
                self.supc(self.fx.t, self.fx.a),
                self.subc(self.fx.t, self.fx.u),
                self.subc(self.fx.u, self.fx.s),
                self.subc(self.fx.s, self.fx.b),
            ],
            [None, None, None],
            allow_polymorphic=True,
        )

    def test_simple_chain_closure(self) -> None:
        self.assert_transitive_closure(
            [self.fx.t.id, self.fx.s.id],
            [
                self.supc(self.fx.t, self.fx.b),
                self.subc(self.fx.t, self.fx.s),
                self.subc(self.fx.s, self.fx.a),
            ],
            {(self.fx.t.id, self.fx.s.id)},
            {self.fx.t.id: {self.fx.b}, self.fx.s.id: {self.fx.b}},
            {self.fx.t.id: {self.fx.a}, self.fx.s.id: {self.fx.a}},
        )

    def test_reverse_chain_closure(self) -> None:
        self.assert_transitive_closure(
            [self.fx.t.id, self.fx.s.id],
            [
                self.subc(self.fx.t, self.fx.s),
                self.subc(self.fx.t, self.fx.a),
                self.supc(self.fx.s, self.fx.b),
            ],
            {(self.fx.t.id, self.fx.s.id)},
            {self.fx.t.id: set(), self.fx.s.id: {self.fx.b}},
            {self.fx.t.id: {self.fx.a}, self.fx.s.id: set()},
        )

    def test_secondary_constraint_closure(self) -> None:
        self.assert_transitive_closure(
            [self.fx.t.id, self.fx.s.id],
            [self.supc(self.fx.s, self.fx.gt), self.subc(self.fx.s, self.fx.ga)],
            set(),
            {self.fx.t.id: set(), self.fx.s.id: {self.fx.gt}},
            {self.fx.t.id: {self.fx.a}, self.fx.s.id: {self.fx.ga}},
        )

    def assert_solve(
        self,
        vars: list[TypeVarLikeType],
        constraints: list[Constraint],
        results: list[None | Type],
        free_vars: list[TypeVarLikeType] | None = None,
        allow_polymorphic: bool = False,
    ) -> None:
        if free_vars is None:
            free_vars = []
        actual, actual_free = solve_constraints(
            vars, constraints, allow_polymorphic=allow_polymorphic
        )
        assert_equal(actual, results)
        assert_equal(actual_free, free_vars)

    def assert_transitive_closure(
        self,
        vars: list[TypeVarId],
        constraints: list[Constraint],
        graph: Graph,
        lowers: Bounds,
        uppers: Bounds,
    ) -> None:
        actual_graph, actual_lowers, actual_uppers = transitive_closure(vars, constraints)
        # Add trivial elements.
        for v in vars:
            graph.add((v, v))
        assert_equal(actual_graph, graph)
        assert_equal(dict(actual_lowers), lowers)
        assert_equal(dict(actual_uppers), uppers)

    def supc(self, type_var: TypeVarType, bound: Type) -> Constraint:
        return Constraint(type_var, SUPERTYPE_OF, bound)

    def subc(self, type_var: TypeVarType, bound: Type) -> Constraint:
        return Constraint(type_var, SUBTYPE_OF, bound)