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