"""Test equality and order comparisons."""
import unittest
from test.support import ALWAYS_EQ
from fractions import Fraction
from decimal import Decimal


class ComparisonSimpleTest(unittest.TestCase):
    """Test equality and order comparisons for some simple cases."""

    class Empty:
        def __repr__(self):
            return '<Empty>'

    class Cmp:
        def __init__(self, arg):
            self.arg = arg

        def __repr__(self):
            return '<Cmp %s>' % self.arg

        def __eq__(self, other):
            return self.arg == other

    set1 = [2, 2.0, 2, 2+0j, Cmp(2.0)]
    set2 = [[1], (3,), None, Empty()]
    candidates = set1 + set2

    def test_comparisons(self):
        for a in self.candidates:
            for b in self.candidates:
                if ((a in self.set1) and (b in self.set1)) or a is b:
                    self.assertEqual(a, b)
                else:
                    self.assertNotEqual(a, b)

    def test_id_comparisons(self):
        # Ensure default comparison compares id() of args
        L = []
        for i in range(10):
            L.insert(len(L)//2, self.Empty())
        for a in L:
            for b in L:
                self.assertEqual(a == b, a is b, 'a=%r, b=%r' % (a, b))

    def test_ne_defaults_to_not_eq(self):
        a = self.Cmp(1)
        b = self.Cmp(1)
        c = self.Cmp(2)
        self.assertIs(a == b, True)
        self.assertIs(a != b, False)
        self.assertIs(a != c, True)

    def test_ne_high_priority(self):
        """object.__ne__() should allow reflected __ne__() to be tried"""
        calls = []
        class Left:
            # Inherits object.__ne__()
            def __eq__(*args):
                calls.append('Left.__eq__')
                return NotImplemented
        class Right:
            def __eq__(*args):
                calls.append('Right.__eq__')
                return NotImplemented
            def __ne__(*args):
                calls.append('Right.__ne__')
                return NotImplemented
        Left() != Right()
        self.assertSequenceEqual(calls, ['Left.__eq__', 'Right.__ne__'])

    def test_ne_low_priority(self):
        """object.__ne__() should not invoke reflected __eq__()"""
        calls = []
        class Base:
            # Inherits object.__ne__()
            def __eq__(*args):
                calls.append('Base.__eq__')
                return NotImplemented
        class Derived(Base):  # Subclassing forces higher priority
            def __eq__(*args):
                calls.append('Derived.__eq__')
                return NotImplemented
            def __ne__(*args):
                calls.append('Derived.__ne__')
                return NotImplemented
        Base() != Derived()
        self.assertSequenceEqual(calls, ['Derived.__ne__', 'Base.__eq__'])

    def test_other_delegation(self):
        """No default delegation between operations except __ne__()"""
        ops = (
            ('__eq__', lambda a, b: a == b),
            ('__lt__', lambda a, b: a < b),
            ('__le__', lambda a, b: a <= b),
            ('__gt__', lambda a, b: a > b),
            ('__ge__', lambda a, b: a >= b),
        )
        for name, func in ops:
            with self.subTest(name):
                def unexpected(*args):
                    self.fail('Unexpected operator method called')
                class C:
                    __ne__ = unexpected
                for other, _ in ops:
                    if other != name:
                        setattr(C, other, unexpected)
                if name == '__eq__':
                    self.assertIs(func(C(), object()), False)
                else:
                    self.assertRaises(TypeError, func, C(), object())

    def test_issue_1393(self):
        x = lambda: None
        self.assertEqual(x, ALWAYS_EQ)
        self.assertEqual(ALWAYS_EQ, x)
        y = object()
        self.assertEqual(y, ALWAYS_EQ)
        self.assertEqual(ALWAYS_EQ, y)


class ComparisonFullTest(unittest.TestCase):
    """Test equality and ordering comparisons for built-in types and
    user-defined classes that implement relevant combinations of rich
    comparison methods.
    """

    class CompBase:
        """Base class for classes with rich comparison methods.

        The "x" attribute should be set to an underlying value to compare.

        Derived classes have a "meth" tuple attribute listing names of
        comparison methods implemented. See assert_total_order().
        """

    # Class without any rich comparison methods.
    class CompNone(CompBase):
        meth = ()

    # Classes with all combinations of value-based equality comparison methods.
    class CompEq(CompBase):
        meth = ("eq",)
        def __eq__(self, other):
            return self.x == other.x

    class CompNe(CompBase):
        meth = ("ne",)
        def __ne__(self, other):
            return self.x != other.x

    class CompEqNe(CompBase):
        meth = ("eq", "ne")
        def __eq__(self, other):
            return self.x == other.x
        def __ne__(self, other):
            return self.x != other.x

    # Classes with all combinations of value-based less/greater-than order
    # comparison methods.
    class CompLt(CompBase):
        meth = ("lt",)
        def __lt__(self, other):
            return self.x < other.x

    class CompGt(CompBase):
        meth = ("gt",)
        def __gt__(self, other):
            return self.x > other.x

    class CompLtGt(CompBase):
        meth = ("lt", "gt")
        def __lt__(self, other):
            return self.x < other.x
        def __gt__(self, other):
            return self.x > other.x

    # Classes with all combinations of value-based less/greater-or-equal-than
    # order comparison methods
    class CompLe(CompBase):
        meth = ("le",)
        def __le__(self, other):
            return self.x <= other.x

    class CompGe(CompBase):
        meth = ("ge",)
        def __ge__(self, other):
            return self.x >= other.x

    class CompLeGe(CompBase):
        meth = ("le", "ge")
        def __le__(self, other):
            return self.x <= other.x
        def __ge__(self, other):
            return self.x >= other.x

    # It should be sufficient to combine the comparison methods only within
    # each group.
    all_comp_classes = (
            CompNone,
            CompEq, CompNe, CompEqNe,  # equal group
            CompLt, CompGt, CompLtGt,  # less/greater-than group
            CompLe, CompGe, CompLeGe)  # less/greater-or-equal group

    def create_sorted_instances(self, class_, values):
        """Create objects of type `class_` and return them in a list.

        `values` is a list of values that determines the value of data
        attribute `x` of each object.

        Objects in the returned list are sorted by their identity.  They
        assigned values in `values` list order.  By assign decreasing
        values to objects with increasing identities, testcases can assert
        that order comparison is performed by value and not by identity.
        """

        instances = [class_() for __ in range(len(values))]
        instances.sort(key=id)
        # Assign the provided values to the instances.
        for inst, value in zip(instances, values):
            inst.x = value
        return instances

    def assert_equality_only(self, a, b, equal):
        """Assert equality result and that ordering is not implemented.

        a, b: Instances to be tested (of same or different type).
        equal: Boolean indicating the expected equality comparison results.
        """
        self.assertEqual(a == b, equal)
        self.assertEqual(b == a, equal)
        self.assertEqual(a != b, not equal)
        self.assertEqual(b != a, not equal)
        with self.assertRaisesRegex(TypeError, "not supported"):
            a < b
        with self.assertRaisesRegex(TypeError, "not supported"):
            a <= b
        with self.assertRaisesRegex(TypeError, "not supported"):
            a > b
        with self.assertRaisesRegex(TypeError, "not supported"):
            a >= b
        with self.assertRaisesRegex(TypeError, "not supported"):
            b < a
        with self.assertRaisesRegex(TypeError, "not supported"):
            b <= a
        with self.assertRaisesRegex(TypeError, "not supported"):
            b > a
        with self.assertRaisesRegex(TypeError, "not supported"):
            b >= a

    def assert_total_order(self, a, b, comp, a_meth=None, b_meth=None):
        """Test total ordering comparison of two instances.

        a, b: Instances to be tested (of same or different type).

        comp: -1, 0, or 1 indicates that the expected order comparison
           result for operations that are supported by the classes is
           a <, ==, or > b.

        a_meth, b_meth: Either None, indicating that all rich comparison
           methods are available, aa for builtins, or the tuple (subset)
           of "eq", "ne", "lt", "le", "gt", and "ge" that are available
           for the corresponding instance (of a user-defined class).
        """
        self.assert_eq_subtest(a, b, comp, a_meth, b_meth)
        self.assert_ne_subtest(a, b, comp, a_meth, b_meth)
        self.assert_lt_subtest(a, b, comp, a_meth, b_meth)
        self.assert_le_subtest(a, b, comp, a_meth, b_meth)
        self.assert_gt_subtest(a, b, comp, a_meth, b_meth)
        self.assert_ge_subtest(a, b, comp, a_meth, b_meth)

    # The body of each subtest has form:
    #
    #     if value-based comparison methods:
    #         expect what the testcase defined for a op b and b rop a;
    #     else:  no value-based comparison
    #         expect default behavior of object for a op b and b rop a.

    def assert_eq_subtest(self, a, b, comp, a_meth, b_meth):
        if a_meth is None or "eq" in a_meth or "eq" in b_meth:
            self.assertEqual(a == b, comp == 0)
            self.assertEqual(b == a, comp == 0)
        else:
            self.assertEqual(a == b, a is b)
            self.assertEqual(b == a, a is b)

    def assert_ne_subtest(self, a, b, comp, a_meth, b_meth):
        if a_meth is None or not {"ne", "eq"}.isdisjoint(a_meth + b_meth):
            self.assertEqual(a != b, comp != 0)
            self.assertEqual(b != a, comp != 0)
        else:
            self.assertEqual(a != b, a is not b)
            self.assertEqual(b != a, a is not b)

    def assert_lt_subtest(self, a, b, comp, a_meth, b_meth):
        if a_meth is None or "lt" in a_meth or "gt" in b_meth:
            self.assertEqual(a < b, comp < 0)
            self.assertEqual(b > a, comp < 0)
        else:
            with self.assertRaisesRegex(TypeError, "not supported"):
                a < b
            with self.assertRaisesRegex(TypeError, "not supported"):
                b > a

    def assert_le_subtest(self, a, b, comp, a_meth, b_meth):
        if a_meth is None or "le" in a_meth or "ge" in b_meth:
            self.assertEqual(a <= b, comp <= 0)
            self.assertEqual(b >= a, comp <= 0)
        else:
            with self.assertRaisesRegex(TypeError, "not supported"):
                a <= b
            with self.assertRaisesRegex(TypeError, "not supported"):
                b >= a

    def assert_gt_subtest(self, a, b, comp, a_meth, b_meth):
        if a_meth is None or "gt" in a_meth or "lt" in b_meth:
            self.assertEqual(a > b, comp > 0)
            self.assertEqual(b < a, comp > 0)
        else:
            with self.assertRaisesRegex(TypeError, "not supported"):
                a > b
            with self.assertRaisesRegex(TypeError, "not supported"):
                b < a

    def assert_ge_subtest(self, a, b, comp, a_meth, b_meth):
        if a_meth is None or "ge" in a_meth or "le" in b_meth:
            self.assertEqual(a >= b, comp >= 0)
            self.assertEqual(b <= a, comp >= 0)
        else:
            with self.assertRaisesRegex(TypeError, "not supported"):
                a >= b
            with self.assertRaisesRegex(TypeError, "not supported"):
                b <= a

    def test_objects(self):
        """Compare instances of type 'object'."""
        a = object()
        b = object()
        self.assert_equality_only(a, a, True)
        self.assert_equality_only(a, b, False)

    def test_comp_classes_same(self):
        """Compare same-class instances with comparison methods."""

        for cls in self.all_comp_classes:
            with self.subTest(cls):
                instances = self.create_sorted_instances(cls, (1, 2, 1))

                # Same object.
                self.assert_total_order(instances[0], instances[0], 0,
                                        cls.meth, cls.meth)

                # Different objects, same value.
                self.assert_total_order(instances[0], instances[2], 0,
                                        cls.meth, cls.meth)

                # Different objects, value ascending for ascending identities.
                self.assert_total_order(instances[0], instances[1], -1,
                                        cls.meth, cls.meth)

                # different objects, value descending for ascending identities.
                # This is the interesting case to assert that order comparison
                # is performed based on the value and not based on the identity.
                self.assert_total_order(instances[1], instances[2], +1,
                                        cls.meth, cls.meth)

    def test_comp_classes_different(self):
        """Compare different-class instances with comparison methods."""

        for cls_a in self.all_comp_classes:
            for cls_b in self.all_comp_classes:
                with self.subTest(a=cls_a, b=cls_b):
                    a1 = cls_a()
                    a1.x = 1
                    b1 = cls_b()
                    b1.x = 1
                    b2 = cls_b()
                    b2.x = 2

                    self.assert_total_order(
                        a1, b1, 0, cls_a.meth, cls_b.meth)
                    self.assert_total_order(
                        a1, b2, -1, cls_a.meth, cls_b.meth)

    def test_str_subclass(self):
        """Compare instances of str and a subclass."""
        class StrSubclass(str):
            pass

        s1 = str("a")
        s2 = str("b")
        c1 = StrSubclass("a")
        c2 = StrSubclass("b")
        c3 = StrSubclass("b")

        self.assert_total_order(s1, s1,   0)
        self.assert_total_order(s1, s2, -1)
        self.assert_total_order(c1, c1,   0)
        self.assert_total_order(c1, c2, -1)
        self.assert_total_order(c2, c3,   0)

        self.assert_total_order(s1, c2, -1)
        self.assert_total_order(s2, c3,   0)
        self.assert_total_order(c1, s2, -1)
        self.assert_total_order(c2, s2,   0)

    def test_numbers(self):
        """Compare number types."""

        # Same types.
        i1 = 1001
        i2 = 1002
        self.assert_total_order(i1, i1, 0)
        self.assert_total_order(i1, i2, -1)

        f1 = 1001.0
        f2 = 1001.1
        self.assert_total_order(f1, f1, 0)
        self.assert_total_order(f1, f2, -1)

        q1 = Fraction(2002, 2)
        q2 = Fraction(2003, 2)
        self.assert_total_order(q1, q1, 0)
        self.assert_total_order(q1, q2, -1)

        d1 = Decimal('1001.0')
        d2 = Decimal('1001.1')
        self.assert_total_order(d1, d1, 0)
        self.assert_total_order(d1, d2, -1)

        c1 = 1001+0j
        c2 = 1001+1j
        self.assert_equality_only(c1, c1, True)
        self.assert_equality_only(c1, c2, False)


        # Mixing types.
        for n1, n2 in ((i1,f1), (i1,q1), (i1,d1), (f1,q1), (f1,d1), (q1,d1)):
            self.assert_total_order(n1, n2, 0)
        for n1 in (i1, f1, q1, d1):
            self.assert_equality_only(n1, c1, True)

    def test_sequences(self):
        """Compare list, tuple, and range."""
        l1 = [1, 2]
        l2 = [2, 3]
        self.assert_total_order(l1, l1, 0)
        self.assert_total_order(l1, l2, -1)

        t1 = (1, 2)
        t2 = (2, 3)
        self.assert_total_order(t1, t1, 0)
        self.assert_total_order(t1, t2, -1)

        r1 = range(1, 2)
        r2 = range(2, 2)
        self.assert_equality_only(r1, r1, True)
        self.assert_equality_only(r1, r2, False)

        self.assert_equality_only(t1, l1, False)
        self.assert_equality_only(l1, r1, False)
        self.assert_equality_only(r1, t1, False)

    def test_bytes(self):
        """Compare bytes and bytearray."""
        bs1 = b'a1'
        bs2 = b'b2'
        self.assert_total_order(bs1, bs1, 0)
        self.assert_total_order(bs1, bs2, -1)

        ba1 = bytearray(b'a1')
        ba2 = bytearray(b'b2')
        self.assert_total_order(ba1, ba1,  0)
        self.assert_total_order(ba1, ba2, -1)

        self.assert_total_order(bs1, ba1, 0)
        self.assert_total_order(bs1, ba2, -1)
        self.assert_total_order(ba1, bs1, 0)
        self.assert_total_order(ba1, bs2, -1)

    def test_sets(self):
        """Compare set and frozenset."""
        s1 = {1, 2}
        s2 = {1, 2, 3}
        self.assert_total_order(s1, s1, 0)
        self.assert_total_order(s1, s2, -1)

        f1 = frozenset(s1)
        f2 = frozenset(s2)
        self.assert_total_order(f1, f1,  0)
        self.assert_total_order(f1, f2, -1)

        self.assert_total_order(s1, f1, 0)
        self.assert_total_order(s1, f2, -1)
        self.assert_total_order(f1, s1, 0)
        self.assert_total_order(f1, s2, -1)

    def test_mappings(self):
        """ Compare dict.
        """
        d1 = {1: "a", 2: "b"}
        d2 = {2: "b", 3: "c"}
        d3 = {3: "c", 2: "b"}
        self.assert_equality_only(d1, d1, True)
        self.assert_equality_only(d1, d2, False)
        self.assert_equality_only(d2, d3, True)


if __name__ == '__main__':
    unittest.main()
