-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCompareToContract.java
170 lines (150 loc) · 7.42 KB
/
CompareToContract.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package yoyodyne;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction;
import static org.junit.Assert.*;
import static yoyodyne.CompareToContract.CompToZero.*;
/**
Tests the various properties the Comparable contract is supposed to uphold. If you think this is
confusing, realize that like equals(), it is often not possible to implement a one-sided
compareTo() correctly with inheritance - it's a broken concept, but it's still used so often that
you have to do your best with it.
I got the idea of contract-based testing from watching Bill Venners:
https://www.youtube.com/watch?v=bCTZQi2dpl8
This file is copied from https://github.com/GlenKPeterson/TestUtils
The issues have been fixed in the original.
*/
public class CompareToContract {
public enum CompToZero {
LTZ {
@Override public String english() { return "less than"; }
@Override public boolean vsZero(int i) { return i < 0; }
},
GTZ {
@Override public String english() { return "greater than"; }
@Override public boolean vsZero(int i) { return i > 0; }
},
EQZ {
@Override public String english() { return "equal to"; }
@Override public boolean vsZero(int i) { return i == 0; }
};
public abstract String english();
public abstract boolean vsZero(int i);
}
@SuppressWarnings("rawtypes")
private static class NamedPair {
final Comparable a;
final Comparable b;
final String name;
NamedPair(Comparable theA, Comparable theB, String nm) { a = theA; b = theB; name = nm; }
}
@SuppressWarnings("rawtypes")
private static NamedPair t3(Comparable a, Comparable b, String c) {
return new NamedPair(a, b, c);
}
@SuppressWarnings("unchecked")
private static void pairComp(NamedPair first, CompToZero comp, NamedPair second) {
assertTrue("Item A in the " + first.name + " pair must be " + comp.english() +
" item A in the " + second.name + " pair",
comp.vsZero(first.a.compareTo(second.a)));
assertTrue("Item A in the " + first.name + " pair must be " + comp.english() +
" item B in the " + second.name + " pair",
comp.vsZero(first.a.compareTo(second.b)));
assertTrue("Item B in the " + first.name + " pair must be " + comp.english() +
" item A in the " + second.name + " pair",
comp.vsZero(first.b.compareTo(second.a)));
assertTrue("Item B in the " + first.name + " pair must be " + comp.english() +
" item B in the " + second.name + " pair",
comp.vsZero(first.b.compareTo(second.b)));
}
/**
Apply the given function against all unique pairings of items in the list. Does this belong on Function2 instead
of List?
*/
static <T> void permutations(List<T> items, BiFunction<? super T,? super T,?> f) {
for (int i = 0; i < items.size(); i++) {
for (int j = i + 1; j < items.size(); j++) {
f.apply(items.get(i), items.get(j));
}
}
}
/**
Tests the various properties the Comparable contract is supposed to uphold. Also tests that
the behavior of compareTo() is compatible with equals() and hashCode() which is strongly
suggested, but not actually required. Write your own test if you don't want that. Expects
three pair of unique objects. Within a pair, the two objects should be equal. Both objects in
the first pair are less than the ones in the second pair, which in turn are less than the
objects in the third pair.
See note in class documentation.
*/
// Many of the comments in this method are paraphrases or direct quotes from the Javadocs for
// the Comparable interface. That is where this contract is specified.
// https://docs.oracle.com/javase/8/docs/api/
// THIS IS WHAT MAKES KOTLIN COMPILE SLOW.
// A great fix is to change the signature to:
// public static <S extends Comparable<? super S>>
// void testCompareTo(
// @NotNull S least1,
// @NotNull S least2,
// @NotNull S middle1,
// @NotNull S middle2,
// @NotNull S greatest1,
// @NotNull S greatest2
// ) {
// Still, Kotlin should:
// 1. Not be so slow with this
// 2. Cache whatever it decides about it so that calling this method multiple times
// is not additive in terms of slowness.
@SuppressWarnings("unchecked")
public static <S extends Comparable<? super S>, T1 extends S, T2 extends S, T3 extends S>
void testCompareTo(T1 least1, T1 least2, T2 middle1, T2 middle2, T3 greatest1, T3 greatest2) {
AtomicBoolean anySame = new AtomicBoolean();
permutations(Arrays.asList(least1, least2, middle1, middle2, greatest1, greatest2),
(S a, S b) -> {
if (a == b) {
anySame.set(true);
}
return null;
});
if (anySame.get()) {
throw new IllegalArgumentException("You must provide three pair of different objects in order");
}
NamedPair least = t3(least1, least2, "Least");
NamedPair middle = t3(middle1, middle2, "Middle");
NamedPair greatest = t3(greatest1, greatest2, "Greatest");
for (NamedPair comp : Arrays.asList(least, middle, greatest)) {
// Consistent with equals: (e1.compareTo(e2) == 0) if and only if e1.equals(e2)
pairComp(comp, EQZ, comp);
assertEquals(comp.name + " A must be compatibly equal to its paired B element", comp.a, comp.b);
assertEquals(comp.name + " B must be compatibly equal to its paired A element", comp.b, comp.a);
}
int i = 0;
for (@SuppressWarnings("rawtypes")
Comparable comp : Arrays.asList(least1, least2, middle1, middle2, greatest1, greatest2)
) {
i++;
assertEquals("item.equals(itself) should have return true for item " + i, comp, comp);
// It is strongly recommended (though not required) that natural orderings be consistent
// with equals.
// One exception is java.math.BigDecimal, whose natural ordering equates BigDecimal
// objects with equal values and different precisions (such as 4.0 and 4.00).
// null is not an instance of any class, and e.compareTo(null) should throw a
// NullPointerException even though e.equals(null) returns false.
try {
//noinspection ConstantConditions,ResultOfMethodCallIgnored
comp.compareTo(null);
fail("e.compareTo(null) should throw a NullPointerException even though e.equals(null)" +
" returns false, but item " + i + "did not.");
} catch (NullPointerException | IllegalArgumentException ignore) {
}
assertNotEquals("item.equals(null) should always be false. Item " + i + " failed", null, comp);
}
pairComp(least, LTZ, middle);
pairComp(least, LTZ, greatest);
pairComp(middle, LTZ, greatest);
pairComp(greatest, GTZ, middle);
pairComp(greatest, GTZ, least);
pairComp(middle, GTZ, least);
}
}