Skip to content

Commit fb8cf22

Browse files
committed
Merge pull request hamcrest#74 from kay/relative_order
Relative order
2 parents 051d44e + fdab7f4 commit fb8cf22

File tree

3 files changed

+218
-0
lines changed

3 files changed

+218
-0
lines changed

hamcrest-library/matchers.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<factory class="org.hamcrest.collection.IsEmptyIterable"/>
3131
<factory class="org.hamcrest.collection.IsIterableContainingInOrder"/>
3232
<factory class="org.hamcrest.collection.IsIterableContainingInAnyOrder"/>
33+
<factory class="org.hamcrest.collection.IsIterableContainingInRelativeOrder"/>
3334
<factory class="org.hamcrest.collection.IsIterableWithSize"/>
3435
<factory class="org.hamcrest.collection.IsMapContaining"/>
3536
<factory class="org.hamcrest.collection.IsIn"/>
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package org.hamcrest.collection;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import org.hamcrest.Description;
7+
import org.hamcrest.Factory;
8+
import org.hamcrest.Matcher;
9+
import org.hamcrest.TypeSafeDiagnosingMatcher;
10+
11+
import static java.util.Arrays.asList;
12+
import static org.hamcrest.core.IsEqual.equalTo;
13+
14+
public class IsIterableContainingInRelativeOrder<E> extends TypeSafeDiagnosingMatcher<Iterable<? extends E>> {
15+
private final List<Matcher<? super E>> matchers;
16+
17+
public IsIterableContainingInRelativeOrder(List<Matcher<? super E>> matchers) {
18+
this.matchers = matchers;
19+
}
20+
21+
@Override
22+
protected boolean matchesSafely(Iterable<? extends E> iterable, Description mismatchDescription) {
23+
MatchSeriesInRelativeOrder<E> matchSeriesInRelativeOrder = new MatchSeriesInRelativeOrder<E>(matchers, mismatchDescription);
24+
matchSeriesInRelativeOrder.processItems(iterable);
25+
return matchSeriesInRelativeOrder.isFinished();
26+
}
27+
28+
public void describeTo(Description description) {
29+
description.appendText("iterable containing ").appendList("[", ", ", "]", matchers).appendText(" in relative order");
30+
}
31+
32+
private static class MatchSeriesInRelativeOrder<F> {
33+
public final List<Matcher<? super F>> matchers;
34+
private final Description mismatchDescription;
35+
private int nextMatchIx = 0;
36+
private F lastMatchedItem = null;
37+
38+
public MatchSeriesInRelativeOrder(List<Matcher<? super F>> matchers, Description mismatchDescription) {
39+
this.mismatchDescription = mismatchDescription;
40+
if (matchers.isEmpty()) {
41+
throw new IllegalArgumentException("Should specify at least one expected element");
42+
}
43+
this.matchers = matchers;
44+
}
45+
46+
public void processItems(Iterable<? extends F> iterable) {
47+
for (F item : iterable) {
48+
if (nextMatchIx < matchers.size()) {
49+
Matcher<? super F> matcher = matchers.get(nextMatchIx);
50+
if (matcher.matches(item)) {
51+
lastMatchedItem = item;
52+
nextMatchIx++;
53+
}
54+
}
55+
}
56+
}
57+
58+
public boolean isFinished() {
59+
if (nextMatchIx < matchers.size()) {
60+
mismatchDescription.appendDescriptionOf(matchers.get(nextMatchIx)).appendText(" was not found");
61+
if (lastMatchedItem != null) {
62+
mismatchDescription.appendText(" after ").appendValue(lastMatchedItem);
63+
}
64+
return false;
65+
}
66+
return true;
67+
}
68+
69+
}
70+
71+
/**
72+
* Creates a matcher for {@link Iterable}s that matches when a single pass over the
73+
* examined {@link Iterable} yields a series of items, that contains items logically equal to the
74+
* corresponding item in the specified items, in the same relative order
75+
* <p/>
76+
* For example:
77+
* <pre>assertThat(Arrays.asList("a", "b", "c", "d", "e"), containsInRelativeOrder("b", "d"))</pre>
78+
*
79+
* @param items
80+
* the items that must be contained within items provided by an examined {@link Iterable} in the same relative order
81+
*/
82+
@Factory
83+
public static <E> Matcher<Iterable<? extends E>> containsInRelativeOrder(E... items) {
84+
List<Matcher<? super E>> matchers = new ArrayList<Matcher<? super E>>();
85+
for (E item : items) {
86+
matchers.add(equalTo(item));
87+
}
88+
89+
return containsInRelativeOrder(matchers);
90+
}
91+
92+
/**
93+
* Creates a matcher for {@link Iterable}s that matches when a single pass over the
94+
* examined {@link Iterable} yields a series of items, that each satisfying the corresponding
95+
* matcher in the specified matchers, in the same relative order.
96+
* <p/>
97+
* For example:
98+
* <pre>assertThat(Arrays.asList("a", "b", "c", "d", "e"), containsInRelativeOrder(equalTo("b"), equalTo("d")))</pre>
99+
*
100+
* @param itemMatchers
101+
* the matchers that must be satisfied by the items provided by an examined {@link Iterable} in the same relative order
102+
*/
103+
@Factory
104+
public static <E> Matcher<Iterable<? extends E>> containsInRelativeOrder(Matcher<? super E>... itemMatchers) {
105+
return containsInRelativeOrder(asList(itemMatchers));
106+
}
107+
108+
/**
109+
* Creates a matcher for {@link Iterable}s that matches when a single pass over the
110+
* examined {@link Iterable} yields a series of items, that contains items satisfying the corresponding
111+
* matcher in the specified list of matchers, in the same relative order.
112+
* <p/>
113+
* For example:
114+
* <pre>assertThat(Arrays.asList("a", "b", "c", "d", "e"), contains(Arrays.asList(equalTo("b"), equalTo("d"))))</pre>
115+
*
116+
* @param itemMatchers
117+
* a list of matchers, each of which must be satisfied by the items provided by
118+
* an examined {@link Iterable} in the same relative order
119+
*/
120+
@Factory
121+
public static <E> Matcher<Iterable<? extends E>> containsInRelativeOrder(List<Matcher<? super E>> itemMatchers) {
122+
return new IsIterableContainingInRelativeOrder<E>(itemMatchers);
123+
}
124+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package org.hamcrest.collection;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import org.hamcrest.AbstractMatcherTest;
7+
import org.hamcrest.FeatureMatcher;
8+
import org.hamcrest.Matcher;
9+
10+
import static java.util.Arrays.asList;
11+
import static org.hamcrest.collection.IsIterableContainingInRelativeOrder.containsInRelativeOrder;
12+
import static org.hamcrest.core.IsEqual.equalTo;
13+
14+
@SuppressWarnings("unchecked")
15+
public class IsIterableContainingInRelativeOrderTest extends AbstractMatcherTest {
16+
// temporary hack until the Java type system works
17+
private final Matcher<Iterable<? extends WithValue>> contains123 = containsInRelativeOrder(value(1), value(2), value(3));
18+
19+
@Override
20+
protected Matcher<?> createMatcher() {
21+
return containsInRelativeOrder(1, 2);
22+
}
23+
24+
public void testMatchingSingleItemIterable() throws Exception {
25+
assertMatches("Single item iterable", containsInRelativeOrder(1), asList(1));
26+
}
27+
28+
public void testMatchingMultipleItemIterable() throws Exception {
29+
assertMatches("Multiple item iterable", containsInRelativeOrder(1, 2, 3), asList(1, 2, 3));
30+
}
31+
32+
public void testMatchesWithMoreElementsThanExpectedAtBeginning() throws Exception {
33+
assertMatches("More elements at beginning", containsInRelativeOrder(2, 3, 4), asList(1, 2, 3, 4));
34+
}
35+
36+
public void testMatchesWithMoreElementsThanExpectedAtEnd() throws Exception {
37+
assertMatches("More elements at end", containsInRelativeOrder(1, 2, 3), asList(1, 2, 3, 4));
38+
}
39+
40+
public void testMatchesWithMoreElementsThanExpectedInBetween() throws Exception {
41+
assertMatches("More elements in between", containsInRelativeOrder(1, 3), asList(1, 2, 3));
42+
}
43+
44+
public void testMatchesSubSection() throws Exception {
45+
assertMatches("Sub section of iterable", containsInRelativeOrder(2, 3), asList(1, 2, 3, 4));
46+
}
47+
48+
public void testMatchesWithSingleGapAndNotFirstOrLast() throws Exception {
49+
assertMatches("Sub section with single gaps without a first or last match", containsInRelativeOrder(2, 4), asList(1, 2, 3, 4, 5));
50+
}
51+
52+
public void testMatchingSubSectionWithManyGaps() throws Exception {
53+
assertMatches("Sub section with many gaps iterable", containsInRelativeOrder(2, 4, 6), asList(1, 2, 3, 4, 5, 6, 7));
54+
}
55+
56+
public void testDoesNotMatchWithFewerElementsThanExpected() throws Exception {
57+
List<WithValue> valueList = asList(make(1), make(2));
58+
assertMismatchDescription("value with <3> was not found after <WithValue 2>", contains123, valueList);
59+
}
60+
61+
public void testDoesNotMatchIfSingleItemNotFound() throws Exception {
62+
assertMismatchDescription("value with <4> was not found", containsInRelativeOrder(value(4)), asList(make(3)));
63+
}
64+
65+
public void testDoesNotMatchIfOneOfMultipleItemsNotFound() throws Exception {
66+
assertMismatchDescription("value with <3> was not found after <WithValue 2>", contains123, asList(make(1), make(2), make(4)));
67+
}
68+
69+
public void testDoesNotMatchEmptyIterable() throws Exception {
70+
assertMismatchDescription("value with <4> was not found", containsInRelativeOrder(value(4)), new ArrayList<WithValue>());
71+
}
72+
73+
public void testHasAReadableDescription() {
74+
assertDescription("iterable containing [<1>, <2>] in relative order", containsInRelativeOrder(1, 2));
75+
}
76+
77+
public static class WithValue {
78+
private final int value;
79+
public WithValue(int value) { this.value = value; }
80+
public int getValue() { return value; }
81+
@Override public String toString() { return "WithValue " + value; }
82+
}
83+
84+
public static WithValue make(int value) {
85+
return new WithValue(value);
86+
}
87+
88+
public static Matcher<WithValue> value(int value) {
89+
return new FeatureMatcher<WithValue, Integer>(equalTo(value), "value with", "value") {
90+
@Override protected Integer featureValueOf(WithValue actual) { return actual.getValue(); }
91+
};
92+
}
93+
}

0 commit comments

Comments
 (0)