# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt

from __future__ import annotations

import io
import re
import sys
import unittest

import pytest

import astroid
from astroid import MANAGER, builder, nodes, objects, test_utils, util
from astroid.bases import Instance
from astroid.brain.brain_namedtuple_enum import _get_namedtuple_fields
from astroid.const import PY312_PLUS, PY313_PLUS
from astroid.exceptions import (
    AttributeInferenceError,
    InferenceError,
    UseInferenceDefault,
)


def assertEqualMro(klass: nodes.ClassDef, expected_mro: list[str]) -> None:
    """Check mro names."""
    assert [member.qname() for member in klass.mro()] == expected_mro


class CollectionsDequeTests(unittest.TestCase):
    def _inferred_queue_instance(self) -> Instance:
        node = builder.extract_node("""
        import collections
        q = collections.deque([])
        q
        """)
        return next(node.infer())

    def test_deque(self) -> None:
        inferred = self._inferred_queue_instance()
        self.assertTrue(inferred.getattr("__len__"))

    def test_deque_py35methods(self) -> None:
        inferred = self._inferred_queue_instance()
        self.assertIn("copy", inferred.locals)
        self.assertIn("insert", inferred.locals)
        self.assertIn("index", inferred.locals)

    def test_deque_py39methods(self):
        inferred = self._inferred_queue_instance()
        self.assertTrue(inferred.getattr("__class_getitem__"))


class OrderedDictTest(unittest.TestCase):
    def _inferred_ordered_dict_instance(self) -> Instance:
        node = builder.extract_node("""
        import collections
        d = collections.OrderedDict()
        d
        """)
        return next(node.infer())

    def test_ordered_dict_py34method(self) -> None:
        inferred = self._inferred_ordered_dict_instance()
        self.assertIn("move_to_end", inferred.locals)


class DefaultDictTest(unittest.TestCase):
    def test_1(self) -> None:
        node = builder.extract_node("""
        from collections import defaultdict

        X = defaultdict(int)
        X[0]
        """)
        inferred = next(node.infer())
        self.assertIs(util.Uninferable, inferred)


class ModuleExtenderTest(unittest.TestCase):
    def test_extension_modules(self) -> None:
        transformer = MANAGER._transform
        for extender, _ in transformer.transforms[nodes.Module]:
            n = nodes.Module("__main__")
            extender(n)


def streams_are_fine():
    """Check if streams are being overwritten,
    for example, by pytest

    stream inference will not work if they are overwritten

    PY3 only
    """
    return all(isinstance(s, io.IOBase) for s in (sys.stdout, sys.stderr, sys.stdin))


class IOBrainTest(unittest.TestCase):
    @unittest.skipUnless(
        streams_are_fine(),
        "Needs Python 3 io model / doesn't work with plain pytest."
        "use pytest -s for this test to work",
    )
    def test_sys_streams(self):
        for name in ("stdout", "stderr", "stdin"):
            node = astroid.extract_node(f"""
            import sys
            sys.{name}
            """)
            inferred = next(node.infer())
            buffer_attr = next(inferred.igetattr("buffer"))
            self.assertIsInstance(buffer_attr, astroid.Instance)
            self.assertEqual(buffer_attr.name, "BufferedWriter")
            raw = next(buffer_attr.igetattr("raw"))
            self.assertIsInstance(raw, astroid.Instance)
            self.assertEqual(raw.name, "FileIO")


class TypeBrain(unittest.TestCase):
    def test_type_subscript(self):
        """
        Check that type object has the __class_getitem__ method
        when it is used as a subscript
        """
        src = builder.extract_node("""
            a: type[int] = int
            """)
        val_inf = src.annotation.value.inferred()[0]
        self.assertIsInstance(val_inf, nodes.ClassDef)
        self.assertEqual(val_inf.name, "type")
        meth_inf = val_inf.getattr("__class_getitem__")[0]
        self.assertIsInstance(meth_inf, nodes.FunctionDef)

    def test_invalid_type_subscript(self):
        """
        Check that a type (str for example) that inherits
        from type does not have __class_getitem__ method even
        when it is used as a subscript
        """
        src = builder.extract_node("""
            a: str[int] = "abc"
            """)
        val_inf = src.annotation.value.inferred()[0]
        self.assertIsInstance(val_inf, nodes.ClassDef)
        self.assertEqual(val_inf.name, "str")
        with self.assertRaises(AttributeInferenceError):
            # pylint: disable=expression-not-assigned
            # noinspection PyStatementEffect
            val_inf.getattr("__class_getitem__")[0]

    def test_builtin_subscriptable(self):
        """Starting with python3.9 builtin types such as list are subscriptable.
        Any builtin class such as "enumerate" or "staticmethod" also works."""
        for typename in ("tuple", "list", "dict", "set", "frozenset", "enumerate"):
            src = f"""
            {typename:s}[int]
            """
            right_node = builder.extract_node(src)
            inferred = next(right_node.infer())
            self.assertIsInstance(inferred, nodes.ClassDef)
            self.assertIsInstance(inferred.getattr("__iter__")[0], nodes.FunctionDef)


def check_metaclass_is_abc(node: nodes.ClassDef):
    if PY312_PLUS and node.name == "ByteString":
        # .metaclass() finds the first metaclass in the mro(),
        # which, from 3.12, is _DeprecateByteStringMeta (unhelpful)
        # until ByteString is removed in 3.17.
        # Jump over the first two ByteString classes in the mro().
        check_metaclass_is_abc(node.mro()[2])
    else:
        meta = node.metaclass()
        assert isinstance(meta, nodes.ClassDef)
        assert meta.name == "ABCMeta"


