Skip to content

Commit 083d258

Browse files
Add/update unit tests
1 parent 8fb9bdd commit 083d258

File tree

10 files changed

+208
-8
lines changed

10 files changed

+208
-8
lines changed

python/ql/src/Classes/Comparisons/Comparisons.qll

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import semmle.python.ApiGraphs
55

66
/** Holds if `cls` has the `functools.total_ordering` decorator. */
77
predicate totalOrdering(Class cls) {
8-
cls.getADecorator() =
9-
API::moduleImport("functools").getMember("total_ordering").asSource().asExpr()
8+
API::moduleImport("functools")
9+
.getMember("total_ordering")
10+
.asSource()
11+
.flowsTo(DataFlow::exprNode(cls.getADecorator()))
1012
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
| equalsHash.py:13:1:13:8 | Class C | This class implements $@, but does not implement __eq__. | equalsHash.py:14:5:14:23 | Function __hash__ | __hash__ |
2+
| equalsHash.py:17:1:17:11 | Class D | This class implements $@, but does not implement __eq__. | equalsHash.py:18:5:18:23 | Function __hash__ | __hash__ |
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
query: Classes/Comparisons/EqualsOrHash.ql
2+
postprocess: utils/test/InlineExpectationsTestQuery.ql
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
class A:
2+
def __eq__(self, other):
3+
return True
4+
5+
def __hash__(self, other):
6+
return 7
7+
8+
# B is automatically non-hashable - so eq without hash never needs to alert
9+
class B:
10+
def __eq__(self, other):
11+
return True
12+
13+
class C: # $ Alert
14+
def __hash__(self):
15+
return 5
16+
17+
class D(A): # $ Alert
18+
def __hash__(self):
19+
return 4
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
| EqualsOrNotEquals.py:14:1:14:8 | Class B | This class implements $@, but does not implement __eq__. | EqualsOrNotEquals.py:19:5:19:28 | Function __ne__ | __ne__ |
2+
| EqualsOrNotEquals.py:37:1:37:11 | Class D | This class implements $@, but does not implement __ne__. | EqualsOrNotEquals.py:43:5:43:28 | Function __eq__ | __eq__ |
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
class A:
2+
def __init__(self, a):
3+
self.a = a
4+
5+
# OK: __ne__ if not defined delegates to eq automatically
6+
def __eq__(self, other):
7+
return self.a == other.a
8+
9+
assert (A(1) == A(1))
10+
assert not (A(1) == A(2))
11+
assert not (A(1) != A(1))
12+
assert (A(1) != A(2))
13+
14+
class B: # $ Alert
15+
def __init__(self, b):
16+
self.b = b
17+
18+
# BAD: eq defaults to `is`
19+
def __ne__(self, other):
20+
return self.b != other.b
21+
22+
assert not (B(1) == B(1)) # potentially unexpected
23+
assert not (B(2) == B(2))
24+
assert not (B(1) != B(1))
25+
assert (B(1) != B(2))
26+
27+
class C:
28+
def __init__(self, c):
29+
self.c = c
30+
31+
def __eq__(self, other):
32+
return self.c == other.c
33+
34+
def __ne__(self, other):
35+
return self.c != other.c
36+
37+
class D(C): # $ Alert
38+
def __init__(self, c, d):
39+
super().__init__(c)
40+
self.d = d
41+
42+
# BAD: ne is not defined, but the superclass ne is used instead of delegating, which may be incorrect
43+
def __eq__(self, other):
44+
return self.c == other.c and self.d == other.d
45+
46+
assert (D(1,2) == D(1,2))
47+
assert not (D(1,2) == D(1,3))
48+
assert (D(1,2) != D(3,2))
49+
assert not (D(1,2) != D(1,3)) # Potentially unexpected
50+
51+
class E:
52+
def __init__(self, e):
53+
self.e = e
54+
55+
def __eq__(self, other):
56+
return self.e == other.e
57+
58+
def __ne__(self, other):
59+
return not self.__eq__(other)
60+
61+
class F(E):
62+
def __init__(self, e, f):
63+
super().__init__(e)
64+
self.f = f
65+
66+
# OK: superclass ne delegates to eq
67+
def __eq__(self, other):
68+
return self.e == other.e and self.f == other.f
69+
70+
assert (F(1,2) == F(1,2))
71+
assert not (F(1,2) == F(1,3))
72+
assert (F(1,2) != F(3,2))
73+
assert (F(1,2) != F(1,3))
74+
75+
# Variations
76+
77+
class E2:
78+
def __init__(self, e):
79+
self.e = e
80+
81+
def __eq__(self, other):
82+
return self.e == other.e
83+
84+
def __ne__(self, other):
85+
return not self == other
86+
87+
class F2(E2):
88+
def __init__(self, e, f):
89+
super().__init__(e)
90+
self.f = f
91+
92+
# OK: superclass ne delegates to eq
93+
def __eq__(self, other):
94+
return self.e == other.e and self.f == other.f
95+
96+
assert (F2(1,2) == F2(1,2))
97+
assert not (F2(1,2) == F2(1,3))
98+
assert (F2(1,2) != F2(3,2))
99+
assert (F2(1,2) != F2(1,3))
100+
101+
class E3:
102+
def __init__(self, e):
103+
self.e = e
104+
105+
def __eq__(self, other):
106+
return self.e == other.e
107+
108+
def __ne__(self, other):
109+
return not other.__eq__(self)
110+
111+
class F3(E3):
112+
def __init__(self, e, f):
113+
super().__init__(e)
114+
self.f = f
115+
116+
# OK: superclass ne delegates to eq
117+
def __eq__(self, other):
118+
return self.e == other.e and self.f == other.f
119+
120+
assert (F3(1,2) == F3(1,2))
121+
assert not (F3(1,2) == F3(1,3))
122+
assert (F3(1,2) != F3(3,2))
123+
assert (F3(1,2) != F3(1,3))
124+
125+
class E4:
126+
def __init__(self, e):
127+
self.e = e
128+
129+
def __eq__(self, other):
130+
return self.e == other.e
131+
132+
def __ne__(self, other):
133+
return not other == self
134+
135+
class F4(E4):
136+
def __init__(self, e, f):
137+
super().__init__(e)
138+
self.f = f
139+
140+
# OK: superclass ne delegates to eq
141+
def __eq__(self, other):
142+
return self.e == other.e and self.f == other.f
143+
144+
assert (F4(1,2) == F4(1,2))
145+
assert not (F4(1,2) == F4(1,3))
146+
assert (F4(1,2) != F4(3,2))
147+
assert (F4(1,2) != F4(1,3))
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
query: Classes/Comparisons/EqualsOrNotEquals.ql
2+
postprocess: utils/test/InlineExpectationsTestQuery.ql
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
| incomplete_ordering.py:3:1:3:26 | class PartOrdered | Class PartOrdered implements $@, but does not implement __le__ or __gt__ or __ge__. | incomplete_ordering.py:13:5:13:28 | Function PartOrdered.__lt__ | __lt__ |
1+
| incomplete_ordering.py:3:1:3:26 | Class LtWithoutLe | This class implements $@, but does not implement __le__ or __ge__. | incomplete_ordering.py:13:5:13:28 | Function __lt__ | __lt__ |
2+
| incomplete_ordering.py:28:1:28:17 | Class LendGeNoLt | This class implements $@, but does not implement __lt__ or __gt__. | incomplete_ordering.py:29:5:29:28 | Function __le__ | __le__ |
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
Classes/IncompleteOrdering.ql
1+
query: Classes/Comparisons/IncompleteOrdering.ql
2+
postprocess: utils/test/InlineExpectationsTestQuery.ql

python/ql/test/query-tests/Classes/incomplete-ordering/incomplete_ordering.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#Incomplete ordering
22

3-
class PartOrdered(object):
3+
class LtWithoutLe(object): # $ Alert
44
def __eq__(self, other):
55
return self is other
66

@@ -13,6 +13,28 @@ def __hash__(self):
1313
def __lt__(self, other):
1414
return False
1515

16-
#Don't blame a sub-class for super-class's sins.
17-
class DerivedPartOrdered(PartOrdered):
18-
pass
16+
# Don't alert on subclass
17+
class LtWithoutLeSub(LtWithoutLe):
18+
pass
19+
20+
class LeSub(LtWithoutLe):
21+
def __le__(self, other):
22+
return self < other or self == other
23+
24+
class GeSub(LtWithoutLe):
25+
def __ge__(self, other):
26+
return self > other or self == other
27+
28+
class LendGeNoLt: # $ Alert
29+
def __le__(self, other):
30+
return True
31+
32+
def __ge__(self, other):
33+
return other <= self
34+
35+
from functools import total_ordering
36+
37+
@total_ordering
38+
class Total:
39+
def __le__(self, other):
40+
return True

0 commit comments

Comments
 (0)