|
15 | 15 | */
|
16 | 16 | package com.google.cloud.bigtable.gaxx.reframing;
|
17 | 17 |
|
| 18 | +import com.google.api.gax.rpc.StreamController; |
18 | 19 | import com.google.cloud.bigtable.gaxx.testing.FakeStreamingApi.ServerStreamingStashCallable;
|
19 | 20 | import com.google.cloud.bigtable.gaxx.testing.FakeStreamingApi.ServerStreamingStashCallable.StreamControllerStash;
|
20 | 21 | import com.google.cloud.bigtable.gaxx.testing.MockStreamingApi.MockResponseObserver;
|
|
41 | 42 | import org.junit.Test;
|
42 | 43 | import org.junit.runner.RunWith;
|
43 | 44 | import org.junit.runners.JUnit4;
|
| 45 | +import org.mockito.Mockito; |
44 | 46 |
|
45 | 47 | @RunWith(JUnit4.class)
|
46 | 48 | public class ReframingResponseObserverTest {
|
@@ -374,6 +376,61 @@ public String pop() {
|
374 | 376 | Truth.assertThat(lastCall.getNumDelivered()).isEqualTo(2);
|
375 | 377 | }
|
376 | 378 |
|
| 379 | + /** |
| 380 | + * Test the scenario where the reframer throws an exception on incoming data and the upstream |
| 381 | + * throws an exception during cleanup when cancel is called. |
| 382 | + */ |
| 383 | + @Test |
| 384 | + public void testFailedRecoveryHandling() { |
| 385 | + MockResponseObserver<String> outerObserver = new MockResponseObserver<>(true); |
| 386 | + final RuntimeException fakeReframerError = new RuntimeException("fake reframer error"); |
| 387 | + |
| 388 | + Reframer<String, String> brokenReframer = |
| 389 | + new Reframer<String, String>() { |
| 390 | + @Override |
| 391 | + public void push(String ignored) { |
| 392 | + throw fakeReframerError; |
| 393 | + } |
| 394 | + |
| 395 | + @Override |
| 396 | + public boolean hasFullFrame() { |
| 397 | + return false; |
| 398 | + } |
| 399 | + |
| 400 | + @Override |
| 401 | + public boolean hasPartialFrame() { |
| 402 | + return false; |
| 403 | + } |
| 404 | + |
| 405 | + @Override |
| 406 | + public String pop() { |
| 407 | + throw new IllegalStateException("should not be called"); |
| 408 | + } |
| 409 | + }; |
| 410 | + ReframingResponseObserver<String, String> middleware = |
| 411 | + new ReframingResponseObserver<>(outerObserver, brokenReframer); |
| 412 | + |
| 413 | + // Configure the mock inner controller to fail cancellation. |
| 414 | + StreamController mockInnerController = Mockito.mock(StreamController.class); |
| 415 | + RuntimeException fakeCancelError = new RuntimeException("fake cancel error"); |
| 416 | + Mockito.doThrow(fakeCancelError).when(mockInnerController).cancel(); |
| 417 | + |
| 418 | + // Jumpstart a call & feed it data |
| 419 | + middleware.onStartImpl(mockInnerController); |
| 420 | + middleware.onResponseImpl("1"); |
| 421 | + |
| 422 | + // Make sure that the outer observer was notified with the reframer, which contains a suppressed |
| 423 | + // cancellation error. |
| 424 | + Throwable finalError = outerObserver.getFinalError(); |
| 425 | + Truth.assertThat(finalError).isSameInstanceAs(fakeReframerError); |
| 426 | + Truth.assertThat(ImmutableList.of(finalError.getSuppressed())).hasSize(1); |
| 427 | + Truth.assertThat(finalError.getSuppressed()[0]).isInstanceOf(IllegalStateException.class); |
| 428 | + Truth.assertThat(finalError.getSuppressed()[0]) |
| 429 | + .hasMessageThat() |
| 430 | + .isEqualTo("Failed to cancel upstream while recovering from an unexpected error"); |
| 431 | + Truth.assertThat(finalError.getSuppressed()[0].getCause()).isSameInstanceAs(fakeCancelError); |
| 432 | + } |
| 433 | + |
377 | 434 | /**
|
378 | 435 | * A simple implementation of a {@link Reframer}. The input string is split by dash, and the
|
379 | 436 | * output is concatenated by dashes. The test can verify M:N behavior by adjusting the
|
|
0 commit comments