class CollectionsBrain(unittest.TestCase):
    def test_collections_abc_is_importable(self) -> None:
        """
        Test that we can import `collections.abc`.

        The collections.abc has gone through various formats of being frozen. Therefore, we ensure
        that we can still import it (correctly).
        """
        import_node = builder.extract_node("import collections.abc")
        assert isinstance(import_node, nodes.Import)
        imported_module = import_node.do_import_module(import_node.names[0][0])
        # Make sure that the file we have imported is actually the submodule of collections and
        # not the `abc` module. (Which would happen if you call `importlib.util.find_spec("abc")`
        # instead of `importlib.util.find_spec("collections.abc")`)
        assert isinstance(imported_module.file, str)
        assert "collections" in imported_module.file

    def test_collections_object_not_subscriptable(self) -> None:
        """
        Test that unsubscriptable types are detected
        Hashable is not subscriptable even with python39
        """
        wrong_node = builder.extract_node("""
        import collections.abc
        collections.abc.Hashable[int]
        """)
        with self.assertRaises(InferenceError):
            next(wrong_node.infer())
        right_node = builder.extract_node("""
        import collections.abc
        collections.abc.Hashable
        """)
        inferred = next(right_node.infer())
        check_metaclass_is_abc(inferred)
        assertEqualMro(
            inferred,
            [
                "_collections_abc.Hashable",
                "builtins.object",
            ],
        )
        with self.assertRaises(AttributeInferenceError):
            inferred.getattr("__class_getitem__")

    def test_collections_object_subscriptable(self):
        """Starting with python39 some object of collections module are subscriptable. Test one of them"""
        right_node = builder.extract_node("""
        import collections.abc
        collections.abc.MutableSet[int]
        """)
        inferred = next(right_node.infer())
        check_metaclass_is_abc(inferred)
        assertEqualMro(
            inferred,
            [
                "_collections_abc.MutableSet",
                "_collections_abc.Set",
                "_collections_abc.Collection",
                "_collections_abc.Sized",
                "_collections_abc.Iterable",
                "_collections_abc.Container",
                "builtins.object",
            ],
        )
        self.assertIsInstance(
            inferred.getattr("__class_getitem__")[0], nodes.FunctionDef
        )

    def test_collections_object_subscriptable_2(self):
        """Starting with python39 Iterator in the collection.abc module is subscriptable"""
        node = builder.extract_node("""
        import collections.abc
        class Derived(collections.abc.Iterator[int]):
            pass
        """)
        inferred = next(node.infer())
        check_metaclass_is_abc(inferred)
        assertEqualMro(
            inferred,
            [
                ".Derived",
                "_collections_abc.Iterator",
                "_collections_abc.Iterable",
                "builtins.object",
            ],
        )

    def test_collections_object_subscriptable_3(self):
        """With Python 3.9 the ByteString class of the collections module is subscriptable
        (but not the same class from typing module)"""
        right_node = builder.extract_node("""
        import collections.abc
        collections.abc.ByteString[int]
        """)
        inferred = next(right_node.infer())
        check_metaclass_is_abc(inferred)
        self.assertIsInstance(
            inferred.getattr("__class_getitem__")[0], nodes.FunctionDef
        )

    def test_collections_object_subscriptable_4(self):
        """Multiple inheritance with subscriptable collection class"""
        node = builder.extract_node("""
        import collections.abc
        class Derived(collections.abc.Hashable, collections.abc.Iterator[int]):
            pass
        """)
        inferred = next(node.infer())
        assertEqualMro(
            inferred,
            [
                ".Derived",
                "_collections_abc.Hashable",
                "_collections_abc.Iterator",
                "_collections_abc.Iterable",
                "builtins.object",
            ],
        )

    def test_statistics_quantiles_from_import(self):
        node = builder.extract_node("""
        from statistics import quantiles
        quantiles([1, 2, 3, 4, 5, 6, 7, 8, 9], n=4)
        """)
        inferred = next(node.infer())
        self.assertIs(inferred, util.Uninferable)


