aboutsummaryrefslogtreecommitdiff
path: root/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureFallbackAtomicHelperTest.java
blob: 77d04ff8ea234ecc59ff22f79fc7a8df7a32842d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/*
 * 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);
      }
    };
  }
}