mirror of https://github.com/grpc/grpc-java.git
testing: added junit rule for in-process servers
GrpcServerRule configures an in-process server and channel. It is useful for asserting requests being made to a service. A consumer can create a mock implementation of their service that records each request, then make assertions on those records in their test.
This commit is contained in:
parent
a10261af48
commit
b6ebede94f
|
@ -3,16 +3,6 @@ apply plugin: 'application'
|
|||
description = "gRPC: Integration Testing"
|
||||
startScripts.enabled = false
|
||||
|
||||
// Add dependency on the protobuf plugin
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath libraries.protobuf_plugin
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':grpc-auth'),
|
||||
project(':grpc-core'),
|
||||
|
@ -21,6 +11,7 @@ dependencies {
|
|||
project(':grpc-protobuf'),
|
||||
project(':grpc-stub'),
|
||||
project(':grpc-testing'),
|
||||
project(':grpc-testing-proto'),
|
||||
libraries.junit,
|
||||
libraries.mockito,
|
||||
libraries.netty_tcnative,
|
||||
|
@ -84,13 +75,3 @@ applicationDistribution.into("bin") {
|
|||
from(stresstest_client)
|
||||
fileMode = 0755
|
||||
}
|
||||
|
||||
configureProtoCompilation()
|
||||
|
||||
// Let intellij projects refer to generated code
|
||||
idea {
|
||||
module {
|
||||
sourceDirs += file("${projectDir}/src/generated/main/java");
|
||||
sourceDirs += file("${projectDir}/src/generated/main/grpc");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ include ":grpc-protobuf-nano"
|
|||
include ":grpc-netty"
|
||||
include ":grpc-grpclb"
|
||||
include ":grpc-testing"
|
||||
include ":grpc-testing-proto"
|
||||
include ":grpc-interop-testing"
|
||||
include ":grpc-all"
|
||||
include ":grpc-benchmarks"
|
||||
|
@ -27,6 +28,7 @@ project(':grpc-protobuf-nano').projectDir = "$rootDir/protobuf-nano" as File
|
|||
project(':grpc-netty').projectDir = "$rootDir/netty" as File
|
||||
project(':grpc-grpclb').projectDir = "$rootDir/grpclb" as File
|
||||
project(':grpc-testing').projectDir = "$rootDir/testing" as File
|
||||
project(':grpc-testing-proto').projectDir = "$rootDir/testing-proto" as File
|
||||
project(':grpc-interop-testing').projectDir = "$rootDir/interop-testing" as File
|
||||
project(':grpc-all').projectDir = "$rootDir/all" as File
|
||||
project(':grpc-benchmarks').projectDir = "$rootDir/benchmarks" as File
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
description = "gRPC: Testing Protos"
|
||||
|
||||
// Add dependency on the protobuf plugin
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath libraries.protobuf_plugin
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':grpc-protobuf'),
|
||||
project(':grpc-stub')
|
||||
}
|
||||
|
||||
configureProtoCompilation()
|
||||
|
||||
// Let intellij projects refer to generated code
|
||||
idea {
|
||||
module {
|
||||
sourceDirs += file("${projectDir}/src/generated/main/java")
|
||||
sourceDirs += file("${projectDir}/src/generated/main/grpc")
|
||||
}
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
// Copyright 2015, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
|
@ -43,4 +42,4 @@ option java_outer_classname = "EmptyProtos";
|
|||
// rpc Bar (grpc.testing.Empty) returns (grpc.testing.Empty) { };
|
||||
// };
|
||||
//
|
||||
message Empty {}
|
||||
message Empty {}
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
// Copyright 2015, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
|
@ -176,4 +175,4 @@ message ReconnectParams {
|
|||
message ReconnectInfo {
|
||||
bool passed = 1;
|
||||
repeated int32 backoff_ms = 2;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
// Copyright 2015, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
|
@ -80,7 +79,7 @@ service TestService {
|
|||
// that case.
|
||||
service UnimplementedService {
|
||||
// A call that no server should implement
|
||||
rpc UnimplementedCall(grpc.testing.Empty) returns(grpc.testing.Empty);
|
||||
rpc UnimplementedCall(grpc.testing.Empty) returns(grpc.testing.Empty);
|
||||
}
|
||||
|
||||
// A service used to control reconnect server.
|
|
@ -1,8 +1,11 @@
|
|||
description = "gRPC: Testing"
|
||||
|
||||
dependencies {
|
||||
compile project(':grpc-core'),
|
||||
project(':grpc-stub'),
|
||||
libraries.junit,
|
||||
libraries.mockito,
|
||||
libraries.truth
|
||||
|
||||
testCompile project(':grpc-testing-proto')
|
||||
}
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* 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.testing;
|
||||
|
||||
import io.grpc.BindableService;
|
||||
import io.grpc.ExperimentalApi;
|
||||
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.stub.AbstractStub;
|
||||
import io.grpc.util.MutableHandlerRegistry;
|
||||
|
||||
import org.junit.rules.ExternalResource;
|
||||
import org.junit.rules.TestRule;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* {@code GrpcServerRule} is a JUnit {@link TestRule} that starts an in-process gRPC service with
|
||||
* a {@link MutableHandlerRegistry} for adding services. It is particularly useful for mocking out
|
||||
* external gRPC-based services and asserting that the expected requests were made.
|
||||
*
|
||||
* <p>An {@link AbstractStub} can be created against this service by using the
|
||||
* {@link ManagedChannel} provided by {@link GrpcServerRule#getChannel()}.
|
||||
*/
|
||||
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/2488")
|
||||
public class GrpcServerRule extends ExternalResource {
|
||||
|
||||
private ManagedChannel channel;
|
||||
private Server server;
|
||||
private String serverName;
|
||||
private MutableHandlerRegistry serviceRegistry;
|
||||
private boolean useDirectExecutor;
|
||||
|
||||
/**
|
||||
* Returns {@code this} configured to use a direct executor for the {@link ManagedChannel} and
|
||||
* {@link Server}.
|
||||
*/
|
||||
public final GrpcServerRule directExecutor() {
|
||||
useDirectExecutor = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link ManagedChannel} connected to this service.
|
||||
*/
|
||||
public final ManagedChannel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying gRPC {@link Server} for this service.
|
||||
*/
|
||||
public final Server getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the randomly generated server name for this service.
|
||||
*/
|
||||
public final String getServerName() {
|
||||
return serverName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the service registry for this service. The registry is used to add service instances
|
||||
* (e.g. {@link BindableService} or {@link ServerServiceDefinition} to the server.
|
||||
*/
|
||||
public final MutableHandlerRegistry getServiceRegistry() {
|
||||
return serviceRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* After the test has completed, clean up the channel and server.
|
||||
*/
|
||||
@Override
|
||||
protected void after() {
|
||||
serverName = null;
|
||||
serviceRegistry = null;
|
||||
|
||||
channel.shutdown();
|
||||
server.shutdown();
|
||||
|
||||
try {
|
||||
channel.awaitTermination(1, TimeUnit.MINUTES);
|
||||
server.awaitTermination(1, TimeUnit.MINUTES);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
channel.shutdownNow();
|
||||
channel = null;
|
||||
|
||||
server.shutdownNow();
|
||||
server = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Before the test has started, create the server and channel.
|
||||
*/
|
||||
@Override
|
||||
protected void before() throws Throwable {
|
||||
serverName = UUID.randomUUID().toString();
|
||||
|
||||
serviceRegistry = new MutableHandlerRegistry();
|
||||
|
||||
InProcessServerBuilder serverBuilder = InProcessServerBuilder.forName(serverName)
|
||||
.fallbackHandlerRegistry(serviceRegistry);
|
||||
|
||||
if (useDirectExecutor) {
|
||||
serverBuilder.directExecutor();
|
||||
}
|
||||
|
||||
server = serverBuilder.build().start();
|
||||
|
||||
InProcessChannelBuilder channelBuilder = InProcessChannelBuilder.forName(serverName);
|
||||
|
||||
if (useDirectExecutor) {
|
||||
channelBuilder.directExecutor();
|
||||
}
|
||||
|
||||
channel = channelBuilder.build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,254 @@
|
|||
/*
|
||||
* 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.testing;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.EmptyProtos;
|
||||
|
||||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.Server;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
import io.grpc.testing.integration.Messages;
|
||||
import io.grpc.testing.integration.TestServiceGrpc;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runners.model.Statement;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
public class GrpcServerRuleTest {
|
||||
|
||||
public static class WithoutDirectExecutor {
|
||||
|
||||
@Rule
|
||||
public final GrpcServerRule grpcServerRule = new GrpcServerRule();
|
||||
|
||||
@Test
|
||||
public void serverAndChannelAreStarted() {
|
||||
assertThat(grpcServerRule.getServer().isShutdown()).isFalse();
|
||||
assertThat(grpcServerRule.getServer().isTerminated()).isFalse();
|
||||
|
||||
assertThat(grpcServerRule.getChannel().isShutdown()).isFalse();
|
||||
assertThat(grpcServerRule.getChannel().isTerminated()).isFalse();
|
||||
|
||||
assertThat(grpcServerRule.getServerName()).isNotNull();
|
||||
assertThat(grpcServerRule.getServiceRegistry()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serverAllowsServicesToBeAddedViaServiceRegistry() {
|
||||
TestServiceImpl testService = new TestServiceImpl();
|
||||
|
||||
grpcServerRule.getServiceRegistry().addService(testService);
|
||||
|
||||
TestServiceGrpc.TestServiceBlockingStub stub =
|
||||
TestServiceGrpc.newBlockingStub(grpcServerRule.getChannel());
|
||||
|
||||
Messages.SimpleRequest request1 = Messages.SimpleRequest.newBuilder()
|
||||
.setPayload(Messages.Payload.newBuilder()
|
||||
.setBody(ByteString.copyFromUtf8(UUID.randomUUID().toString())))
|
||||
.build();
|
||||
|
||||
Messages.SimpleRequest request2 = Messages.SimpleRequest.newBuilder()
|
||||
.setPayload(Messages.Payload.newBuilder()
|
||||
.setBody(ByteString.copyFromUtf8(UUID.randomUUID().toString())))
|
||||
.build();
|
||||
|
||||
stub.unaryCall(request1);
|
||||
stub.unaryCall(request2);
|
||||
|
||||
assertThat(testService.unaryCallRequests)
|
||||
.containsExactly(request1, request2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serviceIsNotRunOnSameThreadAsTest() {
|
||||
TestServiceImpl testService = new TestServiceImpl();
|
||||
|
||||
grpcServerRule.getServiceRegistry().addService(testService);
|
||||
|
||||
TestServiceGrpc.TestServiceBlockingStub stub =
|
||||
TestServiceGrpc.newBlockingStub(grpcServerRule.getChannel());
|
||||
|
||||
// Make a garbage request first due to https://github.com/grpc/grpc-java/issues/2444.
|
||||
stub.emptyCall(EmptyProtos.Empty.newBuilder().build());
|
||||
stub.emptyCall(EmptyProtos.Empty.newBuilder().build());
|
||||
|
||||
assertThat(testService.lastEmptyCallRequestThread).isNotEqualTo(Thread.currentThread());
|
||||
}
|
||||
}
|
||||
|
||||
public static class WithDirectExecutor {
|
||||
|
||||
@Rule
|
||||
public final GrpcServerRule grpcServerRule = new GrpcServerRule().directExecutor();
|
||||
|
||||
@Test
|
||||
public void serverAndChannelAreStarted() {
|
||||
assertThat(grpcServerRule.getServer().isShutdown()).isFalse();
|
||||
assertThat(grpcServerRule.getServer().isTerminated()).isFalse();
|
||||
|
||||
assertThat(grpcServerRule.getChannel().isShutdown()).isFalse();
|
||||
assertThat(grpcServerRule.getChannel().isTerminated()).isFalse();
|
||||
|
||||
assertThat(grpcServerRule.getServerName()).isNotNull();
|
||||
assertThat(grpcServerRule.getServiceRegistry()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serverAllowsServicesToBeAddedViaServiceRegistry() {
|
||||
TestServiceImpl testService = new TestServiceImpl();
|
||||
|
||||
grpcServerRule.getServiceRegistry().addService(testService);
|
||||
|
||||
TestServiceGrpc.TestServiceBlockingStub stub =
|
||||
TestServiceGrpc.newBlockingStub(grpcServerRule.getChannel());
|
||||
|
||||
Messages.SimpleRequest request1 = Messages.SimpleRequest.newBuilder()
|
||||
.setPayload(Messages.Payload.newBuilder()
|
||||
.setBody(ByteString.copyFromUtf8(UUID.randomUUID().toString())))
|
||||
.build();
|
||||
|
||||
Messages.SimpleRequest request2 = Messages.SimpleRequest.newBuilder()
|
||||
.setPayload(Messages.Payload.newBuilder()
|
||||
.setBody(ByteString.copyFromUtf8(UUID.randomUUID().toString())))
|
||||
.build();
|
||||
|
||||
stub.unaryCall(request1);
|
||||
stub.unaryCall(request2);
|
||||
|
||||
assertThat(testService.unaryCallRequests)
|
||||
.containsExactly(request1, request2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serviceIsRunOnSameThreadAsTest() {
|
||||
TestServiceImpl testService = new TestServiceImpl();
|
||||
|
||||
grpcServerRule.getServiceRegistry().addService(testService);
|
||||
|
||||
TestServiceGrpc.TestServiceBlockingStub stub =
|
||||
TestServiceGrpc.newBlockingStub(grpcServerRule.getChannel());
|
||||
|
||||
// Make a garbage request first due to https://github.com/grpc/grpc-java/issues/2444.
|
||||
stub.emptyCall(EmptyProtos.Empty.newBuilder().build());
|
||||
stub.emptyCall(EmptyProtos.Empty.newBuilder().build());
|
||||
|
||||
assertThat(testService.lastEmptyCallRequestThread).isEqualTo(Thread.currentThread());
|
||||
}
|
||||
}
|
||||
|
||||
public static class ResourceCleanup {
|
||||
|
||||
@Test
|
||||
public void serverAndChannelAreShutdownAfterRule() throws Throwable {
|
||||
GrpcServerRule grpcServerRule = new GrpcServerRule();
|
||||
|
||||
// Before the rule has been executed, all of its resources should be null.
|
||||
assertThat(grpcServerRule.getChannel()).isNull();
|
||||
assertThat(grpcServerRule.getServer()).isNull();
|
||||
assertThat(grpcServerRule.getServerName()).isNull();
|
||||
assertThat(grpcServerRule.getServiceRegistry()).isNull();
|
||||
|
||||
// The TestStatement stores the channel and server instances so that we can inspect them after
|
||||
// the rule cleans up.
|
||||
TestStatement statement = new TestStatement(grpcServerRule);
|
||||
|
||||
grpcServerRule.apply(statement, null).evaluate();
|
||||
|
||||
// Ensure that the stored channel and server instances were shut down.
|
||||
assertThat(statement.channel.isShutdown()).isTrue();
|
||||
assertThat(statement.server.isShutdown()).isTrue();
|
||||
|
||||
// All references to the resources that we created should be set to null.
|
||||
assertThat(grpcServerRule.getChannel()).isNull();
|
||||
assertThat(grpcServerRule.getServer()).isNull();
|
||||
assertThat(grpcServerRule.getServerName()).isNull();
|
||||
assertThat(grpcServerRule.getServiceRegistry()).isNull();
|
||||
}
|
||||
|
||||
private static class TestStatement extends Statement {
|
||||
|
||||
private final GrpcServerRule grpcServerRule;
|
||||
|
||||
private ManagedChannel channel;
|
||||
private Server server;
|
||||
|
||||
private TestStatement(GrpcServerRule grpcServerRule) {
|
||||
this.grpcServerRule = grpcServerRule;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
channel = grpcServerRule.getChannel();
|
||||
server = grpcServerRule.getServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestServiceImpl extends TestServiceGrpc.TestServiceImplBase {
|
||||
|
||||
private final Collection<Messages.SimpleRequest> unaryCallRequests =
|
||||
new ConcurrentLinkedQueue<Messages.SimpleRequest>();
|
||||
|
||||
private volatile Thread lastEmptyCallRequestThread;
|
||||
|
||||
@Override
|
||||
public void emptyCall(
|
||||
EmptyProtos.Empty request,
|
||||
StreamObserver<EmptyProtos.Empty> responseObserver) {
|
||||
|
||||
lastEmptyCallRequestThread = Thread.currentThread();
|
||||
|
||||
responseObserver.onNext(EmptyProtos.Empty.newBuilder().build());
|
||||
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unaryCall(
|
||||
Messages.SimpleRequest request,
|
||||
StreamObserver<Messages.SimpleResponse> responseObserver) {
|
||||
|
||||
unaryCallRequests.add(request);
|
||||
|
||||
responseObserver.onNext(Messages.SimpleResponse.newBuilder().build());
|
||||
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue