• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016 The gRPC 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 io.grpc;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static io.grpc.Contexts.interceptCall;
21 import static io.grpc.Contexts.statusFromCancelled;
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertFalse;
24 import static org.junit.Assert.assertNotNull;
25 import static org.junit.Assert.assertNull;
26 import static org.junit.Assert.assertSame;
27 import static org.junit.Assert.assertTrue;
28 import static org.junit.Assert.fail;
29 
30 import com.google.common.util.concurrent.testing.TestingExecutors;
31 import io.grpc.internal.NoopServerCall;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.List;
35 import java.util.concurrent.ScheduledExecutorService;
36 import java.util.concurrent.ScheduledFuture;
37 import java.util.concurrent.TimeUnit;
38 import java.util.concurrent.TimeoutException;
39 import org.junit.Test;
40 import org.junit.runner.RunWith;
41 import org.junit.runners.JUnit4;
42 
43 /**
44  * Tests for {@link Contexts}.
45  */
46 @RunWith(JUnit4.class)
47 public class ContextsTest {
48   private static Context.Key<Object> contextKey = Context.key("key");
49   /** For use in comparing context by reference. */
50   private Context uniqueContext = Context.ROOT.withValue(contextKey, new Object());
51   @SuppressWarnings("unchecked")
52   private ServerCall<Object, Object> call = new NoopServerCall<>();
53   private Metadata headers = new Metadata();
54 
55   @Test
interceptCall_basic()56   public void interceptCall_basic() {
57     Context origContext = Context.current();
58     final Object message = new Object();
59     final List<Integer> methodCalls = new ArrayList<>();
60     final ServerCall.Listener<Object> listener = new ServerCall.Listener<Object>() {
61       @Override public void onMessage(Object messageIn) {
62         assertSame(message, messageIn);
63         assertSame(uniqueContext, Context.current());
64         methodCalls.add(1);
65       }
66 
67       @Override public void onHalfClose() {
68         assertSame(uniqueContext, Context.current());
69         methodCalls.add(2);
70       }
71 
72       @Override public void onCancel() {
73         assertSame(uniqueContext, Context.current());
74         methodCalls.add(3);
75       }
76 
77       @Override public void onComplete() {
78         assertSame(uniqueContext, Context.current());
79         methodCalls.add(4);
80       }
81 
82       @Override public void onReady() {
83         assertSame(uniqueContext, Context.current());
84         methodCalls.add(5);
85       }
86     };
87     ServerCall.Listener<Object> wrapped = interceptCall(uniqueContext, call, headers,
88         new ServerCallHandler<Object, Object>() {
89           @Override
90           public ServerCall.Listener<Object> startCall(
91               ServerCall<Object, Object> call, Metadata headers) {
92             assertSame(ContextsTest.this.call, call);
93             assertSame(ContextsTest.this.headers, headers);
94             assertSame(uniqueContext, Context.current());
95             return listener;
96           }
97         });
98     assertSame(origContext, Context.current());
99 
100     wrapped.onMessage(message);
101     wrapped.onHalfClose();
102     wrapped.onCancel();
103     wrapped.onComplete();
104     wrapped.onReady();
105     assertEquals(Arrays.asList(1, 2, 3, 4, 5), methodCalls);
106     assertSame(origContext, Context.current());
107   }
108 
109   @Test
interceptCall_restoresIfNextThrows()110   public void interceptCall_restoresIfNextThrows() {
111     Context origContext = Context.current();
112     try {
113       interceptCall(uniqueContext, call, headers, new ServerCallHandler<Object, Object>() {
114         @Override
115         public ServerCall.Listener<Object> startCall(
116             ServerCall<Object, Object> call, Metadata headers) {
117           throw new RuntimeException();
118         }
119       });
120       fail("Expected exception");
121     } catch (RuntimeException expected) {
122     }
123     assertSame(origContext, Context.current());
124   }
125 
126   @Test
interceptCall_restoresIfListenerThrows()127   public void interceptCall_restoresIfListenerThrows() {
128     Context origContext = Context.current();
129     final ServerCall.Listener<Object> listener = new ServerCall.Listener<Object>() {
130       @Override public void onMessage(Object messageIn) {
131         throw new RuntimeException();
132       }
133 
134       @Override public void onHalfClose() {
135         throw new RuntimeException();
136       }
137 
138       @Override public void onCancel() {
139         throw new RuntimeException();
140       }
141 
142       @Override public void onComplete() {
143         throw new RuntimeException();
144       }
145 
146       @Override public void onReady() {
147         throw new RuntimeException();
148       }
149     };
150     ServerCall.Listener<Object> wrapped = interceptCall(uniqueContext, call, headers,
151         new ServerCallHandler<Object, Object>() {
152           @Override
153           public ServerCall.Listener<Object> startCall(
154               ServerCall<Object, Object> call, Metadata headers) {
155             return listener;
156           }
157         });
158 
159     try {
160       wrapped.onMessage(new Object());
161       fail("Exception expected");
162     } catch (RuntimeException expected) {
163     }
164     try {
165       wrapped.onHalfClose();
166       fail("Exception expected");
167     } catch (RuntimeException expected) {
168     }
169     try {
170       wrapped.onCancel();
171       fail("Exception expected");
172     } catch (RuntimeException expected) {
173     }
174     try {
175       wrapped.onComplete();
176       fail("Exception expected");
177     } catch (RuntimeException expected) {
178     }
179     try {
180       wrapped.onReady();
181       fail("Exception expected");
182     } catch (RuntimeException expected) {
183     }
184     assertSame(origContext, Context.current());
185   }
186 
187   @Test
statusFromCancelled_returnNullIfCtxNotCancelled()188   public void statusFromCancelled_returnNullIfCtxNotCancelled() {
189     Context context = Context.current();
190     assertFalse(context.isCancelled());
191     assertNull(statusFromCancelled(context));
192   }
193 
194   @Test
statusFromCancelled_returnStatusAsSetOnCtx()195   public void statusFromCancelled_returnStatusAsSetOnCtx() {
196     Context.CancellableContext cancellableContext = Context.current().withCancellation();
197     cancellableContext.cancel(Status.DEADLINE_EXCEEDED.withDescription("foo bar").asException());
198     Status status = statusFromCancelled(cancellableContext);
199     assertNotNull(status);
200     assertEquals(Status.Code.DEADLINE_EXCEEDED, status.getCode());
201     assertEquals("foo bar", status.getDescription());
202   }
203 
204   @Test
statusFromCancelled_shouldReturnStatusWithCauseAttached()205   public void statusFromCancelled_shouldReturnStatusWithCauseAttached() {
206     Context.CancellableContext cancellableContext = Context.current().withCancellation();
207     Throwable t = new Throwable();
208     cancellableContext.cancel(t);
209     Status status = statusFromCancelled(cancellableContext);
210     assertNotNull(status);
211     assertEquals(Status.Code.CANCELLED, status.getCode());
212     assertSame(t, status.getCause());
213   }
214 
215   @Test
statusFromCancelled_TimeoutExceptionShouldMapToDeadlineExceeded()216   public void statusFromCancelled_TimeoutExceptionShouldMapToDeadlineExceeded() {
217     final long expectedDelay = 100;
218     final TimeUnit expectedUnit = TimeUnit.SECONDS;
219     class MockScheduledExecutorService extends ForwardingScheduledExecutorService {
220       private ScheduledExecutorService delegate = TestingExecutors.noOpScheduledExecutor();
221       Runnable command;
222 
223       @Override public ScheduledExecutorService delegate() {
224         return delegate;
225       }
226 
227       @Override public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
228         if (delay > unit.convert(expectedDelay, expectedUnit)) {
229           fail("Delay larger than expected: " + delay + " " + unit);
230         }
231         this.command = command;
232         return super.schedule(command, delay, unit);
233       }
234     }
235 
236     MockScheduledExecutorService executorService = new MockScheduledExecutorService();
237     Context.CancellableContext cancellableContext = Context.current()
238         .withDeadlineAfter(expectedDelay, expectedUnit, executorService);
239     executorService.command.run();
240 
241     assertTrue(cancellableContext.isCancelled());
242     assertThat(cancellableContext.cancellationCause()).isInstanceOf(TimeoutException.class);
243 
244     Status status = statusFromCancelled(cancellableContext);
245     assertNotNull(status);
246     assertEquals(Status.Code.DEADLINE_EXCEEDED, status.getCode());
247     assertEquals("context timed out", status.getDescription());
248   }
249 
250   @Test
statusFromCancelled_returnCancelledIfCauseIsNull()251   public void statusFromCancelled_returnCancelledIfCauseIsNull() {
252     Context.CancellableContext cancellableContext = Context.current().withCancellation();
253     cancellableContext.cancel(null);
254     assertTrue(cancellableContext.isCancelled());
255     Status status = statusFromCancelled(cancellableContext);
256     assertNotNull(status);
257     assertEquals(Status.Code.CANCELLED, status.getCode());
258   }
259 
260   /** This is a whitebox test, to verify a special case of the implementation. */
261   @Test
statusFromCancelled_StatusUnknownShouldWork()262   public void statusFromCancelled_StatusUnknownShouldWork() {
263     Context.CancellableContext cancellableContext = Context.current().withCancellation();
264     Exception e = Status.UNKNOWN.asException();
265     cancellableContext.cancel(e);
266     assertTrue(cancellableContext.isCancelled());
267 
268     Status status = statusFromCancelled(cancellableContext);
269     assertNotNull(status);
270     assertEquals(Status.Code.UNKNOWN, status.getCode());
271     assertSame(e, status.getCause());
272   }
273 
274   @Test
statusFromCancelled_shouldThrowIfCtxIsNull()275   public void statusFromCancelled_shouldThrowIfCtxIsNull() {
276     try {
277       statusFromCancelled(null);
278       fail("NPE expected");
279     } catch (NullPointerException npe) {
280       assertEquals("context must not be null", npe.getMessage());
281     }
282   }
283 }
284