blob: d075f801667e7edb99fca1b5b3dd4da25fd43d75 [file] [log] [blame]
/*
* 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 disallows {@link 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 disallows {@link sun.misc.Unsafe} and {@link 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 initializing 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 disallows
// 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> disallowedClassNames) {
final String concurrentPackage = SettableFuture.class.getPackage().getName();
ClassLoader classLoader = AbstractFutureFallbackAtomicHelperTest.class.getClassLoader();
// we delegate to the current classloader so both loaders agree on classes like TestCase
return new URLClassLoader(ClassPathUtil.getClassPathUrls(), classLoader) {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (disallowedClassNames.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);
}
};
}
}