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