class TypingBrain(unittest.TestCase):
    def test_namedtuple_base(self) -> None:
        klass = builder.extract_node("""
        from typing import NamedTuple

        class X(NamedTuple("X", [("a", int), ("b", str), ("c", bytes)])):
           pass
        """)
        self.assertEqual(
            [anc.name for anc in klass.ancestors()], ["X", "tuple", "object"]
        )
        for anc in klass.ancestors():
            self.assertFalse(anc.parent is None)

    def test_namedtuple_can_correctly_access_methods(self) -> None:
        klass, called = builder.extract_node("""
        from typing import NamedTuple

        class X(NamedTuple): #@
            a: int
            b: int
            def as_string(self):
                return '%s' % self.a
            def as_integer(self):
                return 2 + 3
        X().as_integer() #@
        """)
        self.assertEqual(len(klass.getattr("as_string")), 1)
        inferred = next(called.infer())
        self.assertIsInstance(inferred, nodes.Const)
        self.assertEqual(inferred.value, 5)

    def test_namedtuple_inference(self) -> None:
        klass = builder.extract_node("""
        from typing import NamedTuple

        class X(NamedTuple("X", [("a", int), ("b", str), ("c", bytes)])):
           pass
        """)
        base = next(base for base in klass.ancestors() if base.name == "X")
        self.assertSetEqual({"a", "b", "c"}, set(base.instance_attrs))

    def test_namedtuple_inference_nonliteral(self) -> None:
        # Note: NamedTuples in mypy only work with literals.
        klass = builder.extract_node("""
        from typing import NamedTuple

        name = "X"
        fields = [("a", int), ("b", str), ("c", bytes)]
        NamedTuple(name, fields)
        """)
        inferred = next(klass.infer())
        self.assertIsInstance(inferred, astroid.Instance)
        self.assertEqual(inferred.qname(), "typing.NamedTuple")

    def test_namedtuple_instance_attrs(self) -> None:
        result = builder.extract_node("""
        from typing import NamedTuple
        NamedTuple("A", [("a", int), ("b", str), ("c", bytes)])(1, 2, 3) #@
        """)
        inferred = next(result.infer())
        for name, attr in inferred.instance_attrs.items():
            self.assertEqual(attr[0].attrname, name)

    def test_namedtuple_simple(self) -> None:
        result = builder.extract_node("""
        from typing import NamedTuple
        NamedTuple("A", [("a", int), ("b", str), ("c", bytes)])
        """)
        inferred = next(result.infer())
        self.assertIsInstance(inferred, nodes.ClassDef)
        self.assertSetEqual({"a", "b", "c"}, set(inferred.instance_attrs))

    def test_namedtuple_few_args(self) -> None:
        result = builder.extract_node("""
        from typing import NamedTuple
        NamedTuple("A")
        """)
        inferred = next(result.infer())
        self.assertIsInstance(inferred, astroid.Instance)
        self.assertEqual(inferred.qname(), "typing.NamedTuple")

    def test_namedtuple_few_fields(self) -> None:
        result = builder.extract_node("""
        from typing import NamedTuple
        NamedTuple("A", [("a",), ("b", str), ("c", bytes)])
        """)
        inferred = next(result.infer())
        self.assertIsInstance(inferred, astroid.Instance)
        self.assertEqual(inferred.qname(), "typing.NamedTuple")

    def test_namedtuple_class_form(self) -> None:
        result = builder.extract_node("""
        from typing import NamedTuple

        class Example(NamedTuple):
            CLASS_ATTR = "class_attr"
            mything: int

        Example(mything=1)
        """)
        inferred = next(result.infer())
        self.assertIsInstance(inferred, astroid.Instance)

        class_attr = inferred.getattr("CLASS_ATTR")[0]
        self.assertIsInstance(class_attr, nodes.AssignName)
        const = next(class_attr.infer())
        self.assertEqual(const.value, "class_attr")

    def test_namedtuple_inferred_as_class(self) -> None:
        node = builder.extract_node("""
        from typing import NamedTuple
        NamedTuple
        """)
        inferred = next(node.infer())
        assert isinstance(inferred, nodes.ClassDef)
        assert inferred.name == "NamedTuple"

    def test_namedtuple_bug_pylint_4383(self) -> None:
        """Inference of 'NamedTuple' function shouldn't cause InferenceError.

        https://github.com/pylint-dev/pylint/issues/4383
        """
        node = builder.extract_node("""
        if True:
            def NamedTuple():
                pass
        NamedTuple
        """)
        next(node.infer())

    def test_namedtuple_uninferable_member(self) -> None:
        call = builder.extract_node("""
        from typing import namedtuple
        namedtuple('uninf', {x: x for x in range(0)})  #@""")
        with pytest.raises(UseInferenceDefault):
            _get_namedtuple_fields(call)

        call = builder.extract_node("""
        from typing import namedtuple
        uninferable = {x: x for x in range(0)}
        namedtuple('uninferable', uninferable)  #@
        """)
        with pytest.raises(UseInferenceDefault):
            _get_namedtuple_fields(call)

    def test_typing_types(self) -> None:
        ast_nodes = builder.extract_node("""
        from typing import TypeVar, Iterable, Tuple, NewType, Dict, Union
        TypeVar('MyTypeVar', int, float, complex) #@
        Iterable[Tuple[MyTypeVar, MyTypeVar]] #@
        TypeVar('AnyStr', str, bytes) #@
        NewType('UserId', str) #@
        Dict[str, str] #@
        Union[int, str] #@
        """)
        for node in ast_nodes:
            inferred = next(node.infer())
            self.assertIsInstance(inferred, nodes.ClassDef, node.as_string())

    def test_typing_type_without_tip(self):
        """Regression test for https://github.com/pylint-dev/pylint/issues/5770"""
        node = builder.extract_node("""
        from typing import NewType

        def make_new_type(t):
            new_type = NewType(f'IntRange_{t}', t) #@
        """)
        with self.assertRaises(UseInferenceDefault):
            astroid.brain.brain_typing.infer_typing_typevar_or_newtype(node.value)

    def test_namedtuple_nested_class(self):
        result = builder.extract_node("""
        from typing import NamedTuple

        class Example(NamedTuple):
            class Foo:
                bar = "bar"

        Example
        """)
        inferred = next(result.infer())
        self.assertIsInstance(inferred, nodes.ClassDef)

        class_def_attr = inferred.getattr("Foo")[0]
        self.assertIsInstance(class_def_attr, nodes.ClassDef)
        attr_def = class_def_attr.getattr("bar")[0]
        attr = next(attr_def.infer())
        self.assertEqual(attr.value, "bar")

    def test_tuple_type(self):
        node = builder.extract_node("""
        from typing import Tuple
        Tuple[int, int]
        """)
        inferred = next(node.infer())
        assert isinstance(inferred, nodes.ClassDef)
        assert isinstance(inferred.getattr("__class_getitem__")[0], nodes.FunctionDef)
        assert inferred.qname() == "typing.Tuple"

    def test_callable_type(self):
        node = builder.extract_node("""
        from typing import Callable, Any
        Callable[..., Any]
        """)
        inferred = next(node.infer())
        assert isinstance(inferred, nodes.ClassDef)
        assert isinstance(inferred.getattr("__class_getitem__")[0], nodes.FunctionDef)
        assert inferred.qname() == "typing.Callable"

    def test_typing_generic_subscriptable(self):
        """Test typing.Generic is subscriptable with __class_getitem__ (added in PY37)"""
        node = builder.extract_node("""
        from typing import Generic, TypeVar
        T = TypeVar('T')
        Generic[T]
        """)
        inferred = next(node.infer())
        assert isinstance(inferred, nodes.ClassDef)
        assert isinstance(inferred.getattr("__class_getitem__")[0], nodes.FunctionDef)

    @test_utils.require_version(minver="3.12")
    def test_typing_generic_subscriptable_pep695(self):
        """Test class using type parameters is subscriptable with __class_getitem__ (added in PY312)"""
        node = builder.extract_node("""
        class Foo[T]: ...
        class Bar[T](Foo[T]): ...
        """)
        inferred = next(node.infer())
        assert isinstance(inferred, nodes.ClassDef)
        assert inferred.name == "Bar"
        assert isinstance(inferred.getattr("__class_getitem__")[0], nodes.FunctionDef)
        ancestors = list(inferred.ancestors())
        assert len(ancestors) == 2
        assert ancestors[0].name == "Foo"
        assert ancestors[1].name == "object"

    def test_typing_annotated_subscriptable(self):
        """typing.Annotated is subscriptable with __class_getitem__ below 3.13."""
        node = builder.extract_node("""
        import typing
        typing.Annotated[str, "data"]
        """)
        inferred = next(node.infer())
        if PY313_PLUS:
            assert isinstance(inferred, nodes.FunctionDef)
        else:
            assert isinstance(inferred, nodes.ClassDef)
            assert isinstance(
                inferred.getattr("__class_getitem__")[0], nodes.FunctionDef
            )

    def test_typing_generic_slots(self):
        """Test slots for Generic subclass."""
        node = builder.extract_node("""
        from typing import Generic, TypeVar
        T = TypeVar('T')
        class A(Generic[T]):
            __slots__ = ['value']
            def __init__(self, value):
                self.value = value
        """)
        inferred = next(node.infer())
        slots = inferred.slots()
        assert len(slots) == 1
        assert isinstance(slots[0], nodes.Const)
        assert slots[0].value == "value"

    def test_typing_no_duplicates(self):
        node = builder.extract_node("""
        from typing import List
        List[int]
        """)
        assert len(node.inferred()) == 1

    def test_typing_no_duplicates_2(self):
        node = builder.extract_node("""
        from typing import Optional, Tuple
        Tuple[Optional[int], ...]
        """)
        assert len(node.inferred()) == 1

    def test_typing_param_spec(self):
        node = builder.extract_node("""
        from typing import ParamSpec

        P = ParamSpec("P")
        """)
        inferred = next(node.targets[0].infer())
        assert next(inferred.igetattr("args")) is not None
        assert next(inferred.igetattr("kwargs")) is not None

    def test_collections_generic_alias_slots(self):
        """Test slots for a class which is a subclass of a generic alias type."""
        node = builder.extract_node("""
        import collections
        import typing
        Type = typing.TypeVar('Type')
        class A(collections.abc.AsyncIterator[Type]):
            __slots__ = ('_value',)
            def __init__(self, value: collections.abc.AsyncIterator[Type]):
                self._value = value
        """)
        inferred = next(node.infer())
        assert isinstance(inferred, nodes.ClassDef)
        slots = inferred.slots()
        assert len(slots) == 1
        assert isinstance(slots[0], nodes.Const)
        assert slots[0].value == "_value"

    def test_has_dunder_args(self) -> None:
        ast_node = builder.extract_node("""
        from typing import Union
        NumericTypes = Union[int, float]
        NumericTypes.__args__ #@
        """)
        inferred = next(ast_node.infer())
        assert isinstance(inferred, nodes.Tuple)

    def test_typing_namedtuple_dont_crash_on_no_fields(self) -> None:
        node = builder.extract_node("""
        from typing import NamedTuple

        Bar = NamedTuple("bar", [])

        Bar()
        """)
        inferred = next(node.infer())
        self.assertIsInstance(inferred, astroid.Instance)

    def test_typed_dict(self):
        code = builder.extract_node("""
        from typing import TypedDict
        class CustomTD(TypedDict):  #@
            var: int
        CustomTD(var=1)  #@
        """)
        inferred_base = next(code[0].bases[0].infer())
        assert isinstance(inferred_base, nodes.ClassDef)
        assert inferred_base.qname() == "typing.TypedDict"
        typedDict_base = next(inferred_base.bases[0].infer())
        assert typedDict_base.qname() == "builtins.dict"

        # Test TypedDict has `__call__` method
        local_call = inferred_base.locals.get("__call__", None)
        assert local_call and len(local_call) == 1
        assert isinstance(local_call[0], nodes.Name) and local_call[0].name == "dict"

        # Test TypedDict instance is callable
        assert next(code[1].infer()).callable() is True

    def test_typing_alias_type(self):
        """
        Test that the type aliased thanks to typing._alias function are
        correctly inferred.
        typing_alias function is introduced with python37
        """
        node = builder.extract_node("""
        from typing import TypeVar, MutableSet

        T = TypeVar("T")
        MutableSet[T]

        class Derived1(MutableSet[T]):
            pass
        """)
        inferred = next(node.infer())
        assertEqualMro(
            inferred,
            [
                ".Derived1",
                "typing.MutableSet",
                "_collections_abc.MutableSet",
                "_collections_abc.Set",
                "_collections_abc.Collection",
                "_collections_abc.Sized",
                "_collections_abc.Iterable",
                "_collections_abc.Container",
                "builtins.object",
            ],
        )

    def test_typing_alias_type_2(self):
        """
        Test that the type aliased thanks to typing._alias function are
        correctly inferred.
        typing_alias function is introduced with python37.
        OrderedDict in the typing module appears only with python 3.7.2
        """
        node = builder.extract_node("""
        import typing
        class Derived2(typing.OrderedDict[int, str]):
            pass
        """)
        inferred = next(node.infer())
        assertEqualMro(
            inferred,
            [
                ".Derived2",
                "typing.OrderedDict",
                "collections.OrderedDict",
                "builtins.dict",
                "builtins.object",
            ],
        )

    def test_typing_object_not_subscriptable(self):
        """Hashable is not subscriptable"""
        wrong_node = builder.extract_node("""
        import typing
        typing.Hashable[int]
        """)
        with self.assertRaises(InferenceError):
            next(wrong_node.infer())
        right_node = builder.extract_node("""
        import typing
        typing.Hashable
        """)
        inferred = next(right_node.infer())
        assertEqualMro(
            inferred,
            [
                "typing.Hashable",
                "_collections_abc.Hashable",
                "builtins.object",
            ],
        )
        with self.assertRaises(AttributeInferenceError):
            inferred.getattr("__class_getitem__")

    def test_typing_object_subscriptable(self):
        """Test that MutableSet is subscriptable"""
        right_node = builder.extract_node("""
        import typing
        typing.MutableSet[int]
        """)
        inferred = next(right_node.infer())
        assertEqualMro(
            inferred,
            [
                "typing.MutableSet",
                "_collections_abc.MutableSet",
                "_collections_abc.Set",
                "_collections_abc.Collection",
                "_collections_abc.Sized",
                "_collections_abc.Iterable",
                "_collections_abc.Container",
                "builtins.object",
            ],
        )
        self.assertIsInstance(
            inferred.getattr("__class_getitem__")[0], nodes.FunctionDef
        )

    def test_typing_object_subscriptable_2(self):
        """Multiple inheritance with subscriptable typing alias"""
        node = builder.extract_node("""
        import typing
        class Derived(typing.Hashable, typing.Iterator[int]):
            pass
        """)
        inferred = next(node.infer())
        assertEqualMro(
            inferred,
            [
                ".Derived",
                "typing.Hashable",
                "_collections_abc.Hashable",
                "typing.Iterator",
                "_collections_abc.Iterator",
                "_collections_abc.Iterable",
                "builtins.object",
            ],
        )

    def test_typing_object_notsubscriptable_3(self):
        """The ByteString class of the typing module is not
        subscriptable (whereas it is in the collections' module)"""
        right_node = builder.extract_node("""
        import typing
        typing.ByteString
        """)
        inferred = next(right_node.infer())
        check_metaclass_is_abc(inferred)
        with self.assertRaises(AttributeInferenceError):
            self.assertIsInstance(
                inferred.getattr("__class_getitem__")[0], nodes.FunctionDef
            )

    def test_typing_object_builtin_subscriptable(self):
        """
        Test that builtins alias, such as typing.List, are subscriptable
        """
        for typename in ("List", "Dict", "Set", "FrozenSet", "Tuple"):
            src = f"""
            import typing
            typing.{typename:s}[int]
            """
            right_node = builder.extract_node(src)
            inferred = next(right_node.infer())
            self.assertIsInstance(inferred, nodes.ClassDef)
            self.assertIsInstance(inferred.getattr("__iter__")[0], nodes.FunctionDef)

    @staticmethod
    def test_typing_type_subscriptable():
        node = builder.extract_node("""
        from typing import Type
        Type[int]
        """)
        inferred = next(node.infer())
        assert isinstance(inferred, nodes.ClassDef)
        assert isinstance(inferred.getattr("__class_getitem__")[0], nodes.FunctionDef)
        assert inferred.qname() == "typing.Type"

    def test_typing_cast(self) -> None:
        node = builder.extract_node("""
        from typing import cast
        class A:
            pass

        b = 42
        cast(A, b)
        """)
        inferred = next(node.infer())
        assert isinstance(inferred, nodes.Const)
        assert inferred.value == 42

    def test_typing_cast_attribute(self) -> None:
        node = builder.extract_node("""
        import typing
        class A:
            pass

        b = 42
        typing.cast(A, b)
        """)
        inferred = next(node.infer())
        assert isinstance(inferred, nodes.Const)
        assert inferred.value == 42

    def test_typing_cast_multiple_inference_calls(self) -> None:
        """Inference of an outer function should not store the result for cast."""
        ast_nodes = builder.extract_node("""
        from typing import TypeVar, cast
        T = TypeVar("T")
        def ident(var: T) -> T:
            return cast(T, var)

        ident(2)  #@
        ident("Hello")  #@
        """)
        i0 = next(ast_nodes[0].infer())
        assert isinstance(i0, nodes.Const)
        assert i0.value == 2

        i1 = next(ast_nodes[1].infer())
        assert isinstance(i1, nodes.Const)
        assert i1.value == "Hello"


