• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.collect.testing;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS;
21 import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES;
22 
23 import com.google.common.collect.Lists;
24 import com.google.common.collect.Maps;
25 import com.google.common.collect.testing.features.CollectionFeature;
26 import com.google.common.collect.testing.features.CollectionSize;
27 import com.google.common.collect.testing.features.Feature;
28 import com.google.common.collect.testing.features.MapFeature;
29 import com.google.common.reflect.Reflection;
30 import java.io.Serializable;
31 import java.lang.reflect.InvocationHandler;
32 import java.lang.reflect.InvocationTargetException;
33 import java.lang.reflect.Method;
34 import java.util.AbstractMap;
35 import java.util.AbstractSet;
36 import java.util.Collection;
37 import java.util.Collections;
38 import java.util.HashMap;
39 import java.util.Iterator;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Map.Entry;
43 import java.util.Set;
44 import java.util.concurrent.atomic.AtomicBoolean;
45 import java.util.function.Predicate;
46 import junit.framework.Test;
47 import junit.framework.TestCase;
48 import junit.framework.TestSuite;
49 import org.checkerframework.checker.nullness.qual.Nullable;
50 
51 /**
52  * Tests {@link MapTestSuiteBuilder} by using it against maps that have various negative behaviors.
53  *
54  * @author George van den Driessche
55  */
56 public final class MapTestSuiteBuilderTests extends TestCase {
MapTestSuiteBuilderTests()57   private MapTestSuiteBuilderTests() {}
58 
suite()59   public static Test suite() {
60     TestSuite suite = new TestSuite(MapTestSuiteBuilderTests.class.getSimpleName());
61     suite.addTest(testsForHashMapNullKeysForbidden());
62     suite.addTest(testsForHashMapNullValuesForbidden());
63     suite.addTest(testsForSetUpTearDown());
64     return suite;
65   }
66 
67   private abstract static class WrappedHashMapGenerator extends TestStringMapGenerator {
68     @Override
create(Entry<String, String>[] entries)69     protected final Map<String, String> create(Entry<String, String>[] entries) {
70       HashMap<String, String> map = Maps.newHashMap();
71       for (Entry<String, String> entry : entries) {
72         map.put(entry.getKey(), entry.getValue());
73       }
74       return wrap(map);
75     }
76 
wrap(HashMap<String, String> map)77     abstract Map<String, String> wrap(HashMap<String, String> map);
78   }
79 
wrappedHashMapTests( WrappedHashMapGenerator generator, String name, Feature<?>... features)80   private static TestSuite wrappedHashMapTests(
81       WrappedHashMapGenerator generator, String name, Feature<?>... features) {
82     List<Feature<?>> featuresList = Lists.newArrayList(features);
83     Collections.addAll(
84         featuresList,
85         MapFeature.GENERAL_PURPOSE,
86         CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
87         CollectionSize.ANY);
88     return MapTestSuiteBuilder.using(generator)
89         .named(name)
90         .withFeatures(featuresList)
91         .createTestSuite();
92   }
93 
94   // TODO: consider being null-hostile in these tests
95 
testsForHashMapNullKeysForbidden()96   private static Test testsForHashMapNullKeysForbidden() {
97     return wrappedHashMapTests(
98         new WrappedHashMapGenerator() {
99           @Override
100           Map<String, String> wrap(final HashMap<String, String> map) {
101             if (map.containsKey(null)) {
102               throw new NullPointerException();
103             }
104             return new AbstractMap<String, String>() {
105               @Override
106               public Set<Entry<String, String>> entrySet() {
107                 return map.entrySet();
108               }
109 
110               @Override
111               public @Nullable String put(String key, String value) {
112                 checkNotNull(key);
113                 return map.put(key, value);
114               }
115             };
116           }
117         },
118         "HashMap w/out null keys",
119         ALLOWS_NULL_VALUES);
120   }
121 
122   private static Test testsForHashMapNullValuesForbidden() {
123     return wrappedHashMapTests(
124         new WrappedHashMapGenerator() {
125           @Override
126           Map<String, String> wrap(final HashMap<String, String> map) {
127             if (map.containsValue(null)) {
128               throw new NullPointerException();
129             }
130 
131             return new AbstractMap<String, String>() {
132               @Override
133               public Set<Entry<String, String>> entrySet() {
134                 return new EntrySet();
135               }
136 
137               @Override
138               public int hashCode() {
139                 return map.hashCode();
140               }
141 
142               @Override
143               public boolean equals(@Nullable Object o) {
144                 return map.equals(o);
145               }
146 
147               @Override
148               public String toString() {
149                 return map.toString();
150               }
151 
152               @Override
153               public @Nullable String remove(Object key) {
154                 return map.remove(key);
155               }
156 
157               @Override
158               public boolean remove(Object key, Object value) {
159                 return map.remove(key, value);
160               }
161 
162               class EntrySet extends AbstractSet<Map.Entry<String, String>> {
163                 @Override
164                 public Iterator<Entry<String, String>> iterator() {
165                   return new Iterator<Entry<String, String>>() {
166 
167                     final Iterator<Entry<String, String>> iterator = map.entrySet().iterator();
168 
169                     @Override
170                     public void remove() {
171                       iterator.remove();
172                     }
173 
174                     @Override
175                     public boolean hasNext() {
176                       return iterator.hasNext();
177                     }
178 
179                     @Override
180                     public Entry<String, String> next() {
181                       return transform(iterator.next());
182                     }
183 
184                     private Entry<String, String> transform(final Entry<String, String> next) {
185                       return new Entry<String, String>() {
186 
187                         @Override
188                         public String setValue(String value) {
189                           checkNotNull(value);
190                           return next.setValue(value);
191                         }
192 
193                         @Override
194                         public String getValue() {
195                           return next.getValue();
196                         }
197 
198                         @Override
199                         public String getKey() {
200                           return next.getKey();
201                         }
202 
203                         @Override
204                         public boolean equals(@Nullable Object obj) {
205                           return next.equals(obj);
206                         }
207 
208                         @Override
209                         public int hashCode() {
210                           return next.hashCode();
211                         }
212                       };
213                     }
214                   };
215                 }
216 
217                 @Override
218                 public int size() {
219                   return map.size();
220                 }
221 
222                 @Override
223                 public boolean remove(Object o) {
224                   return map.entrySet().remove(o);
225                 }
226 
227                 @Override
228                 public boolean removeIf(Predicate<? super Entry<String, String>> filter) {
229                   return map.entrySet().removeIf(filter);
230                 }
231 
232                 @Override
233                 public boolean containsAll(Collection<?> c) {
234                   return map.entrySet().containsAll(c);
235                 }
236 
237                 @Override
238                 public boolean removeAll(Collection<?> c) {
239                   return map.entrySet().removeAll(c);
240                 }
241 
242                 @Override
243                 public boolean retainAll(Collection<?> c) {
244                   return map.entrySet().retainAll(c);
245                 }
246 
247                 @Override
248                 public int hashCode() {
249                   return map.entrySet().hashCode();
250                 }
251 
252                 @Override
253                 public boolean equals(@Nullable Object o) {
254                   return map.entrySet().equals(o);
255                 }
256 
257                 @Override
258                 public String toString() {
259                   return map.entrySet().toString();
260                 }
261               }
262 
263               @Override
264               public @Nullable String put(String key, String value) {
265                 checkNotNull(value);
266                 return map.put(key, value);
267               }
268             };
269           }
270         },
271         "HashMap w/out null values",
272         ALLOWS_NULL_KEYS);
273   }
274 
275   /**
276    * Map generator that verifies that {@code setUp()} methods are called in all the test cases. The
277    * {@code setUpRan} parameter is set true by the {@code setUp} that every test case is supposed to
278    * have registered, and set false by the {@code tearDown}. We use a dynamic proxy to intercept all
279    * of the {@code Map} method calls and check that {@code setUpRan} is true.
280    */
281   private static class CheckSetUpHashMapGenerator extends WrappedHashMapGenerator {
282     private final AtomicBoolean setUpRan;
283 
284     CheckSetUpHashMapGenerator(AtomicBoolean setUpRan) {
285       this.setUpRan = setUpRan;
286     }
287 
288     @Override
289     Map<String, String> wrap(HashMap<String, String> map) {
290       @SuppressWarnings("unchecked")
291       Map<String, String> proxy =
292           Reflection.newProxy(Map.class, new CheckSetUpInvocationHandler(map, setUpRan));
293       return proxy;
294     }
295   }
296 
297   /**
298    * Intercepts calls to a {@code Map} to check that {@code setUpRan} is true when they happen. Then
299    * forwards the calls to the underlying {@code Map}.
300    */
301   private static class CheckSetUpInvocationHandler implements InvocationHandler, Serializable {
302     private final Map<String, String> map;
303     private final AtomicBoolean setUpRan;
304 
305     CheckSetUpInvocationHandler(Map<String, String> map, AtomicBoolean setUpRan) {
306       this.map = map;
307       this.setUpRan = setUpRan;
308     }
309 
310     @Override
311     public Object invoke(Object target, Method method, Object[] args) throws Throwable {
312       assertTrue("setUp should have run", setUpRan.get());
313       try {
314         return method.invoke(map, args);
315       } catch (InvocationTargetException e) {
316         throw e.getCause();
317       } catch (IllegalAccessException e) {
318         throw newLinkageError(e);
319       }
320     }
321   }
322 
323   /** Verifies that {@code setUp} and {@code tearDown} are called in all map test cases. */
324   private static Test testsForSetUpTearDown() {
325     final AtomicBoolean setUpRan = new AtomicBoolean();
326     Runnable setUp =
327         new Runnable() {
328           @Override
329           public void run() {
330             assertFalse("previous tearDown should have run before setUp", setUpRan.getAndSet(true));
331           }
332         };
333     Runnable tearDown =
334         new Runnable() {
335           @Override
336           public void run() {
337             assertTrue("setUp should have run", setUpRan.getAndSet(false));
338           }
339         };
340     return MapTestSuiteBuilder.using(new CheckSetUpHashMapGenerator(setUpRan))
341         .named("setUpTearDown")
342         .withFeatures(
343             MapFeature.GENERAL_PURPOSE,
344             MapFeature.ALLOWS_NULL_KEYS,
345             MapFeature.ALLOWS_NULL_VALUES,
346             CollectionFeature.SERIALIZABLE,
347             CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
348             CollectionSize.ANY)
349         .withSetUp(setUp)
350         .withTearDown(tearDown)
351         .createTestSuite();
352   }
353 
354   private static LinkageError newLinkageError(Throwable cause) {
355     LinkageError error = new LinkageError(cause.toString());
356     error.initCause(cause);
357     return error;
358   }
359 }
360