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