• 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 
50 /**
51  * Tests {@link MapTestSuiteBuilder} by using it against maps that have various negative behaviors.
52  *
53  * @author George van den Driessche
54  */
55 public final class MapTestSuiteBuilderTests extends TestCase {
MapTestSuiteBuilderTests()56   private MapTestSuiteBuilderTests() {}
57 
suite()58   public static Test suite() {
59     TestSuite suite = new TestSuite(MapTestSuiteBuilderTests.class.getSimpleName());
60     suite.addTest(testsForHashMapNullKeysForbidden());
61     suite.addTest(testsForHashMapNullValuesForbidden());
62     suite.addTest(testsForSetUpTearDown());
63     return suite;
64   }
65 
66   private abstract static class WrappedHashMapGenerator extends TestStringMapGenerator {
67     @Override
create(Entry<String, String>[] entries)68     protected final Map<String, String> create(Entry<String, String>[] entries) {
69       HashMap<String, String> map = Maps.newHashMap();
70       for (Entry<String, String> entry : entries) {
71         map.put(entry.getKey(), entry.getValue());
72       }
73       return wrap(map);
74     }
75 
wrap(HashMap<String, String> map)76     abstract Map<String, String> wrap(HashMap<String, String> map);
77   }
78 
wrappedHashMapTests( WrappedHashMapGenerator generator, String name, Feature<?>... features)79   private static TestSuite wrappedHashMapTests(
80       WrappedHashMapGenerator generator, String name, Feature<?>... features) {
81     List<Feature<?>> featuresList = Lists.newArrayList(features);
82     Collections.addAll(
83         featuresList,
84         MapFeature.GENERAL_PURPOSE,
85         CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
86         CollectionSize.ANY);
87     return MapTestSuiteBuilder.using(generator)
88         .named(name)
89         .withFeatures(featuresList)
90         .createTestSuite();
91   }
92 
93   // TODO: consider being null-hostile in these tests
94 
testsForHashMapNullKeysForbidden()95   private static Test testsForHashMapNullKeysForbidden() {
96     return wrappedHashMapTests(
97         new WrappedHashMapGenerator() {
98           @Override
99           Map<String, String> wrap(final HashMap<String, String> map) {
100             if (map.containsKey(null)) {
101               throw new NullPointerException();
102             }
103             return new AbstractMap<String, String>() {
104               @Override
105               public Set<Entry<String, String>> entrySet() {
106                 return map.entrySet();
107               }
108 
109               @Override
110               public String put(String key, String value) {
111                 checkNotNull(key);
112                 return map.put(key, value);
113               }
114             };
115           }
116         },
117         "HashMap w/out null keys",
118         ALLOWS_NULL_VALUES);
119   }
120 
121   private static Test testsForHashMapNullValuesForbidden() {
122     return wrappedHashMapTests(
123         new WrappedHashMapGenerator() {
124           @Override
125           Map<String, String> wrap(final HashMap<String, String> map) {
126             if (map.containsValue(null)) {
127               throw new NullPointerException();
128             }
129 
130             return new AbstractMap<String, String>() {
131               @Override
132               public Set<Entry<String, String>> entrySet() {
133                 return new EntrySet();
134               }
135 
136               @Override
137               public int hashCode() {
138                 return map.hashCode();
139               }
140 
141               @Override
142               public boolean equals(Object o) {
143                 return map.equals(o);
144               }
145 
146               @Override
147               public String toString() {
148                 return map.toString();
149               }
150 
151               @Override
152               public String remove(Object key) {
153                 return map.remove(key);
154               }
155 
156               @Override
157               public boolean remove(Object key, Object value) {
158                 return map.remove(key, value);
159               }
160 
161               class EntrySet extends AbstractSet<Map.Entry<String, String>> {
162                 @Override
163                 public Iterator<Entry<String, String>> iterator() {
164                   return new Iterator<Entry<String, String>>() {
165 
166                     final Iterator<Entry<String, String>> iterator = map.entrySet().iterator();
167 
168                     @Override
169                     public void remove() {
170                       iterator.remove();
171                     }
172 
173                     @Override
174                     public boolean hasNext() {
175                       return iterator.hasNext();
176                     }
177 
178                     @Override
179                     public Entry<String, String> next() {
180                       return transform(iterator.next());
181                     }
182 
183                     private Entry<String, String> transform(final Entry<String, String> next) {
184                       return new Entry<String, String>() {
185 
186                         @Override
187                         public String setValue(String value) {
188                           checkNotNull(value);
189                           return next.setValue(value);
190                         }
191 
192                         @Override
193                         public String getValue() {
194                           return next.getValue();
195                         }
196 
197                         @Override
198                         public String getKey() {
199                           return next.getKey();
200                         }
201 
202                         @Override
203                         public boolean equals(Object obj) {
204                           return next.equals(obj);
205                         }
206 
207                         @Override
208                         public int hashCode() {
209                           return next.hashCode();
210                         }
211                       };
212                     }
213                   };
214                 }
215 
216                 @Override
217                 public int size() {
218                   return map.size();
219                 }
220 
221                 @Override
222                 public boolean remove(Object o) {
223                   return map.entrySet().remove(o);
224                 }
225 
226                 @Override
227                 public boolean removeIf(Predicate<? super Entry<String, String>> filter) {
228                   return map.entrySet().removeIf(filter);
229                 }
230 
231                 @Override
232                 public boolean containsAll(Collection<?> c) {
233                   return map.entrySet().containsAll(c);
234                 }
235 
236                 @Override
237                 public boolean removeAll(Collection<?> c) {
238                   return map.entrySet().removeAll(c);
239                 }
240 
241                 @Override
242                 public boolean retainAll(Collection<?> c) {
243                   return map.entrySet().retainAll(c);
244                 }
245 
246                 @Override
247                 public int hashCode() {
248                   return map.entrySet().hashCode();
249                 }
250 
251                 @Override
252                 public boolean equals(Object o) {
253                   return map.entrySet().equals(o);
254                 }
255 
256                 @Override
257                 public String toString() {
258                   return map.entrySet().toString();
259                 }
260               }
261 
262               @Override
263               public String put(String key, String value) {
264                 checkNotNull(value);
265                 return map.put(key, value);
266               }
267             };
268           }
269         },
270         "HashMap w/out null values",
271         ALLOWS_NULL_KEYS);
272   }
273 
274   /**
275    * Map generator that verifies that {@code setUp()} methods are called in all the test cases. The
276    * {@code setUpRan} parameter is set true by the {@code setUp} that every test case is supposed to
277    * have registered, and set false by the {@code tearDown}. We use a dynamic proxy to intercept all
278    * of the {@code Map} method calls and check that {@code setUpRan} is true.
279    */
280   private static class CheckSetUpHashMapGenerator extends WrappedHashMapGenerator {
281     private final AtomicBoolean setUpRan;
282 
283     CheckSetUpHashMapGenerator(AtomicBoolean setUpRan) {
284       this.setUpRan = setUpRan;
285     }
286 
287     @Override
288     Map<String, String> wrap(HashMap<String, String> map) {
289       @SuppressWarnings("unchecked")
290       Map<String, String> proxy =
291           Reflection.newProxy(Map.class, new CheckSetUpInvocationHandler(map, setUpRan));
292       return proxy;
293     }
294   }
295 
296   /**
297    * Intercepts calls to a {@code Map} to check that {@code setUpRan} is true when they happen. Then
298    * forwards the calls to the underlying {@code Map}.
299    */
300   private static class CheckSetUpInvocationHandler implements InvocationHandler, Serializable {
301     private final Map<String, String> map;
302     private final AtomicBoolean setUpRan;
303 
304     CheckSetUpInvocationHandler(Map<String, String> map, AtomicBoolean setUpRan) {
305       this.map = map;
306       this.setUpRan = setUpRan;
307     }
308 
309     @Override
310     public Object invoke(Object target, Method method, Object[] args) throws Throwable {
311       assertTrue("setUp should have run", setUpRan.get());
312       try {
313         return method.invoke(map, args);
314       } catch (InvocationTargetException e) {
315         throw e.getCause();
316       } catch (IllegalAccessException e) {
317         throw newLinkageError(e);
318       }
319     }
320   }
321 
322   /** Verifies that {@code setUp} and {@code tearDown} are called in all map test cases. */
323   private static Test testsForSetUpTearDown() {
324     final AtomicBoolean setUpRan = new AtomicBoolean();
325     Runnable setUp =
326         new Runnable() {
327           @Override
328           public void run() {
329             assertFalse("previous tearDown should have run before setUp", setUpRan.getAndSet(true));
330           }
331         };
332     Runnable tearDown =
333         new Runnable() {
334           @Override
335           public void run() {
336             assertTrue("setUp should have run", setUpRan.getAndSet(false));
337           }
338         };
339     return MapTestSuiteBuilder.using(new CheckSetUpHashMapGenerator(setUpRan))
340         .named("setUpTearDown")
341         .withFeatures(
342             MapFeature.GENERAL_PURPOSE,
343             MapFeature.ALLOWS_NULL_KEYS,
344             MapFeature.ALLOWS_NULL_VALUES,
345             CollectionFeature.SERIALIZABLE,
346             CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
347             CollectionSize.ANY)
348         .withSetUp(setUp)
349         .withTearDown(tearDown)
350         .createTestSuite();
351   }
352 
353   private static LinkageError newLinkageError(Throwable cause) {
354     LinkageError error = new LinkageError(cause.toString());
355     error.initCause(cause);
356     return error;
357   }
358 }
359