• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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