class ReBrainTest(unittest.TestCase):
    def test_regex_flags(self) -> None:
        names = [name for name in dir(re) if name.isupper()]
        re_ast = MANAGER.ast_from_module_name("re")
        for name in names:
            self.assertIn(name, re_ast)
            self.assertEqual(next(re_ast[name].infer()).value, getattr(re, name))

    def test_re_pattern_subscriptable(self):
        """Test re.Pattern and re.Match are subscriptable in PY39+"""
        node1 = builder.extract_node("""
        import re
        re.Pattern[str]
        """)
        inferred1 = next(node1.infer())
        assert isinstance(inferred1, nodes.ClassDef)
        assert isinstance(inferred1.getattr("__class_getitem__")[0], nodes.FunctionDef)

        node2 = builder.extract_node("""
        import re
        re.Match[str]
        """)
        inferred2 = next(node2.infer())
        assert isinstance(inferred2, nodes.ClassDef)
        assert isinstance(inferred2.getattr("__class_getitem__")[0], nodes.FunctionDef)


class BrainNamedtupleAnnAssignTest(unittest.TestCase):
    def test_no_crash_on_ann_assign_in_namedtuple(self) -> None:
        node = builder.extract_node("""
        from enum import Enum
        from typing import Optional

        class A(Enum):
            B: str = 'B'
        """)
        inferred = next(node.infer())
        self.assertIsInstance(inferred, nodes.ClassDef)


