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.AtomicIntegerFieldUpdater; 24 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 25 import junit.framework.TestCase; 26 import junit.framework.TestSuite; 27 28 /** 29 * Tests our AtomicHelper fallback strategy in AggregateFutureState. 30 * 31 * <p>On different platforms AggregateFutureState uses different strategies for its core 32 * synchronization primitives. The strategies are all implemented as subtypes of AtomicHelper and 33 * the strategy is selected in the static initializer of AggregateFutureState. This is convenient 34 * and performant but introduces some testing difficulties. This test exercises the two fallback 35 * strategies. 36 * 37 * <ul> 38 * <li>SafeAtomicHelper: uses Atomic FieldsUpdaters to implement synchronization 39 * <li>SynchronizedHelper: uses {@code synchronized} blocks for synchronization 40 * </ul> 41 * 42 * To force selection of our fallback strategies we load {@link AggregateFutureState} (and all of 43 * {@code com.google.common.util.concurrent} in degenerate class loaders which make certain platform 44 * classes unavailable. Then we construct a test suite so we can run the normal FuturesTest test 45 * methods in these degenerate classloaders. 46 */ 47 48 public class AggregateFutureStateFallbackAtomicHelperTest extends TestCase { 49 50 /** 51 * This classloader disallows AtomicReferenceFieldUpdater and AtomicIntegerFieldUpdate which will 52 * prevent us from selecting our {@code SafeAtomicHelper} strategy. 53 * 54 * <p>Stashing this in a static field avoids loading it over and over again and speeds up test 55 * execution significantly. 56 */ 57 private static final ClassLoader NO_ATOMIC_FIELD_UPDATER = 58 getClassLoader( 59 ImmutableSet.of( 60 AtomicIntegerFieldUpdater.class.getName(), 61 AtomicReferenceFieldUpdater.class.getName())); 62 suite()63 public static TestSuite suite() { 64 // we create a test suite containing a test for every FuturesTest test method and we 65 // set it as the name of the test. Then in runTest we can reflectively load and invoke the 66 // corresponding method on FuturesTest in the correct classloader. 67 TestSuite suite = new TestSuite(AggregateFutureStateFallbackAtomicHelperTest.class.getName()); 68 for (Method method : FuturesTest.class.getDeclaredMethods()) { 69 if (Modifier.isPublic(method.getModifiers()) && method.getName().startsWith("test")) { 70 suite.addTest( 71 TestSuite.createTest( 72 AggregateFutureStateFallbackAtomicHelperTest.class, method.getName())); 73 } 74 } 75 return suite; 76 } 77 78 @Override runTest()79 public void runTest() throws Exception { 80 // First ensure that our classloaders are initializing the correct helper versions 81 checkHelperVersion(getClass().getClassLoader(), "SafeAtomicHelper"); 82 checkHelperVersion(NO_ATOMIC_FIELD_UPDATER, "SynchronizedAtomicHelper"); 83 84 // Run the corresponding FuturesTest test method in a new classloader that disallows 85 // certain core jdk classes. 86 ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); 87 Thread.currentThread().setContextClassLoader(NO_ATOMIC_FIELD_UPDATER); 88 try { 89 runTestMethod(NO_ATOMIC_FIELD_UPDATER); 90 // TODO(lukes): assert that the logs are full of errors 91 } finally { 92 Thread.currentThread().setContextClassLoader(oldClassLoader); 93 } 94 } 95 runTestMethod(ClassLoader classLoader)96 private void runTestMethod(ClassLoader classLoader) throws Exception { 97 Class<?> test = classLoader.loadClass(FuturesTest.class.getName()); 98 Object testInstance = test.getDeclaredConstructor().newInstance(); 99 test.getMethod("setUp").invoke(testInstance); 100 test.getMethod(getName()).invoke(testInstance); 101 test.getMethod("tearDown").invoke(testInstance); 102 } 103 checkHelperVersion(ClassLoader classLoader, String expectedHelperClassName)104 private void checkHelperVersion(ClassLoader classLoader, String expectedHelperClassName) 105 throws Exception { 106 // Make sure we are actually running with the expected helper implementation 107 Class<?> abstractFutureClass = classLoader.loadClass(AggregateFutureState.class.getName()); 108 Field helperField = abstractFutureClass.getDeclaredField("ATOMIC_HELPER"); 109 helperField.setAccessible(true); 110 assertEquals(expectedHelperClassName, helperField.get(null).getClass().getSimpleName()); 111 } 112 getClassLoader(final Set<String> blocklist)113 private static ClassLoader getClassLoader(final Set<String> blocklist) { 114 final String concurrentPackage = SettableFuture.class.getPackage().getName(); 115 ClassLoader classLoader = AggregateFutureStateFallbackAtomicHelperTest.class.getClassLoader(); 116 // we delegate to the current classloader so both loaders agree on classes like TestCase 117 return new URLClassLoader(ClassPathUtil.getClassPathUrls(), classLoader) { 118 @Override 119 public Class<?> loadClass(String name) throws ClassNotFoundException { 120 if (blocklist.contains(name)) { 121 throw new ClassNotFoundException("I'm sorry Dave, I'm afraid I can't do that."); 122 } 123 if (name.startsWith(concurrentPackage)) { 124 Class<?> c = findLoadedClass(name); 125 if (c == null) { 126 return super.findClass(name); 127 } 128 return c; 129 } 130 return super.loadClass(name); 131 } 132 }; 133 } 134 } 135