cmp3 1.0.1__tar.gz → 1.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cmp3-1.0.1/src/cmp3.egg-info → cmp3-1.0.2}/PKG-INFO +1 -1
- {cmp3-1.0.1 → cmp3-1.0.2}/pyproject.toml +1 -1
- {cmp3-1.0.1 → cmp3-1.0.2}/src/cmp3/core/__init__.py +27 -5
- {cmp3-1.0.1 → cmp3-1.0.2}/src/cmp3/tests/test_all_TestCmpFunction.py +3 -3
- {cmp3-1.0.1 → cmp3-1.0.2}/src/cmp3/tests/test_all_TestCmpPoset.py +5 -5
- {cmp3-1.0.1 → cmp3-1.0.2}/src/cmp3/tests/test_core_TestCmpModePoset.py +3 -3
- {cmp3-1.0.1 → cmp3-1.0.2}/src/cmp3/tests/test_core_TestCmpPoset.py +5 -5
- cmp3-1.0.2/src/cmp3/tests/test_fix.py +19 -0
- cmp3-1.0.2/src/cmp3/tests/test_m_TestCmpDecoAndCmpABC.py +51 -0
- cmp3-1.0.2/src/cmp3/tests/test_m_TestCmpFunctions.py +100 -0
- cmp3-1.0.2/src/cmp3/tests/test_redo_Point.py +74 -0
- cmp3-1.0.2/src/cmp3/tests/test_redo_TestCmpFunction.py +39 -0
- cmp3-1.0.2/src/cmp3/tests/test_redo_TestIncomparable.py +111 -0
- {cmp3-1.0.1 → cmp3-1.0.2/src/cmp3.egg-info}/PKG-INFO +1 -1
- {cmp3-1.0.1 → cmp3-1.0.2}/src/cmp3.egg-info/SOURCES.txt +7 -1
- {cmp3-1.0.1 → cmp3-1.0.2}/LICENSE.txt +0 -0
- {cmp3-1.0.1 → cmp3-1.0.2}/MANIFEST.in +0 -0
- {cmp3-1.0.1 → cmp3-1.0.2}/README.rst +0 -0
- {cmp3-1.0.1 → cmp3-1.0.2}/setup.cfg +0 -0
- {cmp3-1.0.1 → cmp3-1.0.2}/src/cmp3/__init__.py +0 -0
- {cmp3-1.0.1 → cmp3-1.0.2}/src/cmp3/tests/__init__.py +0 -0
- {cmp3-1.0.1 → cmp3-1.0.2}/src/cmp3/tests/test_all_TestCmpABCAndDeco.py +0 -0
- {cmp3-1.0.1 → cmp3-1.0.2}/src/cmp3/tests/test_core_TestCmpDecoAndCmpABC.py +0 -0
- {cmp3-1.0.1 → cmp3-1.0.2}/src/cmp3/tests/test_core_TestCmpDecoOnPlainClass.py +0 -0
- {cmp3-1.0.1 → cmp3-1.0.2}/src/cmp3/tests/test_core_TestCmpPortingGuide.py +0 -0
- {cmp3-1.0.1 → cmp3-1.0.2}/src/cmp3.egg-info/dependency_links.txt +0 -0
- {cmp3-1.0.1 → cmp3-1.0.2}/src/cmp3.egg-info/requires.txt +0 -0
- {cmp3-1.0.1 → cmp3-1.0.2}/src/cmp3.egg-info/top_level.txt +0 -0
|
@@ -8,16 +8,38 @@ __all__ = ["CmpABC", "cmp", "cmpDeco"]
|
|
|
8
8
|
|
|
9
9
|
def cmp(x: Any, y: Any, /, *, mode: str = "portingguide") -> Any:
|
|
10
10
|
"This function returns a value that compares to 0 as x compares to y."
|
|
11
|
-
|
|
11
|
+
errors: list[Exception]
|
|
12
|
+
part: str
|
|
13
|
+
if not any(map(str.isspace, mode)):
|
|
14
|
+
return cmp_mode(x, y, mode=mode)
|
|
15
|
+
errors = list()
|
|
16
|
+
for part in mode.split():
|
|
17
|
+
try:
|
|
18
|
+
return cmp_mode(x, y, mode=part)
|
|
19
|
+
except Exception as exc:
|
|
20
|
+
errors.append(exc)
|
|
21
|
+
if len(errors):
|
|
22
|
+
raise ExceptionGroup("No submode worked.", errors)
|
|
23
|
+
else:
|
|
24
|
+
raise ValueError("No submodes provided.")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def cmp_mode(x: Any, y: Any, /, *, mode: str) -> Any:
|
|
28
|
+
"This function implements submodes."
|
|
29
|
+
if mode == "eq":
|
|
30
|
+
return 0 if x == y else float("nan")
|
|
31
|
+
if mode == "eq_strict":
|
|
32
|
+
return 0 if x == y else None
|
|
33
|
+
if mode == "le":
|
|
34
|
+
return cmp_le(x, y)
|
|
35
|
+
if mode == "magic":
|
|
12
36
|
return x.__cmp__(y)
|
|
13
37
|
if mode == "portingguide":
|
|
14
38
|
return (x > y) - (x < y)
|
|
15
|
-
|
|
16
|
-
return cmp_poset(x, y)
|
|
17
|
-
raise ValueError("%r is not an appropriate value for mode." % mode)
|
|
39
|
+
raise ValueError("%r is not a recognized mode." % mode)
|
|
18
40
|
|
|
19
41
|
|
|
20
|
-
def
|
|
42
|
+
def cmp_le(x: Any, y: Any, /) -> float | int:
|
|
21
43
|
"This function returns a value that compares to 0 as x compares to y assuming a partial order."
|
|
22
44
|
if x <= y:
|
|
23
45
|
if y <= x:
|
|
@@ -21,9 +21,9 @@ class TestCmpFunction(unittest.TestCase):
|
|
|
21
21
|
|
|
22
22
|
def test_poset_mode_uses_poset_semantics(self: Self) -> None:
|
|
23
23
|
# For totally ordered ints, this should behave like normal cmp
|
|
24
|
-
self.assertEqual(cmp(1, 2, mode="
|
|
25
|
-
self.assertEqual(cmp(2, 1, mode="
|
|
26
|
-
self.assertEqual(cmp(5, 5, mode="
|
|
24
|
+
self.assertEqual(cmp(1, 2, mode="le"), -1)
|
|
25
|
+
self.assertEqual(cmp(2, 1, mode="le"), 1)
|
|
26
|
+
self.assertEqual(cmp(5, 5, mode="le"), 0)
|
|
27
27
|
|
|
28
28
|
def test_invalid_mode_raises(self: Self) -> None:
|
|
29
29
|
with self.assertRaises(ValueError):
|
|
@@ -2,16 +2,16 @@ import math
|
|
|
2
2
|
import unittest
|
|
3
3
|
from typing import *
|
|
4
4
|
|
|
5
|
-
from cmp3.core import
|
|
5
|
+
from cmp3.core import cmp
|
|
6
6
|
|
|
7
7
|
__all__ = ["TestCmpPoset"]
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class TestCmpPoset(unittest.TestCase):
|
|
11
11
|
def test_total_order_ints(self: Self) -> None:
|
|
12
|
-
self.assertEqual(
|
|
13
|
-
self.assertEqual(
|
|
14
|
-
self.assertEqual(
|
|
12
|
+
self.assertEqual(cmp(1, 2, mode="le"), -1)
|
|
13
|
+
self.assertEqual(cmp(2, 1, mode="le"), 1)
|
|
14
|
+
self.assertEqual(cmp(3, 3, mode="le"), 0)
|
|
15
15
|
|
|
16
16
|
def test_incomparable_returns_nan(self: Self) -> None:
|
|
17
17
|
class Incomparable:
|
|
@@ -25,7 +25,7 @@ class TestCmpPoset(unittest.TestCase):
|
|
|
25
25
|
|
|
26
26
|
x = Incomparable()
|
|
27
27
|
y = Incomparable()
|
|
28
|
-
result =
|
|
28
|
+
result = cmp(x, y, mode="le")
|
|
29
29
|
self.assertTrue(math.isnan(result))
|
|
30
30
|
|
|
31
31
|
|
|
@@ -9,9 +9,9 @@ __all__ = ["TestCmpModePoset"]
|
|
|
9
9
|
|
|
10
10
|
class TestCmpModePoset(unittest.TestCase):
|
|
11
11
|
def test_cmp_uses_poset_mode(self: Self) -> None:
|
|
12
|
-
self.assertEqual(core.cmp({1}, {1, 2}, mode="
|
|
13
|
-
self.assertEqual(core.cmp({1, 2}, {1}, mode="
|
|
14
|
-
self.assertTrue(math.isnan(core.cmp({1}, {2}, mode="
|
|
12
|
+
self.assertEqual(core.cmp({1}, {1, 2}, mode="le"), -1)
|
|
13
|
+
self.assertEqual(core.cmp({1, 2}, {1}, mode="le"), 1)
|
|
14
|
+
self.assertTrue(math.isnan(core.cmp({1}, {2}, mode="le")))
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
if __name__ == "__main__":
|
|
@@ -2,7 +2,7 @@ import math
|
|
|
2
2
|
import unittest
|
|
3
3
|
from typing import *
|
|
4
4
|
|
|
5
|
-
from cmp3 import
|
|
5
|
+
from cmp3.core import cmp
|
|
6
6
|
|
|
7
7
|
__all__ = ["TestCmpPoset"]
|
|
8
8
|
|
|
@@ -10,20 +10,20 @@ __all__ = ["TestCmpPoset"]
|
|
|
10
10
|
class TestCmpPoset(unittest.TestCase):
|
|
11
11
|
def test_poset_equal(self: Self) -> None:
|
|
12
12
|
# subset ordering: same set
|
|
13
|
-
self.assertEqual(
|
|
13
|
+
self.assertEqual(cmp({1, 2}, {1, 2}, mode="le"), 0)
|
|
14
14
|
|
|
15
15
|
def test_poset_less(self: Self) -> None:
|
|
16
16
|
# subset ordering: strict subset
|
|
17
|
-
self.assertEqual(
|
|
17
|
+
self.assertEqual(cmp({1}, {1, 2}, mode="le"), -1)
|
|
18
18
|
|
|
19
19
|
def test_poset_greater(self: Self) -> None:
|
|
20
20
|
# subset ordering: strict superset
|
|
21
|
-
self.assertEqual(
|
|
21
|
+
self.assertEqual(cmp({1, 2}, {1}, mode="le"), 1)
|
|
22
22
|
|
|
23
23
|
def test_poset_incomparable(self: Self) -> None:
|
|
24
24
|
# incomparable in subset ordering
|
|
25
25
|
res: Any
|
|
26
|
-
res =
|
|
26
|
+
res = cmp({1}, {2}, mode="le")
|
|
27
27
|
self.assertTrue(math.isnan(res))
|
|
28
28
|
|
|
29
29
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from typing import *
|
|
3
|
+
|
|
4
|
+
from cmp3.core import cmp
|
|
5
|
+
|
|
6
|
+
__all__ = ["TestFix"]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestFix(unittest.TestCase):
|
|
10
|
+
|
|
11
|
+
def test_fix(self: Self) -> None:
|
|
12
|
+
with self.assertRaises(ValueError):
|
|
13
|
+
cmp(4, 2, mode="")
|
|
14
|
+
with self.assertRaises(ValueError):
|
|
15
|
+
cmp(4, 2, mode=" ")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
unittest.main()
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from typing import *
|
|
3
|
+
|
|
4
|
+
from cmp3 import core
|
|
5
|
+
|
|
6
|
+
__all__ = ["TestCmpDecoAndCmpABC"]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Number(core.CmpABC):
|
|
10
|
+
def __cmp__(self: Self, other: Any) -> Any:
|
|
11
|
+
if not isinstance(other, Number):
|
|
12
|
+
return NotImplemented
|
|
13
|
+
return (self.value > other.value) - (self.value < other.value)
|
|
14
|
+
|
|
15
|
+
def __init__(self: Self, value: int) -> None:
|
|
16
|
+
self.value = value
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestCmpDecoAndCmpABC(unittest.TestCase):
|
|
20
|
+
def test_rich_comparisons_lt_gt(self: Self) -> None:
|
|
21
|
+
a: Number
|
|
22
|
+
b: Number
|
|
23
|
+
a = Number(1)
|
|
24
|
+
b = Number(2)
|
|
25
|
+
self.assertTrue(a < b)
|
|
26
|
+
self.assertTrue(b > a)
|
|
27
|
+
|
|
28
|
+
def test_rich_comparisons_le_ge_eq(self: Self) -> None:
|
|
29
|
+
a1: Number
|
|
30
|
+
a2: Number
|
|
31
|
+
b: Number
|
|
32
|
+
a1 = Number(5)
|
|
33
|
+
a2 = Number(5)
|
|
34
|
+
b = Number(7)
|
|
35
|
+
self.assertTrue(a1 <= a2)
|
|
36
|
+
self.assertTrue(a1 >= a2)
|
|
37
|
+
self.assertTrue(a1 == a2)
|
|
38
|
+
self.assertTrue(a1 <= b)
|
|
39
|
+
self.assertTrue(b >= a1)
|
|
40
|
+
|
|
41
|
+
def test_rich_comparisons_ne(self: Self) -> None:
|
|
42
|
+
a: Number
|
|
43
|
+
b: Number
|
|
44
|
+
a = Number(1)
|
|
45
|
+
b = Number(2)
|
|
46
|
+
self.assertTrue(a != b)
|
|
47
|
+
self.assertFalse(a != Number(1))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
unittest.main()
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import unittest
|
|
3
|
+
from typing import *
|
|
4
|
+
|
|
5
|
+
from cmp3 import core
|
|
6
|
+
|
|
7
|
+
__all__ = ["TestCmpFunctions"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Number(core.CmpABC):
|
|
11
|
+
def __cmp__(self: Self, other) -> Any:
|
|
12
|
+
if not isinstance(other, Number):
|
|
13
|
+
return NotImplemented
|
|
14
|
+
return (self.value > other.value) - (self.value < other.value)
|
|
15
|
+
|
|
16
|
+
def __init__(self: Self, value: int) -> None:
|
|
17
|
+
self.value = value
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TestCmpFunctions(unittest.TestCase):
|
|
21
|
+
def test_eq_mode_equal(self: Self) -> None:
|
|
22
|
+
self.assertEqual(core.cmp(1, 1, mode="eq"), 0)
|
|
23
|
+
|
|
24
|
+
def test_eq_mode_not_equal(self: Self) -> None:
|
|
25
|
+
result: Any
|
|
26
|
+
result = core.cmp(1, 2, mode="eq")
|
|
27
|
+
self.assertTrue(math.isnan(result))
|
|
28
|
+
|
|
29
|
+
def test_eq_strict_mode_equal(self: Self) -> None:
|
|
30
|
+
self.assertEqual(core.cmp(1, 1, mode="eq_strict"), 0)
|
|
31
|
+
|
|
32
|
+
def test_eq_strict_mode_not_equal(self: Self) -> None:
|
|
33
|
+
self.assertIsNone(core.cmp(1, 2, mode="eq_strict"))
|
|
34
|
+
|
|
35
|
+
def test_portingguide_less(self: Self) -> None:
|
|
36
|
+
self.assertEqual(core.cmp(1, 2, mode="portingguide"), -1)
|
|
37
|
+
|
|
38
|
+
def test_portingguide_equal(self: Self) -> None:
|
|
39
|
+
self.assertEqual(core.cmp(3, 3, mode="portingguide"), 0)
|
|
40
|
+
|
|
41
|
+
def test_portingguide_greater(self: Self) -> None:
|
|
42
|
+
self.assertEqual(core.cmp(5, 2, mode="portingguide"), 1)
|
|
43
|
+
|
|
44
|
+
def test_le_mode(self: Self) -> None:
|
|
45
|
+
# Using total-order ints to also test cmp_le
|
|
46
|
+
self.assertEqual(core.cmp(1, 2, mode="le"), -1)
|
|
47
|
+
self.assertEqual(core.cmp(2, 1, mode="le"), 1)
|
|
48
|
+
self.assertEqual(core.cmp(3, 3, mode="le"), 0)
|
|
49
|
+
|
|
50
|
+
def test_le_mode_nan_for_incomparable(self: Self) -> None:
|
|
51
|
+
# Simulate incomparable with a custom class
|
|
52
|
+
class P:
|
|
53
|
+
def __init__(self, v):
|
|
54
|
+
self.v = v
|
|
55
|
+
|
|
56
|
+
x: P
|
|
57
|
+
y: P
|
|
58
|
+
x = P(1)
|
|
59
|
+
y = P(2)
|
|
60
|
+
# They don't define <=, so this should raise TypeError in cmp_le
|
|
61
|
+
with self.assertRaises(TypeError):
|
|
62
|
+
core.cmp_le(x, y)
|
|
63
|
+
|
|
64
|
+
def test_magic_mode_with_cmpabc(self: Self) -> None:
|
|
65
|
+
a: Number
|
|
66
|
+
b: Number
|
|
67
|
+
a = Number(1)
|
|
68
|
+
b = Number(3)
|
|
69
|
+
self.assertEqual(core.cmp(a, b, mode="magic"), -1)
|
|
70
|
+
self.assertEqual(core.cmp(b, a, mode="magic"), 1)
|
|
71
|
+
self.assertEqual(core.cmp(a, Number(1), mode="magic"), 0)
|
|
72
|
+
|
|
73
|
+
def test_multi_mode_first_fails_second_succeeds(self: Self) -> None:
|
|
74
|
+
# "magic" will work for Number, but let's create an object without __cmp__
|
|
75
|
+
class NoCmp:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
x: NoCmp
|
|
79
|
+
y: NoCmp
|
|
80
|
+
|
|
81
|
+
x = NoCmp()
|
|
82
|
+
y = NoCmp()
|
|
83
|
+
with self.assertRaises(Exception):
|
|
84
|
+
core.cmp(x, y, mode="magic portingguide")
|
|
85
|
+
|
|
86
|
+
def test_unknown_modes_raise_exceptiongroup(self: Self) -> None:
|
|
87
|
+
exc: Any
|
|
88
|
+
with self.assertRaises(ExceptionGroup) as cm:
|
|
89
|
+
core.cmp(1, 2, mode="foo bar")
|
|
90
|
+
exc = cm.exception
|
|
91
|
+
self.assertEqual(len(exc.exceptions), 2)
|
|
92
|
+
self.assertTrue(all(isinstance(e, ValueError) for e in exc.exceptions))
|
|
93
|
+
|
|
94
|
+
def test_only_whitespace_mode_raises_valueerror(self: Self) -> None:
|
|
95
|
+
with self.assertRaises(ValueError):
|
|
96
|
+
core.cmp(1, 2, mode=" ")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
if __name__ == "__main__":
|
|
100
|
+
unittest.main()
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from typing import *
|
|
3
|
+
|
|
4
|
+
from cmp3.core import CmpABC, cmp_mode
|
|
5
|
+
|
|
6
|
+
__all__ = ["TestPoint"]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Point(CmpABC):
|
|
10
|
+
"""Simple comparable type based on an integer value."""
|
|
11
|
+
|
|
12
|
+
__slots__ = ("value",)
|
|
13
|
+
|
|
14
|
+
def __init__(self: Self, value: int) -> None:
|
|
15
|
+
self.value = value
|
|
16
|
+
|
|
17
|
+
def __cmp__(self: Self, other: Any) -> int:
|
|
18
|
+
if isinstance(other, Point):
|
|
19
|
+
return (self.value > other.value) - (self.value < other.value)
|
|
20
|
+
return NotImplemented
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TestPoint(unittest.TestCase):
|
|
24
|
+
|
|
25
|
+
def test_dunder_mode_uses___cmp__(self: Self) -> None:
|
|
26
|
+
a: Point
|
|
27
|
+
b: Point
|
|
28
|
+
|
|
29
|
+
a = Point(1)
|
|
30
|
+
b = Point(2)
|
|
31
|
+
self.assertEqual(cmp_mode(a, b, mode="magic"), -1)
|
|
32
|
+
self.assertEqual(cmp_mode(b, a, mode="magic"), 1)
|
|
33
|
+
self.assertEqual(cmp_mode(a, a, mode="magic"), 0)
|
|
34
|
+
|
|
35
|
+
def test_cmpabc_is_abstract(self: Self) -> None:
|
|
36
|
+
with self.assertRaises(TypeError):
|
|
37
|
+
CmpABC() # type: ignore[abstract]
|
|
38
|
+
|
|
39
|
+
def test_cmpabc_slots(self: Self) -> None:
|
|
40
|
+
self.assertEqual(CmpABC.__slots__, ())
|
|
41
|
+
|
|
42
|
+
def test_subclass_comparisons_use___cmp__(self: Self) -> None:
|
|
43
|
+
a: Point
|
|
44
|
+
b: Point
|
|
45
|
+
c: Point
|
|
46
|
+
a = Point(1)
|
|
47
|
+
b = Point(2)
|
|
48
|
+
c = Point(1)
|
|
49
|
+
|
|
50
|
+
self.assertTrue(a < b)
|
|
51
|
+
self.assertTrue(a <= b)
|
|
52
|
+
self.assertTrue(b > a)
|
|
53
|
+
self.assertTrue(b >= a)
|
|
54
|
+
self.assertTrue(a == c)
|
|
55
|
+
self.assertTrue(a != b)
|
|
56
|
+
|
|
57
|
+
self.assertFalse(a > b)
|
|
58
|
+
self.assertFalse(a >= b)
|
|
59
|
+
self.assertFalse(b < a)
|
|
60
|
+
self.assertFalse(b <= a)
|
|
61
|
+
self.assertFalse(a != c)
|
|
62
|
+
self.assertFalse(a == b)
|
|
63
|
+
|
|
64
|
+
def test_cmpdeco_adds_comparison_methods(self: Self) -> None:
|
|
65
|
+
self.assertTrue(hasattr(Point, "__eq__"))
|
|
66
|
+
self.assertTrue(hasattr(Point, "__lt__"))
|
|
67
|
+
self.assertTrue(hasattr(Point, "__le__"))
|
|
68
|
+
self.assertTrue(hasattr(Point, "__gt__"))
|
|
69
|
+
self.assertTrue(hasattr(Point, "__ge__"))
|
|
70
|
+
self.assertTrue(hasattr(Point, "__ne__"))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
unittest.main()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from typing import *
|
|
3
|
+
|
|
4
|
+
from cmp3.core import cmp as cmp_fn
|
|
5
|
+
|
|
6
|
+
__all__ = ["TestCmpFunction"]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestCmpFunction(unittest.TestCase):
|
|
10
|
+
def test_default_mode_is_portingguide(self: Self) -> None:
|
|
11
|
+
self.assertEqual(cmp_fn(1, 1), 0)
|
|
12
|
+
self.assertEqual(cmp_fn(2, 1), 1)
|
|
13
|
+
self.assertEqual(cmp_fn(1, 2), -1)
|
|
14
|
+
|
|
15
|
+
def test_mode_without_whitespace_goes_directly_to_cmp_mode(self: Self) -> None:
|
|
16
|
+
# Just checks result; behavior should match portingguide
|
|
17
|
+
self.assertEqual(cmp_fn(3, 4, mode="portingguide"), -1)
|
|
18
|
+
|
|
19
|
+
def test_whitespace_modes_first_successful_used(self: Self) -> None:
|
|
20
|
+
# "portingguide" works; "equality" would give None instead
|
|
21
|
+
self.assertEqual(cmp_fn(1, 2, mode="portingguide eq_strict"), -1)
|
|
22
|
+
|
|
23
|
+
def test_whitespace_modes_falls_back_on_exception(self: Self) -> None:
|
|
24
|
+
# int has no __cmp__, so "dunder" fails, then "portingguide" works
|
|
25
|
+
self.assertEqual(cmp_fn(1, 2, mode="magic portingguide"), -1)
|
|
26
|
+
|
|
27
|
+
def test_whitespace_modes_all_fail_raise_exceptiongroup(self: Self) -> None:
|
|
28
|
+
exc: ExceptionGroup
|
|
29
|
+
# For int vs str, "dunder", "poset", and "portingguide" all raise
|
|
30
|
+
with self.assertRaises(ExceptionGroup) as cm:
|
|
31
|
+
cmp_fn(1, "a", mode="magic le portingguide")
|
|
32
|
+
|
|
33
|
+
exc = cm.exception
|
|
34
|
+
# There should be one sub-exception per submode
|
|
35
|
+
self.assertEqual(len(exc.exceptions), 3)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if __name__ == "__main__":
|
|
39
|
+
unittest.main()
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import unittest
|
|
3
|
+
from typing import *
|
|
4
|
+
|
|
5
|
+
from cmp3.core import CmpABC, cmp_le, cmp_mode
|
|
6
|
+
|
|
7
|
+
__all__ = ["TestIncomparable"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Incomparable:
|
|
11
|
+
"""Type whose instances are never <= each other."""
|
|
12
|
+
|
|
13
|
+
def __le__(self: Self, other: Any) -> bool:
|
|
14
|
+
return False
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestIncomparable(unittest.TestCase):
|
|
18
|
+
def test_portingguide_ints(self: Self) -> None:
|
|
19
|
+
self.assertEqual(cmp_mode(1, 1, mode="portingguide"), 0)
|
|
20
|
+
self.assertEqual(cmp_mode(2, 1, mode="portingguide"), 1)
|
|
21
|
+
self.assertEqual(cmp_mode(1, 2, mode="portingguide"), -1)
|
|
22
|
+
|
|
23
|
+
def test_equality_mode(self: Self) -> None:
|
|
24
|
+
self.assertEqual(cmp_mode(10, 10, mode="eq_strict"), 0)
|
|
25
|
+
self.assertIsNone(cmp_mode(10, 11, mode="eq_strict"))
|
|
26
|
+
|
|
27
|
+
def test_poset_total_order_ints(self: Self) -> None:
|
|
28
|
+
self.assertEqual(cmp_le(1, 1), 0)
|
|
29
|
+
self.assertEqual(cmp_le(1, 2), -1)
|
|
30
|
+
self.assertEqual(cmp_le(2, 1), 1)
|
|
31
|
+
|
|
32
|
+
def test_poset_incomparable(self: Self) -> None:
|
|
33
|
+
result: object
|
|
34
|
+
x: Incomparable
|
|
35
|
+
y: Incomparable
|
|
36
|
+
x = Incomparable()
|
|
37
|
+
y = Incomparable()
|
|
38
|
+
result = cmp_le(x, y)
|
|
39
|
+
self.assertTrue(math.isnan(result))
|
|
40
|
+
|
|
41
|
+
def test_cmp_mode_invalid_raises(self: Self) -> None:
|
|
42
|
+
with self.assertRaises(ValueError):
|
|
43
|
+
cmp_mode(1, 2, mode="not-a-mode")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Point(CmpABC):
|
|
47
|
+
"""Simple comparable type based on an integer value."""
|
|
48
|
+
|
|
49
|
+
__slots__ = ("value",)
|
|
50
|
+
|
|
51
|
+
def __init__(self: Self, value: int) -> None:
|
|
52
|
+
self.value = value
|
|
53
|
+
|
|
54
|
+
def __cmp__(self: Self, other: Any) -> int:
|
|
55
|
+
if isinstance(other, Point):
|
|
56
|
+
return (self.value > other.value) - (self.value < other.value)
|
|
57
|
+
return NotImplemented
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class TestPoint(unittest.TestCase):
|
|
61
|
+
|
|
62
|
+
def test_dunder_mode_uses___cmp__(self: Self) -> None:
|
|
63
|
+
a: Point
|
|
64
|
+
b: Point
|
|
65
|
+
|
|
66
|
+
a = Point(1)
|
|
67
|
+
b = Point(2)
|
|
68
|
+
self.assertEqual(cmp_mode(a, b, mode="magic"), -1)
|
|
69
|
+
self.assertEqual(cmp_mode(b, a, mode="magic"), 1)
|
|
70
|
+
self.assertEqual(cmp_mode(a, a, mode="magic"), 0)
|
|
71
|
+
|
|
72
|
+
def test_cmpabc_is_abstract(self: Self) -> None:
|
|
73
|
+
with self.assertRaises(TypeError):
|
|
74
|
+
CmpABC() # type: ignore[abstract]
|
|
75
|
+
|
|
76
|
+
def test_cmpabc_slots(self: Self) -> None:
|
|
77
|
+
self.assertEqual(CmpABC.__slots__, ())
|
|
78
|
+
|
|
79
|
+
def test_subclass_comparisons_use___cmp__(self: Self) -> None:
|
|
80
|
+
a: Point
|
|
81
|
+
b: Point
|
|
82
|
+
c: Point
|
|
83
|
+
a = Point(1)
|
|
84
|
+
b = Point(2)
|
|
85
|
+
c = Point(1)
|
|
86
|
+
|
|
87
|
+
self.assertTrue(a < b)
|
|
88
|
+
self.assertTrue(a <= b)
|
|
89
|
+
self.assertTrue(b > a)
|
|
90
|
+
self.assertTrue(b >= a)
|
|
91
|
+
self.assertTrue(a == c)
|
|
92
|
+
self.assertTrue(a != b)
|
|
93
|
+
|
|
94
|
+
self.assertFalse(a > b)
|
|
95
|
+
self.assertFalse(a >= b)
|
|
96
|
+
self.assertFalse(b < a)
|
|
97
|
+
self.assertFalse(b <= a)
|
|
98
|
+
self.assertFalse(a != c)
|
|
99
|
+
self.assertFalse(a == b)
|
|
100
|
+
|
|
101
|
+
def test_cmpdeco_adds_comparison_methods(self: Self) -> None:
|
|
102
|
+
self.assertTrue(hasattr(Point, "__eq__"))
|
|
103
|
+
self.assertTrue(hasattr(Point, "__lt__"))
|
|
104
|
+
self.assertTrue(hasattr(Point, "__le__"))
|
|
105
|
+
self.assertTrue(hasattr(Point, "__gt__"))
|
|
106
|
+
self.assertTrue(hasattr(Point, "__ge__"))
|
|
107
|
+
self.assertTrue(hasattr(Point, "__ne__"))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
unittest.main()
|
|
@@ -18,4 +18,10 @@ src/cmp3/tests/test_core_TestCmpDecoAndCmpABC.py
|
|
|
18
18
|
src/cmp3/tests/test_core_TestCmpDecoOnPlainClass.py
|
|
19
19
|
src/cmp3/tests/test_core_TestCmpModePoset.py
|
|
20
20
|
src/cmp3/tests/test_core_TestCmpPortingGuide.py
|
|
21
|
-
src/cmp3/tests/test_core_TestCmpPoset.py
|
|
21
|
+
src/cmp3/tests/test_core_TestCmpPoset.py
|
|
22
|
+
src/cmp3/tests/test_fix.py
|
|
23
|
+
src/cmp3/tests/test_m_TestCmpDecoAndCmpABC.py
|
|
24
|
+
src/cmp3/tests/test_m_TestCmpFunctions.py
|
|
25
|
+
src/cmp3/tests/test_redo_Point.py
|
|
26
|
+
src/cmp3/tests/test_redo_TestCmpFunction.py
|
|
27
|
+
src/cmp3/tests/test_redo_TestIncomparable.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|