class BrainUUIDTest(unittest.TestCase):
    def test_uuid_has_int_member(self) -> None:
        node = builder.extract_node("""
        import uuid
        u = uuid.UUID('{12345678-1234-5678-1234-567812345678}')
        u.int
        """)
        inferred = next(node.infer())
        self.assertIsInstance(inferred, nodes.Const)


class RandomSampleTest(unittest.TestCase):
    def test_inferred_successfully(self) -> None:
        node = astroid.extract_node("""
        import random
        random.sample([1, 2], 2) #@
        """)
        inferred = next(node.infer())
        self.assertIsInstance(inferred, nodes.List)
        elems = sorted(elem.value for elem in inferred.elts)
        self.assertEqual(elems, [1, 2])

    def test_arguments_inferred_successfully(self) -> None:
        """Test inference of `random.sample` when both arguments are of type `nodes.Call`."""
        node = astroid.extract_node("""
        import random

        def sequence():
            return [1, 2]

        random.sample(sequence(), len([1,2])) #@
        """)
        # Check that arguments are of type `nodes.Call`.
        sequence, length = node.args
        self.assertIsInstance(sequence, nodes.Call)
        self.assertIsInstance(length, nodes.Call)

        # Check the inference of `random.sample` call.
        inferred = next(node.infer())
        self.assertIsInstance(inferred, nodes.List)
        elems = sorted(elem.value for elem in inferred.elts)
        self.assertEqual(elems, [1, 2])

    def test_no_crash_on_evaluatedobject(self) -> None:
        node = astroid.extract_node("""
        from random import sample
        class A: pass
        sample(list({1: A()}.values()), 1)""")
        inferred = next(node.infer())
        assert isinstance(inferred, nodes.List)
        assert len(inferred.elts) == 1
        assert isinstance(inferred.elts[0], nodes.Call)

    def test_no_crash_on_uninferable_element(self) -> None:
        """Test that random.sample does not crash when elements are Uninferable.

        Regression test for https://github.com/pylint-dev/astroid/issues/2518
        """
        node = astroid.extract_node("""
        import random
        random.sample(1*[b], 1)  #@
        """)
        inferred = next(node.infer())
        assert inferred is astroid.Uninferable

    def test_no_crash_on_classdef_clone(self) -> None:
        """Test that random.sample does not crash when cloning ClassDef nodes.

        Regression test for https://github.com/pylint-dev/astroid/issues/2923
        """
        node = astroid.extract_node("""
        import random
        random.sample([dict] * 2, 1)  #@
        """)
        inferred = next(node.infer())
        assert isinstance(inferred, nodes.List)
        assert len(inferred.elts) == 1
        assert isinstance(inferred.elts[0], nodes.ClassDef)
        assert inferred.elts[0].name == "dict"

    def test_no_crash_on_functiondef_clone(self) -> None:
        """Test that random.sample does not crash when cloning FunctionDef nodes.

        Regression test for https://github.com/pylint-dev/astroid/issues/2923
        """
        node = astroid.extract_node("""
        import random
        random.sample([len] * 2, 1)  #@
        """)
        inferred = next(node.infer())
        assert isinstance(inferred, nodes.List)
        assert len(inferred.elts) == 1
        assert isinstance(inferred.elts[0], nodes.FunctionDef)
        assert inferred.elts[0].name == "len"


class SubprocessTest(unittest.TestCase):
    """Test subprocess brain"""

    def test_subprocess_args(self) -> None:
        """Make sure the args attribute exists for Popen

        Test for https://github.com/pylint-dev/pylint/issues/1860"""
        name = astroid.extract_node("""
        import subprocess
        p = subprocess.Popen(['ls'])
        p #@
        """)
        [inst] = name.inferred()
        self.assertIsInstance(next(inst.igetattr("args")), nodes.List)

    def test_subprcess_check_output(self) -> None:
        code = """
        import subprocess

        subprocess.check_output(['echo', 'hello']);
        """
        node = astroid.extract_node(code)
        inferred = next(node.infer())
        # Can be either str or bytes
        assert isinstance(inferred, nodes.Const)
        assert isinstance(inferred.value, (str, bytes))

    def test_popen_does_not_have_class_getitem(self):
        code = """import subprocess; subprocess.Popen"""
        node = astroid.extract_node(code)
        inferred = next(node.infer())
        assert "__class_getitem__" in inferred


class TestIsinstanceInference:
    """Test isinstance builtin inference"""

    def test_type_type(self) -> None:
        assert _get_result("isinstance(type, type)") == "True"

    def test_object_type(self) -> None:
        assert _get_result("isinstance(object, type)") == "True"

    def test_type_object(self) -> None:
        assert _get_result("isinstance(type, object)") == "True"

    def test_isinstance_int_true(self) -> None:
        """Make sure isinstance can check builtin int types"""
        assert _get_result("isinstance(1, int)") == "True"

    def test_isinstance_int_false(self) -> None:
        assert _get_result("isinstance('a', int)") == "False"

    def test_isinstance_object_true(self) -> None:
        assert _get_result("""
        class Bar(object):
            pass
        isinstance(Bar(), object)
        """) == "True"

    def test_isinstance_object_true3(self) -> None:
        assert _get_result("""
        class Bar(object):
            pass
        isinstance(Bar(), Bar)
        """) == "True"

    def test_isinstance_class_false(self) -> None:
        assert _get_result("""
        class Foo(object):
            pass
        class Bar(object):
            pass
        isinstance(Bar(), Foo)
        """) == "False"

    def test_isinstance_type_false(self) -> None:
        assert _get_result("""
        class Bar(object):
            pass
        isinstance(Bar(), type)
        """) == "False"

    def test_isinstance_str_true(self) -> None:
        """Make sure isinstance can check builtin str types"""
        assert _get_result("isinstance('a', str)") == "True"

    def test_isinstance_str_false(self) -> None:
        assert _get_result("isinstance(1, str)") == "False"

    def test_isinstance_tuple_argument(self) -> None:
        """obj just has to be an instance of ANY class/type on the right"""
        assert _get_result("isinstance(1, (str, int))") == "True"

    def test_isinstance_type_false2(self) -> None:
        assert _get_result("""
        isinstance(1, type)
        """) == "False"

    def test_isinstance_object_true2(self) -> None:
        assert _get_result("""
        class Bar(type):
            pass
        mainbar = Bar("Bar", tuple(), {})
        isinstance(mainbar, object)
        """) == "True"

    def test_isinstance_type_true(self) -> None:
        assert _get_result("""
        class Bar(type):
            pass
        mainbar = Bar("Bar", tuple(), {})
        isinstance(mainbar, type)
        """) == "True"

    def test_isinstance_edge_case(self) -> None:
        """isinstance allows bad type short-circuting"""
        assert _get_result("isinstance(1, (int, 1))") == "True"

    def test_uninferable_bad_type(self) -> None:
        """The second argument must be a class or a tuple of classes"""
        with pytest.raises(InferenceError):
            _get_result_node("isinstance(int, 1)")

    def test_uninferable_keywords(self) -> None:
        """isinstance does not allow keywords"""
        with pytest.raises(InferenceError):
            _get_result_node("isinstance(1, class_or_tuple=int)")

    def test_too_many_args(self) -> None:
        """isinstance must have two arguments"""
        with pytest.raises(InferenceError):
            _get_result_node("isinstance(1, int, str)")

    def test_first_param_is_uninferable(self) -> None:
        with pytest.raises(InferenceError):
            _get_result_node("isinstance(something, int)")


