1 /* 2 * Copyright (C) 2015 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.util.concurrent; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static org.junit.Assert.assertThrows; 21 22 import java.lang.reflect.Method; 23 import java.net.URLClassLoader; 24 import java.util.HashMap; 25 import java.util.Map; 26 import java.util.concurrent.CancellationException; 27 import java.util.concurrent.Executor; 28 import java.util.concurrent.Future; 29 import java.util.concurrent.TimeUnit; 30 import javax.annotation.concurrent.GuardedBy; 31 import junit.framework.TestCase; 32 33 /** Tests for {@link AbstractFuture} with the cancellation cause system property set */ 34 @AndroidIncompatible // custom classloading 35 36 public class AbstractFutureCancellationCauseTest extends TestCase { 37 38 private ClassLoader oldClassLoader; 39 private URLClassLoader classReloader; 40 private Class<?> settableFutureClass; 41 private Class<?> abstractFutureClass; 42 43 @Override setUp()44 protected void setUp() throws Exception { 45 // Load the "normal" copy of SettableFuture and related classes. 46 SettableFuture<?> unused = SettableFuture.create(); 47 // Hack to load AbstractFuture et. al. in a new classloader so that it re-reads the cancellation 48 // cause system property. This allows us to run with both settings of the property in one jvm 49 // without resorting to even crazier hacks to reset static final boolean fields. 50 System.setProperty("guava.concurrent.generate_cancellation_cause", "true"); 51 final String concurrentPackage = SettableFuture.class.getPackage().getName(); 52 classReloader = 53 new URLClassLoader(ClassPathUtil.getClassPathUrls()) { 54 @GuardedBy("loadedClasses") 55 final Map<String, Class<?>> loadedClasses = new HashMap<>(); 56 57 @Override 58 public Class<?> loadClass(String name) throws ClassNotFoundException { 59 if (name.startsWith(concurrentPackage) 60 // Use other classloader for ListenableFuture, so that the objects can interact 61 && !ListenableFuture.class.getName().equals(name)) { 62 synchronized (loadedClasses) { 63 Class<?> toReturn = loadedClasses.get(name); 64 if (toReturn == null) { 65 toReturn = super.findClass(name); 66 loadedClasses.put(name, toReturn); 67 } 68 return toReturn; 69 } 70 } 71 return super.loadClass(name); 72 } 73 }; 74 oldClassLoader = Thread.currentThread().getContextClassLoader(); 75 Thread.currentThread().setContextClassLoader(classReloader); 76 abstractFutureClass = classReloader.loadClass(AbstractFuture.class.getName()); 77 settableFutureClass = classReloader.loadClass(SettableFuture.class.getName()); 78 } 79 80 @Override tearDown()81 protected void tearDown() throws Exception { 82 classReloader.close(); 83 Thread.currentThread().setContextClassLoader(oldClassLoader); 84 System.clearProperty("guava.concurrent.generate_cancellation_cause"); 85 } 86 testCancel_notDoneNoInterrupt()87 public void testCancel_notDoneNoInterrupt() throws Exception { 88 Future<?> future = newFutureInstance(); 89 assertTrue(future.cancel(false)); 90 assertTrue(future.isCancelled()); 91 assertTrue(future.isDone()); 92 assertNull(tryInternalFastPathGetFailure(future)); 93 CancellationException e = assertThrows(CancellationException.class, () -> future.get()); 94 assertNotNull(e.getCause()); 95 } 96 testCancel_notDoneInterrupt()97 public void testCancel_notDoneInterrupt() throws Exception { 98 Future<?> future = newFutureInstance(); 99 assertTrue(future.cancel(true)); 100 assertTrue(future.isCancelled()); 101 assertTrue(future.isDone()); 102 assertNull(tryInternalFastPathGetFailure(future)); 103 CancellationException e = assertThrows(CancellationException.class, () -> future.get()); 104 assertNotNull(e.getCause()); 105 } 106 testSetFuture_misbehavingFutureDoesNotThrow()107 public void testSetFuture_misbehavingFutureDoesNotThrow() throws Exception { 108 ListenableFuture<String> badFuture = 109 new ListenableFuture<String>() { 110 @Override 111 public boolean cancel(boolean interrupt) { 112 return false; 113 } 114 115 @Override 116 public boolean isDone() { 117 return true; 118 } 119 120 @Override 121 public boolean isCancelled() { 122 return true; // BAD!! 123 } 124 125 @Override 126 public String get() { 127 return "foo"; // BAD!! 128 } 129 130 @Override 131 public String get(long time, TimeUnit unit) { 132 return "foo"; // BAD!! 133 } 134 135 @Override 136 public void addListener(Runnable runnable, Executor executor) { 137 executor.execute(runnable); 138 } 139 }; 140 Future<?> future = newFutureInstance(); 141 future 142 .getClass() 143 .getMethod( 144 "setFuture", 145 future.getClass().getClassLoader().loadClass(ListenableFuture.class.getName())) 146 .invoke(future, badFuture); 147 CancellationException expected = assertThrows(CancellationException.class, () -> future.get()); 148 assertThat(expected).hasCauseThat().isInstanceOf(IllegalArgumentException.class); 149 assertThat(expected).hasCauseThat().hasMessageThat().contains(badFuture.toString()); 150 } 151 newFutureInstance()152 private Future<?> newFutureInstance() throws Exception { 153 return (Future<?>) settableFutureClass.getMethod("create").invoke(null); 154 } 155 tryInternalFastPathGetFailure(Future<?> future)156 private Throwable tryInternalFastPathGetFailure(Future<?> future) throws Exception { 157 Method tryInternalFastPathGetFailureMethod = 158 abstractFutureClass.getDeclaredMethod("tryInternalFastPathGetFailure"); 159 tryInternalFastPathGetFailureMethod.setAccessible(true); 160 return (Throwable) tryInternalFastPathGetFailureMethod.invoke(future); 161 } 162 } 163