diff --git a/api/src/main/java/io/grpc/InternalStatus.java b/api/src/main/java/io/grpc/InternalStatus.java index b6549bb435f..0bad6e02c37 100644 --- a/api/src/main/java/io/grpc/InternalStatus.java +++ b/api/src/main/java/io/grpc/InternalStatus.java @@ -44,6 +44,10 @@ private InternalStatus() {} @Internal public static final StatusRuntimeException asRuntimeException(Status status, @Nullable Metadata trailers, boolean fillInStackTrace) { - return new StatusRuntimeException(status, trailers, fillInStackTrace); + return new StatusRuntimeException.Builder() + .setStatus(status) + .setTrailers(trailers) + .setFillInStackTrace(fillInStackTrace) + .build(); } } diff --git a/api/src/main/java/io/grpc/Status.java b/api/src/main/java/io/grpc/Status.java index 8e7f0b835c2..dd920313a36 100644 --- a/api/src/main/java/io/grpc/Status.java +++ b/api/src/main/java/io/grpc/Status.java @@ -522,7 +522,7 @@ public boolean isOk() { * to recover this {@link Status} instance when the returned exception is in the causal chain. */ public StatusRuntimeException asRuntimeException() { - return new StatusRuntimeException(this); + return new StatusRuntimeException.Builder().setStatus(this).build(); } /** @@ -530,7 +530,7 @@ public StatusRuntimeException asRuntimeException() { * exception. */ public StatusRuntimeException asRuntimeException(@Nullable Metadata trailers) { - return new StatusRuntimeException(this, trailers); + return new StatusRuntimeException.Builder().setStatus(this).setTrailers(trailers).build(); } /** @@ -538,14 +538,14 @@ public StatusRuntimeException asRuntimeException(@Nullable Metadata trailers) { * to recover this {@link Status} instance when the returned exception is in the causal chain. */ public StatusException asException() { - return new StatusException(this); + return new StatusException.Builder().setStatus(this).build(); } /** * Same as {@link #asException()} but includes the provided trailers in the returned exception. */ public StatusException asException(@Nullable Metadata trailers) { - return new StatusException(this, trailers); + return new StatusException.Builder().setStatus(this).setTrailers(trailers).build(); } /** A string representation of the status useful for debugging. */ diff --git a/api/src/main/java/io/grpc/StatusException.java b/api/src/main/java/io/grpc/StatusException.java index e89ac16dc6c..b4e5e74a1d9 100644 --- a/api/src/main/java/io/grpc/StatusException.java +++ b/api/src/main/java/io/grpc/StatusException.java @@ -30,30 +30,16 @@ public class StatusException extends Exception { private final boolean fillInStackTrace; /** - * Constructs an exception with both a status. See also {@link Status#asException()}. + * Constructs an exception with status, trailers, and whether to fill in the stack trace. + * See also {@link Status#asException()} and {@link Status#asException(Metadata)}. * * @since 1.0.0 */ - public StatusException(Status status) { - this(status, null); - } - - /** - * Constructs an exception with both a status and trailers. See also - * {@link Status#asException(Metadata)}. - * - * @since 1.0.0 - */ - public StatusException(Status status, @Nullable Metadata trailers) { - this(status, trailers, /*fillInStackTrace=*/ true); - } - - StatusException(Status status, @Nullable Metadata trailers, boolean fillInStackTrace) { + protected StatusException(Status status, @Nullable Metadata trailers, boolean fillInStackTrace) { super(Status.formatThrowableMessage(status), status.getCause()); this.status = status; this.trailers = trailers; this.fillInStackTrace = fillInStackTrace; - fillInStackTrace(); } @Override @@ -83,4 +69,57 @@ public final Status getStatus() { public final Metadata getTrailers() { return trailers; } + + /** + * Builder for creating a {@link StatusException}. + * + * @since 1.62.0 + */ + public static class Builder { + private Status status; + private Metadata trailers = null; + private boolean fillInStackTrace = true; + + /** + * Sets the status. + * + * @since 1.62.0 + */ + public Builder setStatus(final Status status) { + this.status = status; + return this; + } + + /** + * Sets the trailers. + * + * @since 1.62.0 + */ + public Builder setTrailers(final Metadata trailers) { + this.trailers = trailers; + return this; + } + + /** + * Sets whether to fill in the stack trace. + * + * @since 1.62.0 + */ + public Builder setFillInStackTrace(final boolean fillInStackTrace) { + this.fillInStackTrace = fillInStackTrace; + return this; + } + + /** + * Builds the exception. + * + * @since 1.62.0 + */ + public StatusException build() { + final StatusException statusException = + new StatusException(status, trailers, fillInStackTrace); + statusException.fillInStackTrace(); + return statusException; + } + } } diff --git a/api/src/main/java/io/grpc/StatusRuntimeException.java b/api/src/main/java/io/grpc/StatusRuntimeException.java index 68b816fc7fa..d07474e0013 100644 --- a/api/src/main/java/io/grpc/StatusRuntimeException.java +++ b/api/src/main/java/io/grpc/StatusRuntimeException.java @@ -32,30 +32,17 @@ public class StatusRuntimeException extends RuntimeException { private final boolean fillInStackTrace; /** - * Constructs the exception with both a status. See also {@link Status#asRuntimeException()}. + * Constructs an exception with status, trailers, and whether to fill in the stack trace. + * See also {@link Status#asRuntimeException()} and {@link Status#asRuntimeException(Metadata)}. * * @since 1.0.0 */ - public StatusRuntimeException(Status status) { - this(status, null); - } - - /** - * Constructs the exception with both a status and trailers. See also {@link - * Status#asRuntimeException(Metadata)}. - * - * @since 1.0.0 - */ - public StatusRuntimeException(Status status, @Nullable Metadata trailers) { - this(status, trailers, /*fillInStackTrace=*/ true); - } - - StatusRuntimeException(Status status, @Nullable Metadata trailers, boolean fillInStackTrace) { + protected StatusRuntimeException(Status status, @Nullable Metadata trailers, + boolean fillInStackTrace) { super(Status.formatThrowableMessage(status), status.getCause()); this.status = status; this.trailers = trailers; this.fillInStackTrace = fillInStackTrace; - fillInStackTrace(); } @Override @@ -86,4 +73,57 @@ public final Status getStatus() { public final Metadata getTrailers() { return trailers; } + + /** + * Builder for creating a {@link StatusRuntimeException}. + * + * @since 1.62.0 + */ + public static class Builder { + private Status status; + private Metadata trailers = null; + private boolean fillInStackTrace = true; + + /** + * Sets the status. + * + * @since 1.62.0 + */ + public Builder setStatus(final Status status) { + this.status = status; + return this; + } + + /** + * Sets the trailers. + * + * @since 1.62.0 + */ + public Builder setTrailers(final Metadata trailers) { + this.trailers = trailers; + return this; + } + + /** + * Sets whether to fill in the stack trace. + * + * @since 1.62.0 + */ + public Builder setFillInStackTrace(final boolean fillInStackTrace) { + this.fillInStackTrace = fillInStackTrace; + return this; + } + + /** + * Builds the exception. + * + * @since 1.62.0 + */ + public StatusRuntimeException build() { + final StatusRuntimeException statusRuntimeException = + new StatusRuntimeException(status, trailers, fillInStackTrace); + statusRuntimeException.fillInStackTrace(); + return statusRuntimeException; + } + } } diff --git a/api/src/test/java/io/grpc/StatusExceptionTest.java b/api/src/test/java/io/grpc/StatusExceptionTest.java index dd0d12dccda..2ba3f42ba7b 100644 --- a/api/src/test/java/io/grpc/StatusExceptionTest.java +++ b/api/src/test/java/io/grpc/StatusExceptionTest.java @@ -31,7 +31,8 @@ public class StatusExceptionTest { @Test public void internalCtorRemovesStack() { StackTraceElement[] trace = - new StatusException(Status.CANCELLED, null, false) {}.getStackTrace(); + new StatusException.Builder().setStatus(Status.CANCELLED).setTrailers(null) + .setFillInStackTrace(false).build().getStackTrace(); assertThat(trace).isEmpty(); } @@ -39,14 +40,16 @@ public void internalCtorRemovesStack() { @Test public void normalCtorKeepsStack() { StackTraceElement[] trace = - new StatusException(Status.CANCELLED, null) {}.getStackTrace(); + new StatusException.Builder().setStatus(Status.CANCELLED).setTrailers(null) + .build().getStackTrace(); assertThat(trace).isNotEmpty(); } @Test public void extendPreservesStack() { - StackTraceElement[] trace = new StatusException(Status.CANCELLED) {}.getStackTrace(); + StackTraceElement[] trace = + new StatusException.Builder().setStatus(Status.CANCELLED).build().getStackTrace(); assertThat(trace).isNotEmpty(); } @@ -54,7 +57,7 @@ public void extendPreservesStack() { @Test public void extendAndOverridePreservesStack() { final StackTraceElement element = new StackTraceElement("a", "b", "c", 4); - StatusException exception = new StatusException(Status.CANCELLED, new Metadata()) { + StatusException exception = new StatusException(Status.CANCELLED, new Metadata(), true) { @Override public synchronized Throwable fillInStackTrace() { diff --git a/api/src/test/java/io/grpc/StatusRuntimeExceptionTest.java b/api/src/test/java/io/grpc/StatusRuntimeExceptionTest.java index ab20c111254..c4d85ef047c 100644 --- a/api/src/test/java/io/grpc/StatusRuntimeExceptionTest.java +++ b/api/src/test/java/io/grpc/StatusRuntimeExceptionTest.java @@ -31,7 +31,8 @@ public class StatusRuntimeExceptionTest { @Test public void internalCtorRemovesStack() { StackTraceElement[] trace = - new StatusRuntimeException(Status.CANCELLED, null, false) {}.getStackTrace(); + new StatusRuntimeException.Builder().setStatus(Status.CANCELLED).setTrailers(null) + .setFillInStackTrace(false).build().getStackTrace(); assertThat(trace).isEmpty(); } @@ -39,14 +40,16 @@ public void internalCtorRemovesStack() { @Test public void normalCtorKeepsStack() { StackTraceElement[] trace = - new StatusRuntimeException(Status.CANCELLED, null) {}.getStackTrace(); + new StatusRuntimeException.Builder().setStatus(Status.CANCELLED).setTrailers(null) + .build().getStackTrace(); assertThat(trace).isNotEmpty(); } @Test public void extendPreservesStack() { - StackTraceElement[] trace = new StatusRuntimeException(Status.CANCELLED) {}.getStackTrace(); + StackTraceElement[] trace = + new StatusRuntimeException.Builder().setStatus(Status.CANCELLED).build().getStackTrace(); assertThat(trace).isNotEmpty(); } @@ -54,13 +57,14 @@ public void extendPreservesStack() { @Test public void extendAndOverridePreservesStack() { final StackTraceElement element = new StackTraceElement("a", "b", "c", 4); - StatusRuntimeException error = new StatusRuntimeException(Status.CANCELLED, new Metadata()) { - @Override - public synchronized Throwable fillInStackTrace() { - setStackTrace(new StackTraceElement[]{element}); - return this; - } - }; + StatusRuntimeException error = + new StatusRuntimeException(Status.CANCELLED, new Metadata(), true) { + @Override + public synchronized Throwable fillInStackTrace() { + setStackTrace(new StackTraceElement[]{element}); + return this; + } + }; assertThat(error.getStackTrace()).asList().containsExactly(element); } } diff --git a/core/src/test/java/io/grpc/internal/AbstractServerStreamTest.java b/core/src/test/java/io/grpc/internal/AbstractServerStreamTest.java index 618af766c08..7140ae7c335 100644 --- a/core/src/test/java/io/grpc/internal/AbstractServerStreamTest.java +++ b/core/src/test/java/io/grpc/internal/AbstractServerStreamTest.java @@ -127,8 +127,8 @@ public void messagesAvailable(StreamListener.MessageProducer producer) { @Override public void halfClosed() { if (streamListenerMessageQueue.isEmpty()) { - throw new StatusRuntimeException(Status.INTERNAL.withDescription( - "Half close without request")); + throw new StatusRuntimeException.Builder().setStatus(Status.INTERNAL.withDescription( + "Half close without request")).build(); } } diff --git a/core/src/test/java/io/grpc/internal/DelayedClientCallTest.java b/core/src/test/java/io/grpc/internal/DelayedClientCallTest.java index 45682b3a385..08604f34fee 100644 --- a/core/src/test/java/io/grpc/internal/DelayedClientCallTest.java +++ b/core/src/test/java/io/grpc/internal/DelayedClientCallTest.java @@ -168,7 +168,8 @@ public void cancelThenSetCall() { callExecutor, fakeClock.getScheduledExecutorService(), null); delayedClientCall.start(listener, new Metadata()); delayedClientCall.request(1); - delayedClientCall.cancel("cancel", new StatusException(Status.CANCELLED)); + delayedClientCall.cancel("cancel", + new StatusException.Builder().setStatus(Status.CANCELLED).build()); Runnable r = delayedClientCall.setCall(mockRealCall); assertThat(r).isNull(); verify(mockRealCall, never()).start(any(Listener.class), any(Metadata.class)); @@ -187,7 +188,8 @@ public void setCallThenCancel() { Runnable r = delayedClientCall.setCall(mockRealCall); assertThat(r).isNotNull(); r.run(); - delayedClientCall.cancel("cancel", new StatusException(Status.CANCELLED)); + delayedClientCall.cancel("cancel", + new StatusException.Builder().setStatus(Status.CANCELLED).build()); @SuppressWarnings("unchecked") ArgumentCaptor> listenerCaptor = ArgumentCaptor.forClass(Listener.class); verify(mockRealCall).start(listenerCaptor.capture(), any(Metadata.class)); diff --git a/examples/src/test/java/io/grpc/examples/routeguide/RouteGuideClientTest.java b/examples/src/test/java/io/grpc/examples/routeguide/RouteGuideClientTest.java index 4c184fb82ee..2bb380ddb9d 100644 --- a/examples/src/test/java/io/grpc/examples/routeguide/RouteGuideClientTest.java +++ b/examples/src/test/java/io/grpc/examples/routeguide/RouteGuideClientTest.java @@ -26,6 +26,7 @@ import com.google.protobuf.Message; import io.grpc.Status; import io.grpc.StatusRuntimeException; +import io.grpc.StatusRuntimeException; import io.grpc.examples.routeguide.RouteGuideClient.TestHelper; import io.grpc.examples.routeguide.RouteGuideGrpc.RouteGuideImplBase; import io.grpc.inprocess.InProcessChannelBuilder; @@ -135,7 +136,8 @@ public void getFeature(Point point, StreamObserver responseObserver) { public void getFeature_error() { Point requestPoint = Point.newBuilder().setLatitude(-1).setLongitude(-1).build(); final AtomicReference pointDelivered = new AtomicReference(); - final StatusRuntimeException fakeError = new StatusRuntimeException(Status.DATA_LOSS); + final StatusRuntimeException fakeError = new StatusRuntimeException.Builder() + .setStatus(Status.DATA_LOSS).build(); // implement the fake service RouteGuideImplBase getFeatureImpl = @@ -202,7 +204,8 @@ public void listFeatures_error() { final Feature responseFeature1 = Feature.newBuilder().setName("feature 1").build(); final AtomicReference rectangleDelivered = new AtomicReference(); - final StatusRuntimeException fakeError = new StatusRuntimeException(Status.INVALID_ARGUMENT); + final StatusRuntimeException fakeError = new StatusRuntimeException.Builder() + .setStatus(Status.INVALID_ARGUMENT).build(); // implement the fake service RouteGuideImplBase listFeaturesImpl = @@ -311,7 +314,8 @@ public void recordRoute_serverError() throws Exception { final Feature requestFeature1 = Feature.newBuilder().setLocation(point1).build(); final List features = Arrays.asList(requestFeature1); - final StatusRuntimeException fakeError = new StatusRuntimeException(Status.INVALID_ARGUMENT); + final StatusRuntimeException fakeError = new StatusRuntimeException.Builder() + .setStatus(Status.INVALID_ARGUMENT).build(); // implement the fake service RouteGuideImplBase recordRouteImpl = @@ -473,7 +477,8 @@ public void onCompleted() { @Test public void routeChat_errorResponse() throws Exception { final List notesDelivered = new ArrayList<>(); - final StatusRuntimeException fakeError = new StatusRuntimeException(Status.PERMISSION_DENIED); + final StatusRuntimeException fakeError = new StatusRuntimeException.Builder() + .setStatus(Status.PERMISSION_DENIED).build(); // implement the fake service RouteGuideImplBase routeChatImpl = diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/StressTestClient.java b/interop-testing/src/main/java/io/grpc/testing/integration/StressTestClient.java index 5739f7e7469..7821144202c 100644 --- a/interop-testing/src/main/java/io/grpc/testing/integration/StressTestClient.java +++ b/interop-testing/src/main/java/io/grpc/testing/integration/StressTestClient.java @@ -655,7 +655,7 @@ public void getGauge(Metrics.GaugeRequest request, responseObserver.onNext(gauge); responseObserver.onCompleted(); } else { - responseObserver.onError(new StatusException(Status.NOT_FOUND)); + responseObserver.onError(new StatusException.Builder().setStatus(Status.NOT_FOUND).build()); } } } diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceImpl.java b/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceImpl.java index 8fa272122d0..e71472d0aed 100644 --- a/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceImpl.java +++ b/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceImpl.java @@ -220,8 +220,10 @@ public void onNext(StreamingOutputCallRequest request) { try { lock.acquire(); } catch (InterruptedException ex) { - responseObserver.onError(new StatusRuntimeException( - Status.ABORTED.withDescription("server service interrupted").withCause(ex))); + responseObserver.onError( + new StatusRuntimeException.Builder() + .setStatus(Status.ABORTED.withDescription("server service interrupted") + .withCause(ex)).build()); return; } oobTestLocked = true; diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/MoreInProcessTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/MoreInProcessTest.java index d97aa8cd36c..98f29048a01 100644 --- a/interop-testing/src/test/java/io/grpc/testing/integration/MoreInProcessTest.java +++ b/interop-testing/src/test/java/io/grpc/testing/integration/MoreInProcessTest.java @@ -143,7 +143,7 @@ public void asyncClientStreaming_serverErrorPriorToRequest() throws Exception { public StreamObserver streamingInputCall( StreamObserver responseObserver) { // send error directly - responseObserver.onError(new StatusRuntimeException(fakeError)); + responseObserver.onError(new StatusRuntimeException.Builder().setStatus(fakeError).build()); responseObserver.onCompleted(); return new StreamObserver() { @Override diff --git a/istio-interop-testing/src/main/java/io/grpc/testing/istio/EchoTestServer.java b/istio-interop-testing/src/main/java/io/grpc/testing/istio/EchoTestServer.java index 2c6ae38d41a..a4116449c32 100644 --- a/istio-interop-testing/src/main/java/io/grpc/testing/istio/EchoTestServer.java +++ b/istio-interop-testing/src/main/java/io/grpc/testing/istio/EchoTestServer.java @@ -356,8 +356,9 @@ private ForwardEchoResponse buildEchoResponse(ForwardEchoRequest request) String rawUrl = request.getUrl(); List urlParts = Splitter.on(':').limit(2).splitToList(rawUrl); if (urlParts.size() < 2) { - throw new StatusRuntimeException( - Status.INVALID_ARGUMENT.withDescription("No protocol configured for url " + rawUrl)); + throw new StatusRuntimeException.Builder().setStatus( + Status.INVALID_ARGUMENT.withDescription("No protocol configured for url " + rawUrl)) + .build(); } ChannelCredentials creds; String target = null; diff --git a/services/src/main/java/io/grpc/protobuf/services/HealthServiceImpl.java b/services/src/main/java/io/grpc/protobuf/services/HealthServiceImpl.java index 6ce602b9295..11ef698c25d 100644 --- a/services/src/main/java/io/grpc/protobuf/services/HealthServiceImpl.java +++ b/services/src/main/java/io/grpc/protobuf/services/HealthServiceImpl.java @@ -69,8 +69,8 @@ public void check(HealthCheckRequest request, StreamObserver responseObserver) { ServingStatus status = statusMap.get(request.getService()); if (status == null) { - responseObserver.onError(new StatusException( - Status.NOT_FOUND.withDescription("unknown service " + request.getService()))); + responseObserver.onError(new StatusException.Builder().setStatus( + Status.NOT_FOUND.withDescription("unknown service " + request.getService())).build()); } else { HealthCheckResponse response = HealthCheckResponse.newBuilder().setStatus(status).build(); responseObserver.onNext(response); diff --git a/stub/src/main/java/io/grpc/stub/ClientCalls.java b/stub/src/main/java/io/grpc/stub/ClientCalls.java index 13fb00d3b3e..606bd38ef85 100644 --- a/stub/src/main/java/io/grpc/stub/ClientCalls.java +++ b/stub/src/main/java/io/grpc/stub/ClientCalls.java @@ -262,10 +262,12 @@ private static StatusRuntimeException toStatusRuntimeException(Throwable t) { // If we have an embedded status, use it and replace the cause if (cause instanceof StatusException) { StatusException se = (StatusException) cause; - return new StatusRuntimeException(se.getStatus(), se.getTrailers()); + return new StatusRuntimeException.Builder().setStatus(se.getStatus()) + .setTrailers(se.getTrailers()).build(); } else if (cause instanceof StatusRuntimeException) { StatusRuntimeException se = (StatusRuntimeException) cause; - return new StatusRuntimeException(se.getStatus(), se.getTrailers()); + return new StatusRuntimeException.Builder().setStatus(se.getStatus()) + .setTrailers(se.getTrailers()).build(); } cause = cause.getCause(); } diff --git a/util/src/test/java/io/grpc/util/UtilServerInterceptorsTest.java b/util/src/test/java/io/grpc/util/UtilServerInterceptorsTest.java index cfd1d1354fc..e9ec07db547 100644 --- a/util/src/test/java/io/grpc/util/UtilServerInterceptorsTest.java +++ b/util/src/test/java/io/grpc/util/UtilServerInterceptorsTest.java @@ -78,7 +78,8 @@ public void statusRuntimeExceptionTransmitter() { FakeServerCall call = new FakeServerCall<>(expectedStatus, expectedMetadata); final StatusRuntimeException exception = - new StatusRuntimeException(expectedStatus, expectedMetadata); + new StatusRuntimeException.Builder().setStatus(expectedStatus).setTrailers(expectedMetadata) + .build(); listener = new VoidCallListener() { @Override public void onMessage(Void message) { @@ -128,7 +129,8 @@ public void statusRuntimeExceptionTransmitterIgnoresClosedCalls() { FakeServerCall call = new FakeServerCall<>(expectedStatus, expectedMetadata); final StatusRuntimeException exception = - new StatusRuntimeException(expectedStatus, expectedMetadata); + new StatusRuntimeException.Builder().setStatus(expectedStatus).setTrailers(expectedMetadata) + .build(); listener = new VoidCallListener() { @Override diff --git a/xds/src/main/java/io/grpc/xds/CsdsService.java b/xds/src/main/java/io/grpc/xds/CsdsService.java index 20f78c1db19..13815a226cd 100644 --- a/xds/src/main/java/io/grpc/xds/CsdsService.java +++ b/xds/src/main/java/io/grpc/xds/CsdsService.java @@ -129,8 +129,9 @@ private boolean handleRequest( private ClientStatusResponse getConfigDumpForRequest(ClientStatusRequest request) throws StatusException, InterruptedException { if (request.getNodeMatchersCount() > 0) { - throw new StatusException( - Status.INVALID_ARGUMENT.withDescription("node_matchers not supported")); + throw new StatusException.Builder() + .setStatus(Status.INVALID_ARGUMENT.withDescription("node_matchers not supported")) + .build(); } ObjectPool xdsClientPool = xdsClientPoolFactory.get(); diff --git a/xds/src/test/java/io/grpc/xds/CsdsServiceTest.java b/xds/src/test/java/io/grpc/xds/CsdsServiceTest.java index 904bcdcdedc..23d14c1ef40 100644 --- a/xds/src/test/java/io/grpc/xds/CsdsServiceTest.java +++ b/xds/src/test/java/io/grpc/xds/CsdsServiceTest.java @@ -252,7 +252,8 @@ public void streamClientStatus_onClientError() { csdsAsyncStub.streamClientStatus(responseObserver); requestObserver.onNext(REQUEST); - requestObserver.onError(new StatusRuntimeException(Status.DATA_LOSS)); + requestObserver.onError(new StatusRuntimeException.Builder().setStatus(Status.DATA_LOSS) + .build()); List responses = responseObserver.getValues(); assertThat(responses).hasSize(1);