class TestIssubclassBrain:
    """Test issubclass() builtin inference"""

    def test_type_type(self) -> None:
        assert _get_result("issubclass(type, type)") == "True"

    def test_object_type(self) -> None:
        assert _get_result("issubclass(object, type)") == "False"

    def test_type_object(self) -> None:
        assert _get_result("issubclass(type, object)") == "True"

    def test_issubclass_same_class(self) -> None:
        assert _get_result("issubclass(int, int)") == "True"

    def test_issubclass_not_the_same_class(self) -> None:
        assert _get_result("issubclass(str, int)") == "False"

    def test_issubclass_object_true(self) -> None:
        assert _get_result("""
        class Bar(object):
            pass
        issubclass(Bar, object)
        """) == "True"

    def test_issubclass_same_user_defined_class(self) -> None:
        assert _get_result("""
        class Bar(object):
            pass
        issubclass(Bar, Bar)
        """) == "True"

    def test_issubclass_different_user_defined_classes(self) -> None:
        assert _get_result("""
        class Foo(object):
            pass
        class Bar(object):
            pass
        issubclass(Bar, Foo)
        """) == "False"

    def test_issubclass_type_false(self) -> None:
        assert _get_result("""
        class Bar(object):
            pass
        issubclass(Bar, type)
        """) == "False"

    def test_isinstance_tuple_argument(self) -> None:
        """obj just has to be a subclass of ANY class/type on the right"""
        assert _get_result("issubclass(int, (str, int))") == "True"

    def test_isinstance_object_true2(self) -> None:
        assert _get_result("""
        class Bar(type):
            pass
        issubclass(Bar, object)
        """) == "True"

    def test_issubclass_short_circuit(self) -> None:
        """issubclasss allows bad type short-circuting"""
        assert _get_result("issubclass(int, (int, 1))") == "True"

    def test_uninferable_bad_type(self) -> None:
        """The second argument must be a class or a tuple of classes"""
        # Should I subclass
        with pytest.raises(InferenceError):
            _get_result_node("issubclass(int, 1)")

    def test_uninferable_keywords(self) -> None:
        """issubclass does not allow keywords"""
        with pytest.raises(InferenceError):
            _get_result_node("issubclass(int, class_or_tuple=int)")

    def test_too_many_args(self) -> None:
        """issubclass must have two arguments"""
        with pytest.raises(InferenceError):
            _get_result_node("issubclass(int, int, str)")


def _get_result_node(code: str) -> nodes.Const:
    node = next(astroid.extract_node(code).infer())
    return node


def _get_result(code: str) -> str:
    return _get_result_node(code).as_string()


class TestLenBuiltinInference:
    def test_len_list(self) -> None:
        # Uses .elts
        node = astroid.extract_node("""
        len(['a','b','c'])
        """)
        node = next(node.infer())
        assert node.as_string() == "3"
        assert isinstance(node, nodes.Const)

    def test_len_tuple(self) -> None:
        node = astroid.extract_node("""
        len(('a','b','c'))
        """)
        node = next(node.infer())
        assert node.as_string() == "3"

    def test_len_var(self) -> None:
        # Make sure argument is inferred
        node = astroid.extract_node("""
        a = [1,2,'a','b','c']
        len(a)
        """)
        node = next(node.infer())
        assert node.as_string() == "5"

    def test_len_dict(self) -> None:
        # Uses .items
        node = astroid.extract_node("""
        a = {'a': 1, 'b': 2}
        len(a)
        """)
        node = next(node.infer())
        assert node.as_string() == "2"

    def test_len_set(self) -> None:
        node = astroid.extract_node("""
        len({'a'})
        """)
        inferred_node = next(node.infer())
        assert inferred_node.as_string() == "1"

    def test_len_object(self) -> None:
        """Test len with objects that implement the len protocol"""
        node = astroid.extract_node("""
        class A:
            def __len__(self):
                return 57
        len(A())
        """)
        inferred_node = next(node.infer())
        assert inferred_node.as_string() == "57"

    def test_len_class_with_metaclass(self) -> None:
        """Make sure proper len method is located"""
        cls_node, inst_node = astroid.extract_node("""
        class F2(type):
            def __new__(cls, name, bases, attrs):
                return super().__new__(cls, name, bases, {})
            def __len__(self):
                return 57
        class F(metaclass=F2):
            def __len__(self):
                return 4
        len(F) #@
        len(F()) #@
        """)
        assert next(cls_node.infer()).as_string() == "57"
        assert next(inst_node.infer()).as_string() == "4"

    def test_len_object_failure(self) -> None:
        """If taking the length of a class, do not use an instance method"""
        node = astroid.extract_node("""
        class F:
            def __len__(self):
                return 57
        len(F)
        """)
        with pytest.raises(InferenceError):
            next(node.infer())

    def test_len_string(self) -> None:
        node = astroid.extract_node("""
        len("uwu")
        """)
        assert next(node.infer()).as_string() == "3"

    def test_len_generator_failure(self) -> None:
        node = astroid.extract_node("""
        def gen():
            yield 'a'
            yield 'b'
        len(gen())
        """)
        with pytest.raises(InferenceError):
            next(node.infer())

    def test_len_failure_missing_variable(self) -> None:
        node = astroid.extract_node("""
        len(a)
        """)
        with pytest.raises(InferenceError):
            next(node.infer())

    def test_len_bytes(self) -> None:
        node = astroid.extract_node("""
        len(b'uwu')
        """)
        assert next(node.infer()).as_string() == "3"

    def test_int_subclass_result(self) -> None:
        """Check that a subclass of an int can still be inferred

        This test does not properly infer the value passed to the
        int subclass (5) but still returns a proper integer as we
        fake the result of the `len()` call.
        """
        node = astroid.extract_node("""
        class IntSubclass(int):
            pass

        class F:
            def __len__(self):
                return IntSubclass(5)
        len(F())
        """)
        assert next(node.infer()).as_string() == "0"

    @pytest.mark.xfail(reason="Can't use list special astroid fields")
    def test_int_subclass_argument(self):
        """I am unable to access the length of an object which
        subclasses list"""
        node = astroid.extract_node("""
        class ListSubclass(list):
            pass
        len(ListSubclass([1,2,3,4,4]))
        """)
        assert next(node.infer()).as_string() == "5"

    def test_len_builtin_inference_attribute_error_str(self) -> None:
        """Make sure len builtin doesn't raise an AttributeError
        on instances of str or bytes

        See https://github.com/pylint-dev/pylint/issues/1942
        """
        code = 'len(str("F"))'
        try:
            next(astroid.extract_node(code).infer())
        except InferenceError:
            pass

    def test_len_builtin_inference_recursion_error_self_referential_attribute(
        self,
    ) -> None:
        """Make sure len calls do not trigger
        recursion errors for self referential assignment

        See https://github.com/pylint-dev/pylint/issues/2734
        """
        code = """
        class Data:
            def __init__(self):
                self.shape = []

        data = Data()
        data.shape = len(data.shape)
        data.shape #@
        """
        try:
            astroid.extract_node(code).inferred()
        except RecursionError:
            pytest.fail("Inference call should not trigger a recursion error")


