/
IterableSubject.java
2075 lines (1941 loc) · 90.3 KB
/
IterableSubject.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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (c) 2011 Google, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.truth;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.lenientFormat;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.size;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.truth.Fact.fact;
import static com.google.common.truth.Fact.simpleFact;
import static com.google.common.truth.IterableSubject.ElementFactGrouping.ALL_IN_ONE_FACT;
import static com.google.common.truth.IterableSubject.ElementFactGrouping.FACT_PER_ELEMENT;
import static com.google.common.truth.SubjectUtils.accumulate;
import static com.google.common.truth.SubjectUtils.annotateEmptyStrings;
import static com.google.common.truth.SubjectUtils.countDuplicates;
import static com.google.common.truth.SubjectUtils.countDuplicatesAndAddTypeInfo;
import static com.google.common.truth.SubjectUtils.countDuplicatesAndMaybeAddTypeInfoReturnObject;
import static com.google.common.truth.SubjectUtils.entryString;
import static com.google.common.truth.SubjectUtils.hasMatchingToStringPair;
import static com.google.common.truth.SubjectUtils.iterableToCollection;
import static com.google.common.truth.SubjectUtils.iterableToList;
import static com.google.common.truth.SubjectUtils.objectToTypeName;
import static com.google.common.truth.SubjectUtils.retainMatchingToString;
import static java.util.Arrays.asList;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.LinkedHashMultiset;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multiset;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import com.google.common.truth.Correspondence.DiffFormatter;
import com.google.common.truth.SubjectUtils.DuplicateGroupedAndTyped;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Propositions for {@link Iterable} subjects.
*
* <p><b>Note:</b>
*
* <ul>
* <li>Assertions may iterate through the given {@link Iterable} more than once. If you have an
* unusual implementation of {@link Iterable} which does not support multiple iterations
* (sometimes known as a "one-shot iterable"), you must copy your iterable into a collection
* which does (e.g. {@code ImmutableList.copyOf(iterable)} or, if your iterable may contain
* null, {@code newArrayList(iterable)}). If you don't, you may see surprising failures.
* <li>Assertions may also require that the elements in the given {@link Iterable} implement
* {@link Object#hashCode} correctly.
* </ul>
*
* @author Kurt Alfred Kluever
* @author Pete Gillin
*/
// Can't be final since MultisetSubject and SortedSetSubject extend it
public class IterableSubject extends Subject {
private final Iterable<?> actual;
/**
* Constructor for use by subclasses. If you want to create an instance of this class itself, call
* {@link Subject#check(String, Object...) check(...)}{@code .that(actual)}.
*/
protected IterableSubject(FailureMetadata metadata, @Nullable Iterable<?> iterable) {
super(metadata, iterable);
this.actual = iterable;
}
@Override
protected String actualCustomStringRepresentation() {
if (actual != null) {
// Check the value of iterable.toString() against the default Object.toString() implementation
// so we can avoid things like "com.google.common.graph.Traverser$GraphTraverser$1@5e316c74"
String objectToString =
actual.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(actual));
if (actual.toString().equals(objectToString)) {
return Iterables.toString(actual);
}
}
return super.actualCustomStringRepresentation();
}
@Override
public void isEqualTo(@Nullable Object expected) {
@SuppressWarnings("UndefinedEquals") // method contract requires testing iterables for equality
boolean equal = Objects.equal(actual, expected);
if (equal) {
return;
}
// Fail but with a more descriptive message:
if (actual instanceof List && expected instanceof List) {
containsExactlyElementsIn((List<?>) expected).inOrder();
} else if ((actual instanceof Set && expected instanceof Set)
|| (actual instanceof Multiset && expected instanceof Multiset)) {
containsExactlyElementsIn((Collection<?>) expected);
} else {
/*
* TODO(b/18430105): Consider a special message if comparing incompatible collection types
* (similar to what MultimapSubject has).
*/
super.isEqualTo(expected);
}
}
/** Fails if the subject is not empty. */
public final void isEmpty() {
if (!Iterables.isEmpty(actual)) {
failWithActual(simpleFact("expected to be empty"));
}
}
/** Fails if the subject is empty. */
public final void isNotEmpty() {
if (Iterables.isEmpty(actual)) {
failWithoutActual(simpleFact("expected not to be empty"));
}
}
/** Fails if the subject does not have the given size. */
public final void hasSize(int expectedSize) {
checkArgument(expectedSize >= 0, "expectedSize(%s) must be >= 0", expectedSize);
int actualSize = size(actual);
check("size()").that(actualSize).isEqualTo(expectedSize);
}
/** Checks (with a side-effect failure) that the subject contains the supplied item. */
public final void contains(@Nullable Object element) {
if (!Iterables.contains(actual, element)) {
List<Object> elementList = newArrayList(element);
if (hasMatchingToStringPair(actual, elementList)) {
failWithoutActual(
fact("expected to contain", element),
fact("an instance of", objectToTypeName(element)),
simpleFact("but did not"),
fact(
"though it did contain",
countDuplicatesAndAddTypeInfo(
retainMatchingToString(actual, elementList /* itemsToCheck */))),
fullContents());
} else {
failWithActual("expected to contain", element);
}
}
}
/** Checks (with a side-effect failure) that the subject does not contain the supplied item. */
public final void doesNotContain(@Nullable Object element) {
if (Iterables.contains(actual, element)) {
failWithActual("expected not to contain", element);
}
}
/** Checks that the subject does not contain duplicate elements. */
public final void containsNoDuplicates() {
List<Multiset.Entry<?>> duplicates = newArrayList();
for (Multiset.Entry<?> entry : LinkedHashMultiset.create(actual).entrySet()) {
if (entry.getCount() > 1) {
duplicates.add(entry);
}
}
if (!duplicates.isEmpty()) {
failWithoutActual(
simpleFact("expected not to contain duplicates"),
fact("but contained", duplicates),
fullContents());
}
}
/** Checks that the subject contains at least one of the provided objects or fails. */
public final void containsAnyOf(
@Nullable Object first, @Nullable Object second, @Nullable Object @Nullable ... rest) {
containsAnyIn(accumulate(first, second, rest));
}
/**
* Checks that the subject contains at least one of the objects contained in the provided
* collection or fails.
*/
// TODO(cpovirk): Consider using makeElementFacts-style messages here, in contains(), etc.
public final void containsAnyIn(Iterable<?> expected) {
Collection<?> actual = iterableToCollection(this.actual);
for (Object item : expected) {
if (actual.contains(item)) {
return;
}
}
if (hasMatchingToStringPair(actual, expected)) {
failWithoutActual(
fact("expected to contain any of", countDuplicatesAndAddTypeInfo(expected)),
simpleFact("but did not"),
fact(
"though it did contain",
countDuplicatesAndAddTypeInfo(
retainMatchingToString(this.actual, expected /* itemsToCheck */))),
fullContents());
} else {
failWithActual("expected to contain any of", expected);
}
}
/**
* Checks that the subject contains at least one of the objects contained in the provided array or
* fails.
*/
public final void containsAnyIn(Object[] expected) {
containsAnyIn(asList(expected));
}
/**
* Checks that the actual iterable contains at least all of the expected elements or fails. If an
* element appears more than once in the expected elements to this call then it must appear at
* least that number of times in the actual elements.
*
* <p>To also test that the contents appear in the given order, make a call to {@code inOrder()}
* on the object returned by this method. The expected elements must appear in the given order
* within the actual elements, but they are not required to be consecutive.
*/
@CanIgnoreReturnValue
public final Ordered containsAtLeast(
@Nullable Object firstExpected,
@Nullable Object secondExpected,
@Nullable Object @Nullable ... restOfExpected) {
return containsAtLeastElementsIn(accumulate(firstExpected, secondExpected, restOfExpected));
}
/**
* Checks that the actual iterable contains at least all of the expected elements or fails. If an
* element appears more than once in the expected elements then it must appear at least that
* number of times in the actual elements.
*
* <p>To also test that the contents appear in the given order, make a call to {@code inOrder()}
* on the object returned by this method. The expected elements must appear in the given order
* within the actual elements, but they are not required to be consecutive.
*/
@CanIgnoreReturnValue
public final Ordered containsAtLeastElementsIn(Iterable<?> expectedIterable) {
List<?> actual = Lists.newLinkedList(this.actual);
final Collection<?> expected = iterableToCollection(expectedIterable);
List<Object> missing = newArrayList();
List<Object> actualNotInOrder = newArrayList();
boolean ordered = true;
// step through the expected elements...
for (Object e : expected) {
int index = actual.indexOf(e);
if (index != -1) { // if we find the element in the actual list...
// drain all the elements that come before that element into actualNotInOrder
moveElements(actual, actualNotInOrder, index);
// and remove the element from the actual list
actual.remove(0);
} else { // otherwise try removing it from actualNotInOrder...
if (actualNotInOrder.remove(e)) { // if it was in actualNotInOrder, we're not in order
ordered = false;
} else { // if it's not in actualNotInOrder, we're missing an expected element
missing.add(e);
}
}
}
// if we have any missing expected elements, fail
if (!missing.isEmpty()) {
return failAtLeast(expected, missing);
}
/*
* TODO(cpovirk): In the NotInOrder case, also include a Fact that shows _only_ the required
* elements (that is, without any extras) but in the order they were actually found. That should
* make it easier for users to compare the actual order of the required elements to the expected
* order. Or, if that's too much trouble, at least try to find a better title for the full
* actual iterable than the default of "but was," which may _sound_ like it should show only the
* required elements, rather than the full actual iterable.
*/
return ordered
? IN_ORDER
: new Ordered() {
@Override
public void inOrder() {
failWithActual(
simpleFact("required elements were all found, but order was wrong"),
fact("expected order for required elements", expected));
}
};
}
/**
* Checks that the actual iterable contains at least all of the expected elements or fails. If an
* element appears more than once in the expected elements then it must appear at least that
* number of times in the actual elements.
*
* <p>To also test that the contents appear in the given order, make a call to {@code inOrder()}
* on the object returned by this method. The expected elements must appear in the given order
* within the actual elements, but they are not required to be consecutive.
*/
@CanIgnoreReturnValue
public final Ordered containsAtLeastElementsIn(Object[] expected) {
return containsAtLeastElementsIn(asList(expected));
}
private Ordered failAtLeast(Collection<?> expected, Collection<?> missingRawObjects) {
Collection<?> nearMissRawObjects =
retainMatchingToString(actual, missingRawObjects /* itemsToCheck */);
ImmutableList.Builder<Fact> facts = ImmutableList.builder();
facts.addAll(
makeElementFactsForBoth(
"missing", missingRawObjects, "though it did contain", nearMissRawObjects));
/*
* TODO(cpovirk): Make makeElementFactsForBoth support generating just "though it did contain"
* rather than "though it did contain (2)?" Users might interpret the number as the *total*
* number of actual elements (or the total number of non-matched elements). (Frankly, they might
* think that even *without* the number.... Can we do better than the phrase "though it did
* contain," which has been our standard so far?) Or maybe it's all clear enough in context,
* since this error shows up only to inform users of type mismatches.
*/
facts.add(fact("expected to contain at least", expected));
facts.add(butWas());
failWithoutActual(facts.build());
return ALREADY_FAILED;
}
/**
* Removes at most the given number of available elements from the input list and adds them to the
* given output collection.
*/
private static void moveElements(List<?> input, Collection<Object> output, int maxElements) {
for (int i = 0; i < maxElements; i++) {
output.add(input.remove(0));
}
}
/**
* Checks that a subject contains exactly the provided objects or fails.
*
* <p>Multiplicity is respected. For example, an object duplicated exactly 3 times in the
* parameters asserts that the object must likewise be duplicated exactly 3 times in the subject.
*
* <p>To also test that the contents appear in the given order, make a call to {@code inOrder()}
* on the object returned by this method.
*
* <p>To test that the iterable contains the same elements as an array, prefer {@link
* #containsExactlyElementsIn(Object[])}. It makes clear that the given array is a list of
* elements, not an element itself. This helps human readers and avoids a compiler warning.
*/
@CanIgnoreReturnValue
public final Ordered containsExactly(@Nullable Object @Nullable ... varargs) {
List<Object> expected = (varargs == null) ? newArrayList((Object) null) : asList(varargs);
return containsExactlyElementsIn(
expected, varargs != null && varargs.length == 1 && varargs[0] instanceof Iterable);
}
/**
* Checks that a subject contains exactly the provided objects or fails.
*
* <p>Multiplicity is respected. For example, an object duplicated exactly 3 times in the {@code
* Iterable} parameter asserts that the object must likewise be duplicated exactly 3 times in the
* subject.
*
* <p>To also test that the contents appear in the given order, make a call to {@code inOrder()}
* on the object returned by this method.
*/
@CanIgnoreReturnValue
public final Ordered containsExactlyElementsIn(Iterable<?> expected) {
return containsExactlyElementsIn(expected, false);
}
/**
* Checks that a subject contains exactly the provided objects or fails.
*
* <p>Multiplicity is respected. For example, an object duplicated exactly 3 times in the array
* parameter asserts that the object must likewise be duplicated exactly 3 times in the subject.
*
* <p>To also test that the contents appear in the given order, make a call to {@code inOrder()}
* on the object returned by this method.
*/
@CanIgnoreReturnValue
public final Ordered containsExactlyElementsIn(Object[] expected) {
return containsExactlyElementsIn(asList(expected));
}
private Ordered containsExactlyElementsIn(
final Iterable<?> required, boolean addElementsInWarning) {
Iterator<?> actualIter = actual.iterator();
Iterator<?> requiredIter = required.iterator();
if (!requiredIter.hasNext()) {
if (actualIter.hasNext()) {
isEmpty(); // fails
return ALREADY_FAILED;
} else {
return IN_ORDER;
}
}
// Step through both iterators comparing elements pairwise.
boolean isFirst = true;
while (actualIter.hasNext() && requiredIter.hasNext()) {
Object actualElement = actualIter.next();
Object requiredElement = requiredIter.next();
// As soon as we encounter a pair of elements that differ, we know that inOrder()
// cannot succeed, so we can check the rest of the elements more normally.
// Since any previous pairs of elements we iterated over were equal, they have no
// effect on the result now.
if (!Objects.equal(actualElement, requiredElement)) {
if (isFirst && !actualIter.hasNext() && !requiredIter.hasNext()) {
/*
* There's exactly one actual element and exactly one expected element, and they don't
* match, so throw a ComparisonFailure. The logical way to do that would be
* `check(...).that(actualElement).isEqualTo(requiredElement)`. But isEqualTo has magic
* behavior for arrays and primitives, behavior that's inconsistent with how this method
* otherwise behaves. For consistency, we want to rely only on the equal() call we've
* already made. So we expose a special method for this and call it from here.
*
* TODO(b/135918662): Consider always throwing ComparisonFailure if there is exactly one
* missing and exactly one extra element, even if there were additional (matching)
* elements. However, this will probably be useful less often, and it will be tricky to
* explain. First, what would we say, "value of: iterable.onlyElementThatDidNotMatch()?"
* And second, it feels weirder to call out a single element when the expected and actual
* values had multiple elements. Granted, Fuzzy Truth already does this, so maybe it's OK?
* But Fuzzy Truth doesn't (yet) make the mismatched value so prominent.
*/
checkNoNeedToDisplayBothValues("onlyElement()")
.that(actualElement)
.failEqualityCheckForEqualsWithoutDescription(requiredElement);
return ALREADY_FAILED;
}
// Missing elements; elements that are not missing will be removed as we iterate.
Collection<Object> missing = newArrayList();
missing.add(requiredElement);
Iterators.addAll(missing, requiredIter);
// Extra elements that the subject had but shouldn't have.
Collection<Object> extra = newArrayList();
// Remove all actual elements from missing, and add any that weren't in missing
// to extra.
if (!missing.remove(actualElement)) {
extra.add(actualElement);
}
while (actualIter.hasNext()) {
Object item = actualIter.next();
if (!missing.remove(item)) {
extra.add(item);
}
}
if (missing.isEmpty() && extra.isEmpty()) {
/*
* This containsExactly() call is a success. But the iterables were not in the same order,
* so return an object that will fail the test if the user calls inOrder().
*/
return new Ordered() {
@Override
public void inOrder() {
failWithActual(
simpleFact("contents match, but order was wrong"), fact("expected", required));
}
};
}
return failExactly(required, addElementsInWarning, missing, extra);
}
isFirst = false;
}
// Here, we must have reached the end of one of the iterators without finding any
// pairs of elements that differ. If the actual iterator still has elements, they're
// extras. If the required iterator has elements, they're missing elements.
if (actualIter.hasNext()) {
return failExactly(
required,
addElementsInWarning,
/* missingRawObjects= */ ImmutableList.of(),
/* extraRawObjects= */ newArrayList(actualIter));
} else if (requiredIter.hasNext()) {
return failExactly(
required,
addElementsInWarning,
/* missingRawObjects= */ newArrayList(requiredIter),
/* extraRawObjects= */ ImmutableList.of());
}
// If neither iterator has elements, we reached the end and the elements were in
// order, so inOrder() can just succeed.
return IN_ORDER;
}
private Ordered failExactly(
Iterable<?> required,
boolean addElementsInWarning,
Collection<?> missingRawObjects,
Collection<?> extraRawObjects) {
ImmutableList.Builder<Fact> facts = ImmutableList.builder();
facts.addAll(
makeElementFactsForBoth("missing", missingRawObjects, "unexpected", extraRawObjects));
facts.add(fact("expected", required));
facts.add(butWas());
if (addElementsInWarning) {
facts.add(
simpleFact(
"Passing an iterable to the varargs method containsExactly(Object...) is "
+ "often not the correct thing to do. Did you mean to call "
+ "containsExactlyElementsIn(Iterable) instead?"));
}
failWithoutActual(facts.build());
return ALREADY_FAILED;
}
private static ImmutableList<Fact> makeElementFactsForBoth(
String firstKey,
Collection<?> firstCollection,
String secondKey,
Collection<?> secondCollection) {
// TODO(kak): Possible enhancement: Include "[1 copy]" if the element does appear in
// the subject but not enough times. Similarly for unexpected extra items.
boolean addTypeInfo = hasMatchingToStringPair(firstCollection, secondCollection);
DuplicateGroupedAndTyped first =
countDuplicatesAndMaybeAddTypeInfoReturnObject(firstCollection, addTypeInfo);
DuplicateGroupedAndTyped second =
countDuplicatesAndMaybeAddTypeInfoReturnObject(secondCollection, addTypeInfo);
ElementFactGrouping grouping = pickGrouping(first.entrySet(), second.entrySet());
ImmutableList.Builder<Fact> facts = ImmutableList.builder();
ImmutableList<Fact> firstFacts = makeElementFacts(firstKey, first, grouping);
ImmutableList<Fact> secondFacts = makeElementFacts(secondKey, second, grouping);
facts.addAll(firstFacts);
if (firstFacts.size() > 1 && secondFacts.size() > 1) {
facts.add(simpleFact(""));
}
facts.addAll(secondFacts);
facts.add(simpleFact("---"));
return facts.build();
}
/**
* Returns a list of facts (zero, one, or many, depending on the number of elements and the
* grouping policy) describing the given missing, unexpected, or near-miss elements.
*/
private static ImmutableList<Fact> makeElementFacts(
String label, DuplicateGroupedAndTyped elements, ElementFactGrouping grouping) {
if (elements.isEmpty()) {
return ImmutableList.of();
}
if (grouping == ALL_IN_ONE_FACT) {
return ImmutableList.of(fact(keyToGoWithElementsString(label, elements), elements));
}
ImmutableList.Builder<Fact> facts = ImmutableList.builder();
facts.add(simpleFact(keyToServeAsHeader(label, elements)));
int n = 1;
for (Multiset.Entry<?> entry : elements.entrySet()) {
int count = entry.getCount();
Object item = entry.getElement();
facts.add(fact(numberString(n, count), item));
n += count;
}
return facts.build();
}
/*
* Fact keys like "missing (1)" go against our recommendation that keys should be fixed strings.
* But this violation lets the fact value contain only the elements (instead of also containing
* the count), so it feels worthwhile.
*/
private static String keyToGoWithElementsString(String label, DuplicateGroupedAndTyped elements) {
/*
* elements.toString(), which the caller is going to use, includes the homogeneous type (if
* any), so we don't want to include it here. (And it's better to have it in the value, rather
* than in the key, so that it doesn't push the horizontally aligned values over too far.)
*/
return lenientFormat("%s (%s)", label, elements.totalCopies());
}
private static String keyToServeAsHeader(String label, DuplicateGroupedAndTyped elements) {
/*
* The caller of this method outputs each individual element manually (as opposed to calling
* elements.toString()), so the homogeneous type isn't present unless we add it. Fortunately, we
* can add it here without pushing the horizontally aligned values over, as this key won't have
* an associated value, so it won't factor into alignment.
*/
String key = keyToGoWithElementsString(label, elements);
if (elements.homogeneousTypeToDisplay.isPresent()) {
key += " (" + elements.homogeneousTypeToDisplay.get() + ")";
}
return key;
}
private static String numberString(int n, int count) {
return count == 1 ? lenientFormat("#%s", n) : lenientFormat("#%s [%s copies]", n, count);
}
private static ElementFactGrouping pickGrouping(
Iterable<Multiset.Entry<?>> first, Iterable<Multiset.Entry<?>> second) {
boolean firstHasMultiple = hasMultiple(first);
boolean secondHasMultiple = hasMultiple(second);
if ((firstHasMultiple || secondHasMultiple) && anyContainsCommaOrNewline(first, second)) {
return FACT_PER_ELEMENT;
}
if (firstHasMultiple && containsEmptyOrLong(first)) {
return FACT_PER_ELEMENT;
}
if (secondHasMultiple && containsEmptyOrLong(second)) {
return FACT_PER_ELEMENT;
}
return ALL_IN_ONE_FACT;
}
private static boolean anyContainsCommaOrNewline(Iterable<Multiset.Entry<?>>... lists) {
for (Multiset.Entry<?> entry : concat(lists)) {
String s = String.valueOf(entry.getElement());
if (s.contains("\n") || s.contains(",")) {
return true;
}
}
return false;
}
private static boolean hasMultiple(Iterable<Multiset.Entry<?>> entries) {
int totalCount = 0;
for (Multiset.Entry<?> entry : entries) {
totalCount += entry.getCount();
if (totalCount > 1) {
return true;
}
}
return false;
}
private static boolean containsEmptyOrLong(Iterable<Multiset.Entry<?>> entries) {
int totalLength = 0;
for (Multiset.Entry<?> entry : entries) {
String s = entryString(entry);
if (s.isEmpty()) {
return true;
}
totalLength += s.length();
}
return totalLength > 200;
}
/**
* Whether to output each missing/unexpected item as its own {@link Fact} or to group all those
* items together into a single {@code Fact}.
*/
enum ElementFactGrouping {
ALL_IN_ONE_FACT,
FACT_PER_ELEMENT;
}
/**
* Checks that a actual iterable contains none of the excluded objects or fails. (Duplicates are
* irrelevant to this test, which fails if any of the actual elements equal any of the excluded.)
*/
public final void containsNoneOf(
@Nullable Object firstExcluded,
@Nullable Object secondExcluded,
@Nullable Object @Nullable ... restOfExcluded) {
containsNoneIn(accumulate(firstExcluded, secondExcluded, restOfExcluded));
}
/**
* Checks that the actual iterable contains none of the elements contained in the excluded
* iterable or fails. (Duplicates are irrelevant to this test, which fails if any of the actual
* elements equal any of the excluded.)
*/
public final void containsNoneIn(Iterable<?> excluded) {
Collection<?> actual = iterableToCollection(this.actual);
Collection<Object> present = new ArrayList<>();
for (Object item : Sets.newLinkedHashSet(excluded)) {
if (actual.contains(item)) {
present.add(item);
}
}
if (!present.isEmpty()) {
failWithoutActual(
fact("expected not to contain any of", annotateEmptyStrings(excluded)),
fact("but contained", annotateEmptyStrings(present)),
fullContents());
}
}
/**
* Checks that the actual iterable contains none of the elements contained in the excluded array
* or fails. (Duplicates are irrelevant to this test, which fails if any of the actual elements
* equal any of the excluded.)
*/
public final void containsNoneIn(Object[] excluded) {
containsNoneIn(asList(excluded));
}
/** Ordered implementation that does nothing because it's already known to be true. */
@SuppressWarnings("UnnecessaryAnonymousClass") // for Java 7 compatibility
private static final Ordered IN_ORDER =
new Ordered() {
@Override
public void inOrder() {}
};
/** Ordered implementation that does nothing because an earlier check already caused a failure. */
@SuppressWarnings("UnnecessaryAnonymousClass") // for Java 7 compatibility
private static final Ordered ALREADY_FAILED =
new Ordered() {
@Override
public void inOrder() {}
};
/**
* Fails if the iterable is not strictly ordered, according to the natural ordering of its
* elements. Strictly ordered means that each element in the iterable is <i>strictly</i> greater
* than the element that preceded it.
*
* @throws ClassCastException if any pair of elements is not mutually Comparable
* @throws NullPointerException if any element is null
*/
/*
* non-final because it's overridden by IterableOfProtosSubject.
*
* If we really, really wanted it to be final, we could make IterableOfProtosSubject implement a
* package-private(?) interface that redeclares this method as deprecated.
*
* Alternatively, we could avoid deprecating the method there, relying instead on Error Prone
* static analysis. It's _possible_ that users would be confused by having one overload deprecated
* and the other not, anyway.
*/
public void isInStrictOrder() {
isInStrictOrder(Ordering.natural());
}
/**
* Fails if the iterable is not strictly ordered, according to the given comparator. Strictly
* ordered means that each element in the iterable is <i>strictly</i> greater than the element
* that preceded it.
*
* @throws ClassCastException if any pair of elements is not mutually Comparable
*/
@SuppressWarnings({"unchecked"})
public final void isInStrictOrder(final Comparator<?> comparator) {
checkNotNull(comparator);
pairwiseCheck(
"expected to be in strict order",
new PairwiseChecker() {
@Override
public boolean check(Object prev, Object next) {
return ((Comparator<Object>) comparator).compare(prev, next) < 0;
}
});
}
/**
* Fails if the iterable is not ordered, according to the natural ordering of its elements.
* Ordered means that each element in the iterable is greater than or equal to the element that
* preceded it.
*
* @throws ClassCastException if any pair of elements is not mutually Comparable
* @throws NullPointerException if any element is null
*/
// non-final because it's overridden by IterableOfProtosSubject. See isInStrictOrder.
public void isInOrder() {
isInOrder(Ordering.natural());
}
/**
* Fails if the iterable is not ordered, according to the given comparator. Ordered means that
* each element in the iterable is greater than or equal to the element that preceded it.
*
* @throws ClassCastException if any pair of elements is not mutually Comparable
*/
@SuppressWarnings({"unchecked"})
public final void isInOrder(final Comparator<?> comparator) {
checkNotNull(comparator);
pairwiseCheck(
"expected to be in order",
new PairwiseChecker() {
@Override
public boolean check(Object prev, Object next) {
return ((Comparator<Object>) comparator).compare(prev, next) <= 0;
}
});
}
private interface PairwiseChecker {
boolean check(Object prev, Object next);
}
private void pairwiseCheck(String expectedFact, PairwiseChecker checker) {
Iterator<?> iterator = actual.iterator();
if (iterator.hasNext()) {
Object prev = iterator.next();
while (iterator.hasNext()) {
Object next = iterator.next();
if (!checker.check(prev, next)) {
failWithoutActual(
simpleFact(expectedFact),
fact("but contained", prev),
fact("followed by", next),
fullContents());
return;
}
prev = next;
}
}
}
/** @deprecated You probably meant to call {@link #containsNoneOf} instead. */
@Override
@Deprecated
public void isNoneOf(
@Nullable Object first, @Nullable Object second, @Nullable Object @Nullable ... rest) {
super.isNoneOf(first, second, rest);
}
/** @deprecated You probably meant to call {@link #containsNoneIn} instead. */
@Override
@Deprecated
public void isNotIn(Iterable<?> iterable) {
if (Iterables.contains(iterable, actual)) {
failWithActual("expected not to be any of", iterable);
}
List<Object> nonIterables = new ArrayList<>();
for (Object element : iterable) {
if (!(element instanceof Iterable<?>)) {
nonIterables.add(element);
}
}
if (!nonIterables.isEmpty()) {
failWithoutActual(
simpleFact(
lenientFormat(
"The actual value is an Iterable, and you've written a test that compares it to "
+ "some objects that are not Iterables. Did you instead mean to check "
+ "whether its *contents* match any of the *contents* of the given values? "
+ "If so, call containsNoneOf(...)/containsNoneIn(...) instead. "
+ "Non-iterables: %s",
nonIterables)));
}
}
private Fact fullContents() {
return fact("full contents", actualCustomStringRepresentationForPackageMembersToCall());
}
/**
* Starts a method chain for a check in which the actual elements (i.e. the elements of the {@link
* Iterable} under test) are compared to expected elements using the given {@link Correspondence}.
* The actual elements must be of type {@code A}, the expected elements must be of type {@code E}.
* The check is actually executed by continuing the method chain. For example:
*
* <pre>{@code
* assertThat(actualIterable).comparingElementsUsing(correspondence).contains(expected);
* }</pre>
*
* where {@code actualIterable} is an {@code Iterable<A>} (or, more generally, an {@code
* Iterable<? extends A>}), {@code correspondence} is a {@code Correspondence<A, E>}, and {@code
* expected} is an {@code E}.
*
* <p>Any of the methods on the returned object may throw {@link ClassCastException} if they
* encounter an actual element that is not of type {@code A}.
*/
public <A, E> UsingCorrespondence<A, E> comparingElementsUsing(
Correspondence<? super A, ? super E> correspondence) {
return new UsingCorrespondence<>(this, correspondence);
}
/**
* Starts a method chain for a check in which failure messages may use the given {@link
* DiffFormatter} to describe the difference between an actual elements (i.e. an element of the
* {@link Iterable} under test) and the element it is expected to be equal to, but isn't. The
* actual and expected elements must be of type {@code T}. The check is actually executed by
* continuing the method chain. You may well want to use {@link
* UsingCorrespondence#displayingDiffsPairedBy} to specify how the elements should be paired up
* for diffing. For example:
*
* <pre>{@code
* assertThat(actualFoos)
* .formattingDiffsUsing(FooTestHelper::formatDiff)
* .displayingDiffsPairedBy(Foo::getId)
* .containsExactly(foo1, foo2, foo3);
* }</pre>
*
* where {@code actualFoos} is an {@code Iterable<Foo>}, {@code FooTestHelper.formatDiff} is a
* static method taking two {@code Foo} arguments and returning a {@link String}, {@code
* Foo.getId} is a no-arg instance method returning some kind of ID, and {@code foo1}, {code
* foo2}, and {@code foo3} are {@code Foo} instances.
*
* <p>Unlike when using {@link #comparingElementsUsing}, the elements are still compared using
* object equality, so this method does not affect whether a test passes or fails.
*
* <p>Any of the methods on the returned object may throw {@link ClassCastException} if they
* encounter an actual element that is not of type {@code T}.
*
* @since 1.1
*/
public <T> UsingCorrespondence<T, T> formattingDiffsUsing(
DiffFormatter<? super T, ? super T> formatter) {
return comparingElementsUsing(Correspondence.<T>equality().formattingDiffsUsing(formatter));
}
/**
* A partially specified check in which the actual elements (normally the elements of the {@link
* Iterable} under test) are compared to expected elements using a {@link Correspondence}. The
* expected elements are of type {@code E}. Call methods on this object to actually execute the
* check.
*/
public static class UsingCorrespondence<A, E> {
private final IterableSubject subject;
private final Correspondence<? super A, ? super E> correspondence;
private final Optional<Pairer> pairer;
UsingCorrespondence(
IterableSubject subject, Correspondence<? super A, ? super E> correspondence) {
this.subject = checkNotNull(subject);
this.correspondence = checkNotNull(correspondence);
this.pairer = Optional.absent();
}
UsingCorrespondence(
IterableSubject subject,
Correspondence<? super A, ? super E> correspondence,
Pairer pairer) {
this.subject = checkNotNull(subject);
this.correspondence = checkNotNull(correspondence);
this.pairer = Optional.of(pairer);
}
/**
* Specifies a way to pair up unexpected and missing elements in the message when an assertion
* fails. For example:
*
* <pre>{@code
* assertThat(actualRecords)
* .comparingElementsUsing(RECORD_CORRESPONDENCE)
* .displayingDiffsPairedBy(Record::getId)
* .containsExactlyElementsIn(expectedRecords);
* }</pre>
*
* <p><b>Important</b>: The {code keyFunction} function must be able to accept both the actual
* and the unexpected elements, i.e. it must satisfy {@code Function<? super A, ?>} as well as
* {@code Function<? super E, ?>}. If that constraint is not met then a subsequent method may
* throw {@link ClassCastException}. Use the two-parameter overload if you need to specify
* different key functions for the actual and expected elements.
*
* <p>On assertions where it makes sense to do so, the elements are paired as follows: they are
* keyed by {@code keyFunction}, and if an unexpected element and a missing element have the
* same non-null key then the they are paired up. (Elements with null keys are not paired.) The
* failure message will show paired elements together, and a diff will be shown if the {@link
* Correspondence#formatDiff} method returns non-null.
*
* <p>The expected elements given in the assertion should be uniquely keyed by {@code
* keyFunction}. If multiple missing elements have the same key then the pairing will be
* skipped.
*
* <p>Useful key functions will have the property that key equality is less strict than the
* correspondence, i.e. given {@code actual} and {@code expected} values with keys {@code
* actualKey} and {@code expectedKey}, if {@code correspondence.compare(actual, expected)} is
* true then it is guaranteed that {@code actualKey} is equal to {@code expectedKey}, but there
* are cases where {@code actualKey} is equal to {@code expectedKey} but {@code
* correspondence.compare(actual, expected)} is false.
*
* <p>If the {@code apply} method on the key function throws an exception then the element will