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 21 import java.lang.reflect.Method; 22 import java.net.URLClassLoader; 23 import java.util.HashMap; 24 import java.util.Map; 25 import java.util.concurrent.CancellationException; 26 import java.util.concurrent.Executor; 27 import java.util.concurrent.Future; 28 import java.util.concurrent.TimeUnit; 29 import javax.annotation.concurrent.GuardedBy; 30 import junit.framework.TestCase; 31 32 /** Tests for {@link AbstractFuture} with the cancellation cause system property set */ 33 34 public class AbstractFutureCancellationCauseTest extends TestCase { 35 36 private ClassLoader oldClassLoader; 37 private URLClassLoader classReloader; 38 private Class<?> settableFutureClass; 39 private Class<?> abstractFutureClass; 40 41 @Override setUp()42 protected void setUp() throws Exception { 43 // Load the "normal" copy of SettableFuture and related classes. 44 SettableFuture<?> unused = SettableFuture.create(); 45 // Hack to load AbstractFuture et. al. in a new classloader so that it re-reads the cancellation 46 // cause system property. This allows us to run with both settings of the property in one jvm 47 // without resorting to even crazier hacks to reset static final boolean fields. 48 System.setProperty("guava.concurrent.generate_cancellation_cause", "true"); 49 final String concurrentPackage = SettableFuture.class.getPackage().getName(); 50 classReloader = 51 new URLClassLoader(ClassPathUtil.getClassPathUrls()) { 52 @GuardedBy("loadedClasses") 53 final Map<String, Class<?>> loadedClasses = new HashMap<>(); 54 55 @Override 56 public Class<?> loadClass(String name) throws ClassNotFoundException { 57 if (name.startsWith(concurrentPackage) 58 // Use other classloader for ListenableFuture, so that the objects can interact 59 && !ListenableFuture.class.getName().equals(name)) { 60 synchronized (loadedClasses) { 61 Class<?> toReturn = loadedClasses.get(name); 62 if (toReturn == null) { 63 toReturn = super.findClass(name); 64 loadedClasses.put(name, toReturn); 65 } 66 return toReturn; 67 } 68 } 69 return super.loadClass(name); 70 } 71 }; 72 oldClassLoader = Thread.currentThread().getContextClassLoader(); 73 Thread.currentThread().setContextClassLoader(classReloader); 74 abstractFutureClass = classReloader.loadClass(AbstractFuture.class.getName()); 75 settableFutureClass = classReloader.loadClass(SettableFuture.class.getName()); 76 } 77 78 @Override tearDown()79 protected void tearDown() throws Exception { 80 classReloader.close(); 81 Thread.currentThread().setContextClassLoader(oldClassLoader); 82 System.clearProperty("guava.concurrent.generate_cancellation_cause"); 83 } 84 testCancel_notDoneNoInterrupt()85 public void testCancel_notDoneNoInterrupt() throws Exception { 86 Future<?> future = newFutureInstance(); 87 assertTrue(future.cancel(false)); 88 assertTrue(future.isCancelled()); 89 assertTrue(future.isDone()); 90 assertNull(tryInternalFastPathGetFailure(future)); 91 try { 92 future.get(); 93 fail("Expected CancellationException"); 94 } catch (CancellationException e) { 95 assertNotNull(e.getCause()); 96 } 97 } 98 testCancel_notDoneInterrupt()99 public void testCancel_notDoneInterrupt() throws Exception { 100 Future<?> future = newFutureInstance(); 101 assertTrue(future.cancel(true)); 102 assertTrue(future.isCancelled()); 103 assertTrue(future.isDone()); 104 assertNull(tryInternalFastPathGetFailure(future)); 105 try { 106 future.get(); 107 fail("Expected CancellationException"); 108 } catch (CancellationException e) { 109 assertNotNull(e.getCause()); 110 } 111 } 112 testSetFuture_misbehavingFutureDoesNotThrow()113 public void testSetFuture_misbehavingFutureDoesNotThrow() throws Exception { 114 ListenableFuture<String> badFuture = 115 new ListenableFuture<String>() { 116 @Override 117 public boolean cancel(boolean interrupt) { 118 return false; 119 } 120 121 @Override 122 public boolean isDone() { 123 return true; 124 } 125 126 @Override 127 public boolean isCancelled() { 128 return true; // BAD!! 129 } 130 131 @Override 132 public String get() { 133 return "foo"; // BAD!! 134 } 135 136 @Override 137 public String get(long time, TimeUnit unit) { 138 return "foo"; // BAD!! 139 } 140 141 @Override 142 public void addListener(Runnable runnable, Executor executor) { 143 executor.execute(runnable); 144 } 145 }; 146 Future<?> future = newFutureInstance(); 147 future 148 .getClass() 149 .getMethod( 150 "setFuture", 151 future.getClass().getClassLoader().loadClass(ListenableFuture.class.getName())) 152 .invoke(future, badFuture); 153 try { 154 future.get(); 155 fail(); 156 } catch (CancellationException expected) { 157 assertThat(expected).hasCauseThat().isInstanceOf(IllegalArgumentException.class); 158 assertThat(expected).hasCauseThat().hasMessageThat().contains(badFuture.toString()); 159 } 160 } 161 newFutureInstance()162 private Future<?> newFutureInstance() throws Exception { 163 return (Future<?>) settableFutureClass.getMethod("create").invoke(null); 164 } 165 tryInternalFastPathGetFailure(Future<?> future)166 private Throwable tryInternalFastPathGetFailure(Future<?> future) throws Exception { 167 Method tryInternalFastPathGetFailureMethod = 168 abstractFutureClass.getDeclaredMethod("tryInternalFastPathGetFailure"); 169 tryInternalFastPathGetFailureMethod.setAccessible(true); 170 return (Throwable) tryInternalFastPathGetFailureMethod.invoke(future); 171 } 172 } 173