All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.grpc.testing.integration.AbstractInteropTest Maven / Gradle / Ivy

There is a newer version: 1.66.0
Show newest version
/*
 * Copyright 2014, gRPC Authors All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.grpc.testing.integration;

import static com.google.common.truth.Truth.assertThat;
import static io.grpc.testing.integration.Messages.PayloadType.COMPRESSABLE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;

import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.ComputeEngineCredentials;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.OAuth2Credentials;
import com.google.auth.oauth2.ServiceAccountCredentials;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.instrumentation.stats.RpcConstants;
import com.google.instrumentation.stats.StatsContextFactory;
import com.google.instrumentation.stats.TagValue;
import com.google.protobuf.BoolValue;
import com.google.protobuf.ByteString;
import com.google.protobuf.EmptyProtos.Empty;
import com.google.protobuf.MessageLite;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.ClientInterceptors;
import io.grpc.ClientStreamTracer;
import io.grpc.Context;
import io.grpc.Grpc;
import io.grpc.ManagedChannel;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.Server;
import io.grpc.ServerCall;
import io.grpc.ServerInterceptor;
import io.grpc.ServerInterceptors;
import io.grpc.ServerStreamTracer;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.grpc.auth.MoreCallCredentials;
import io.grpc.internal.AbstractServerImplBuilder;
import io.grpc.internal.GrpcUtil;
import io.grpc.internal.testing.StatsTestUtils.FakeStatsContextFactory;
import io.grpc.internal.testing.StatsTestUtils.MetricsRecord;
import io.grpc.internal.testing.TestClientStreamTracer;
import io.grpc.internal.testing.TestServerStreamTracer;
import io.grpc.internal.testing.TestStreamTracer;
import io.grpc.protobuf.ProtoUtils;
import io.grpc.stub.ClientCallStreamObserver;
import io.grpc.stub.ClientCalls;
import io.grpc.stub.MetadataUtils;
import io.grpc.stub.StreamObserver;
import io.grpc.testing.StreamRecorder;
import io.grpc.testing.TestUtils;
import io.grpc.testing.integration.Messages.EchoStatus;
import io.grpc.testing.integration.Messages.Payload;
import io.grpc.testing.integration.Messages.PayloadType;
import io.grpc.testing.integration.Messages.ResponseParameters;
import io.grpc.testing.integration.Messages.SimpleRequest;
import io.grpc.testing.integration.Messages.SimpleResponse;
import io.grpc.testing.integration.Messages.StreamingInputCallRequest;
import io.grpc.testing.integration.Messages.StreamingInputCallResponse;
import io.grpc.testing.integration.Messages.StreamingOutputCallRequest;
import io.grpc.testing.integration.Messages.StreamingOutputCallResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketAddress;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.verification.VerificationMode;

/**
 * Abstract base class for all GRPC transport tests.
 *
 * 

New tests should avoid using Mockito to support running on AppEngine.

*/ public abstract class AbstractInteropTest { /** Must be at least {@link #unaryPayloadLength()}, plus some to account for encoding overhead. */ public static final int MAX_MESSAGE_SIZE = 16 * 1024 * 1024; public static final Metadata.Key METADATA_KEY = ProtoUtils.keyForProto(Messages.SimpleContext.getDefaultInstance()); private static final AtomicReference> serverCallCapture = new AtomicReference>(); private static final AtomicReference requestHeadersCapture = new AtomicReference(); private static ScheduledExecutorService testServiceExecutor; private static Server server; private static final FakeStatsContextFactory clientStatsCtxFactory = new FakeStatsContextFactory(); private static final FakeStatsContextFactory serverStatsCtxFactory = new FakeStatsContextFactory(); private static final LinkedBlockingQueue serverStreamTracers = new LinkedBlockingQueue(); private static final class ServerStreamTracerInfo { final String fullMethodName; final InteropServerStreamTracer tracer; ServerStreamTracerInfo(String fullMethodName, InteropServerStreamTracer tracer) { this.fullMethodName = fullMethodName; this.tracer = tracer; } private static final class InteropServerStreamTracer extends TestServerStreamTracer { private volatile Context contextCapture; @Override public Context filterContext(Context context) { contextCapture = context; return super.filterContext(context); } } } private static final ServerStreamTracer.Factory serverStreamTracerFactory = new ServerStreamTracer.Factory() { @Override public ServerStreamTracer newServerStreamTracer(String fullMethodName, Metadata headers) { ServerStreamTracerInfo.InteropServerStreamTracer tracer = new ServerStreamTracerInfo.InteropServerStreamTracer(); serverStreamTracers.add(new ServerStreamTracerInfo(fullMethodName, tracer)); return tracer; } }; protected static final Empty EMPTY = Empty.getDefaultInstance(); protected static void startStaticServer( AbstractServerImplBuilder builder, ServerInterceptor ... interceptors) { testServiceExecutor = Executors.newScheduledThreadPool(2); List allInterceptors = ImmutableList.builder() .add(TestUtils.recordServerCallInterceptor(serverCallCapture)) .add(TestUtils.recordRequestHeadersInterceptor(requestHeadersCapture)) .addAll(TestServiceImpl.interceptors()) .add(interceptors) .build(); builder .addService( ServerInterceptors.intercept( new TestServiceImpl(testServiceExecutor), allInterceptors)) .addStreamTracerFactory(serverStreamTracerFactory); io.grpc.internal.TestingAccessor.setStatsContextFactory(builder, serverStatsCtxFactory); try { server = builder.build().start(); } catch (IOException ex) { throw new RuntimeException(ex); } } protected static void stopStaticServer() { server.shutdownNow(); testServiceExecutor.shutdown(); } @VisibleForTesting static int getPort() { return server.getPort(); } protected ManagedChannel channel; protected TestServiceGrpc.TestServiceBlockingStub blockingStub; protected TestServiceGrpc.TestServiceStub asyncStub; private final LinkedBlockingQueue clientStreamTracers = new LinkedBlockingQueue(); private final ClientStreamTracer.Factory clientStreamTracerFactory = new ClientStreamTracer.Factory() { @Override public ClientStreamTracer newClientStreamTracer(CallOptions callOptions, Metadata headers) { TestClientStreamTracer tracer = new TestClientStreamTracer(); clientStreamTracers.add(tracer); return tracer; } }; private final ClientInterceptor tracerSetupInterceptor = new ClientInterceptor() { @Override public ClientCall interceptCall( MethodDescriptor method, CallOptions callOptions, Channel next) { return next.newCall( method, callOptions.withStreamTracerFactory(clientStreamTracerFactory)); } }; /** * Must be called by the subclass setup method if overridden. */ @Before public void setUp() { channel = createChannel(); blockingStub = TestServiceGrpc.newBlockingStub(channel).withInterceptors(tracerSetupInterceptor); asyncStub = TestServiceGrpc.newStub(channel).withInterceptors(tracerSetupInterceptor); requestHeadersCapture.set(null); clientStatsCtxFactory.rolloverRecords(); serverStatsCtxFactory.rolloverRecords(); serverStreamTracers.clear(); } /** Clean up. */ @After public void tearDown() throws Exception { if (channel != null) { channel.shutdown(); } } protected abstract ManagedChannel createChannel(); protected final StatsContextFactory getClientStatsFactory() { return clientStatsCtxFactory; } protected boolean metricsExpected() { return true; } @Test(timeout = 10000) public void emptyUnary() throws Exception { assertEquals(EMPTY, blockingStub.emptyCall(EMPTY)); } /** Sends a cacheable unary rpc using GET. Requires that the server is behind a caching proxy. */ public void cacheableUnary() { // Set safe to true. MethodDescriptor safeCacheableUnaryCallMethod = TestServiceGrpc.METHOD_CACHEABLE_UNARY_CALL.toBuilder().setSafe(true).build(); // Set fake user IP since some proxies (GFE) won't cache requests from localhost. Metadata.Key userIpKey = Metadata.Key.of("x-user-ip", Metadata.ASCII_STRING_MARSHALLER); Metadata metadata = new Metadata(); metadata.put(userIpKey, "1.2.3.4"); Channel channelWithUserIpKey = ClientInterceptors.intercept(channel, MetadataUtils.newAttachHeadersInterceptor(metadata)); SimpleRequest requests1And2 = SimpleRequest.newBuilder() .setPayload( Payload.newBuilder() .setBody(ByteString.copyFromUtf8(String.valueOf(System.nanoTime())))) .build(); SimpleRequest request3 = SimpleRequest.newBuilder() .setPayload( Payload.newBuilder() .setBody(ByteString.copyFromUtf8(String.valueOf(System.nanoTime())))) .build(); SimpleResponse response1 = ClientCalls.blockingUnaryCall( channelWithUserIpKey, safeCacheableUnaryCallMethod, CallOptions.DEFAULT, requests1And2); SimpleResponse response2 = ClientCalls.blockingUnaryCall( channelWithUserIpKey, safeCacheableUnaryCallMethod, CallOptions.DEFAULT, requests1And2); SimpleResponse response3 = ClientCalls.blockingUnaryCall( channelWithUserIpKey, safeCacheableUnaryCallMethod, CallOptions.DEFAULT, request3); assertEquals(response1, response2); assertNotEquals(response1, response3); } @Test(timeout = 10000) public void largeUnary() throws Exception { assumeEnoughMemory(); final SimpleRequest request = SimpleRequest.newBuilder() .setResponseSize(314159) .setResponseType(PayloadType.COMPRESSABLE) .setPayload(Payload.newBuilder() .setBody(ByteString.copyFrom(new byte[271828]))) .build(); final SimpleResponse goldenResponse = SimpleResponse.newBuilder() .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[314159]))) .build(); assertEquals(goldenResponse, blockingStub.unaryCall(request)); if (metricsExpected()) { assertMetrics("grpc.testing.TestService/UnaryCall", Status.Code.OK, Collections.singleton(request), Collections.singleton(goldenResponse)); } } /** * Tests client per-message compression for unary calls. The Java API does not support inspecting * a message's compression level, so this is primarily intended to run against a gRPC C++ server. */ public void clientCompressedUnary() throws Exception { assumeEnoughMemory(); final SimpleRequest expectCompressedRequest = SimpleRequest.newBuilder() .setExpectCompressed(BoolValue.newBuilder().setValue(true)) .setResponseSize(314159) .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[271828]))) .build(); final SimpleRequest expectUncompressedRequest = SimpleRequest.newBuilder() .setExpectCompressed(BoolValue.newBuilder().setValue(false)) .setResponseSize(314159) .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[271828]))) .build(); final SimpleResponse goldenResponse = SimpleResponse.newBuilder() .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[314159]))) .build(); // Send a non-compressed message with expectCompress=true. Servers supporting this test case // should return INVALID_ARGUMENT. try { blockingStub.unaryCall(expectCompressedRequest); fail("expected INVALID_ARGUMENT"); } catch (StatusRuntimeException e) { assertEquals(Status.INVALID_ARGUMENT.getCode(), e.getStatus().getCode()); } if (metricsExpected()) { assertMetrics("grpc.testing.TestService/UnaryCall", Status.Code.INVALID_ARGUMENT); } assertEquals( goldenResponse, blockingStub.withCompression("gzip").unaryCall(expectCompressedRequest)); if (metricsExpected()) { assertMetrics( "grpc.testing.TestService/UnaryCall", Status.Code.OK, Collections.singleton(expectCompressedRequest), Collections.singleton(goldenResponse)); } assertEquals(goldenResponse, blockingStub.unaryCall(expectUncompressedRequest)); if (metricsExpected()) { assertMetrics( "grpc.testing.TestService/UnaryCall", Status.Code.OK, Collections.singleton(expectUncompressedRequest), Collections.singleton(goldenResponse)); } } /** * Tests if the server can send a compressed unary response. Ideally we would assert that the * responses have the requested compression, but this is not supported by the API. Given a * compliant server, this test will exercise the code path for receiving a compressed response but * cannot itself verify that the response was compressed. */ @Test(timeout = 10000) public void serverCompressedUnary() throws Exception { assumeEnoughMemory(); final SimpleRequest responseShouldBeCompressed = SimpleRequest.newBuilder() .setResponseCompressed(BoolValue.newBuilder().setValue(true)) .setResponseSize(314159) .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[271828]))) .build(); final SimpleRequest responseShouldBeUncompressed = SimpleRequest.newBuilder() .setResponseCompressed(BoolValue.newBuilder().setValue(false)) .setResponseSize(314159) .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[271828]))) .build(); final SimpleResponse goldenResponse = SimpleResponse.newBuilder() .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[314159]))) .build(); assertEquals(goldenResponse, blockingStub.unaryCall(responseShouldBeCompressed)); if (metricsExpected()) { assertMetrics( "grpc.testing.TestService/UnaryCall", Status.Code.OK, Collections.singleton(responseShouldBeCompressed), Collections.singleton(goldenResponse)); } assertEquals(goldenResponse, blockingStub.unaryCall(responseShouldBeUncompressed)); if (metricsExpected()) { assertMetrics( "grpc.testing.TestService/UnaryCall", Status.Code.OK, Collections.singleton(responseShouldBeUncompressed), Collections.singleton(goldenResponse)); } } @Test(timeout = 10000) public void serverStreaming() throws Exception { final StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder() .setResponseType(PayloadType.COMPRESSABLE) .addResponseParameters(ResponseParameters.newBuilder() .setSize(31415)) .addResponseParameters(ResponseParameters.newBuilder() .setSize(9)) .addResponseParameters(ResponseParameters.newBuilder() .setSize(2653)) .addResponseParameters(ResponseParameters.newBuilder() .setSize(58979)) .build(); final List goldenResponses = Arrays.asList( StreamingOutputCallResponse.newBuilder() .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[31415]))) .build(), StreamingOutputCallResponse.newBuilder() .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[9]))) .build(), StreamingOutputCallResponse.newBuilder() .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[2653]))) .build(), StreamingOutputCallResponse.newBuilder() .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[58979]))) .build()); StreamRecorder recorder = StreamRecorder.create(); asyncStub.streamingOutputCall(request, recorder); recorder.awaitCompletion(); assertSuccess(recorder); assertEquals(goldenResponses, recorder.getValues()); } @Test(timeout = 10000) public void clientStreaming() throws Exception { final List requests = Arrays.asList( StreamingInputCallRequest.newBuilder() .setPayload(Payload.newBuilder() .setBody(ByteString.copyFrom(new byte[27182]))) .build(), StreamingInputCallRequest.newBuilder() .setPayload(Payload.newBuilder() .setBody(ByteString.copyFrom(new byte[8]))) .build(), StreamingInputCallRequest.newBuilder() .setPayload(Payload.newBuilder() .setBody(ByteString.copyFrom(new byte[1828]))) .build(), StreamingInputCallRequest.newBuilder() .setPayload(Payload.newBuilder() .setBody(ByteString.copyFrom(new byte[45904]))) .build()); final StreamingInputCallResponse goldenResponse = StreamingInputCallResponse.newBuilder() .setAggregatedPayloadSize(74922) .build(); StreamRecorder responseObserver = StreamRecorder.create(); StreamObserver requestObserver = asyncStub.streamingInputCall(responseObserver); for (StreamingInputCallRequest request : requests) { requestObserver.onNext(request); } requestObserver.onCompleted(); assertEquals(goldenResponse, responseObserver.firstValue().get()); responseObserver.awaitCompletion(); } /** * Tests client per-message compression for streaming calls. The Java API does not support * inspecting a message's compression level, so this is primarily intended to run against a gRPC * C++ server. */ public void clientCompressedStreaming() throws Exception { final StreamingInputCallRequest expectCompressedRequest = StreamingInputCallRequest.newBuilder() .setExpectCompressed(BoolValue.newBuilder().setValue(true)) .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[27182]))) .build(); final StreamingInputCallRequest expectUncompressedRequest = StreamingInputCallRequest.newBuilder() .setExpectCompressed(BoolValue.newBuilder().setValue(false)) .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[45904]))) .build(); final StreamingInputCallResponse goldenResponse = StreamingInputCallResponse.newBuilder().setAggregatedPayloadSize(73086).build(); StreamRecorder responseObserver = StreamRecorder.create(); StreamObserver requestObserver = asyncStub.streamingInputCall(responseObserver); // Send a non-compressed message with expectCompress=true. Servers supporting this test case // should return INVALID_ARGUMENT. requestObserver.onNext(expectCompressedRequest); responseObserver.awaitCompletion(operationTimeoutMillis(), TimeUnit.MILLISECONDS); Throwable e = responseObserver.getError(); assertNotNull("expected INVALID_ARGUMENT", e); assertEquals(Status.INVALID_ARGUMENT.getCode(), Status.fromThrowable(e).getCode()); // Start a new stream responseObserver = StreamRecorder.create(); @SuppressWarnings("unchecked") ClientCallStreamObserver clientCallStreamObserver = (ClientCallStreamObserver) asyncStub.withCompression("gzip").streamingInputCall(responseObserver); clientCallStreamObserver.setMessageCompression(true); clientCallStreamObserver.onNext(expectCompressedRequest); clientCallStreamObserver.setMessageCompression(false); clientCallStreamObserver.onNext(expectUncompressedRequest); clientCallStreamObserver.onCompleted(); responseObserver.awaitCompletion(); assertSuccess(responseObserver); assertEquals(goldenResponse, responseObserver.firstValue().get()); } /** * Tests server per-message compression in a streaming response. Ideally we would assert that the * responses have the requested compression, but this is not supported by the API. Given a * compliant server, this test will exercise the code path for receiving a compressed response but * cannot itself verify that the response was compressed. */ public void serverCompressedStreaming() throws Exception { final StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder() .addResponseParameters( ResponseParameters.newBuilder() .setCompressed(BoolValue.newBuilder().setValue(true)) .setSize(31415)) .addResponseParameters( ResponseParameters.newBuilder() .setCompressed(BoolValue.newBuilder().setValue(false)) .setSize(92653)) .build(); final List goldenResponses = Arrays.asList( StreamingOutputCallResponse.newBuilder() .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[31415]))) .build(), StreamingOutputCallResponse.newBuilder() .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[92653]))) .build()); StreamRecorder recorder = StreamRecorder.create(); asyncStub.streamingOutputCall(request, recorder); recorder.awaitCompletion(); assertSuccess(recorder); assertEquals(goldenResponses, recorder.getValues()); } @Test(timeout = 10000) public void pingPong() throws Exception { final List requests = Arrays.asList( StreamingOutputCallRequest.newBuilder() .addResponseParameters(ResponseParameters.newBuilder() .setSize(31415)) .setPayload(Payload.newBuilder() .setBody(ByteString.copyFrom(new byte[27182]))) .build(), StreamingOutputCallRequest.newBuilder() .addResponseParameters(ResponseParameters.newBuilder() .setSize(9)) .setPayload(Payload.newBuilder() .setBody(ByteString.copyFrom(new byte[8]))) .build(), StreamingOutputCallRequest.newBuilder() .addResponseParameters(ResponseParameters.newBuilder() .setSize(2653)) .setPayload(Payload.newBuilder() .setBody(ByteString.copyFrom(new byte[1828]))) .build(), StreamingOutputCallRequest.newBuilder() .addResponseParameters(ResponseParameters.newBuilder() .setSize(58979)) .setPayload(Payload.newBuilder() .setBody(ByteString.copyFrom(new byte[45904]))) .build()); final List goldenResponses = Arrays.asList( StreamingOutputCallResponse.newBuilder() .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[31415]))) .build(), StreamingOutputCallResponse.newBuilder() .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[9]))) .build(), StreamingOutputCallResponse.newBuilder() .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[2653]))) .build(), StreamingOutputCallResponse.newBuilder() .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[58979]))) .build()); final ArrayBlockingQueue queue = new ArrayBlockingQueue(5); StreamObserver requestObserver = asyncStub.fullDuplexCall(new StreamObserver() { @Override public void onNext(StreamingOutputCallResponse response) { queue.add(response); } @Override public void onError(Throwable t) { queue.add(t); } @Override public void onCompleted() { queue.add("Completed"); } }); for (int i = 0; i < requests.size(); i++) { assertNull(queue.peek()); requestObserver.onNext(requests.get(i)); assertEquals(goldenResponses.get(i), queue.poll(operationTimeoutMillis(), TimeUnit.MILLISECONDS)); } requestObserver.onCompleted(); assertEquals("Completed", queue.poll(operationTimeoutMillis(), TimeUnit.MILLISECONDS)); } @Test(timeout = 10000) public void emptyStream() throws Exception { StreamRecorder responseObserver = StreamRecorder.create(); StreamObserver requestObserver = asyncStub.fullDuplexCall(responseObserver); requestObserver.onCompleted(); responseObserver.awaitCompletion(operationTimeoutMillis(), TimeUnit.MILLISECONDS); } @Test(timeout = 10000) public void cancelAfterBegin() throws Exception { StreamRecorder responseObserver = StreamRecorder.create(); StreamObserver requestObserver = asyncStub.streamingInputCall(responseObserver); requestObserver.onError(new RuntimeException()); responseObserver.awaitCompletion(); assertEquals(Arrays.asList(), responseObserver.getValues()); assertEquals(Status.Code.CANCELLED, Status.fromThrowable(responseObserver.getError()).getCode()); if (metricsExpected()) { // CensusStreamTracerModule record final status in the interceptor, thus is guaranteed to be // recorded. The tracer stats rely on the stream being created, which is not always the case // in this test. Therefore we don't check the tracer stats. MetricsRecord clientRecord = clientStatsCtxFactory.pollRecord(5, TimeUnit.SECONDS); checkTags( clientRecord, false, "grpc.testing.TestService/StreamingInputCall", Status.CANCELLED.getCode()); // Do not check server-side metrics, because the status on the server side is undetermined. } } @Test(timeout = 10000) public void cancelAfterFirstResponse() throws Exception { final StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder() .addResponseParameters(ResponseParameters.newBuilder() .setSize(31415)) .setPayload(Payload.newBuilder() .setBody(ByteString.copyFrom(new byte[27182]))) .build(); final StreamingOutputCallResponse goldenResponse = StreamingOutputCallResponse.newBuilder() .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[31415]))) .build(); StreamRecorder responseObserver = StreamRecorder.create(); StreamObserver requestObserver = asyncStub.fullDuplexCall(responseObserver); requestObserver.onNext(request); assertEquals(goldenResponse, responseObserver.firstValue().get()); requestObserver.onError(new RuntimeException()); responseObserver.awaitCompletion(operationTimeoutMillis(), TimeUnit.MILLISECONDS); assertEquals(1, responseObserver.getValues().size()); assertEquals(Status.Code.CANCELLED, Status.fromThrowable(responseObserver.getError()).getCode()); if (metricsExpected()) { assertMetrics("grpc.testing.TestService/FullDuplexCall", Status.Code.CANCELLED); } } @Test(timeout = 10000) public void fullDuplexCallShouldSucceed() throws Exception { // Build the request. List responseSizes = Arrays.asList(50, 100, 150, 200); StreamingOutputCallRequest.Builder streamingOutputBuilder = StreamingOutputCallRequest.newBuilder(); streamingOutputBuilder.setResponseType(COMPRESSABLE); for (Integer size : responseSizes) { streamingOutputBuilder.addResponseParametersBuilder().setSize(size).setIntervalUs(0); } final StreamingOutputCallRequest request = streamingOutputBuilder.build(); StreamRecorder recorder = StreamRecorder.create(); StreamObserver requestStream = asyncStub.fullDuplexCall(recorder); final int numRequests = 10; List requests = new ArrayList(numRequests); for (int ix = numRequests; ix > 0; --ix) { requests.add(request); requestStream.onNext(request); } requestStream.onCompleted(); recorder.awaitCompletion(); assertSuccess(recorder); assertEquals(responseSizes.size() * numRequests, recorder.getValues().size()); for (int ix = 0; ix < recorder.getValues().size(); ++ix) { StreamingOutputCallResponse response = recorder.getValues().get(ix); assertEquals(COMPRESSABLE, response.getPayload().getType()); int length = response.getPayload().getBody().size(); int expectedSize = responseSizes.get(ix % responseSizes.size()); assertEquals("comparison failed at index " + ix, expectedSize, length); } if (metricsExpected()) { assertMetrics("grpc.testing.TestService/FullDuplexCall", Status.Code.OK, requests, recorder.getValues()); } } @Test(timeout = 10000) public void halfDuplexCallShouldSucceed() throws Exception { // Build the request. List responseSizes = Arrays.asList(50, 100, 150, 200); StreamingOutputCallRequest.Builder streamingOutputBuilder = StreamingOutputCallRequest.newBuilder(); streamingOutputBuilder.setResponseType(COMPRESSABLE); for (Integer size : responseSizes) { streamingOutputBuilder.addResponseParametersBuilder().setSize(size).setIntervalUs(0); } final StreamingOutputCallRequest request = streamingOutputBuilder.build(); StreamRecorder recorder = StreamRecorder.create(); StreamObserver requestStream = asyncStub.halfDuplexCall(recorder); final int numRequests = 10; List requests = new ArrayList(numRequests); for (int ix = numRequests; ix > 0; --ix) { requests.add(request); requestStream.onNext(request); } requestStream.onCompleted(); recorder.awaitCompletion(); assertSuccess(recorder); assertEquals(responseSizes.size() * numRequests, recorder.getValues().size()); for (int ix = 0; ix < recorder.getValues().size(); ++ix) { StreamingOutputCallResponse response = recorder.getValues().get(ix); assertEquals(COMPRESSABLE, response.getPayload().getType()); int length = response.getPayload().getBody().size(); int expectedSize = responseSizes.get(ix % responseSizes.size()); assertEquals("comparison failed at index " + ix, expectedSize, length); } } @Test(timeout = 10000) public void serverStreamingShouldBeFlowControlled() throws Exception { final StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder() .setResponseType(COMPRESSABLE) .addResponseParameters(ResponseParameters.newBuilder().setSize(100000)) .addResponseParameters(ResponseParameters.newBuilder().setSize(100001)) .build(); final List goldenResponses = Arrays.asList( StreamingOutputCallResponse.newBuilder() .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[100000]))).build(), StreamingOutputCallResponse.newBuilder() .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[100001]))).build()); long start = System.nanoTime(); final ArrayBlockingQueue queue = new ArrayBlockingQueue(10); ClientCall call = channel.newCall(TestServiceGrpc.METHOD_STREAMING_OUTPUT_CALL, CallOptions.DEFAULT); call.start(new ClientCall.Listener() { @Override public void onHeaders(Metadata headers) {} @Override public void onMessage(final StreamingOutputCallResponse message) { queue.add(message); } @Override public void onClose(Status status, Metadata trailers) { queue.add(status); } }, new Metadata()); call.sendMessage(request); call.halfClose(); // Time how long it takes to get the first response. call.request(1); assertEquals(goldenResponses.get(0), queue.poll(operationTimeoutMillis(), TimeUnit.MILLISECONDS)); long firstCallDuration = System.nanoTime() - start; // Without giving additional flow control, make sure that we don't get another response. We wait // until we are comfortable the next message isn't coming. We may have very low nanoTime // resolution (like on Windows) or be using a testing, in-process transport where message // handling is instantaneous. In both cases, firstCallDuration may be 0, so round up sleep time // to at least 1ms. assertNull(queue.poll(Math.max(firstCallDuration * 4, 1 * 1000 * 1000), TimeUnit.NANOSECONDS)); // Make sure that everything still completes. call.request(1); assertEquals(goldenResponses.get(1), queue.poll(operationTimeoutMillis(), TimeUnit.MILLISECONDS)); assertEquals(Status.OK, queue.poll(operationTimeoutMillis(), TimeUnit.MILLISECONDS)); } @Test(timeout = 30000) public void veryLargeRequest() throws Exception { assumeEnoughMemory(); final SimpleRequest request = SimpleRequest.newBuilder() .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[unaryPayloadLength()]))) .setResponseSize(10) .setResponseType(PayloadType.COMPRESSABLE) .build(); final SimpleResponse goldenResponse = SimpleResponse.newBuilder() .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[10]))) .build(); assertEquals(goldenResponse, blockingStub.unaryCall(request)); } @Test(timeout = 30000) public void veryLargeResponse() throws Exception { assumeEnoughMemory(); final SimpleRequest request = SimpleRequest.newBuilder() .setResponseSize(unaryPayloadLength()) .setResponseType(PayloadType.COMPRESSABLE) .build(); final SimpleResponse goldenResponse = SimpleResponse.newBuilder() .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[unaryPayloadLength()]))) .build(); assertEquals(goldenResponse, blockingStub.unaryCall(request)); } @Test(timeout = 10000) public void exchangeMetadataUnaryCall() throws Exception { TestServiceGrpc.TestServiceBlockingStub stub = blockingStub; // Capture the metadata exchange Metadata fixedHeaders = new Metadata(); // Send a context proto (as it's in the default extension registry) Messages.SimpleContext contextValue = Messages.SimpleContext.newBuilder().setValue("dog").build(); fixedHeaders.put(METADATA_KEY, contextValue); stub = MetadataUtils.attachHeaders(stub, fixedHeaders); // .. and expect it to be echoed back in trailers AtomicReference trailersCapture = new AtomicReference(); AtomicReference headersCapture = new AtomicReference(); stub = MetadataUtils.captureMetadata(stub, headersCapture, trailersCapture); assertNotNull(stub.emptyCall(EMPTY)); // Assert that our side channel object is echoed back in both headers and trailers Assert.assertEquals(contextValue, headersCapture.get().get(METADATA_KEY)); Assert.assertEquals(contextValue, trailersCapture.get().get(METADATA_KEY)); } @Test(timeout = 10000) public void exchangeMetadataStreamingCall() throws Exception { TestServiceGrpc.TestServiceStub stub = asyncStub; // Capture the metadata exchange Metadata fixedHeaders = new Metadata(); // Send a context proto (as it's in the default extension registry) Messages.SimpleContext contextValue = Messages.SimpleContext.newBuilder().setValue("dog").build(); fixedHeaders.put(METADATA_KEY, contextValue); stub = MetadataUtils.attachHeaders(stub, fixedHeaders); // .. and expect it to be echoed back in trailers AtomicReference trailersCapture = new AtomicReference(); AtomicReference headersCapture = new AtomicReference(); stub = MetadataUtils.captureMetadata(stub, headersCapture, trailersCapture); List responseSizes = Arrays.asList(50, 100, 150, 200); Messages.StreamingOutputCallRequest.Builder streamingOutputBuilder = Messages.StreamingOutputCallRequest.newBuilder(); streamingOutputBuilder.setResponseType(COMPRESSABLE); for (Integer size : responseSizes) { streamingOutputBuilder.addResponseParametersBuilder().setSize(size).setIntervalUs(0); } final Messages.StreamingOutputCallRequest request = streamingOutputBuilder.build(); StreamRecorder recorder = StreamRecorder.create(); StreamObserver requestStream = stub.fullDuplexCall(recorder); final int numRequests = 10; List requests = new ArrayList(numRequests); for (int ix = numRequests; ix > 0; --ix) { requests.add(request); requestStream.onNext(request); } requestStream.onCompleted(); recorder.awaitCompletion(); assertSuccess(recorder); org.junit.Assert.assertEquals(responseSizes.size() * numRequests, recorder.getValues().size()); // Assert that our side channel object is echoed back in both headers and trailers Assert.assertEquals(contextValue, headersCapture.get().get(METADATA_KEY)); Assert.assertEquals(contextValue, trailersCapture.get().get(METADATA_KEY)); } @Test(timeout = 10000) public void sendsTimeoutHeader() { long configuredTimeoutMinutes = 100; TestServiceGrpc.TestServiceBlockingStub stub = blockingStub.withDeadlineAfter(configuredTimeoutMinutes, TimeUnit.MINUTES); stub.emptyCall(EMPTY); long transferredTimeoutMinutes = TimeUnit.NANOSECONDS.toMinutes( requestHeadersCapture.get().get(GrpcUtil.TIMEOUT_KEY)); Assert.assertTrue( "configuredTimeoutMinutes=" + configuredTimeoutMinutes + ", transferredTimeoutMinutes=" + transferredTimeoutMinutes, configuredTimeoutMinutes - transferredTimeoutMinutes >= 0 && configuredTimeoutMinutes - transferredTimeoutMinutes <= 1); } @Test public void deadlineNotExceeded() { // warm up the channel and JVM blockingStub.emptyCall(Empty.getDefaultInstance()); blockingStub .withDeadlineAfter(10, TimeUnit.SECONDS) .streamingOutputCall(StreamingOutputCallRequest.newBuilder() .addResponseParameters(ResponseParameters.newBuilder() .setIntervalUs(0)) .build()).next(); } @Test(timeout = 25000) public void deadlineExceeded() throws Exception { // warm up the channel and JVM blockingStub.emptyCall(Empty.getDefaultInstance()); TestServiceGrpc.TestServiceBlockingStub stub = blockingStub.withDeadlineAfter(10, TimeUnit.MILLISECONDS); StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder() .addResponseParameters(ResponseParameters.newBuilder() .setIntervalUs((int) TimeUnit.SECONDS.toMicros(20))) .build(); try { stub.streamingOutputCall(request).next(); fail("Expected deadline to be exceeded"); } catch (StatusRuntimeException ex) { assertEquals(Status.DEADLINE_EXCEEDED.getCode(), ex.getStatus().getCode()); } if (metricsExpected()) { assertMetrics("grpc.testing.TestService/EmptyCall", Status.Code.OK); // Stream may not have been created before deadline is exceeded, thus we don't test the tracer // stats. MetricsRecord clientRecord = clientStatsCtxFactory.pollRecord(5, TimeUnit.SECONDS); checkTags( clientRecord, false, "grpc.testing.TestService/StreamingOutputCall", Status.Code.DEADLINE_EXCEEDED); // Do not check server-side metrics, because the status on the server side is undetermined. } } @Test(timeout = 10000) public void deadlineExceededServerStreaming() throws Exception { // warm up the channel and JVM blockingStub.emptyCall(Empty.getDefaultInstance()); ResponseParameters.Builder responseParameters = ResponseParameters.newBuilder() .setSize(1) .setIntervalUs(10000); StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder() .setResponseType(PayloadType.COMPRESSABLE) .addResponseParameters(responseParameters) .addResponseParameters(responseParameters) .addResponseParameters(responseParameters) .addResponseParameters(responseParameters) .build(); StreamRecorder recorder = StreamRecorder.create(); asyncStub .withDeadlineAfter(30, TimeUnit.MILLISECONDS) .streamingOutputCall(request, recorder); recorder.awaitCompletion(); assertEquals(Status.DEADLINE_EXCEEDED.getCode(), Status.fromThrowable(recorder.getError()).getCode()); if (metricsExpected()) { assertMetrics("grpc.testing.TestService/EmptyCall", Status.Code.OK); // Stream may not have been created when deadline is exceeded, thus we don't check tracer // stats. MetricsRecord clientRecord = clientStatsCtxFactory.pollRecord(5, TimeUnit.SECONDS); checkTags( clientRecord, false, "grpc.testing.TestService/StreamingOutputCall", Status.Code.DEADLINE_EXCEEDED); // Do not check server-side metrics, because the status on the server side is undetermined. } } @Test(timeout = 10000) public void deadlineInPast() throws Exception { // Test once with idle channel and once with active channel try { blockingStub .withDeadlineAfter(-10, TimeUnit.SECONDS) .emptyCall(Empty.getDefaultInstance()); fail("Should have thrown"); } catch (StatusRuntimeException ex) { assertEquals(Status.Code.DEADLINE_EXCEEDED, ex.getStatus().getCode()); } // CensusStreamTracerModule record final status in the interceptor, thus is guaranteed to be // recorded. The tracer stats rely on the stream being created, which is not the case if // deadline is exceeded before the call is created. Therefore we don't check the tracer stats. if (metricsExpected()) { MetricsRecord clientRecord = clientStatsCtxFactory.pollRecord(5, TimeUnit.SECONDS); checkTags( clientRecord, false, "grpc.testing.TestService/EmptyCall", Status.DEADLINE_EXCEEDED.getCode()); } // warm up the channel blockingStub.emptyCall(Empty.getDefaultInstance()); try { blockingStub .withDeadlineAfter(-10, TimeUnit.SECONDS) .emptyCall(Empty.getDefaultInstance()); fail("Should have thrown"); } catch (StatusRuntimeException ex) { assertEquals(Status.Code.DEADLINE_EXCEEDED, ex.getStatus().getCode()); } if (metricsExpected()) { assertMetrics("grpc.testing.TestService/EmptyCall", Status.Code.OK); MetricsRecord clientRecord = clientStatsCtxFactory.pollRecord(5, TimeUnit.SECONDS); checkTags( clientRecord, false, "grpc.testing.TestService/EmptyCall", Status.DEADLINE_EXCEEDED.getCode()); } } @Test(timeout = 10000) public void maxInboundSize_exact() { StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder() .addResponseParameters(ResponseParameters.newBuilder().setSize(1)) .build(); int size = blockingStub.streamingOutputCall(request).next().getSerializedSize(); TestServiceGrpc.TestServiceBlockingStub stub = blockingStub.withMaxInboundMessageSize(size); stub.streamingOutputCall(request).next(); } @Test(timeout = 10000) public void maxInboundSize_tooBig() { StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder() .addResponseParameters(ResponseParameters.newBuilder().setSize(1)) .build(); int size = blockingStub.streamingOutputCall(request).next().getSerializedSize(); TestServiceGrpc.TestServiceBlockingStub stub = blockingStub.withMaxInboundMessageSize(size - 1); try { stub.streamingOutputCall(request).next(); fail(); } catch (StatusRuntimeException ex) { Status s = ex.getStatus(); assertThat(s.getCode()).named(s.toString()).isEqualTo(Status.Code.RESOURCE_EXHAUSTED); assertThat(Throwables.getStackTraceAsString(ex)).contains("exceeds maximum"); } } @Test(timeout = 10000) public void maxOutboundSize_exact() { // warm up the channel and JVM blockingStub.emptyCall(Empty.getDefaultInstance()); // set at least one field to ensure the size is non-zero. StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder() .addResponseParameters(ResponseParameters.newBuilder().setSize(1)) .build(); TestServiceGrpc.TestServiceBlockingStub stub = blockingStub.withMaxOutboundMessageSize(request.getSerializedSize()); stub.streamingOutputCall(request).next(); } @Test(timeout = 10000) public void maxOutboundSize_tooBig() { // warm up the channel and JVM blockingStub.emptyCall(Empty.getDefaultInstance()); // set at least one field to ensure the size is non-zero. StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder() .addResponseParameters(ResponseParameters.newBuilder().setSize(1)) .build(); TestServiceGrpc.TestServiceBlockingStub stub = blockingStub.withMaxOutboundMessageSize(request.getSerializedSize() - 1); try { stub.streamingOutputCall(request).next(); fail(); } catch (StatusRuntimeException ex) { Status s = ex.getStatus(); assertThat(s.getCode()).named(s.toString()).isEqualTo(Status.Code.CANCELLED); assertThat(Throwables.getStackTraceAsString(ex)).contains("message too large"); } } protected int unaryPayloadLength() { // 10MiB. return 10485760; } @Test(timeout = 10000) public void gracefulShutdown() throws Exception { final List requests = Arrays.asList( StreamingOutputCallRequest.newBuilder() .addResponseParameters(ResponseParameters.newBuilder() .setSize(3)) .setPayload(Payload.newBuilder() .setBody(ByteString.copyFrom(new byte[2]))) .build(), StreamingOutputCallRequest.newBuilder() .addResponseParameters(ResponseParameters.newBuilder() .setSize(1)) .setPayload(Payload.newBuilder() .setBody(ByteString.copyFrom(new byte[7]))) .build(), StreamingOutputCallRequest.newBuilder() .addResponseParameters(ResponseParameters.newBuilder() .setSize(4)) .setPayload(Payload.newBuilder() .setBody(ByteString.copyFrom(new byte[1]))) .build()); final List goldenResponses = Arrays.asList( StreamingOutputCallResponse.newBuilder() .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[3]))) .build(), StreamingOutputCallResponse.newBuilder() .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[1]))) .build(), StreamingOutputCallResponse.newBuilder() .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[4]))) .build()); @SuppressWarnings("unchecked") StreamObserver responseObserver = mock(StreamObserver.class); StreamObserver requestObserver = asyncStub.fullDuplexCall(responseObserver); requestObserver.onNext(requests.get(0)); verify(responseObserver, timeout(operationTimeoutMillis())).onNext(goldenResponses.get(0)); // Initiate graceful shutdown. channel.shutdown(); requestObserver.onNext(requests.get(1)); verify(responseObserver, timeout(operationTimeoutMillis())).onNext(goldenResponses.get(1)); // The previous ping-pong could have raced with the shutdown, but this one certainly shouldn't. requestObserver.onNext(requests.get(2)); verify(responseObserver, timeout(operationTimeoutMillis())).onNext(goldenResponses.get(2)); requestObserver.onCompleted(); verify(responseObserver, timeout(operationTimeoutMillis())).onCompleted(); verifyNoMoreInteractions(responseObserver); } @Test(timeout = 10000) public void customMetadata() throws Exception { final int responseSize = 314159; final int requestSize = 271828; final SimpleRequest request = SimpleRequest.newBuilder() .setResponseSize(responseSize) .setResponseType(PayloadType.COMPRESSABLE) .setPayload(Payload.newBuilder() .setBody(ByteString.copyFrom(new byte[requestSize]))) .build(); final StreamingOutputCallRequest streamingRequest = StreamingOutputCallRequest.newBuilder() .addResponseParameters(ResponseParameters.newBuilder().setSize(responseSize)) .setResponseType(PayloadType.COMPRESSABLE) .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[requestSize]))) .build(); final SimpleResponse goldenResponse = SimpleResponse.newBuilder() .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[responseSize]))) .build(); final StreamingOutputCallResponse goldenStreamingResponse = StreamingOutputCallResponse.newBuilder() .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[responseSize]))) .build(); final byte[] trailingBytes = {(byte) 0xa, (byte) 0xb, (byte) 0xa, (byte) 0xb, (byte) 0xa, (byte) 0xb}; // Test UnaryCall Metadata metadata = new Metadata(); metadata.put(Util.ECHO_INITIAL_METADATA_KEY, "test_initial_metadata_value"); metadata.put(Util.ECHO_TRAILING_METADATA_KEY, trailingBytes); TestServiceGrpc.TestServiceBlockingStub blockingStub = this.blockingStub; blockingStub = MetadataUtils.attachHeaders(blockingStub, metadata); AtomicReference headersCapture = new AtomicReference(); AtomicReference trailersCapture = new AtomicReference(); blockingStub = MetadataUtils.captureMetadata(blockingStub, headersCapture, trailersCapture); SimpleResponse response = blockingStub.unaryCall(request); assertEquals(goldenResponse, response); assertEquals("test_initial_metadata_value", headersCapture.get().get(Util.ECHO_INITIAL_METADATA_KEY)); assertTrue( Arrays.equals(trailingBytes, trailersCapture.get().get(Util.ECHO_TRAILING_METADATA_KEY))); if (metricsExpected()) { assertMetrics("grpc.testing.TestService/UnaryCall", Status.Code.OK, Collections.singleton(request), Collections.singleton(goldenResponse)); } // Test FullDuplexCall metadata = new Metadata(); metadata.put(Util.ECHO_INITIAL_METADATA_KEY, "test_initial_metadata_value"); metadata.put(Util.ECHO_TRAILING_METADATA_KEY, trailingBytes); TestServiceGrpc.TestServiceStub stub = asyncStub; stub = MetadataUtils.attachHeaders(stub, metadata); headersCapture = new AtomicReference(); trailersCapture = new AtomicReference(); stub = MetadataUtils.captureMetadata(stub, headersCapture, trailersCapture); StreamRecorder recorder = StreamRecorder.create(); StreamObserver requestStream = stub.fullDuplexCall(recorder); requestStream.onNext(streamingRequest); requestStream.onCompleted(); recorder.awaitCompletion(); assertSuccess(recorder); assertEquals(goldenStreamingResponse, recorder.firstValue().get()); assertEquals("test_initial_metadata_value", headersCapture.get().get(Util.ECHO_INITIAL_METADATA_KEY)); assertTrue( Arrays.equals(trailingBytes, trailersCapture.get().get(Util.ECHO_TRAILING_METADATA_KEY))); if (metricsExpected()) { assertMetrics("grpc.testing.TestService/FullDuplexCall", Status.Code.OK, Collections.singleton(streamingRequest), Collections.singleton(goldenStreamingResponse)); } } @Test(timeout = 10000) public void statusCodeAndMessage() throws Exception { int errorCode = 2; String errorMessage = "test status message"; EchoStatus responseStatus = EchoStatus.newBuilder() .setCode(errorCode) .setMessage(errorMessage) .build(); SimpleRequest simpleRequest = SimpleRequest.newBuilder() .setResponseStatus(responseStatus) .build(); StreamingOutputCallRequest streamingRequest = StreamingOutputCallRequest.newBuilder() .setResponseStatus(responseStatus) .build(); // Test UnaryCall try { blockingStub.unaryCall(simpleRequest); fail(); } catch (StatusRuntimeException e) { assertEquals(Status.UNKNOWN.getCode(), e.getStatus().getCode()); assertEquals(errorMessage, e.getStatus().getDescription()); } if (metricsExpected()) { assertMetrics("grpc.testing.TestService/UnaryCall", Status.Code.UNKNOWN); } // Test FullDuplexCall @SuppressWarnings("unchecked") StreamObserver responseObserver = mock(StreamObserver.class); StreamObserver requestObserver = asyncStub.fullDuplexCall(responseObserver); requestObserver.onNext(streamingRequest); requestObserver.onCompleted(); ArgumentCaptor captor = ArgumentCaptor.forClass(Throwable.class); verify(responseObserver, timeout(operationTimeoutMillis())).onError(captor.capture()); assertEquals(Status.UNKNOWN.getCode(), Status.fromThrowable(captor.getValue()).getCode()); assertEquals(errorMessage, Status.fromThrowable(captor.getValue()).getDescription()); verifyNoMoreInteractions(responseObserver); if (metricsExpected()) { assertMetrics("grpc.testing.TestService/FullDuplexCall", Status.Code.UNKNOWN); } } /** Sends an rpc to an unimplemented method within TestService. */ @Test(timeout = 10000) public void unimplementedMethod() { try { blockingStub.unimplementedCall(Empty.getDefaultInstance()); fail(); } catch (StatusRuntimeException e) { assertEquals(Status.UNIMPLEMENTED.getCode(), e.getStatus().getCode()); } if (metricsExpected()) { assertClientMetrics("grpc.testing.TestService/UnimplementedCall", Status.Code.UNIMPLEMENTED); } } /** Sends an rpc to an unimplemented service on the server. */ @Test(timeout = 10000) public void unimplementedService() { UnimplementedServiceGrpc.UnimplementedServiceBlockingStub stub = UnimplementedServiceGrpc.newBlockingStub(channel).withInterceptors(tracerSetupInterceptor); try { stub.unimplementedCall(Empty.getDefaultInstance()); fail(); } catch (StatusRuntimeException e) { assertEquals(Status.UNIMPLEMENTED.getCode(), e.getStatus().getCode()); } if (metricsExpected()) { assertMetrics("grpc.testing.UnimplementedService/UnimplementedCall", Status.Code.UNIMPLEMENTED); } } /** Start a fullDuplexCall which the server will not respond, and verify the deadline expires. */ @SuppressWarnings("MissingFail") @Test(timeout = 10000) public void timeoutOnSleepingServer() throws Exception { TestServiceGrpc.TestServiceStub stub = asyncStub.withDeadlineAfter(1, TimeUnit.MILLISECONDS); StreamRecorder responseObserver = StreamRecorder.create(); StreamObserver requestObserver = stub.fullDuplexCall(responseObserver); StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder() .setPayload(Payload.newBuilder() .setBody(ByteString.copyFrom(new byte[27182]))) .build(); try { requestObserver.onNext(request); } catch (IllegalStateException expected) { // This can happen if the stream has already been terminated due to deadline exceeded. } responseObserver.awaitCompletion(operationTimeoutMillis(), TimeUnit.MILLISECONDS); assertEquals(0, responseObserver.getValues().size()); assertEquals(Status.DEADLINE_EXCEEDED.getCode(), Status.fromThrowable(responseObserver.getError()).getCode()); if (metricsExpected()) { // CensusStreamTracerModule record final status in the interceptor, thus is guaranteed to be // recorded. The tracer stats rely on the stream being created, which is not always the case // in this test, thus we will not check that. MetricsRecord clientRecord = clientStatsCtxFactory.pollRecord(5, TimeUnit.SECONDS); checkTags( clientRecord, false, "grpc.testing.TestService/FullDuplexCall", Status.DEADLINE_EXCEEDED.getCode()); } } /** Sends a large unary rpc with service account credentials. */ public void serviceAccountCreds(String jsonKey, InputStream credentialsStream, String authScope) throws Exception { // cast to ServiceAccountCredentials to double-check the right type of object was created. GoogleCredentials credentials = ServiceAccountCredentials.class.cast(GoogleCredentials.fromStream(credentialsStream)); credentials = credentials.createScoped(Arrays.asList(authScope)); TestServiceGrpc.TestServiceBlockingStub stub = blockingStub .withCallCredentials(MoreCallCredentials.from(credentials)); final SimpleRequest request = SimpleRequest.newBuilder() .setFillUsername(true) .setFillOauthScope(true) .setResponseSize(314159) .setResponseType(PayloadType.COMPRESSABLE) .setPayload(Payload.newBuilder() .setBody(ByteString.copyFrom(new byte[271828]))) .build(); final SimpleResponse response = stub.unaryCall(request); assertFalse(response.getUsername().isEmpty()); assertTrue("Received username: " + response.getUsername(), jsonKey.contains(response.getUsername())); assertFalse(response.getOauthScope().isEmpty()); assertTrue("Received oauth scope: " + response.getOauthScope(), authScope.contains(response.getOauthScope())); final SimpleResponse goldenResponse = SimpleResponse.newBuilder() .setOauthScope(response.getOauthScope()) .setUsername(response.getUsername()) .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[314159]))) .build(); assertEquals(goldenResponse, response); } /** Sends a large unary rpc with compute engine credentials. */ public void computeEngineCreds(String serviceAccount, String oauthScope) throws Exception { ComputeEngineCredentials credentials = new ComputeEngineCredentials(); TestServiceGrpc.TestServiceBlockingStub stub = blockingStub .withCallCredentials(MoreCallCredentials.from(credentials)); final SimpleRequest request = SimpleRequest.newBuilder() .setFillUsername(true) .setFillOauthScope(true) .setResponseSize(314159) .setResponseType(PayloadType.COMPRESSABLE) .setPayload(Payload.newBuilder() .setBody(ByteString.copyFrom(new byte[271828]))) .build(); final SimpleResponse response = stub.unaryCall(request); assertEquals(serviceAccount, response.getUsername()); assertFalse(response.getOauthScope().isEmpty()); assertTrue("Received oauth scope: " + response.getOauthScope(), oauthScope.contains(response.getOauthScope())); final SimpleResponse goldenResponse = SimpleResponse.newBuilder() .setOauthScope(response.getOauthScope()) .setUsername(response.getUsername()) .setPayload(Payload.newBuilder() .setType(PayloadType.COMPRESSABLE) .setBody(ByteString.copyFrom(new byte[314159]))) .build(); assertEquals(goldenResponse, response); } /** Test JWT-based auth. */ public void jwtTokenCreds(InputStream serviceAccountJson) throws Exception { final SimpleRequest request = SimpleRequest.newBuilder() .setResponseType(PayloadType.COMPRESSABLE) .setResponseSize(314159) .setPayload(Payload.newBuilder() .setBody(ByteString.copyFrom(new byte[271828]))) .setFillUsername(true) .build(); ServiceAccountCredentials credentials = (ServiceAccountCredentials) GoogleCredentials.fromStream(serviceAccountJson); TestServiceGrpc.TestServiceBlockingStub stub = blockingStub .withCallCredentials(MoreCallCredentials.from(credentials)); SimpleResponse response = stub.unaryCall(request); assertEquals(credentials.getClientEmail(), response.getUsername()); assertEquals(314159, response.getPayload().getBody().size()); } /** Sends a unary rpc with raw oauth2 access token credentials. */ public void oauth2AuthToken(String jsonKey, InputStream credentialsStream, String authScope) throws Exception { GoogleCredentials utilCredentials = GoogleCredentials.fromStream(credentialsStream); utilCredentials = utilCredentials.createScoped(Arrays.asList(authScope)); AccessToken accessToken = utilCredentials.refreshAccessToken(); // TODO(madongfly): The Auth library may have something like AccessTokenCredentials in the // future, change to the official implementation then. OAuth2Credentials credentials = new OAuth2Credentials(accessToken) { private static final long serialVersionUID = 0; @Override public AccessToken refreshAccessToken() throws IOException { throw new IOException("This credential is based on a certain AccessToken, " + "so you can not refresh AccessToken"); } }; TestServiceGrpc.TestServiceBlockingStub stub = blockingStub .withCallCredentials(MoreCallCredentials.from(credentials)); final SimpleRequest request = SimpleRequest.newBuilder() .setFillUsername(true) .setFillOauthScope(true) .build(); final SimpleResponse response = stub.unaryCall(request); assertFalse(response.getUsername().isEmpty()); assertTrue("Received username: " + response.getUsername(), jsonKey.contains(response.getUsername())); assertFalse(response.getOauthScope().isEmpty()); assertTrue("Received oauth scope: " + response.getOauthScope(), authScope.contains(response.getOauthScope())); } /** Sends a unary rpc with "per rpc" raw oauth2 access token credentials. */ public void perRpcCreds(String jsonKey, InputStream credentialsStream, String oauthScope) throws Exception { // In gRpc Java, we don't have per Rpc credentials, user can use an intercepted stub only once // for that purpose. // So, this test is identical to oauth2_auth_token test. oauth2AuthToken(jsonKey, credentialsStream, oauthScope); } protected static void assertSuccess(StreamRecorder recorder) { if (recorder.getError() != null) { throw new AssertionError(recorder.getError()); } } /** Helper for getting remote address {@link io.grpc.ServerCall#getAttributes()} */ protected SocketAddress obtainRemoteClientAddr() { TestServiceGrpc.TestServiceBlockingStub stub = blockingStub.withDeadlineAfter(5, TimeUnit.SECONDS); stub.unaryCall(SimpleRequest.getDefaultInstance()); return serverCallCapture.get().getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR); } /** Helper for asserting TLS info in SSLSession {@link io.grpc.ServerCall#getAttributes()} */ protected void assertX500SubjectDn(String tlsInfo) { TestServiceGrpc.TestServiceBlockingStub stub = blockingStub.withDeadlineAfter(5, TimeUnit.SECONDS); stub.unaryCall(SimpleRequest.getDefaultInstance()); List certificates = Lists.newArrayList(); SSLSession sslSession = serverCallCapture.get().getAttributes().get(Grpc.TRANSPORT_ATTR_SSL_SESSION); try { certificates = Arrays.asList(sslSession.getPeerCertificates()); } catch (SSLPeerUnverifiedException e) { fail("No cert"); } X509Certificate x509cert = (X509Certificate) certificates.get(0); assertEquals(1, certificates.size()); assertEquals(tlsInfo, x509cert.getSubjectDN().toString()); } protected int operationTimeoutMillis() { return 5000; } /** * Some tests run on memory constrained environments. Rather than OOM, just give up. 64 is * choosen as a maximum amount of memory a large test would need. */ private static void assumeEnoughMemory() { Runtime r = Runtime.getRuntime(); long usedMem = r.totalMemory() - r.freeMemory(); long actuallyFreeMemory = r.maxMemory() - usedMem; Assume.assumeTrue( actuallyFreeMemory + " is not sufficient to run this test", actuallyFreeMemory >= 64 * 1024 * 1024); } /** * Wrapper around {@link Mockito#verify}, to keep log spam down on failure. */ private static T verify(T mock, VerificationMode mode) { try { return Mockito.verify(mock, mode); } catch (AssertionError e) { String msg = e.getMessage(); if (msg.length() >= 256) { throw new AssertionError(msg.substring(0, 256), e); } throw e; } } private static T verify(T mock) { return verify(mock, times(1)); } /** * Wrapper around {@link Mockito#verify}, to keep log spam down on failure. */ private static void verifyNoMoreInteractions(Object... mocks) { try { Mockito.verifyNoMoreInteractions(mocks); } catch (AssertionError e) { String msg = e.getMessage(); if (msg.length() >= 256) { throw new AssertionError(msg.substring(0, 256), e); } throw e; } } /** * Poll the next metrics record and check it against the provided information, including the * message sizes. */ private void assertMetrics(String method, Status.Code status, Collection requests, Collection responses) { assertClientMetrics(method, status, requests, responses); assertServerMetrics(method, status, requests, responses); } /** * Poll the next metrics record and check it against the provided information, without checking * the message sizes. */ private void assertMetrics(String method, Status.Code status) { assertMetrics(method, status, null, null); } private void assertClientMetrics(String method, Status.Code code, Collection requests, Collection responses) { // Tracer-based stats TestClientStreamTracer tracer = clientStreamTracers.poll(); assertNotNull(tracer); assertTrue(tracer.getOutboundHeaders()); ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(Status.class); // assertClientMetrics() is called right after application receives status, // but streamClosed() may be called slightly later than that. So we need a timeout. try { assertTrue(tracer.await(5, TimeUnit.SECONDS)); } catch (InterruptedException e) { throw new AssertionError(e); } assertEquals(code, tracer.getStatus().getCode()); // CensusStreamTracerModule records final status in interceptor, which is guaranteed to be done // before application receives status. MetricsRecord clientRecord = clientStatsCtxFactory.pollRecord(); checkTags(clientRecord, false, method, code); if (requests != null && responses != null) { checkTracerMetrics(tracer, requests, responses); checkCensusMetrics(clientRecord, false, requests, responses); } } private void assertClientMetrics(String method, Status.Code status) { assertClientMetrics(method, status, null, null); } private void assertServerMetrics(String method, Status.Code code, Collection requests, Collection responses) { AssertionError checkFailure = null; boolean passed = false; // Because the server doesn't restart between tests, it may still be processing the requests // from the previous tests when a new test starts, thus the test may see metrics from previous // tests. The best we can do here is to exhaust all records and find one that matches the given // conditions. while (true) { MetricsRecord serverRecord; try { // On the server, the stats is finalized in ServerStreamListener.closed(), which can be run // after the client receives the final status. So we use a timeout. serverRecord = serverStatsCtxFactory.pollRecord(5, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); } if (serverRecord == null) { break; } try { checkTags(serverRecord, true, method, code); if (requests != null && responses != null) { checkCensusMetrics(serverRecord, true, requests, responses); } passed = true; break; } catch (AssertionError e) { // May be the fallout from a previous test, continue trying checkFailure = e; } } if (!passed) { if (checkFailure == null) { throw new AssertionError("No record found"); } throw checkFailure; } // Use the same trick to check ServerStreamTracer records passed = false; while (true) { ServerStreamTracerInfo tracerInfo; tracerInfo = serverStreamTracers.poll(); if (tracerInfo == null) { break; } try { assertEquals(method, tracerInfo.fullMethodName); assertNotNull(tracerInfo.tracer.contextCapture); ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(Status.class); // On the server, streamClosed() may be called after the client receives the final status. // So we use a timeout. try { assertTrue(tracerInfo.tracer.await(1, TimeUnit.SECONDS)); } catch (InterruptedException e) { throw new AssertionError(e); } assertEquals(code, tracerInfo.tracer.getStatus().getCode()); if (requests != null && responses != null) { checkTracerMetrics(tracerInfo.tracer, responses, requests); } passed = true; break; } catch (AssertionError e) { // May be the fallout from a previous test, continue trying checkFailure = e; } } if (!passed) { if (checkFailure == null) { throw new AssertionError("No ServerStreamTracer found"); } throw checkFailure; } } private static void checkTags( MetricsRecord record, boolean server, String methodName, Status.Code status) { assertNotNull("record is not null", record); TagValue methodNameTag = record.tags.get( server ? RpcConstants.RPC_SERVER_METHOD : RpcConstants.RPC_CLIENT_METHOD); assertNotNull("method name tagged", methodNameTag); assertEquals("method names match", methodName, methodNameTag.toString()); TagValue statusTag = record.tags.get(RpcConstants.RPC_STATUS); assertNotNull("status tagged", statusTag); assertEquals(status.toString(), statusTag.toString()); } private static void checkTracerMetrics( TestStreamTracer tracer, Collection sentMessages, Collection receivedMessages) { long uncompressedSentSize = 0; int seqNo = 0; for (MessageLite msg : sentMessages) { assertThat(tracer.nextOutboundEvent()).isEqualTo(String.format("outboundMessage(%d)", seqNo)); assertThat(tracer.nextOutboundEvent()).isEqualTo("outboundMessage()"); assertThat(tracer.nextOutboundEvent()).matches( String.format( "outboundMessageSent\\(%d, -?[0-9]+, %d\\)", seqNo, msg.getSerializedSize())); seqNo++; uncompressedSentSize += msg.getSerializedSize(); } assertNull(tracer.nextOutboundEvent()); long uncompressedReceivedSize = 0; seqNo = 0; for (MessageLite msg : receivedMessages) { assertThat(tracer.nextInboundEvent()).isEqualTo(String.format("inboundMessage(%d)", seqNo)); assertThat(tracer.nextInboundEvent()).isEqualTo("inboundMessage()"); assertThat(tracer.nextInboundEvent()).matches( String.format("inboundMessageRead\\(%d, -?[0-9]+, -?[0-9]+\\)", seqNo)); uncompressedReceivedSize += msg.getSerializedSize(); seqNo++; } assertNull(tracer.nextInboundEvent()); assertEquals(uncompressedSentSize, tracer.getOutboundUncompressedSize()); assertEquals(uncompressedReceivedSize, tracer.getInboundUncompressedSize()); } private static void checkCensusMetrics(MetricsRecord record, boolean server, Collection requests, Collection responses) { int uncompressedRequestsSize = 0; for (MessageLite request : requests) { uncompressedRequestsSize += request.getSerializedSize(); } int uncompressedResponsesSize = 0; for (MessageLite response : responses) { uncompressedResponsesSize += response.getSerializedSize(); } if (server) { assertEquals( requests.size(), record.getMetricAsLongOrFail(RpcConstants.RPC_SERVER_REQUEST_COUNT)); assertEquals( responses.size(), record.getMetricAsLongOrFail(RpcConstants.RPC_SERVER_RESPONSE_COUNT)); assertEquals(uncompressedRequestsSize, record.getMetricAsLongOrFail(RpcConstants.RPC_SERVER_UNCOMPRESSED_REQUEST_BYTES)); assertEquals(uncompressedResponsesSize, record.getMetricAsLongOrFail(RpcConstants.RPC_SERVER_UNCOMPRESSED_RESPONSE_BYTES)); assertNotNull(record.getMetric(RpcConstants.RPC_SERVER_SERVER_LATENCY)); // It's impossible to get the expected wire sizes because it may be compressed, so we just // check if they are recorded. assertNotNull(record.getMetric(RpcConstants.RPC_SERVER_REQUEST_BYTES)); assertNotNull(record.getMetric(RpcConstants.RPC_SERVER_RESPONSE_BYTES)); } else { assertEquals( requests.size(), record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_REQUEST_COUNT)); assertEquals( responses.size(), record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_RESPONSE_COUNT)); assertEquals(uncompressedRequestsSize, record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES)); assertEquals(uncompressedResponsesSize, record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES)); assertNotNull(record.getMetric(RpcConstants.RPC_CLIENT_ROUNDTRIP_LATENCY)); // It's impossible to get the expected wire sizes because it may be compressed, so we just // check if they are recorded. assertNotNull(record.getMetric(RpcConstants.RPC_CLIENT_REQUEST_BYTES)); assertNotNull(record.getMetric(RpcConstants.RPC_CLIENT_RESPONSE_BYTES)); } } }