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