/* * Copyright 2016, Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package io.grpc.protobuf.services; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.protobuf.ByteString; import io.grpc.BindableService; import io.grpc.ManagedChannel; import io.grpc.Server; import io.grpc.ServerServiceDefinition; import io.grpc.inprocess.InProcessChannelBuilder; import io.grpc.inprocess.InProcessServerBuilder; import io.grpc.reflection.testing.AnotherDynamicServiceGrpc; import io.grpc.reflection.testing.DynamicReflectionTestDepthTwoProto; import io.grpc.reflection.testing.DynamicServiceGrpc; import io.grpc.reflection.testing.ReflectableServiceGrpc; import io.grpc.reflection.testing.ReflectionTestDepthThreeProto; import io.grpc.reflection.testing.ReflectionTestDepthTwoAlternateProto; import io.grpc.reflection.testing.ReflectionTestDepthTwoProto; import io.grpc.reflection.testing.ReflectionTestProto; import io.grpc.reflection.v1alpha.ExtensionNumberResponse; import io.grpc.reflection.v1alpha.ExtensionRequest; import io.grpc.reflection.v1alpha.FileDescriptorResponse; import io.grpc.reflection.v1alpha.ServerReflectionGrpc; import io.grpc.reflection.v1alpha.ServerReflectionRequest; import io.grpc.reflection.v1alpha.ServerReflectionResponse; import io.grpc.reflection.v1alpha.ServiceResponse; import io.grpc.stub.ClientCallStreamObserver; import io.grpc.stub.ClientResponseObserver; import io.grpc.stub.StreamObserver; import io.grpc.testing.StreamRecorder; import io.grpc.util.MutableHandlerRegistry; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Tests for {@link ProtoReflectionService}. */ @RunWith(JUnit4.class) public class ProtoReflectionServiceTest { private static final String TEST_HOST = "localhost"; private MutableHandlerRegistry handlerRegistry = new MutableHandlerRegistry(); private BindableService reflectionService; private ServerServiceDefinition dynamicService = (new DynamicServiceGrpc.DynamicServiceImplBase() {}).bindService(); private ServerServiceDefinition anotherDynamicService = (new AnotherDynamicServiceGrpc.AnotherDynamicServiceImplBase() {}).bindService(); private Server server; private ManagedChannel channel; private ServerReflectionGrpc.ServerReflectionStub stub; @Before public void setUp() throws Exception { reflectionService = ProtoReflectionService.newInstance(); server = InProcessServerBuilder.forName("proto-reflection-test") .directExecutor() .addService(reflectionService) .addService(new ReflectableServiceGrpc.ReflectableServiceImplBase() {}) .fallbackHandlerRegistry(handlerRegistry) .build() .start(); channel = InProcessChannelBuilder.forName("proto-reflection-test").directExecutor().build(); stub = ServerReflectionGrpc.newStub(channel); } @After public void tearDown() { if (server != null) { server.shutdownNow(); } if (channel != null) { channel.shutdownNow(); } } @Test @Deprecated public void deprecatedGetterReturnsCorrectType() throws Exception { assertEquals( io.grpc.protobuf.service.ProtoReflectionService.getInstance().getClass(), ProtoReflectionService.class); } @Test public void listServices() throws Exception { Set<ServiceResponse> originalServices = new HashSet<ServiceResponse>( Arrays.asList( ServiceResponse.newBuilder() .setName("grpc.reflection.v1alpha.ServerReflection") .build(), ServiceResponse.newBuilder() .setName("grpc.reflection.testing.ReflectableService") .build())); assertServiceResponseEquals(originalServices); handlerRegistry.addService(dynamicService); assertServiceResponseEquals( new HashSet<ServiceResponse>( Arrays.asList( ServiceResponse.newBuilder() .setName("grpc.reflection.v1alpha.ServerReflection") .build(), ServiceResponse.newBuilder() .setName("grpc.reflection.testing.ReflectableService") .build(), ServiceResponse.newBuilder() .setName("grpc.reflection.testing.DynamicService") .build()))); handlerRegistry.addService(anotherDynamicService); assertServiceResponseEquals( new HashSet<ServiceResponse>( Arrays.asList( ServiceResponse.newBuilder() .setName("grpc.reflection.v1alpha.ServerReflection") .build(), ServiceResponse.newBuilder() .setName("grpc.reflection.testing.ReflectableService") .build(), ServiceResponse.newBuilder() .setName("grpc.reflection.testing.DynamicService") .build(), ServiceResponse.newBuilder() .setName("grpc.reflection.testing.AnotherDynamicService") .build()))); handlerRegistry.removeService(dynamicService); assertServiceResponseEquals( new HashSet<ServiceResponse>( Arrays.asList( ServiceResponse.newBuilder() .setName("grpc.reflection.v1alpha.ServerReflection") .build(), ServiceResponse.newBuilder() .setName("grpc.reflection.testing.ReflectableService") .build(), ServiceResponse.newBuilder() .setName("grpc.reflection.testing.AnotherDynamicService") .build()))); handlerRegistry.removeService(anotherDynamicService); assertServiceResponseEquals(originalServices); } @Test public void fileByFilename() throws Exception { ServerReflectionRequest request = ServerReflectionRequest.newBuilder() .setHost(TEST_HOST) .setFileByFilename("io/grpc/reflection/testing/reflection_test_depth_three.proto") .build(); ServerReflectionResponse goldenResponse = ServerReflectionResponse.newBuilder() .setValidHost(TEST_HOST) .setOriginalRequest(request) .setFileDescriptorResponse( FileDescriptorResponse.newBuilder() .addFileDescriptorProto( ReflectionTestDepthThreeProto.getDescriptor().toProto().toByteString()) .build()) .build(); StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create(); StreamObserver<ServerReflectionRequest> requestObserver = stub.serverReflectionInfo(responseObserver); requestObserver.onNext(request); requestObserver.onCompleted(); assertEquals(goldenResponse, responseObserver.firstValue().get()); } @Test public void fileByFilenameConsistentForMutableServices() throws Exception { ServerReflectionRequest request = ServerReflectionRequest.newBuilder() .setHost(TEST_HOST) .setFileByFilename("io/grpc/reflection/testing/dynamic_reflection_test_depth_two.proto") .build(); ServerReflectionResponse goldenResponse = ServerReflectionResponse.newBuilder() .setValidHost(TEST_HOST) .setOriginalRequest(request) .setFileDescriptorResponse( FileDescriptorResponse.newBuilder() .addFileDescriptorProto( DynamicReflectionTestDepthTwoProto.getDescriptor().toProto().toByteString()) .build()) .build(); StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create(); StreamObserver<ServerReflectionRequest> requestObserver = stub.serverReflectionInfo(responseObserver); handlerRegistry.addService(dynamicService); requestObserver.onNext(request); requestObserver.onCompleted(); StreamRecorder<ServerReflectionResponse> responseObserver2 = StreamRecorder.create(); StreamObserver<ServerReflectionRequest> requestObserver2 = stub.serverReflectionInfo(responseObserver2); handlerRegistry.removeService(dynamicService); requestObserver2.onNext(request); requestObserver2.onCompleted(); StreamRecorder<ServerReflectionResponse> responseObserver3 = StreamRecorder.create(); StreamObserver<ServerReflectionRequest> requestObserver3 = stub.serverReflectionInfo(responseObserver3); requestObserver3.onNext(request); requestObserver3.onCompleted(); assertEquals( ServerReflectionResponse.MessageResponseCase.ERROR_RESPONSE, responseObserver.firstValue().get().getMessageResponseCase()); assertEquals(goldenResponse, responseObserver2.firstValue().get()); assertEquals( ServerReflectionResponse.MessageResponseCase.ERROR_RESPONSE, responseObserver3.firstValue().get().getMessageResponseCase()); } @Test public void fileContainingSymbol() throws Exception { ServerReflectionRequest request = ServerReflectionRequest.newBuilder() .setHost(TEST_HOST) .setFileContainingSymbol("grpc.reflection.testing.ReflectableService.Method") .build(); List<ByteString> goldenResponse = Arrays.asList( ReflectionTestProto.getDescriptor().toProto().toByteString(), ReflectionTestDepthTwoProto.getDescriptor().toProto().toByteString(), ReflectionTestDepthTwoAlternateProto.getDescriptor().toProto().toByteString(), ReflectionTestDepthThreeProto.getDescriptor().toProto().toByteString()); StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create(); StreamObserver<ServerReflectionRequest> requestObserver = stub.serverReflectionInfo(responseObserver); requestObserver.onNext(request); requestObserver.onCompleted(); List<ByteString> response = responseObserver .firstValue() .get() .getFileDescriptorResponse() .getFileDescriptorProtoList(); assertEquals(goldenResponse.size(), response.size()); assertEquals(new HashSet<ByteString>(goldenResponse), new HashSet<ByteString>(response)); } @Test public void fileContainingNestedSymbol() throws Exception { ServerReflectionRequest request = ServerReflectionRequest.newBuilder() .setHost(TEST_HOST) .setFileContainingSymbol("grpc.reflection.testing.NestedTypeOuter.Middle.Inner") .build(); ServerReflectionResponse goldenResponse = ServerReflectionResponse.newBuilder() .setValidHost(TEST_HOST) .setOriginalRequest(request) .setFileDescriptorResponse( FileDescriptorResponse.newBuilder() .addFileDescriptorProto( ReflectionTestDepthThreeProto.getDescriptor().toProto().toByteString()) .build()) .build(); StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create(); StreamObserver<ServerReflectionRequest> requestObserver = stub.serverReflectionInfo(responseObserver); requestObserver.onNext(request); requestObserver.onCompleted(); assertEquals(goldenResponse, responseObserver.firstValue().get()); } @Test public void fileContainingSymbolForMutableServices() throws Exception { ServerReflectionRequest request = ServerReflectionRequest.newBuilder() .setHost(TEST_HOST) .setFileContainingSymbol("grpc.reflection.testing.DynamicRequest") .build(); ServerReflectionResponse goldenResponse = ServerReflectionResponse.newBuilder() .setValidHost(TEST_HOST) .setOriginalRequest(request) .setFileDescriptorResponse( FileDescriptorResponse.newBuilder() .addFileDescriptorProto( DynamicReflectionTestDepthTwoProto.getDescriptor().toProto().toByteString()) .build()) .build(); StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create(); StreamObserver<ServerReflectionRequest> requestObserver = stub.serverReflectionInfo(responseObserver); handlerRegistry.addService(dynamicService); requestObserver.onNext(request); requestObserver.onCompleted(); StreamRecorder<ServerReflectionResponse> responseObserver2 = StreamRecorder.create(); StreamObserver<ServerReflectionRequest> requestObserver2 = stub.serverReflectionInfo(responseObserver2); handlerRegistry.removeService(dynamicService); requestObserver2.onNext(request); requestObserver2.onCompleted(); StreamRecorder<ServerReflectionResponse> responseObserver3 = StreamRecorder.create(); StreamObserver<ServerReflectionRequest> requestObserver3 = stub.serverReflectionInfo(responseObserver3); requestObserver3.onNext(request); requestObserver3.onCompleted(); assertEquals( ServerReflectionResponse.MessageResponseCase.ERROR_RESPONSE, responseObserver.firstValue().get().getMessageResponseCase()); assertEquals(goldenResponse, responseObserver2.firstValue().get()); assertEquals( ServerReflectionResponse.MessageResponseCase.ERROR_RESPONSE, responseObserver3.firstValue().get().getMessageResponseCase()); } @Test public void fileContainingExtension() throws Exception { ServerReflectionRequest request = ServerReflectionRequest.newBuilder() .setHost(TEST_HOST) .setFileContainingExtension( ExtensionRequest.newBuilder() .setContainingType("grpc.reflection.testing.ThirdLevelType") .setExtensionNumber(100) .build()) .build(); List<ByteString> goldenResponse = Arrays.asList( ReflectionTestProto.getDescriptor().toProto().toByteString(), ReflectionTestDepthTwoProto.getDescriptor().toProto().toByteString(), ReflectionTestDepthTwoAlternateProto.getDescriptor().toProto().toByteString(), ReflectionTestDepthThreeProto.getDescriptor().toProto().toByteString()); StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create(); StreamObserver<ServerReflectionRequest> requestObserver = stub.serverReflectionInfo(responseObserver); requestObserver.onNext(request); requestObserver.onCompleted(); List<ByteString> response = responseObserver .firstValue() .get() .getFileDescriptorResponse() .getFileDescriptorProtoList(); assertEquals(goldenResponse.size(), response.size()); assertEquals(new HashSet<ByteString>(goldenResponse), new HashSet<ByteString>(response)); } @Test public void fileContainingNestedExtension() throws Exception { ServerReflectionRequest request = ServerReflectionRequest.newBuilder() .setHost(TEST_HOST) .setFileContainingExtension( ExtensionRequest.newBuilder() .setContainingType("grpc.reflection.testing.ThirdLevelType") .setExtensionNumber(101) .build()) .build(); ServerReflectionResponse goldenResponse = ServerReflectionResponse.newBuilder() .setValidHost(TEST_HOST) .setOriginalRequest(request) .setFileDescriptorResponse( FileDescriptorResponse.newBuilder() .addFileDescriptorProto( ReflectionTestDepthTwoProto.getDescriptor().toProto().toByteString()) .addFileDescriptorProto( ReflectionTestDepthThreeProto.getDescriptor().toProto().toByteString()) .build()) .build(); StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create(); StreamObserver<ServerReflectionRequest> requestObserver = stub.serverReflectionInfo(responseObserver); requestObserver.onNext(request); requestObserver.onCompleted(); assertEquals(goldenResponse, responseObserver.firstValue().get()); } @Test public void fileContainingExtensionForMutableServices() throws Exception { ServerReflectionRequest request = ServerReflectionRequest.newBuilder() .setHost(TEST_HOST) .setFileContainingExtension( ExtensionRequest.newBuilder() .setContainingType("grpc.reflection.testing.TypeWithExtensions") .setExtensionNumber(200) .build()) .build(); ServerReflectionResponse goldenResponse = ServerReflectionResponse.newBuilder() .setValidHost(TEST_HOST) .setOriginalRequest(request) .setFileDescriptorResponse( FileDescriptorResponse.newBuilder() .addFileDescriptorProto( DynamicReflectionTestDepthTwoProto.getDescriptor().toProto().toByteString()) .build()) .build(); StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create(); StreamObserver<ServerReflectionRequest> requestObserver = stub.serverReflectionInfo(responseObserver); handlerRegistry.addService(dynamicService); requestObserver.onNext(request); requestObserver.onCompleted(); StreamRecorder<ServerReflectionResponse> responseObserver2 = StreamRecorder.create(); StreamObserver<ServerReflectionRequest> requestObserver2 = stub.serverReflectionInfo(responseObserver2); handlerRegistry.removeService(dynamicService); requestObserver2.onNext(request); requestObserver2.onCompleted(); StreamRecorder<ServerReflectionResponse> responseObserver3 = StreamRecorder.create(); StreamObserver<ServerReflectionRequest> requestObserver3 = stub.serverReflectionInfo(responseObserver3); requestObserver3.onNext(request); requestObserver3.onCompleted(); assertEquals( ServerReflectionResponse.MessageResponseCase.ERROR_RESPONSE, responseObserver.firstValue().get().getMessageResponseCase()); assertEquals(goldenResponse, responseObserver2.firstValue().get()); assertEquals( ServerReflectionResponse.MessageResponseCase.ERROR_RESPONSE, responseObserver3.firstValue().get().getMessageResponseCase()); } @Test public void allExtensionNumbersOfType() throws Exception { ServerReflectionRequest request = ServerReflectionRequest.newBuilder() .setHost(TEST_HOST) .setAllExtensionNumbersOfType("grpc.reflection.testing.ThirdLevelType") .build(); Set<Integer> goldenResponse = new HashSet<Integer>(Arrays.asList(100, 101)); StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create(); StreamObserver<ServerReflectionRequest> requestObserver = stub.serverReflectionInfo(responseObserver); requestObserver.onNext(request); requestObserver.onCompleted(); Set<Integer> extensionNumberResponseSet = new HashSet<Integer>( responseObserver .firstValue() .get() .getAllExtensionNumbersResponse() .getExtensionNumberList()); assertEquals(goldenResponse, extensionNumberResponseSet); } @Test public void allExtensionNumbersOfTypeForMutableServices() throws Exception { String type = "grpc.reflection.testing.TypeWithExtensions"; ServerReflectionRequest request = ServerReflectionRequest.newBuilder() .setHost(TEST_HOST) .setAllExtensionNumbersOfType(type) .build(); ServerReflectionResponse goldenResponse = ServerReflectionResponse.newBuilder() .setValidHost(TEST_HOST) .setOriginalRequest(request) .setAllExtensionNumbersResponse( ExtensionNumberResponse.newBuilder() .setBaseTypeName(type) .addExtensionNumber(200) .build()) .build(); StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create(); StreamObserver<ServerReflectionRequest> requestObserver = stub.serverReflectionInfo(responseObserver); handlerRegistry.addService(dynamicService); requestObserver.onNext(request); requestObserver.onCompleted(); StreamRecorder<ServerReflectionResponse> responseObserver2 = StreamRecorder.create(); StreamObserver<ServerReflectionRequest> requestObserver2 = stub.serverReflectionInfo(responseObserver2); handlerRegistry.removeService(dynamicService); requestObserver2.onNext(request); requestObserver2.onCompleted(); StreamRecorder<ServerReflectionResponse> responseObserver3 = StreamRecorder.create(); StreamObserver<ServerReflectionRequest> requestObserver3 = stub.serverReflectionInfo(responseObserver3); requestObserver3.onNext(request); requestObserver3.onCompleted(); assertEquals( ServerReflectionResponse.MessageResponseCase.ERROR_RESPONSE, responseObserver.firstValue().get().getMessageResponseCase()); assertEquals(goldenResponse, responseObserver2.firstValue().get()); assertEquals( ServerReflectionResponse.MessageResponseCase.ERROR_RESPONSE, responseObserver3.firstValue().get().getMessageResponseCase()); } @Test public void flowControl() throws Exception { FlowControlClientResponseObserver clientResponseObserver = new FlowControlClientResponseObserver(); ClientCallStreamObserver<ServerReflectionRequest> requestObserver = (ClientCallStreamObserver<ServerReflectionRequest>) stub.serverReflectionInfo(clientResponseObserver); // ClientCalls.startCall() calls request(1) initially, so we should get an immediate response. requestObserver.onNext(flowControlRequest); assertEquals(1, clientResponseObserver.getResponses().size()); assertEquals(flowControlGoldenResponse, clientResponseObserver.getResponses().get(0)); // Verify we don't receive an additional response until we request it. requestObserver.onNext(flowControlRequest); assertEquals(1, clientResponseObserver.getResponses().size()); requestObserver.request(1); assertEquals(2, clientResponseObserver.getResponses().size()); assertEquals(flowControlGoldenResponse, clientResponseObserver.getResponses().get(1)); requestObserver.onCompleted(); assertTrue(clientResponseObserver.onCompleteCalled()); } @Test public void flowControlOnCompleteWithPendingRequest() throws Exception { FlowControlClientResponseObserver clientResponseObserver = new FlowControlClientResponseObserver(); ClientCallStreamObserver<ServerReflectionRequest> requestObserver = (ClientCallStreamObserver<ServerReflectionRequest>) stub.serverReflectionInfo(clientResponseObserver); // ClientCalls.startCall() calls request(1) initially, so make additional request. requestObserver.onNext(flowControlRequest); requestObserver.onNext(flowControlRequest); requestObserver.onCompleted(); assertEquals(1, clientResponseObserver.getResponses().size()); assertFalse(clientResponseObserver.onCompleteCalled()); requestObserver.request(1); assertTrue(clientResponseObserver.onCompleteCalled()); assertEquals(2, clientResponseObserver.getResponses().size()); assertEquals(flowControlGoldenResponse, clientResponseObserver.getResponses().get(1)); } private final ServerReflectionRequest flowControlRequest = ServerReflectionRequest.newBuilder() .setHost(TEST_HOST) .setFileByFilename("io/grpc/reflection/testing/reflection_test_depth_three.proto") .build(); private final ServerReflectionResponse flowControlGoldenResponse = ServerReflectionResponse.newBuilder() .setValidHost(TEST_HOST) .setOriginalRequest(flowControlRequest) .setFileDescriptorResponse( FileDescriptorResponse.newBuilder() .addFileDescriptorProto( ReflectionTestDepthThreeProto.getDescriptor().toProto().toByteString()) .build()) .build(); private static class FlowControlClientResponseObserver implements ClientResponseObserver<ServerReflectionRequest, ServerReflectionResponse> { private final List<ServerReflectionResponse> responses = new ArrayList<ServerReflectionResponse>(); private boolean onCompleteCalled = false; @Override public void beforeStart(final ClientCallStreamObserver<ServerReflectionRequest> requestStream) { requestStream.disableAutoInboundFlowControl(); } @Override public void onNext(ServerReflectionResponse value) { responses.add(value); } @Override public void onError(Throwable t) { fail("onError called"); } @Override public void onCompleted() { onCompleteCalled = true; } public List<ServerReflectionResponse> getResponses() { return responses; } public boolean onCompleteCalled() { return onCompleteCalled; } } private void assertServiceResponseEquals(Set<ServiceResponse> goldenResponse) throws Exception { ServerReflectionRequest request = ServerReflectionRequest.newBuilder().setHost(TEST_HOST).setListServices("services").build(); StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create(); StreamObserver<ServerReflectionRequest> requestObserver = stub.serverReflectionInfo(responseObserver); requestObserver.onNext(request); requestObserver.onCompleted(); List<ServiceResponse> response = responseObserver.firstValue().get().getListServicesResponse().getServiceList(); assertEquals(goldenResponse.size(), response.size()); assertEquals(goldenResponse, new HashSet<ServiceResponse>(response)); } }