• 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 
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