/* * Copyright (C) 2015 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.common.util.concurrent; import com.google.common.collect.ImmutableSet; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URLClassLoader; import java.util.Set; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import junit.framework.TestCase; import junit.framework.TestSuite; /** * Tests our AtomicHelper fallback strategies in AbstractFuture. * * <p>On different platforms AbstractFuture uses different strategies for its core synchronization * primitives. The strategies are all implemented as subtypes of AtomicHelper and the strategy is * selected in the static initializer of AbstractFuture. This is convenient and performant but * introduces some testing difficulties. This test exercises the two fallback strategies in * abstract future. * <ul> * <li>SafeAtomicHelper: uses AtomicReferenceFieldsUpdaters to implement synchronization * <li>SynchronizedHelper: uses {@code synchronized} blocks for synchronization * </ul> * * To force selection of our fallback strategies we load {@link AbstractFuture} (and all of * {@code com.google.common.util.concurrent} in degenerate class loaders which make certain platform * classes unavailable. Then we construct a test suite so we can run the normal AbstractFutureTest * test methods in these degenerate classloaders. */ public class AbstractFutureFallbackAtomicHelperTest extends TestCase { // stash these in static fields to avoid loading them over and over again (speeds up test // execution significantly) /** * This classloader blacklists sun.misc.Unsafe which will prevent us from selecting our preferred * strategy {@code UnsafeAtomicHelper}. */ private static final ClassLoader NO_UNSAFE = getClassLoader(ImmutableSet.of(sun.misc.Unsafe.class.getName())); /** * This classloader blacklists sun.misc.Unsafe and AtomicReferenceFieldUpdater which will prevent * us from selecting our {@code SafeAtomicHelper} strategy. */ private static final ClassLoader NO_ATOMIC_REFERENCE_FIELD_UPDATER = getClassLoader( ImmutableSet.of( sun.misc.Unsafe.class.getName(), AtomicReferenceFieldUpdater.class.getName())); public static TestSuite suite() { // we create a test suite containing a test for every AbstractFutureTest test method and we // set it as the name of the test. Then in runTest we can reflectively load and invoke the // corresponding method on AbstractFutureTest in the correct classloader. TestSuite suite = new TestSuite(AbstractFutureFallbackAtomicHelperTest.class.getName()); for (Method method : AbstractFutureTest.class.getDeclaredMethods()) { if (Modifier.isPublic(method.getModifiers()) && method.getName().startsWith("test")) { suite.addTest( TestSuite.createTest(AbstractFutureFallbackAtomicHelperTest.class, method.getName())); } } return suite; } @Override public void runTest() throws Exception { // First ensure that our classloaders are intializing the correct helper versions checkHelperVersion(getClass().getClassLoader(), "UnsafeAtomicHelper"); checkHelperVersion(NO_UNSAFE, "SafeAtomicHelper"); checkHelperVersion(NO_ATOMIC_REFERENCE_FIELD_UPDATER, "SynchronizedHelper"); // Run the corresponding AbstractFutureTest test method in a new classloader that blacklists // certain core jdk classes. ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(NO_UNSAFE); try { runTestMethod(NO_UNSAFE); } finally { Thread.currentThread().setContextClassLoader(oldClassLoader); } Thread.currentThread().setContextClassLoader(NO_ATOMIC_REFERENCE_FIELD_UPDATER); try { runTestMethod(NO_ATOMIC_REFERENCE_FIELD_UPDATER); // TODO(lukes): assert that the logs are full of errors } finally { Thread.currentThread().setContextClassLoader(oldClassLoader); } } private void runTestMethod(ClassLoader classLoader) throws Exception { Class<?> test = classLoader.loadClass(AbstractFutureTest.class.getName()); test.getMethod(getName()).invoke(test.newInstance()); } private void checkHelperVersion(ClassLoader classLoader, String expectedHelperClassName) throws Exception { // Make sure we are actually running with the expected helper implementation Class<?> abstractFutureClass = classLoader.loadClass(AbstractFuture.class.getName()); Field helperField = abstractFutureClass.getDeclaredField("ATOMIC_HELPER"); helperField.setAccessible(true); assertEquals(expectedHelperClassName, helperField.get(null).getClass().getSimpleName()); } private static ClassLoader getClassLoader(final Set<String> blacklist) { final String concurrentPackage = SettableFuture.class.getPackage().getName(); ClassLoader classLoader = AbstractFutureFallbackAtomicHelperTest.class.getClassLoader(); return // we delegate to the current classloader so both loaders agree on classes like TestCase new URLClassLoader(((URLClassLoader) classLoader).getURLs(), classLoader) { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { if (blacklist.contains(name)) { throw new ClassNotFoundException("I'm sorry Dave, I'm afraid I can't do that."); } if (name.startsWith(concurrentPackage)) { Class<?> c = findLoadedClass(name); if (c == null) { return super.findClass(name); } return c; } return super.loadClass(name); } }; } }