def test_infer_str() -> None:
    ast_nodes = astroid.extract_node("""
    str(s) #@
    str('a') #@
    str(some_object()) #@
    """)
    for node in ast_nodes:
        inferred = next(node.infer())
        assert isinstance(inferred, nodes.Const)

    node = astroid.extract_node("""
    str(s='') #@
    """)
    inferred = next(node.infer())
    assert isinstance(inferred, astroid.Instance)
    assert inferred.qname() == "builtins.str"


def test_infer_int() -> None:
    ast_nodes = astroid.extract_node("""
    int(0) #@
    int('1') #@
    """)
    for node in ast_nodes:
        inferred = next(node.infer())
        assert isinstance(inferred, nodes.Const)

    ast_nodes = astroid.extract_node("""
    int(s='') #@
    int('2.5') #@
    int('something else') #@
    int(unknown) #@
    int(b'a') #@
    """)
    for node in ast_nodes:
        inferred = next(node.infer())
        assert isinstance(inferred, astroid.Instance)
        assert inferred.qname() == "builtins.int"


def test_infer_dict_from_keys() -> None:
    bad_nodes = astroid.extract_node("""
    dict.fromkeys() #@
    dict.fromkeys(1, 2, 3) #@
    dict.fromkeys(a=1) #@
    """)
    for node in bad_nodes:
        with pytest.raises(InferenceError):
            if isinstance(next(node.infer()), util.UninferableBase):
                raise InferenceError

    # Test uninferable values
    good_nodes = astroid.extract_node("""
    from unknown import Unknown
    dict.fromkeys(some_value) #@
    dict.fromkeys(some_other_value) #@
    dict.fromkeys([Unknown(), Unknown()]) #@
    dict.fromkeys([Unknown(), Unknown()]) #@
    """)
    for node in good_nodes:
        inferred = next(node.infer())
        assert isinstance(inferred, nodes.Dict)
        assert inferred.items == []

    # Test inferable values

    # from a dictionary's keys
    from_dict = astroid.extract_node("""
    dict.fromkeys({'a':2, 'b': 3, 'c': 3}) #@
    """)
    inferred = next(from_dict.infer())
    assert isinstance(inferred, nodes.Dict)
    itered = inferred.itered()
    assert all(isinstance(elem, nodes.Const) for elem in itered)
    actual_values = [elem.value for elem in itered]
    assert sorted(actual_values) == ["a", "b", "c"]

    # from a string
    from_string = astroid.extract_node("""
    dict.fromkeys('abc')
    """)
    inferred = next(from_string.infer())
    assert isinstance(inferred, nodes.Dict)
    itered = inferred.itered()
    assert all(isinstance(elem, nodes.Const) for elem in itered)
    actual_values = [elem.value for elem in itered]
    assert sorted(actual_values) == ["a", "b", "c"]

    # from bytes
    from_bytes = astroid.extract_node("""
    dict.fromkeys(b'abc')
    """)
    inferred = next(from_bytes.infer())
    assert isinstance(inferred, nodes.Dict)
    itered = inferred.itered()
    assert all(isinstance(elem, nodes.Const) for elem in itered)
    actual_values = [elem.value for elem in itered]
    assert sorted(actual_values) == [97, 98, 99]

    # From list/set/tuple
    from_others = astroid.extract_node("""
    dict.fromkeys(('a', 'b', 'c')) #@
    dict.fromkeys(['a', 'b', 'c']) #@
    dict.fromkeys({'a', 'b', 'c'}) #@
    """)
    for node in from_others:
        inferred = next(node.infer())
        assert isinstance(inferred, nodes.Dict)
        itered = inferred.itered()
        assert all(isinstance(elem, nodes.Const) for elem in itered)
        actual_values = [elem.value for elem in itered]
        assert sorted(actual_values) == ["a", "b", "c"]


