|
19 | 19 | import static com.google.common.truth.Truth.assertThat; |
20 | 20 | import static com.google.common.truth.Truth8.assertThat; |
21 | 21 | import static org.junit.Assert.assertThrows; |
| 22 | +import static org.junit.Assert.fail; |
22 | 23 | import static org.mockito.ArgumentMatchers.any; |
23 | 24 | import static org.mockito.Mockito.doAnswer; |
24 | | -import static org.mockito.Mockito.mock; |
25 | 25 | import static org.mockito.Mockito.times; |
26 | 26 | import static org.mockito.Mockito.verify; |
27 | 27 | import static org.mockito.Mockito.when; |
|
39 | 39 | import com.google.cloud.pubsublite.TopicName; |
40 | 40 | import com.google.cloud.pubsublite.TopicPaths; |
41 | 41 | import com.google.cloud.pubsublite.internal.ExtractStatus; |
| 42 | +import com.google.cloud.pubsublite.internal.FakeApiService; |
42 | 43 | import com.google.cloud.pubsublite.internal.Publisher; |
43 | 44 | import com.google.common.collect.ImmutableList; |
44 | 45 | import com.google.protobuf.ByteString; |
45 | 46 | import io.grpc.Status; |
46 | 47 | import io.grpc.StatusException; |
47 | 48 | import java.util.Optional; |
| 49 | +import java.util.concurrent.CountDownLatch; |
| 50 | +import java.util.concurrent.ExecutorService; |
48 | 51 | import java.util.concurrent.Executors; |
49 | 52 | import java.util.concurrent.Future; |
50 | 53 | import org.apache.beam.sdk.Pipeline.PipelineExecutionException; |
|
58 | 61 | import org.junit.runners.JUnit4; |
59 | 62 | import org.mockito.ArgumentCaptor; |
60 | 63 | import org.mockito.Captor; |
| 64 | +import org.mockito.MockitoAnnotations; |
| 65 | +import org.mockito.Spy; |
61 | 66 | import org.mockito.stubbing.Answer; |
62 | 67 |
|
63 | 68 | @RunWith(JUnit4.class) |
64 | 69 | public class PubsubLiteSinkTest { |
65 | 70 | @Rule public final TestPipeline pipeline = TestPipeline.create(); |
66 | 71 |
|
67 | | - @SuppressWarnings("unchecked") |
68 | | - private final Publisher<PublishMetadata> publisher = mock(Publisher.class); |
| 72 | + abstract static class PublisherFakeService extends FakeApiService |
| 73 | + implements Publisher<PublishMetadata> {} |
| 74 | + |
| 75 | + @Spy private PublisherFakeService publisher; |
69 | 76 |
|
70 | 77 | private final PublisherOptions defaultOptions() { |
71 | 78 | try { |
@@ -99,6 +106,7 @@ private void runWith(Message... messages) { |
99 | 106 |
|
100 | 107 | @Before |
101 | 108 | public void setUp() throws Exception { |
| 109 | + MockitoAnnotations.initMocks(this); |
102 | 110 | PerServerPublisherCache.cache.set(defaultOptions(), publisher); |
103 | 111 | doAnswer( |
104 | 112 | (Answer<Void>) |
@@ -151,19 +159,49 @@ public void exceptionMixedWithOK() throws Exception { |
151 | 159 | Message message1 = Message.builder().build(); |
152 | 160 | Message message2 = Message.builder().setKey(ByteString.copyFromUtf8("abc")).build(); |
153 | 161 | Message message3 = Message.builder().setKey(ByteString.copyFromUtf8("def")).build(); |
| 162 | + SettableApiFuture<PublishMetadata> future1 = SettableApiFuture.create(); |
| 163 | + SettableApiFuture<PublishMetadata> future2 = SettableApiFuture.create(); |
| 164 | + SettableApiFuture<PublishMetadata> future3 = SettableApiFuture.create(); |
| 165 | + CountDownLatch startedLatch = new CountDownLatch(3); |
154 | 166 | when(publisher.publish(message1)) |
155 | | - .thenReturn(ApiFutures.immediateFuture(PublishMetadata.of(Partition.of(1), Offset.of(2)))); |
| 167 | + .then( |
| 168 | + invocation -> { |
| 169 | + startedLatch.countDown(); |
| 170 | + return future1; |
| 171 | + }); |
156 | 172 | when(publisher.publish(message2)) |
157 | | - .thenReturn(ApiFutures.immediateFailedFuture(Status.INTERNAL.asException())); |
| 173 | + .then( |
| 174 | + invocation -> { |
| 175 | + startedLatch.countDown(); |
| 176 | + return future2; |
| 177 | + }); |
158 | 178 | when(publisher.publish(message3)) |
159 | | - .thenReturn(ApiFutures.immediateFuture(PublishMetadata.of(Partition.of(1), Offset.of(3)))); |
| 179 | + .then( |
| 180 | + invocation -> { |
| 181 | + startedLatch.countDown(); |
| 182 | + return future3; |
| 183 | + }); |
| 184 | + ExecutorService exec = Executors.newCachedThreadPool(); |
| 185 | + exec.execute( |
| 186 | + () -> { |
| 187 | + try { |
| 188 | + startedLatch.await(); |
| 189 | + future1.set(PublishMetadata.of(Partition.of(1), Offset.of(2))); |
| 190 | + future2.setException(Status.INTERNAL.asException()); |
| 191 | + future3.set(PublishMetadata.of(Partition.of(1), Offset.of(3))); |
| 192 | + } catch (StatusException | InterruptedException e) { |
| 193 | + fail(); |
| 194 | + throw new RuntimeException(e); |
| 195 | + } |
| 196 | + }); |
160 | 197 | PipelineExecutionException e = |
161 | 198 | assertThrows(PipelineExecutionException.class, () -> runWith(message1, message2, message3)); |
162 | 199 | verify(publisher, times(3)).publish(publishedMessageCaptor.capture()); |
163 | 200 | assertThat(publishedMessageCaptor.getAllValues()).containsExactly(message1, message2, message3); |
164 | 201 | Optional<Status> statusOr = ExtractStatus.extract(e.getCause()); |
165 | 202 | assertThat(statusOr).isPresent(); |
166 | 203 | assertThat(statusOr.get().getCode()).isEqualTo(Status.Code.INTERNAL); |
| 204 | + exec.shutdownNow(); |
167 | 205 | } |
168 | 206 |
|
169 | 207 | @Test |
|
0 commit comments