• 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.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