View Javadoc
1   package org.wikimedia.search.extra.superdetectnoop;
2   
3   import static java.util.stream.Collectors.toList;
4   import static org.assertj.core.api.Assertions.assertThat;
5   import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
6   
7   import java.util.Collections;
8   import java.util.List;
9   import java.util.stream.IntStream;
10  import java.util.stream.Stream;
11  
12  import javax.annotation.Nullable;
13  
14  import org.junit.Test;
15  
16  import com.google.common.collect.ImmutableList;
17  
18  public class MultiListHandlerTest {
19      private static final List<String> A = ImmutableList.of(
20              "A/foo", "A/bar");
21      private static final List<String> B1 = ImmutableList.of(
22              "B/something");
23      private static final List<String> B2 = ImmutableList.of(
24              "B/otherthing");
25      private static final List<String> U = ImmutableList.of(
26              "unnamed", "also.unnamed");
27  
28      @Test
29      public void testSingleClassNullable() {
30          testCase(null, A, A);
31          testCase(A, null, null);
32          testCase(B1, B2, B2);
33          testCaseCloseEnough(A, A);
34          testCaseCloseEnough(B1, B1);
35          testCaseCloseEnough(U, U);
36          testCase(Collections.emptyList(), A, A);
37      }
38  
39      @Test
40      public void testMultiClass() {
41          testCase(null, concat(A, B1), concat(A, B1));
42          testCase(concat(A, B1), null, null);
43          testCase(A, B1, concat(A, B1));
44          testCaseCloseEnough(concat(A, B1), A);
45          testCaseCloseEnough(concat(A, B1), B1);
46          testCase(concat(A, B1), B2, concat(A, B2));
47          testCase(concat(A, B2), B1, concat(A, B1));
48      }
49  
50      @Test
51      public void testUnnamedGroups() {
52          testCase(A, U, concat(A, U));
53          testCase(U, A, concat(A, U));
54          testCase(A, concat(A, U), concat(A, U));
55          testCase(U, concat(A, U), concat(A, U));
56  
57          testCaseCloseEnough(concat(A, U), U);
58          testCaseCloseEnough(concat(A, B1, U), A);
59          testCaseCloseEnough(concat(A, B1, U), B1);
60          testCaseCloseEnough(concat(A, B1, U), U);
61          testCaseCloseEnough(concat(A, B1, U), concat(A, U));
62  
63          testCase(concat(B1, U), B2, concat(B2, U));
64          testCase(concat(A, B2, U), B1, concat(A, B1, U));
65      }
66  
67      private List<String> deleteList(String group) {
68          // Arguments must be of the form "A/", including the delimiter.
69          // This allows representing the unnamed group with "".
70          return ImmutableList.of(group + MultiListHandler.DELETE);
71      }
72  
73      @Test
74      public void testDelete() {
75          testCaseCloseEnough(A, deleteList("B/"));
76          testCase(A, deleteList("A/"), Collections.emptyList());
77          testCase(concat(A, U), deleteList(""), A);
78          testCase(concat(A, U), deleteList("A/"), U);
79          testCase(concat(A, B1, U), concat(B2, deleteList("A/")), concat(B2, U));
80      }
81  
82      @Test
83      public void testAwkwardInputs() {
84          testFailureCase(0, 1);
85          testFailureCase(A, 4);
86          testFailureCase(A, ImmutableList.of(5));
87          testFailureCase(U, ImmutableList.of("Something", 5, "Otherthing"));
88          testFailureCase(77, A);
89          testFailureCase(ImmutableList.of("Words", 5), B1);
90          testFailureCase(A, Collections.emptyList());
91      }
92  
93      // Test takes ~100ms, give 10x margin for slower machine / over-busy CI
94      @Test(timeout = 1000)
95      public void testOversizedInputs() {
96          List<String> c = IntStream.range(0, 100000)
97              .mapToObj(i -> "B/" + i)
98              .collect(toList());
99          // Comparing the list to itself gives us the worst-case performance, all
100         // values must be compared with no opportunity for early-exit.
101         testCaseCloseEnough(c, c);
102     }
103 
104     private void testCaseCloseEnough(@Nullable List<String> oldValue, @Nullable List<String> newValue) {
105         testCase(oldValue, newValue, null, true);
106     }
107 
108     private void testCase(@Nullable List<String> oldValue, @Nullable List<String> newValue, @Nullable List<String> expected) {
109         testCase(oldValue, newValue, expected, false);
110     }
111 
112     @SuppressWarnings("unchecked")
113     private void testCase(
114             @Nullable List<String> oldValue, @Nullable List<String> newValue,
115             @Nullable List<String> expected, boolean isCloseEnough
116     ) {
117         ChangeHandler.Result result = MultiListHandler.INSTANCE.handle(oldValue, newValue);
118         assertThat(result.isCloseEnough()).isEqualTo(isCloseEnough);
119         if (expected != null && result.newValue() instanceof List) {
120             List<Object> resultList = (List<Object>)result.newValue();
121             assertThat(resultList).containsExactlyInAnyOrder(expected.toArray());
122         } else {
123             assertThat(result.newValue()).isEqualTo(expected);
124         }
125         assertThat(result.isDocumentNooped()).isEqualTo(false);
126     }
127 
128     private void testFailureCase(@Nullable Object oldValue, @Nullable Object newValue) {
129         try {
130             MultiListHandler.INSTANCE.handle(oldValue, newValue);
131         } catch (IllegalArgumentException e) {
132             return;
133         }
134         failBecauseExceptionWasNotThrown(IllegalArgumentException.class);
135     }
136 
137     @SafeVarargs
138     private final <T> List<T> concat(List<T>... lists) {
139         return Stream.of(lists).flatMap(List::stream).collect(toList());
140     }
141 }