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.cronet; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertTrue; 21 import static org.mockito.ArgumentMatchers.any; 22 import static org.mockito.Mockito.mock; 23 import static org.mockito.Mockito.times; 24 import static org.mockito.Mockito.verify; 25 import static org.mockito.Mockito.when; 26 27 import android.os.Build; 28 import io.grpc.Attributes; 29 import io.grpc.CallOptions; 30 import io.grpc.ClientStreamTracer; 31 import io.grpc.Metadata; 32 import io.grpc.MethodDescriptor; 33 import io.grpc.SecurityLevel; 34 import io.grpc.Status; 35 import io.grpc.cronet.CronetChannelBuilder.StreamBuilderFactory; 36 import io.grpc.internal.ClientStreamListener; 37 import io.grpc.internal.GrpcAttributes; 38 import io.grpc.internal.ManagedClientTransport; 39 import io.grpc.internal.TransportTracer; 40 import io.grpc.testing.TestMethodDescriptors; 41 import java.net.InetSocketAddress; 42 import java.util.concurrent.Executor; 43 import org.chromium.net.BidirectionalStream; 44 import org.junit.Before; 45 import org.junit.Rule; 46 import org.junit.Test; 47 import org.junit.runner.RunWith; 48 import org.mockito.ArgumentCaptor; 49 import org.mockito.Mock; 50 import org.mockito.junit.MockitoJUnit; 51 import org.mockito.junit.MockitoRule; 52 import org.robolectric.RobolectricTestRunner; 53 import org.robolectric.annotation.Config; 54 55 @RunWith(RobolectricTestRunner.class) 56 @Config(sdk = Build.VERSION_CODES.P) 57 public final class CronetClientTransportTest { 58 @Rule public final MockitoRule mocks = MockitoJUnit.rule(); 59 60 private static final String AUTHORITY = "test.example.com"; 61 private static final Attributes.Key<String> EAG_ATTR_KEY = 62 Attributes.Key.create("eag-attr"); 63 64 private static final Attributes EAG_ATTRS = 65 Attributes.newBuilder().set(EAG_ATTR_KEY, "value").build(); 66 67 private final ClientStreamTracer[] tracers = 68 new ClientStreamTracer[]{ new ClientStreamTracer() {} }; 69 private CronetClientTransport transport; 70 @Mock private StreamBuilderFactory streamFactory; 71 private MethodDescriptor<Void, Void> descriptor = TestMethodDescriptors.voidMethod(); 72 @Mock private ManagedClientTransport.Listener clientTransportListener; 73 @Mock private BidirectionalStream.Builder builder; 74 private final Executor executor = new Executor() { 75 @Override 76 public void execute(Runnable r) { 77 r.run(); 78 } 79 }; 80 81 @Before setUp()82 public void setUp() { 83 transport = 84 new CronetClientTransport( 85 streamFactory, 86 new InetSocketAddress("localhost", 443), 87 AUTHORITY, 88 null, 89 EAG_ATTRS, 90 executor, 91 5000, 92 false, 93 TransportTracer.getDefaultFactory().create(), 94 false, 95 false); 96 Runnable callback = transport.start(clientTransportListener); 97 assertTrue(callback != null); 98 callback.run(); 99 verify(clientTransportListener).transportReady(); 100 } 101 102 @Test transportAttributes()103 public void transportAttributes() { 104 Attributes attrs = transport.getAttributes(); 105 assertEquals( 106 SecurityLevel.PRIVACY_AND_INTEGRITY, attrs.get(GrpcAttributes.ATTR_SECURITY_LEVEL)); 107 assertEquals(EAG_ATTRS, attrs.get(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS)); 108 } 109 110 @Test shutdownTransport()111 public void shutdownTransport() throws Exception { 112 CronetClientStream stream1 = 113 transport.newStream(descriptor, new Metadata(), CallOptions.DEFAULT, tracers); 114 CronetClientStream stream2 = 115 transport.newStream(descriptor, new Metadata(), CallOptions.DEFAULT, tracers); 116 117 // Create a transport and start two streams on it. 118 ArgumentCaptor<BidirectionalStream.Callback> callbackCaptor = 119 ArgumentCaptor.forClass(BidirectionalStream.Callback.class); 120 when(streamFactory.newBidirectionalStreamBuilder( 121 any(String.class), callbackCaptor.capture(), any(Executor.class))) 122 .thenReturn(builder); 123 BidirectionalStream cronetStream1 = mock(BidirectionalStream.class); 124 when(builder.build()).thenReturn(cronetStream1); 125 stream1.start(mock(ClientStreamListener.class)); 126 BidirectionalStream.Callback callback1 = callbackCaptor.getValue(); 127 128 BidirectionalStream cronetStream2 = mock(BidirectionalStream.class); 129 when(builder.build()).thenReturn(cronetStream2); 130 stream2.start(mock(ClientStreamListener.class)); 131 BidirectionalStream.Callback callback2 = callbackCaptor.getValue(); 132 // Shut down the transport. transportShutdown should be called immediately. 133 transport.shutdown(); 134 verify(clientTransportListener).transportShutdown(any(Status.class)); 135 // Have two live streams. Transport has not been terminated. 136 verify(clientTransportListener, times(0)).transportTerminated(); 137 138 callback1.onCanceled(cronetStream1, null); 139 // Still has one live stream 140 verify(clientTransportListener, times(0)).transportTerminated(); 141 callback2.onCanceled(cronetStream1, null); 142 // All streams are gone now. 143 verify(clientTransportListener, times(1)).transportTerminated(); 144 } 145 146 @Test startStreamAfterShutdown()147 public void startStreamAfterShutdown() throws Exception { 148 CronetClientStream stream = 149 transport.newStream(descriptor, new Metadata(), CallOptions.DEFAULT, tracers); 150 transport.shutdown(); 151 BaseClientStreamListener listener = new BaseClientStreamListener(); 152 stream.start(listener); 153 154 assertEquals(Status.UNAVAILABLE.getCode(), listener.status.getCode()); 155 } 156 157 private static class BaseClientStreamListener implements ClientStreamListener { 158 private Status status; 159 160 @Override messagesAvailable(MessageProducer producer)161 public void messagesAvailable(MessageProducer producer) {} 162 163 @Override onReady()164 public void onReady() {} 165 166 @Override headersRead(Metadata headers)167 public void headersRead(Metadata headers) {} 168 169 @Override closed(Status status, RpcProgress rpcProgress, Metadata trailers)170 public void closed(Status status, RpcProgress rpcProgress, Metadata trailers) { 171 this.status = status; 172 } 173 } 174 } 175