1 /**
2  * Copyright (C) 2014 Google Inc.
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.inject.internal;
18 
19 import static com.google.inject.Asserts.awaitClear;
20 import static com.google.inject.Asserts.awaitFullGc;
21 import static com.google.inject.internal.WeakKeySetUtils.assertBlacklisted;
22 import static com.google.inject.internal.WeakKeySetUtils.assertInSet;
23 import static com.google.inject.internal.WeakKeySetUtils.assertNotBlacklisted;
24 import static com.google.inject.internal.WeakKeySetUtils.assertNotInSet;
25 import static com.google.inject.internal.WeakKeySetUtils.assertSourceNotInSet;
26 
27 import com.google.common.collect.ImmutableList;
28 import com.google.common.collect.ImmutableMap;
29 import com.google.common.collect.ImmutableSet;
30 import com.google.inject.AbstractModule;
31 import com.google.inject.Binding;
32 import com.google.inject.Guice;
33 import com.google.inject.Injector;
34 import com.google.inject.Key;
35 import com.google.inject.Scope;
36 import com.google.inject.TypeLiteral;
37 import com.google.inject.spi.ModuleAnnotatedMethodScannerBinding;
38 import com.google.inject.spi.ProvisionListenerBinding;
39 import com.google.inject.spi.ScopeBinding;
40 import com.google.inject.spi.TypeConverterBinding;
41 import com.google.inject.spi.TypeListenerBinding;
42 
43 import junit.framework.TestCase;
44 
45 import java.lang.annotation.Annotation;
46 import java.lang.ref.WeakReference;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Set;
50 
51 /**
52  * Tests for {@link WeakKeySet}.
53  * <p>
54  * Multibinding specific tests can be found in MultibinderTest and MapBinderTest.
55  *
56  * @author dweis@google.com (Daniel Weis)
57  */
58 public class WeakKeySetTest extends TestCase {
59 
60   private WeakKeySet set;
61 
62   @Override
setUp()63   protected void setUp() throws Exception {
64     set = new WeakKeySet(new Object());
65   }
66 
testEviction()67   public void testEviction() {
68     TestState state = new TestState();
69     Key<Integer> key = Key.get(Integer.class);
70     Object source = new Object();
71 
72     WeakReference<Key<Integer>> weakKeyRef = new WeakReference<Key<Integer>>(key);
73 
74     set.add(key, state, source);
75     assertInSet(set, key, 1, source);
76 
77     state = null;
78 
79     awaitFullGc();
80 
81     assertNotInSet(set, Key.get(Integer.class));
82 
83     // Ensure there are no hanging references.
84     key = null;
85     awaitClear(weakKeyRef);
86   }
87 
testEviction_nullSource()88   public void testEviction_nullSource() {
89     TestState state = new TestState();
90     Key<Integer> key = Key.get(Integer.class);
91     Object source = null;
92 
93     WeakReference<Key<Integer>> weakKeyRef = new WeakReference<Key<Integer>>(key);
94 
95     set.add(key, state, source);
96     assertInSet(set, key, 1, source);
97 
98     state = null;
99 
100     awaitFullGc();
101 
102     assertNotInSet(set, Key.get(Integer.class));
103 
104     // Ensure there are no hanging references.
105     key = null;
106     awaitClear(weakKeyRef);
107   }
108 
testEviction_keyOverlap_2x()109   public void testEviction_keyOverlap_2x() {
110     TestState state1 = new TestState();
111     TestState state2 = new TestState();
112     Key<Integer> key1 = Key.get(Integer.class);
113     Key<Integer> key2 = Key.get(Integer.class);
114     Object source1 = new Object();
115     Object source2 = new Object();
116 
117     set.add(key1, state1, source1);
118     assertInSet(set, key1, 1, source1);
119 
120     set.add(key2, state2, source2);
121     assertInSet(set, key2, 2, source1, source2);
122 
123     WeakReference<Key<Integer>> weakKey1Ref = new WeakReference<Key<Integer>>(key1);
124     WeakReference<Key<Integer>> weakKey2Ref = new WeakReference<Key<Integer>>(key2);
125     WeakReference<Object> weakSource1Ref = new WeakReference<Object>(source1);
126     WeakReference<Object> weakSource2Ref = new WeakReference<Object>(source2);
127 
128     Key<Integer> key = key1 = key2 = Key.get(Integer.class);
129     state1 = null;
130 
131     awaitFullGc();
132 
133     assertSourceNotInSet(set, key, source1);
134     assertInSet(set, key, 1, source2);
135 
136     source1 = source2 = null;
137 
138     awaitClear(weakSource1Ref);
139     // Key1 will be referenced as the key in the sources backingSet and won't be
140     // GC'd.
141 
142     // Should not be GC'd until state2 goes away.
143     assertNotNull(weakSource2Ref.get());
144 
145     state2 = null;
146 
147     awaitFullGc();
148 
149     assertNotInSet(set, key);
150 
151     awaitClear(weakKey2Ref);
152     awaitClear(weakSource2Ref);
153     // Now that the backing set is emptied, key1 is released.
154     awaitClear(weakKey1Ref);
155   }
156 
testNoEviction_keyOverlap_2x()157   public void testNoEviction_keyOverlap_2x() {
158     TestState state1 = new TestState();
159     TestState state2 = new TestState();
160     Key<Integer> key1 = Key.get(Integer.class);
161     Key<Integer> key2 = Key.get(Integer.class);
162     Object source1 = new Object();
163     Object source2 = new Object();
164 
165     set.add(key1, state1, source1);
166     assertInSet(set, key1, 1, source1);
167 
168     set.add(key2, state2, source2);
169     assertInSet(set, key2, 2, source1, source2);
170 
171     WeakReference<Key<Integer>> weakKey1Ref = new WeakReference<Key<Integer>>(key1);
172     WeakReference<Key<Integer>> weakKey2Ref = new WeakReference<Key<Integer>>(key2);
173 
174     Key<Integer> key = key1 = key2 = Key.get(Integer.class);
175 
176     awaitFullGc();
177     assertInSet(set, key, 2, source1, source2);
178 
179     // Ensure the keys don't get GC'd when states are still referenced. key1 will be present in the
180     // as the map key but key2 could be GC'd if the implementation does something wrong.
181     assertNotNull(weakKey1Ref.get());
182     assertNotNull(weakKey2Ref.get());
183   }
184 
testEviction_keyAndSourceOverlap_null()185   public void testEviction_keyAndSourceOverlap_null() {
186     TestState state1 = new TestState();
187     TestState state2 = new TestState();
188     Key<Integer> key1 = Key.get(Integer.class);
189     Key<Integer> key2 = Key.get(Integer.class);
190     Object source = null;
191 
192     set.add(key1, state1, source);
193     assertInSet(set, key1, 1, source);
194 
195     set.add(key2, state2, source);
196     // Same source so still only one value.
197     assertInSet(set, key2, 1, source);
198     assertInSet(set, key1, 1, source);
199 
200     WeakReference<Key<Integer>> weakKey1Ref = new WeakReference<Key<Integer>>(key1);
201     WeakReference<Key<Integer>> weakKey2Ref = new WeakReference<Key<Integer>>(key2);
202     WeakReference<Object> weakSourceRef = new WeakReference<Object>(source);
203 
204     Key<Integer> key = key1 = key2 = Key.get(Integer.class);
205     state1 = null;
206 
207     awaitFullGc();
208     // Should still have a single source.
209     assertInSet(set, key, 1, source);
210 
211     source = null;
212 
213     awaitClear(weakSourceRef);
214     // Key1 will be referenced as the key in the sources backingSet and won't be
215     // GC'd.
216 
217     state2 = null;
218 
219     awaitFullGc();
220     assertNotInSet(set, key);
221 
222     awaitClear(weakKey2Ref);
223     awaitClear(weakSourceRef);
224     // Now that the backing set is emptied, key1 is released.
225     awaitClear(weakKey1Ref);
226   }
227 
testEviction_keyAndSourceOverlap_nonNull()228   public void testEviction_keyAndSourceOverlap_nonNull() {
229     TestState state1 = new TestState();
230     TestState state2 = new TestState();
231     Key<Integer> key1 = Key.get(Integer.class);
232     Key<Integer> key2 = Key.get(Integer.class);
233     Object source = new Object();
234 
235     set.add(key1, state1, source);
236     assertInSet(set, key1, 1, source);
237 
238     set.add(key2, state2, source);
239     // Same source so still only one value.
240     assertInSet(set, key2, 1, source);
241 
242     WeakReference<Key<Integer>> weakKey1Ref = new WeakReference<Key<Integer>>(key1);
243     WeakReference<Key<Integer>> weakKey2Ref = new WeakReference<Key<Integer>>(key2);
244     WeakReference<Object> weakSourceRef = new WeakReference<Object>(source);
245 
246     Key<Integer> key = key1 = key2 = Key.get(Integer.class);
247     state1 = null;
248 
249     awaitFullGc();
250 
251  // Same source so still only one value.
252     assertInSet(set, key, 1, source);
253     assertInSet(set, key1, 1, source);
254 
255     source = null;
256 
257     awaitFullGc();
258     assertNotNull(weakSourceRef.get());
259     // Key1 will be referenced as the key in the sources backingSet and won't be
260     // GC'd.
261 
262     state2 = null;
263 
264     awaitFullGc();
265 
266     assertNotInSet(set, key);
267 
268     awaitClear(weakKey2Ref);
269     awaitClear(weakSourceRef);
270     // Now that the backing set is emptied, key1 is released.
271     awaitClear(weakKey1Ref);
272   }
273 
testEviction_keyOverlap_3x()274   public void testEviction_keyOverlap_3x() {
275     TestState state1 = new TestState();
276     TestState state2 = new TestState();
277     TestState state3 = new TestState();
278     Key<Integer> key1 = Key.get(Integer.class);
279     Key<Integer> key2 = Key.get(Integer.class);
280     Key<Integer> key3 = Key.get(Integer.class);
281     Object source1 = new Object();
282     Object source2 = new Object();
283     Object source3 = new Object();
284 
285     set.add(key1, state1, source1);
286     assertInSet(set, key1, 1, source1);
287 
288     set.add(key2, state2, source2);
289     assertInSet(set, key1, 2, source1, source2);
290 
291     set.add(key3, state3, source3);
292     assertInSet(set, key1, 3, source1, source2, source3);
293 
294     WeakReference<Key<Integer>> weakKey1Ref = new WeakReference<Key<Integer>>(key1);
295     WeakReference<Key<Integer>> weakKey2Ref = new WeakReference<Key<Integer>>(key2);
296     WeakReference<Key<Integer>> weakKey3Ref = new WeakReference<Key<Integer>>(key3);
297     WeakReference<Object> weakSource1Ref = new WeakReference<Object>(source1);
298     WeakReference<Object> weakSource2Ref = new WeakReference<Object>(source2);
299     WeakReference<Object> weakSource3Ref = new WeakReference<Object>(source3);
300 
301     Key<Integer> key = key1 = key2 = key3 = Key.get(Integer.class);
302     state1 = null;
303 
304     awaitFullGc();
305     assertSourceNotInSet(set, key, source1);
306     assertInSet(set, key, 2, source2, source3);
307 
308     source1 = null;
309     // Key1 will be referenced as the key in the sources backingSet and won't be
310     // GC'd.
311     awaitClear(weakSource1Ref);
312 
313     state2 = null;
314     awaitFullGc();
315     assertSourceNotInSet(set, key, source2);
316     assertInSet(set, key, 1, source3);
317 
318     awaitClear(weakKey2Ref);
319 
320     source2 = null;
321     awaitClear(weakSource2Ref);
322     // Key1 will be referenced as the key in the sources backingSet and won't be
323     // GC'd.
324 
325     state3 = null;
326     awaitFullGc();
327     assertNotInSet(set, key);
328 
329     awaitClear(weakKey3Ref);
330     source3 = null;
331     awaitClear(weakSource3Ref);
332     // Now that the backing set is emptied, key1 is released.
333     awaitClear(weakKey1Ref);
334   }
335 
testWeakKeySet_integration()336   public void testWeakKeySet_integration() {
337     Injector parentInjector = Guice.createInjector(new AbstractModule() {
338           @Override protected void configure() {
339             bind(Integer.class).toInstance(4);
340           }
341         });
342     assertNotBlacklisted(parentInjector, Key.get(String.class));
343 
344     Injector childInjector = parentInjector.createChildInjector(new AbstractModule() {
345       @Override protected void configure() {
346         bind(String.class).toInstance("bar");
347       }
348     });
349     WeakReference<Injector> weakRef = new WeakReference<Injector>(childInjector);
350     assertBlacklisted(parentInjector, Key.get(String.class));
351 
352     // Clear the ref, GC, and ensure that we are no longer blacklisting.
353     childInjector = null;
354     awaitClear(weakRef);
355     assertNotBlacklisted(parentInjector, Key.get(String.class));
356   }
357 
testWeakKeySet_integration_multipleChildren()358   public void testWeakKeySet_integration_multipleChildren() {
359     Injector parentInjector = Guice.createInjector(new AbstractModule() {
360           @Override protected void configure() {
361             bind(Integer.class).toInstance(4);
362           }
363         });
364     assertNotBlacklisted(parentInjector, Key.get(String.class));
365     assertNotBlacklisted(parentInjector, Key.get(Long.class));
366 
367     Injector childInjector1 = parentInjector.createChildInjector(new AbstractModule() {
368       @Override protected void configure() {
369         bind(String.class).toInstance("foo");
370       }
371     });
372     WeakReference<Injector> weakRef1 = new WeakReference<Injector>(childInjector1);
373     assertBlacklisted(parentInjector, Key.get(String.class));
374     assertNotBlacklisted(parentInjector, Key.get(Long.class));
375 
376     Injector childInjector2 = parentInjector.createChildInjector(new AbstractModule() {
377       @Override protected void configure() {
378         bind(Long.class).toInstance(6L);
379       }
380     });
381     WeakReference<Injector> weakRef2 = new WeakReference<Injector>(childInjector2);
382     assertBlacklisted(parentInjector, Key.get(String.class));
383     assertBlacklisted(parentInjector, Key.get(Long.class));
384 
385     // Clear ref1, GC, and ensure that we still blacklist.
386     childInjector1 = null;
387     awaitClear(weakRef1);
388     assertNotBlacklisted(parentInjector, Key.get(String.class));
389     assertBlacklisted(parentInjector, Key.get(Long.class));
390 
391     // Clear the ref, GC, and ensure that we are no longer blacklisting.
392     childInjector2 = null;
393     awaitClear(weakRef2);
394     assertNotBlacklisted(parentInjector, Key.get(String.class));
395     assertNotBlacklisted(parentInjector, Key.get(Long.class));
396   }
397 
testWeakKeySet_integration_multipleChildren_overlappingKeys()398   public void testWeakKeySet_integration_multipleChildren_overlappingKeys() {
399     Injector parentInjector = Guice.createInjector(new AbstractModule() {
400           @Override protected void configure() {
401             bind(Integer.class).toInstance(4);
402           }
403         });
404     assertNotBlacklisted(parentInjector, Key.get(String.class));
405 
406     Injector childInjector1 = parentInjector.createChildInjector(new AbstractModule() {
407       @Override protected void configure() {
408         bind(String.class).toInstance("foo");
409       }
410     });
411     WeakReference<Injector> weakRef1 = new WeakReference<Injector>(childInjector1);
412     assertBlacklisted(parentInjector, Key.get(String.class));
413 
414     Injector childInjector2 = parentInjector.createChildInjector(new AbstractModule() {
415       @Override protected void configure() {
416         bind(String.class).toInstance("bar");
417       }
418     });
419     WeakReference<Injector> weakRef2 = new WeakReference<Injector>(childInjector2);
420     assertBlacklisted(parentInjector, Key.get(String.class));
421 
422     // Clear ref1, GC, and ensure that we still blacklist.
423     childInjector1 = null;
424     awaitClear(weakRef1);
425     assertBlacklisted(parentInjector, Key.get(String.class));
426 
427     // Clear the ref, GC, and ensure that we are no longer blacklisting.
428     childInjector2 = null;
429     awaitClear(weakRef2);
430     assertNotBlacklisted(parentInjector, Key.get(String.class));
431   }
432 
433   private static class TestState implements State {
parent()434     public State parent() {
435       return new TestState();
436     }
437 
getExplicitBinding(Key<T> key)438     public <T> BindingImpl<T> getExplicitBinding(Key<T> key) {
439       return null;
440     }
441 
getExplicitBindingsThisLevel()442     public Map<Key<?>, Binding<?>> getExplicitBindingsThisLevel() {
443       throw new UnsupportedOperationException();
444     }
445 
putBinding(Key<?> key, BindingImpl<?> binding)446     public void putBinding(Key<?> key, BindingImpl<?> binding) {
447       throw new UnsupportedOperationException();
448     }
449 
getScopeBinding(Class<? extends Annotation> scopingAnnotation)450     public ScopeBinding getScopeBinding(Class<? extends Annotation> scopingAnnotation) {
451       return null;
452     }
453 
putScopeBinding(Class<? extends Annotation> annotationType, ScopeBinding scope)454     public void putScopeBinding(Class<? extends Annotation> annotationType, ScopeBinding scope) {
455       throw new UnsupportedOperationException();
456     }
457 
addConverter(TypeConverterBinding typeConverterBinding)458     public void addConverter(TypeConverterBinding typeConverterBinding) {
459       throw new UnsupportedOperationException();
460     }
461 
getConverter(String stringValue, TypeLiteral<?> type, Errors errors, Object source)462     public TypeConverterBinding getConverter(String stringValue, TypeLiteral<?> type, Errors errors,
463         Object source) {
464       throw new UnsupportedOperationException();
465     }
466 
getConvertersThisLevel()467     public Iterable<TypeConverterBinding> getConvertersThisLevel() {
468       return ImmutableSet.of();
469     }
470 
471     /*if[AOP]*/
addMethodAspect(MethodAspect methodAspect)472     public void addMethodAspect(MethodAspect methodAspect) {
473       throw new UnsupportedOperationException();
474     }
475 
getMethodAspects()476     public ImmutableList<MethodAspect> getMethodAspects() {
477       return ImmutableList.of();
478     }
479     /*end[AOP]*/
480 
addTypeListener(TypeListenerBinding typeListenerBinding)481     public void addTypeListener(TypeListenerBinding typeListenerBinding) {
482       throw new UnsupportedOperationException();
483     }
484 
getTypeListenerBindings()485     public List<TypeListenerBinding> getTypeListenerBindings() {
486       return ImmutableList.of();
487     }
488 
addProvisionListener(ProvisionListenerBinding provisionListenerBinding)489     public void addProvisionListener(ProvisionListenerBinding provisionListenerBinding) {
490       throw new UnsupportedOperationException();
491     }
492 
getProvisionListenerBindings()493     public List<ProvisionListenerBinding> getProvisionListenerBindings() {
494       return ImmutableList.of();
495     }
496 
addScanner(ModuleAnnotatedMethodScannerBinding scanner)497     public void addScanner(ModuleAnnotatedMethodScannerBinding scanner) {
498       throw new UnsupportedOperationException();
499     }
500 
getScannerBindings()501     public List<ModuleAnnotatedMethodScannerBinding> getScannerBindings() {
502       return ImmutableList.of();
503     }
504 
blacklist(Key<?> key, State state, Object source)505     public void blacklist(Key<?> key, State state, Object source) {
506     }
507 
isBlacklisted(Key<?> key)508     public boolean isBlacklisted(Key<?> key) {
509       return true;
510     }
511 
getSourcesForBlacklistedKey(Key<?> key)512     public Set<Object> getSourcesForBlacklistedKey(Key<?> key) {
513       throw new UnsupportedOperationException();
514     }
515 
lock()516     public Object lock() {
517       throw new UnsupportedOperationException();
518     }
519 
singletonCreationLock()520     public Object singletonCreationLock() {
521       throw new UnsupportedOperationException();
522     }
523 
getScopes()524     public Map<Class<? extends Annotation>, Scope> getScopes() {
525       return ImmutableMap.of();
526     }
527   }
528 }
529