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