Skip to content

Commit 2b57a43

Browse files
committed
Create Ordered interface for passing an 'inOrder()' fluent constraint on top of contains, as well as fixing a bug in iterablesubject, and add some tests. Also, normalize list/iterables methods of comparison.
1 parent 288c29a commit 2b57a43

File tree

8 files changed

+201
-105
lines changed

8 files changed

+201
-105
lines changed

src/main/java/org/junit/contrib/truth/subjects/CollectionSubject.java

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,20 @@
1616
*/
1717
package org.junit.contrib.truth.subjects;
1818

19-
import java.util.ArrayList;
20-
import java.util.Arrays;
19+
import static org.junit.contrib.truth.subjects.SubjectUtils.accumulate;
20+
import static org.junit.contrib.truth.subjects.SubjectUtils.countOf;
21+
2122
import java.util.Collection;
2223
import java.util.HashSet;
24+
import java.util.Iterator;
2325
import java.util.List;
2426
import java.util.Set;
2527

2628
import org.junit.contrib.truth.FailureStrategy;
2729

2830
public class CollectionSubject<S extends CollectionSubject<S, T, C>, T, C extends Collection<T>> extends IterableSubject<S, T, C> {
2931

30-
@SuppressWarnings("unchecked")
32+
@SuppressWarnings({ "unchecked", "rawtypes" })
3133
public static <T, C extends Collection<T>> CollectionSubject<? extends CollectionSubject<?, T, C>, T, C> create(
3234
FailureStrategy failureStrategy, Collection<T> list) {
3335
return new CollectionSubject(failureStrategy, list);
@@ -48,6 +50,9 @@ protected CollectionSubject(FailureStrategy failureStrategy, C list) {
4850
return nextChain();
4951
}
5052

53+
/**
54+
* Attests that a Collection is empty or fails.
55+
*/
5156
@Override public And<S> isEmpty() {
5257
if (!getSubject().isEmpty()) {
5358
fail("isEmpty");
@@ -59,25 +64,27 @@ protected CollectionSubject(FailureStrategy failureStrategy, C list) {
5964
* Attests that a Collection contains at least one of the provided
6065
* objects or fails.
6166
*/
62-
public And<S> containsAnyOf(Object ... items) {
67+
public And<S> containsAnyOf(Object first, Object second, Object ... rest) {
6368
Collection<?> collection = getSubject();
64-
for (Object item : items) {
69+
for (Object item : accumulate(first, second, rest)) {
6570
if (collection.contains(item)) {
6671
return nextChain();
6772
}
6873
}
69-
fail("contains", (Object[])items);
74+
fail("contains", accumulate(first, second, rest));
7075
return nextChain();
7176
}
7277

78+
7379
/**
7480
* Attests that a Collection contains all of the provided objects or fails.
7581
* This copes with duplicates in both the Collection and the parameters.
7682
*/
77-
public And<S> containsAllOf(Object ... items) {
83+
public Ordered<S> contains(Object first, Object second, Object ... rest) {
84+
final And<S> next = nextChain();
7885
Collection<?> collection = getSubject();
7986
// Arrays.asList() does not support remove() so we need a mutable copy.
80-
List<Object> required = new ArrayList<Object>(Arrays.asList(items));
87+
List<Object> required = accumulate(first, second, rest);
8188
for (Object item : collection) {
8289
required.remove(item);
8390
}
@@ -87,21 +94,37 @@ public And<S> containsAllOf(Object ... items) {
8794
Object[] params = new Object[missing.size()];
8895
int n = 0;
8996
for (Object item : missing) {
90-
int count = countOf(item, items);
97+
int count = countOf(item, accumulate(first, second, rest));
9198
params[n++] = (count > 1) ? count + " copies of " + item : item;
9299
}
93100
fail("contains", params);
94101
}
95-
return nextChain();
96-
}
97102

98-
private static int countOf(Object t, Object... items) {
99-
int count = 0;
100-
for (Object item : items) {
101-
if (t == null ? (item == null) : t.equals(item)) {
102-
count++;
103+
final List<?> expectedItems = accumulate(first, second, rest);
104+
return new Ordered<S>() {
105+
@Override public And<S> inOrder() {
106+
Iterator<T> actualItems = getSubject().iterator();
107+
for (Object expected : expectedItems) {
108+
if (!actualItems.hasNext()) {
109+
fail("iterates through", expectedItems);
110+
} else {
111+
Object actual = actualItems.next();
112+
if (actual == expected || actual != null && actual.equals(expected)) {
113+
continue;
114+
} else {
115+
fail("iterates through", expectedItems);
116+
}
117+
}
118+
}
119+
if (actualItems.hasNext()) {
120+
fail("iterates through", expectedItems);
121+
}
122+
return nextChain();
103123
}
104-
}
105-
return count;
124+
@Override public S and() {
125+
return nextChain().and();
126+
}
127+
};
106128
}
129+
107130
}

src/main/java/org/junit/contrib/truth/subjects/IterableSubject.java

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,8 @@
1616

1717
package org.junit.contrib.truth.subjects;
1818

19-
import java.util.ArrayList;
2019
import java.util.Arrays;
21-
import java.util.HashMap;
22-
import java.util.List;
23-
import java.util.Map;
20+
import java.util.Iterator;
2421

2522
import org.junit.contrib.truth.FailureStrategy;
2623

@@ -60,28 +57,28 @@ public And<S> isEmpty() {
6057
return nextChain();
6158
}
6259

63-
public And<S> hasContentsInOrder(Object... expected) {
64-
// TODO(kevinb): prettier error message
65-
List<Object> target = new ArrayList<Object>();
66-
for (Object t : getSubject()) {
67-
target.add(t);
60+
/**
61+
* Asserts that the items are supplied in the order given by the iterable. For
62+
* Collections and other things which contain items but may not have guaranteed
63+
* iteration order, this method should be overridden.
64+
*/
65+
public And<S> iteratesOverSequence(Object... expectedItems) {
66+
Iterator<T> actualItems = getSubject().iterator();
67+
for (Object expected : expectedItems) {
68+
if (!actualItems.hasNext()) {
69+
fail("iterates through", Arrays.asList(expectedItems));
70+
} else {
71+
Object actual = actualItems.next();
72+
if (actual == expected || actual != null && actual.equals(expected)) {
73+
continue;
74+
} else {
75+
fail("iterates through", Arrays.asList(expectedItems));
76+
}
77+
}
6878
}
69-
check().that(target).isEqualTo(Arrays.asList(expected));
70-
return nextChain();
71-
}
72-
73-
public And<S> hasContentsAnyOrder(Object... expected) {
74-
check().that(createFakeMultiset(getSubject()))
75-
.isEqualTo(createFakeMultiset(Arrays.asList(expected)));
76-
return nextChain();
77-
}
78-
79-
private static Map<Object, Integer> createFakeMultiset(Iterable<?> iterable) {
80-
Map<Object, Integer> map = new HashMap<Object, Integer>();
81-
for (Object t : iterable) {
82-
Integer count = map.get(t);
83-
map.put(t, (count == null) ? 1 : count + 1);
79+
if (actualItems.hasNext()) {
80+
fail("iterates through", Arrays.asList(expectedItems));
8481
}
85-
return map;
82+
return nextChain();
8683
}
8784
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.junit.contrib.truth.subjects;
2+
3+
import org.junit.contrib.truth.subjects.Subject.And;
4+
5+
public interface Ordered<Q> extends And<Q> {
6+
7+
/**
8+
* An additional assertion, implemented by some containment subjects
9+
* which allows for a further constraint of orderedness.
10+
*/
11+
And<Q> inOrder();
12+
13+
}
14+
15+

src/main/java/org/junit/contrib/truth/subjects/Subject.java

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
* Copyright (c) 2011 David Saff
33
* Copyright (c) 2011 Christian Gruber
4-
*
4+
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
77
* You may obtain a copy of the License at
@@ -22,7 +22,7 @@
2222
/**
2323
* Propositions for arbitrarily typed subjects and for properties
2424
* of Object
25-
*
25+
*
2626
* @author David Saff
2727
* @author Christian Gruber (cgruber@israfil.net)
2828
*/
@@ -34,9 +34,9 @@ public class Subject<S extends Subject<S,T>,T> {
3434
public Subject(FailureStrategy failureStrategy, T subject) {
3535
this.failureStrategy = failureStrategy;
3636
this.subject = subject;
37-
37+
3838
this.chain = new And<S>(){
39-
@SuppressWarnings("unchecked")
39+
@SuppressWarnings("unchecked")
4040
@Override public S and() {
4141
return (S)Subject.this;
4242
}
@@ -50,10 +50,10 @@ public Subject(FailureStrategy failureStrategy, T subject) {
5050
protected final And<S> nextChain() {
5151
return chain;
5252
}
53-
53+
5454
public And<S> is(T other) {
5555

56-
if (getSubject() == null) {
56+
if (getSubject() == null) {
5757
if(other != null) {
5858
fail("is", other);
5959
}
@@ -71,7 +71,7 @@ public And<S> isNull() {
7171
}
7272
return nextChain();
7373
}
74-
74+
7575
public And<S> isNotNull() {
7676
if (getSubject() == null) {
7777
failWithoutSubject("is not null");
@@ -80,7 +80,7 @@ public And<S> isNotNull() {
8080
}
8181

8282
public And<S> isEqualTo(Object other) {
83-
if (getSubject() == null) {
83+
if (getSubject() == null) {
8484
if(other != null) {
8585
fail("is equal to", other);
8686
}
@@ -93,7 +93,7 @@ public And<S> isEqualTo(Object other) {
9393
}
9494

9595
public And<S> isNotEqualTo(Object other) {
96-
if (getSubject() == null) {
96+
if (getSubject() == null) {
9797
if(other == null) {
9898
fail("is not equal to", other);
9999
}
@@ -127,6 +127,11 @@ protected TestVerb check() {
127127
return new TestVerb(failureStrategy);
128128
}
129129

130+
/**
131+
* Assembles a failure message and passes such to the FailureStrategy
132+
* @param verb the act being asserted
133+
* @param messageParts the expectations against which the subject is compared
134+
*/
130135
protected void fail(String verb, Object... messageParts) {
131136
String message = "Not true that ";
132137
message += "<" + getSubject() + "> " + verb;
@@ -144,13 +149,13 @@ protected void failWithoutSubject(String verb) {
144149

145150
/**
146151
* A convenience class to allow for chaining in the fluent API
147-
* style, such that subjects can make propositions in series.
152+
* style, such that subjects can make propositions in series.
148153
* i.e. ASSERT.that(blah).isNotNull().and().contains(b).and().isNotEmpty();
149154
*/
150155
public static interface And<C> {
151156
/**
152157
* Returns the next object in the chain of anded objects.
153158
*/
154159
C and();
155-
}
160+
}
156161
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright (c) 2011 David Saff
3+
* Copyright (c) 2011 Christian Gruber
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.junit.contrib.truth.subjects;
18+
19+
import java.util.Arrays;
20+
import java.util.LinkedList;
21+
import java.util.List;
22+
23+
24+
/**
25+
* Utility methods used in Subject<T> implementors.
26+
*
27+
* @author Christian Gruber (cgruber@israfil.net)
28+
*/
29+
class SubjectUtils {
30+
31+
private SubjectUtils() {}
32+
33+
static <T> List<T> accumulate(T first, T second, T ... rest) {
34+
List<T> items = new LinkedList<T>();
35+
items.add(first);
36+
items.add(second);
37+
items.addAll(Arrays.asList(rest));
38+
return items;
39+
}
40+
41+
static <T> int countOf(T t, Iterable<T> items) {
42+
int count = 0;
43+
for (T item : items) {
44+
if (t == null ? (item == null) : t.equals(item)) {
45+
count++;
46+
}
47+
}
48+
return count;
49+
}
50+
51+
}

0 commit comments

Comments
 (0)