• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 
15 package com.google.common.util.concurrent;
16 
17 import com.google.common.collect.ImmutableSet;
18 import java.lang.reflect.Field;
19 import java.lang.reflect.Method;
20 import java.lang.reflect.Modifier;
21 import java.net.URLClassLoader;
22 import java.util.Set;
23 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
24 import junit.framework.TestCase;
25 import junit.framework.TestSuite;
26 
27 /**
28  * Tests our AtomicHelper fallback strategies in AbstractFuture.
29  *
30  * <p>On different platforms AbstractFuture uses different strategies for its core synchronization
31  * primitives. The strategies are all implemented as subtypes of AtomicHelper and the strategy is
32  * selected in the static initializer of AbstractFuture. This is convenient and performant but
33  * introduces some testing difficulties. This test exercises the two fallback strategies in abstract
34  * future.
35  *
36  * <ul>
37  *   <li>SafeAtomicHelper: uses AtomicReferenceFieldsUpdaters to implement synchronization
38  *   <li>SynchronizedHelper: uses {@code synchronized} blocks for synchronization
39  * </ul>
40  *
41  * To force selection of our fallback strategies we load {@link AbstractFuture} (and all of {@code
42  * com.google.common.util.concurrent} in degenerate class loaders which make certain platform
43  * classes unavailable. Then we construct a test suite so we can run the normal AbstractFutureTest
44  * test methods in these degenerate classloaders.
45  */
46 
47 public class AbstractFutureFallbackAtomicHelperTest extends TestCase {
48 
49   // stash these in static fields to avoid loading them over and over again (speeds up test
50   // execution significantly)
51 
52   /**
53    * This classloader disallows {@link sun.misc.Unsafe}, which will prevent us from selecting our
54    * preferred strategy {@code UnsafeAtomicHelper}.
55    */
56   private static final ClassLoader NO_UNSAFE =
57       getClassLoader(ImmutableSet.of(sun.misc.Unsafe.class.getName()));
58 
59   /**
60    * This classloader disallows {@link sun.misc.Unsafe} and {@link AtomicReferenceFieldUpdater},
61    * which will prevent us from selecting our {@code SafeAtomicHelper} strategy.
62    */
63   private static final ClassLoader NO_ATOMIC_REFERENCE_FIELD_UPDATER =
64       getClassLoader(
65           ImmutableSet.of(
66               sun.misc.Unsafe.class.getName(), AtomicReferenceFieldUpdater.class.getName()));
67 
suite()68   public static TestSuite suite() {
69     // we create a test suite containing a test for every AbstractFutureTest test method and we
70     // set it as the name of the test.  Then in runTest we can reflectively load and invoke the
71     // corresponding method on AbstractFutureTest in the correct classloader.
72     TestSuite suite = new TestSuite(AbstractFutureFallbackAtomicHelperTest.class.getName());
73     for (Method method : AbstractFutureTest.class.getDeclaredMethods()) {
74       if (Modifier.isPublic(method.getModifiers()) && method.getName().startsWith("test")) {
75         suite.addTest(
76             TestSuite.createTest(AbstractFutureFallbackAtomicHelperTest.class, method.getName()));
77       }
78     }
79     return suite;
80   }
81 
82   @Override
runTest()83   public void runTest() throws Exception {
84     // First ensure that our classloaders are initializing the correct helper versions
85     checkHelperVersion(getClass().getClassLoader(), "UnsafeAtomicHelper");
86     checkHelperVersion(NO_UNSAFE, "SafeAtomicHelper");
87     checkHelperVersion(NO_ATOMIC_REFERENCE_FIELD_UPDATER, "SynchronizedHelper");
88 
89     // Run the corresponding AbstractFutureTest test method in a new classloader that disallows
90     // certain core jdk classes.
91     ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
92     Thread.currentThread().setContextClassLoader(NO_UNSAFE);
93     try {
94       runTestMethod(NO_UNSAFE);
95     } finally {
96       Thread.currentThread().setContextClassLoader(oldClassLoader);
97     }
98 
99     Thread.currentThread().setContextClassLoader(NO_ATOMIC_REFERENCE_FIELD_UPDATER);
100     try {
101       runTestMethod(NO_ATOMIC_REFERENCE_FIELD_UPDATER);
102       // TODO(lukes): assert that the logs are full of errors
103     } finally {
104       Thread.currentThread().setContextClassLoader(oldClassLoader);
105     }
106   }
107 
runTestMethod(ClassLoader classLoader)108   private void runTestMethod(ClassLoader classLoader) throws Exception {
109     Class<?> test = classLoader.loadClass(AbstractFutureTest.class.getName());
110     test.getMethod(getName()).invoke(test.newInstance());
111   }
112 
checkHelperVersion(ClassLoader classLoader, String expectedHelperClassName)113   private void checkHelperVersion(ClassLoader classLoader, String expectedHelperClassName)
114       throws Exception {
115     // Make sure we are actually running with the expected helper implementation
116     Class<?> abstractFutureClass = classLoader.loadClass(AbstractFuture.class.getName());
117     Field helperField = abstractFutureClass.getDeclaredField("ATOMIC_HELPER");
118     helperField.setAccessible(true);
119     assertEquals(expectedHelperClassName, helperField.get(null).getClass().getSimpleName());
120   }
121 
getClassLoader(final Set<String> disallowedClassNames)122   private static ClassLoader getClassLoader(final Set<String> disallowedClassNames) {
123     final String concurrentPackage = SettableFuture.class.getPackage().getName();
124     ClassLoader classLoader = AbstractFutureFallbackAtomicHelperTest.class.getClassLoader();
125     // we delegate to the current classloader so both loaders agree on classes like TestCase
126     return new URLClassLoader(ClassPathUtil.getClassPathUrls(), classLoader) {
127       @Override
128       public Class<?> loadClass(String name) throws ClassNotFoundException {
129         if (disallowedClassNames.contains(name)) {
130           throw new ClassNotFoundException("I'm sorry Dave, I'm afraid I can't do that.");
131         }
132         if (name.startsWith(concurrentPackage)) {
133           Class<?> c = findLoadedClass(name);
134           if (c == null) {
135             return super.findClass(name);
136           }
137           return c;
138         }
139         return super.loadClass(name);
140       }
141     };
142   }
143 }
144