class TestFunctoolsPartial:
    @staticmethod
    def test_infer_partial() -> None:
        ast_node = astroid.extract_node("""
        from functools import partial
        def test(a, b):
            '''Docstring'''
            return a + b
        partial(test, 1)(3) #@
        """)
        assert isinstance(ast_node.func, nodes.Call)
        inferred = ast_node.func.inferred()
        assert len(inferred) == 1
        partial = inferred[0]
        assert isinstance(partial, objects.PartialFunction)
        assert isinstance(partial.as_string(), str)
        assert isinstance(partial.doc_node, nodes.Const)
        assert partial.doc_node.value == "Docstring"
        assert partial.lineno == 3
        assert partial.col_offset == 0

    def test_invalid_functools_partial_calls(self) -> None:
        ast_nodes = astroid.extract_node("""
        from functools import partial
        from unknown import Unknown

        def test(a, b, c):
            return a + b + c

        partial() #@
        partial(test) #@
        partial(func=test) #@
        partial(some_func, a=1) #@
        partial(Unknown, a=1) #@
        partial(2, a=1) #@
        partial(test, unknown=1) #@
        """)
        for node in ast_nodes:
            inferred = next(node.infer())
            assert isinstance(inferred, (nodes.FunctionDef, astroid.Instance))
            assert inferred.qname() in {
                "functools.partial",
                "functools.partial.newfunc",
            }

    def test_inferred_partial_function_calls(self) -> None:
        ast_nodes = astroid.extract_node("""
        from functools import partial
        def test(a, b):
            return a + b
        partial(test, 1)(3) #@
        partial(test, b=4)(3) #@
        partial(test, b=4)(a=3) #@
        def other_test(a, b, *, c=1):
            return (a + b) * c

        partial(other_test, 1, 2)() #@
        partial(other_test, 1, 2)(c=4) #@
        partial(other_test, c=4)(1, 3) #@
        partial(other_test, 4, c=4)(4) #@
        partial(other_test, 4, c=4)(b=5) #@
        test(1, 2) #@
        partial(other_test, 1, 2)(c=3) #@
        partial(test, b=4)(a=3) #@
        """)
        expected_values = [4, 7, 7, 3, 12, 16, 32, 36, 3, 9, 7]
        for node, expected_value in zip(ast_nodes, expected_values):
            inferred = next(node.infer())
            assert isinstance(inferred, nodes.Const)
            assert inferred.value == expected_value

    def test_partial_assignment(self) -> None:
        """Make sure partials are not assigned to original scope."""
        ast_nodes = astroid.extract_node("""
        from functools import partial
        def test(a, b): #@
            return a + b
        test2 = partial(test, 1)
        test2 #@
        def test3_scope(a):
            test3 = partial(test, a)
            test3 #@
        """)
        func1, func2, func3 = ast_nodes
        assert func1.parent.scope() == func2.parent.scope()
        assert func1.parent.scope() != func3.parent.scope()
        partial_func3 = next(func3.infer())
        # use scope of parent, so that it doesn't just refer to self
        scope = partial_func3.parent.scope()
        assert scope.name == "test3_scope", "parented by closure"

    def test_partial_does_not_affect_scope(self) -> None:
        """Make sure partials are not automatically assigned."""
        ast_nodes = astroid.extract_node("""
        from functools import partial
        def test(a, b):
            return a + b
        def scope():
            test2 = partial(test, 1)
            test2 #@
        """)
        test2 = next(ast_nodes.infer())
        mod_scope = test2.root()
        scope = test2.parent.scope()
        assert set(mod_scope) == {"test", "scope", "partial"}
        assert set(scope) == {"test2"}

    def test_multiple_partial_args(self) -> None:
        "Make sure partials remember locked-in args."
        ast_node = astroid.extract_node("""
        from functools import partial
        def test(a, b, c, d, e=5):
            return a + b + c + d + e
        test1 = partial(test, 1)
        test2 = partial(test1, 2)
        test3 = partial(test2, 3)
        test3(4, e=6) #@
        """)
        expected_args = [1, 2, 3, 4]
        expected_keywords = {"e": 6}

        call_site = astroid.arguments.CallSite.from_call(ast_node)
        called_func = next(ast_node.func.infer())
        called_args = called_func.filled_args + call_site.positional_arguments
        called_keywords = {**called_func.filled_keywords, **call_site.keyword_arguments}
        assert len(called_args) == len(expected_args)
        assert [arg.value for arg in called_args] == expected_args
        assert len(called_keywords) == len(expected_keywords)

        for keyword, value in expected_keywords.items():
            assert keyword in called_keywords
            assert called_keywords[keyword].value == value


def test_http_client_brain() -> None:
    node = astroid.extract_node("""
    from http.client import OK
    OK
    """)
    inferred = next(node.infer())
    assert isinstance(inferred, astroid.Instance)


def test_http_status_brain() -> None:
    node = astroid.extract_node("""
    import http
    http.HTTPStatus.CONTINUE.phrase
    """)
    inferred = next(node.infer())
    # Cannot infer the exact value but the field is there.
    assert inferred.value == ""

    node = astroid.extract_node("""
    import http
    http.HTTPStatus(200).phrase
    """)
    inferred = next(node.infer())
    assert isinstance(inferred, nodes.Const)


def test_http_status_brain_iterable() -> None:
    """Astroid inference of `http.HTTPStatus` is an iterable subclass of `enum.IntEnum`"""
    node = astroid.extract_node("""
    import http
    http.HTTPStatus
    """)
    inferred = next(node.infer())
    assert "enum.IntEnum" in [ancestor.qname() for ancestor in inferred.ancestors()]
    assert inferred.getattr("__iter__")


def test_oserror_model() -> None:
    node = astroid.extract_node("""
    try:
        1/0
    except OSError as exc:
        exc #@
    """)
    inferred = next(node.infer())
    strerror = next(inferred.igetattr("strerror"))
    assert isinstance(strerror, nodes.Const)
    assert strerror.value == ""


@pytest.mark.skipif(PY313_PLUS, reason="Python >= 3.13 no longer has a crypt module")
def test_crypt_brain() -> None:
    module = MANAGER.ast_from_module_name("crypt")
    dynamic_attrs = [
        "METHOD_SHA512",
        "METHOD_SHA256",
        "METHOD_BLOWFISH",
        "METHOD_MD5",
        "METHOD_CRYPT",
    ]
    for attr in dynamic_attrs:
        assert attr in module


@pytest.mark.parametrize(
    "code,expected_class,expected_value",
    [
        ("'hey'.encode()", nodes.Const, b""),
        ("b'hey'.decode()", nodes.Const, ""),
        ("'hey'.encode().decode()", nodes.Const, ""),
    ],
)
def test_str_and_bytes(code, expected_class, expected_value):
    node = astroid.extract_node(code)
    inferred = next(node.infer())
    assert isinstance(inferred, expected_class)
    assert inferred.value == expected_value


def test_no_recursionerror_on_self_referential_length_check() -> None:
    """
    Regression test for https://github.com/pylint-dev/astroid/issues/777

    This test should only raise an InferenceError and no RecursionError.
    """
    with pytest.raises(InferenceError):
        node = astroid.extract_node("""
        class Crash:
            def __len__(self) -> int:
                return len(self)
        len(Crash()) #@
        """)
        assert isinstance(node, nodes.NodeNG)
        node.inferred()


def test_inference_on_outer_referential_length_check() -> None:
    """
    Regression test for https://github.com/pylint-dev/pylint/issues/5244
    See also https://github.com/pylint-dev/astroid/pull/1234

    This test should succeed without any error.
    """
    node = astroid.extract_node("""
    class A:
        def __len__(self) -> int:
            return 42

    class Crash:
        def __len__(self) -> int:
            a = A()
            return len(a)

    len(Crash()) #@
    """)
    inferred = node.inferred()
    assert len(inferred) == 1
    assert isinstance(inferred[0], nodes.Const)
    assert inferred[0].value == 42


def test_no_attributeerror_on_self_referential_length_check() -> None:
    """
    Regression test for https://github.com/pylint-dev/pylint/issues/5244
    See also https://github.com/pylint-dev/astroid/pull/1234

    This test should only raise an InferenceError and no AttributeError.
    """
    with pytest.raises(InferenceError):
        node = astroid.extract_node("""
        class MyClass:
            def some_func(self):
                return lambda: 42

            def __len__(self):
                return len(self.some_func())

        len(MyClass()) #@
        """)
        assert isinstance(node, nodes.NodeNG)
        node.inferred()
