diff --git a/gax/src/main/java/com/google/api/gax/batching/BatchStats.java b/gax/src/main/java/com/google/api/gax/batching/BatchStats.java new file mode 100644 index 0000000000..eae5170874 --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/batching/BatchStats.java @@ -0,0 +1,190 @@ +/* + * Copyright 2019 Google LLC + * + * 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 + * /or other materials provided with the + * distribution. + * * Neither the name of Google LLC 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 com.google.api.gax.batching; + +import com.google.api.core.ApiFutureCallback; +import com.google.api.gax.rpc.ApiException; +import com.google.api.gax.rpc.StatusCode; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.Nullable; + +/** + * This class keeps the statistics about failed operations(both at RPC and ElementT level) in {@link + * Batcher}. This provides the count of individual exception failure and count of each failed {@link + * StatusCode.Code} occurred in the batching process. + */ +class BatchStats { + + private final Map requestExceptionCounts = new ConcurrentHashMap<>(); + private final Map requestStatusCounts = new ConcurrentHashMap<>(); + private final AtomicInteger partialBatchFailures = new AtomicInteger(0); + private final Map entryExceptionCounts = new ConcurrentHashMap<>(); + private final Map entryStatusCounts = new ConcurrentHashMap<>(); + + private final Object errorLock = new Object(); + private final Object statusLock = new Object(); + + ApiFutureCallback getRequestCallback() { + return new ApiFutureCallback() { + public void onFailure(Throwable t) { + recordRequestException(t); + } + + @Override + public void onSuccess(T result) {} + }; + } + + ApiFutureCallback getEntryCallback() { + return new ApiFutureCallback() { + public void onFailure(Throwable t) { + recordEntryException(t); + } + + @Override + public void onSuccess(T result) {} + }; + } + + private void recordRequestException(Throwable throwable) { + Class exceptionClass = throwable.getClass(); + + if (throwable instanceof ApiException) { + StatusCode.Code code = ((ApiException) throwable).getStatusCode().getCode(); + exceptionClass = ApiException.class; + + synchronized (statusLock) { + if (requestStatusCounts.containsKey(code)) { + requestStatusCounts.get(code).incrementAndGet(); + } else { + requestStatusCounts.put(code, new AtomicInteger(1)); + } + } + } + + synchronized (errorLock) { + if (requestExceptionCounts.containsKey(exceptionClass)) { + requestExceptionCounts.get(exceptionClass).incrementAndGet(); + } else { + synchronized (errorLock) { + requestExceptionCounts.put(exceptionClass, new AtomicInteger(1)); + } + } + } + } + + private void recordEntryException(Throwable throwable) { + Class exceptionClass = throwable.getClass(); + + if (throwable instanceof ApiException) { + StatusCode.Code code = ((ApiException) throwable).getStatusCode().getCode(); + exceptionClass = ApiException.class; + + synchronized (statusLock) { + if (entryStatusCounts.containsKey(code)) { + entryStatusCounts.get(code).incrementAndGet(); + } else { + entryStatusCounts.put(code, new AtomicInteger(1)); + } + } + } + + synchronized (errorLock) { + if (entryExceptionCounts.containsKey(exceptionClass)) { + entryExceptionCounts.get(exceptionClass).incrementAndGet(); + } else { + partialBatchFailures.incrementAndGet(); + entryExceptionCounts.put(exceptionClass, new AtomicInteger(1)); + } + } + } + + /** Calculates and formats the message with request and entry failure count. */ + @Nullable + BatchingException asException() { + if (requestExceptionCounts.isEmpty() && partialBatchFailures.get() == 0) { + return null; + } + + StringBuilder sb = new StringBuilder(); + int batchFailures = requestExceptionCounts.size(); + + if (requestExceptionCounts.isEmpty()) { + sb.append("Batching finished with "); + } else { + sb.append(String.format("%d batches failed to apply due to: ", batchFailures)); + + // compose the exception and return it + for (Class req : requestExceptionCounts.keySet()) { + sb.append( + String.format("%d %s ", requestExceptionCounts.get(req).get(), req.getSimpleName())); + if (req.equals(ApiException.class)) { + sb.append("("); + for (StatusCode.Code statusCode : requestStatusCounts.keySet()) { + sb.append( + String.format("%d %s ", requestStatusCounts.get(statusCode).get(), statusCode)); + } + sb.append(") "); + } + } + } + + if (partialBatchFailures.get() > 0) { + sb.append(String.format("%d partial failures.", partialBatchFailures.get())); + + int totalEntriesEx = 0; + for (AtomicInteger ai : entryExceptionCounts.values()) { + totalEntriesEx += ai.get(); + } + + sb.append( + String.format( + " The %d partial failures contained %d entries that failed with: ", + partialBatchFailures.get(), totalEntriesEx)); + + for (Class entry : entryExceptionCounts.keySet()) { + sb.append( + String.format("%d %s ", entryExceptionCounts.get(entry).get(), entry.getSimpleName())); + if (entry.equals(ApiException.class)) { + sb.append("("); + for (StatusCode.Code code : entryStatusCounts.keySet()) { + sb.append(String.format("%d %s ", entryStatusCounts.get(code).get(), code)); + } + sb.append(") "); + } + } + } + sb.append("."); + return new BatchingException(sb.toString()); + } +} diff --git a/gax/src/main/java/com/google/api/gax/batching/BatcherImpl.java b/gax/src/main/java/com/google/api/gax/batching/BatcherImpl.java index 3a080f7ea5..cb17ce620d 100644 --- a/gax/src/main/java/com/google/api/gax/batching/BatcherImpl.java +++ b/gax/src/main/java/com/google/api/gax/batching/BatcherImpl.java @@ -37,8 +37,6 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; import com.google.api.core.SettableApiFuture; -import com.google.api.gax.rpc.ApiException; -import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.rpc.UnaryCallable; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; @@ -49,17 +47,14 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import java.util.logging.Logger; - /** * Queues up the elements until {@link #flush()} is called; once batching is over, returned future * resolves. @@ -88,13 +83,9 @@ public class BatcherImpl private final AtomicInteger numOfOutstandingBatches = new AtomicInteger(0); private final Object flushLock = new Object(); private final Object elementLock = new Object(); - private final Object errorLock = new Object(); private final Future scheduledFuture; private volatile boolean isClosed = false; - - private AtomicLong numOfFailure = new AtomicLong(); - private final Map failuresTypeCount = new ConcurrentHashMap<>(); - private final Map failureStatusCodeCount = new ConcurrentHashMap<>(); + private final BatchStats batchStats = new BatchStats(); /** * @param batchingDescriptor a {@link BatchingDescriptor} for transforming individual elements @@ -137,6 +128,7 @@ public ApiFuture add(ElementT element) { Preconditions.checkState(!isClosed, "Cannot add elements on a closed batcher"); SettableApiFuture result = SettableApiFuture.create(); + ApiFutures.addCallback(result, batchStats.getEntryCallback(), directExecutor()); synchronized (elementLock) { currentOpenBatch.add(element, result); } @@ -171,6 +163,9 @@ public void sendOutstanding() { unaryCallable.futureCall(accumulatedBatch.builder.build()); numOfOutstandingBatches.incrementAndGet(); + + ApiFutures.addCallback( + batchResponse, batchStats.getRequestCallback(), directExecutor()); ApiFutures.addCallback( batchResponse, new ApiFutureCallback() { @@ -186,7 +181,6 @@ public void onSuccess(ResponseT response) { @Override public void onFailure(Throwable throwable) { try { - addException(throwable); accumulatedBatch.onBatchFailure(throwable); } finally { onBatchCompletion(); @@ -212,36 +206,6 @@ private void awaitAllOutstandingBatches() throws InterruptedException { } } - /** - * It keeps the count of number of failed RPCs. This method also tracks the count for exception - * type along with counts for different failed {@link StatusCode}s. - */ - private void addException(Throwable throwable) { - numOfFailure.incrementAndGet(); - Class exceptionClass = throwable.getClass(); - - if (throwable instanceof ApiException) { - StatusCode code = ((ApiException) throwable).getStatusCode(); - exceptionClass = ApiException.class; - - synchronized (errorLock) { - if (failureStatusCodeCount.containsKey(code)) { - failureStatusCodeCount.get(code).incrementAndGet(); - } else { - failureStatusCodeCount.put(code, new AtomicInteger(1)); - } - } - } - - synchronized (errorLock) { - if (failuresTypeCount.containsKey(exceptionClass)) { - failuresTypeCount.get(exceptionClass).incrementAndGet(); - } else { - failuresTypeCount.put(exceptionClass, new AtomicInteger(1)); - } - } - } - /** {@inheritDoc} */ @Override public void close() throws InterruptedException { @@ -251,11 +215,12 @@ public void close() throws InterruptedException { flush(); scheduledFuture.cancel(true); isClosed = true; - if (numOfFailure.get() > 0) { - throw new BatchingException(numOfFailure.get(), failuresTypeCount, failureStatusCodeCount); - } currentBatcherReference.closed = true; currentBatcherReference.clear(); + BatchingException exception = batchStats.asException(); + if (exception != null) { + throw exception; + } } /** diff --git a/gax/src/main/java/com/google/api/gax/batching/BatchingException.java b/gax/src/main/java/com/google/api/gax/batching/BatchingException.java index ff246daf7b..d3a535addd 100644 --- a/gax/src/main/java/com/google/api/gax/batching/BatchingException.java +++ b/gax/src/main/java/com/google/api/gax/batching/BatchingException.java @@ -29,41 +29,15 @@ */ package com.google.api.gax.batching; -import com.google.api.gax.rpc.StatusCode; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; +import com.google.api.core.BetaApi; +import com.google.api.core.InternalExtensionOnly; -/** - * This class represents the number of failed exceptions while performing Batching. It also provides - * the count of exceptions types and count of each failed statusCodes occurred in the Batching - * process. - */ +/** Represents exception occurred during batching. */ +@BetaApi("The surface for batching is not stable yet and may change in the future.") +@InternalExtensionOnly("For google-cloud-java client use only.") public class BatchingException extends RuntimeException { - private final long numOfFailure; - private final Map exceptionCount; - private final Map statusCodeCount; - - BatchingException( - long numOfFailure, - Map exceptionCount, - Map statusCodeCount) { - super("Failed to commit " + numOfFailure + " mutations"); - - this.numOfFailure = numOfFailure; - this.exceptionCount = exceptionCount; - this.statusCodeCount = statusCodeCount; - } - - public long getTotalFailureCount() { - return numOfFailure; - } - - public Map getFailureTypesCount() { - return exceptionCount; - } - - public Map getFailureStatusCodeCount() { - return statusCodeCount; + BatchingException(String message) { + super(message); } } diff --git a/gax/src/test/java/com/google/api/gax/batching/BatchStatsTest.java b/gax/src/test/java/com/google/api/gax/batching/BatchStatsTest.java new file mode 100644 index 0000000000..c81d587985 --- /dev/null +++ b/gax/src/test/java/com/google/api/gax/batching/BatchStatsTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2019 Google LLC + * + * 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 LLC 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 com.google.api.gax.batching; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; + +import com.google.api.core.ApiFutures; +import com.google.api.gax.rpc.ApiExceptionFactory; +import com.google.api.gax.rpc.StatusCode; +import com.google.api.gax.rpc.testing.FakeStatusCode; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class BatchStatsTest { + + @Test + public void testWhenNoException() { + BatchStats batchStats = new BatchStats(); + assertThat(batchStats.asException()).isNull(); + } + + @Test + public void testRequestFailuresOnly() { + BatchStats batchStats = new BatchStats(); + + ApiFutures.addCallback( + ApiFutures.immediateFailedFuture( + ApiExceptionFactory.createException( + new RuntimeException(), + FakeStatusCode.of(StatusCode.Code.INVALID_ARGUMENT), + false)), + batchStats.getRequestCallback(), + directExecutor()); + + ApiFutures.addCallback( + ApiFutures.immediateFailedFuture(new RuntimeException("Request failed")), + batchStats.getRequestCallback(), + directExecutor()); + + BatchingException exception = batchStats.asException(); + assertThat(exception).isNotNull(); + assertThat(exception.getMessage()).contains("2 batches failed to apply"); + assertThat(exception.getMessage()).contains("1 RuntimeException"); + assertThat(exception.getMessage()).contains("1 ApiException (1 INVALID_ARGUMENT ) "); + } + + @Test + public void testRequestAndEntryFailures() { + BatchStats batchStats = new BatchStats(); + ApiFutures.addCallback( + ApiFutures.immediateFailedFuture(new RuntimeException("Request failed")), + batchStats.getRequestCallback(), + directExecutor()); + + ApiFutures.addCallback( + ApiFutures.immediateFailedFuture(new NullPointerException()), + batchStats.getEntryCallback(), + directExecutor()); + + ApiFutures.addCallback( + ApiFutures.immediateFailedFuture( + ApiExceptionFactory.createException( + new RuntimeException(), FakeStatusCode.of(StatusCode.Code.UNAVAILABLE), false)), + batchStats.getEntryCallback(), + directExecutor()); + + BatchingException ex = batchStats.asException(); + assertThat(ex).isNotNull(); + assertThat(ex.getMessage()) + .contains("1 batches failed to apply due to: 1 RuntimeException 2 partial failures."); + assertThat(ex.getMessage()).contains("1 NullPointerException"); + assertThat(ex.getMessage()).contains("1 ApiException (1 UNAVAILABLE )"); + } +} diff --git a/gax/src/test/java/com/google/api/gax/batching/BatcherImplTest.java b/gax/src/test/java/com/google/api/gax/batching/BatcherImplTest.java index 1d5fffe9d1..b6ca1a74c0 100644 --- a/gax/src/test/java/com/google/api/gax/batching/BatcherImplTest.java +++ b/gax/src/test/java/com/google/api/gax/batching/BatcherImplTest.java @@ -39,14 +39,10 @@ import com.google.api.core.SettableApiFuture; import com.google.api.gax.batching.BatcherImpl.BatcherReference; import com.google.api.gax.rpc.ApiCallContext; -import com.google.api.gax.rpc.ApiException; -import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.rpc.UnaryCallable; -import com.google.api.gax.rpc.UnimplementedException; import com.google.api.gax.rpc.testing.FakeBatchableApi.LabeledIntList; import com.google.api.gax.rpc.testing.FakeBatchableApi.LabeledIntSquarerCallable; import com.google.api.gax.rpc.testing.FakeBatchableApi.SquarerBatchingDescriptorV2; -import com.google.api.gax.rpc.testing.FakeStatusCode; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; @@ -200,14 +196,14 @@ public ApiFuture> futureCall( return ApiFutures.immediateFailedFuture(fakeError); } }; - underTest = new BatcherImpl<>( SQUARER_BATCHING_DESC_V2, unaryCallable, labeledIntList, batchingSettings, EXECUTOR); Future failedResult = underTest.add(5); + underTest.add(6); + underTest.add(7); underTest.flush(); assertThat(failedResult.isDone()).isTrue(); - Throwable actualError = null; try { failedResult.get(); @@ -221,11 +217,12 @@ public ApiFuture> futureCall( } catch (RuntimeException e) { actualError = e; } + + assertThat(actualError).isNotNull(); assertThat(actualError).isInstanceOf(BatchingException.class); - BatchingException batchingEx = (BatchingException) actualError; - assertThat(batchingEx.getTotalFailureCount()).isEqualTo(1); - assertThat(batchingEx.getFailureTypesCount()).containsKey(RuntimeException.class); - assertThat(batchingEx.getFailureStatusCodeCount()).isEmpty(); + assertThat(actualError.getMessage()) + .contains("1 batches failed to apply due to: 1 RuntimeException"); + assertThat(actualError.getMessage()).contains("3 entries that failed with: 3 RuntimeException"); } /** Resolves future results when {@link BatchingDescriptor#splitResponse} throws exception. */ @@ -254,6 +251,13 @@ public void splitResponse( } assertThat(actualError).hasCauseThat().isSameInstanceAs(fakeError); + try { + underTest.close(); + } catch (Exception batchingEx) { + actualError = batchingEx; + } + assertThat(actualError).isInstanceOf(BatchingException.class); + assertThat(actualError.getMessage()).contains("Batching finished with 1 partial failures."); } /** Resolves future results when {@link BatchingDescriptor#splitException} throws exception */ @@ -287,6 +291,12 @@ public void splitException(Throwable throwable, List> } assertThat(actualError).hasCauseThat().isSameInstanceAs(fakeError); + try { + underTest.close(); + } catch (Exception ex) { + actualError = ex; + } + assertThat(actualError).isInstanceOf(BatchingException.class); } @Test @@ -446,6 +456,81 @@ public ApiFuture> futureCall( underTest.flush(); } + /** To confirm the partial failures in Batching does not mark whole batch failed */ + @Test + public void testPartialFailureWithSplitResponse() throws Exception { + SquarerBatchingDescriptorV2 descriptor = + new SquarerBatchingDescriptorV2() { + @Override + public void splitResponse( + List batchResponse, List> batch) { + for (int i = 0; i < batchResponse.size(); i++) { + if (batchResponse.get(i) > 10_000) { + batch.get(i).setException(new ArithmeticException()); + } else { + batch.get(i).set(batchResponse.get(i)); + } + } + } + }; + + underTest = + new BatcherImpl<>( + descriptor, callLabeledIntSquarer, labeledIntList, batchingSettings, EXECUTOR); + underTest.add(10); + // This will cause partial failure + underTest.add(200); + underTest.flush(); + + underTest.add(40); + underTest.add(50); + underTest.flush(); + + // This will cause partial failure + underTest.add(500); + Exception actualError = null; + try { + underTest.close(); + } catch (Exception e) { + actualError = e; + } + assertThat(actualError).isInstanceOf(BatchingException.class); + assertThat(actualError).isNotNull(); + assertThat(actualError.getMessage()).doesNotContain("batches failed to apply due"); + assertThat(actualError.getMessage()).contains("Batching finished with 1 partial failures."); + assertThat(actualError.getMessage()).contains("2 ArithmeticException"); + } + + @Test + public void testPartialFailureInResultProcessing() throws Exception { + SquarerBatchingDescriptorV2 descriptor = + new SquarerBatchingDescriptorV2() { + + @Override + public void splitResponse( + List batchResponse, List> batch) { + throw new NullPointerException("To verify exceptions from result processing"); + } + }; + + underTest = + new BatcherImpl<>( + descriptor, callLabeledIntSquarer, labeledIntList, batchingSettings, EXECUTOR); + underTest.add(10); + underTest.add(20); + + Exception actualError = null; + try { + underTest.close(); + } catch (Exception e) { + actualError = e; + } + assertThat(actualError).isInstanceOf(BatchingException.class); + assertThat(actualError).isNotNull(); + assertThat(actualError.getMessage()).contains("Batching finished with 1 partial failures."); + assertThat(actualError.getMessage()).contains("2 NullPointerException"); + } + /** * Validates the presence of warning in case {@link BatcherImpl} is garbage collected without * being closed first. @@ -531,37 +616,6 @@ public boolean isLoggable(LogRecord record) { } } - @Test - public void testExceptionWhileBatching() { - final Exception fakeError = new RuntimeException(); - UnaryCallable> unaryCallable = - new UnaryCallable>() { - @Override - public ApiFuture> futureCall( - LabeledIntList request, ApiCallContext context) { - return ApiFutures.immediateFailedFuture( - new UnimplementedException( - fakeError, FakeStatusCode.of(StatusCode.Code.FAILED_PRECONDITION), false)); - } - }; - underTest = - new BatcherImpl<>( - SQUARER_BATCHING_DESC_V2, unaryCallable, labeledIntList, batchingSettings, EXECUTOR); - underTest.add(2); - Exception actualError = null; - try { - underTest.close(); - } catch (Exception e) { - actualError = e; - } - assertThat(actualError).isInstanceOf(BatchingException.class); - BatchingException batchingEx = (BatchingException) actualError; - assertThat(batchingEx.getTotalFailureCount()).isEqualTo(1); - assertThat(batchingEx.getFailureTypesCount()).containsKey(ApiException.class); - assertThat(batchingEx.getFailureStatusCodeCount()) - .containsKey(FakeStatusCode.of(StatusCode.Code.FAILED_PRECONDITION)); - } - private void testElementTriggers(BatchingSettings settings) throws Exception { underTest = new BatcherImpl<>( diff --git a/gax/src/test/java/com/google/api/gax/batching/BatchingExceptionTest.java b/gax/src/test/java/com/google/api/gax/batching/BatchingExceptionTest.java deleted file mode 100644 index 0135f1d109..0000000000 --- a/gax/src/test/java/com/google/api/gax/batching/BatchingExceptionTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * 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 LLC 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 com.google.api.gax.batching; - -import static com.google.common.truth.Truth.assertThat; - -import com.google.api.gax.rpc.StatusCode; -import com.google.api.gax.rpc.testing.FakeStatusCode; -import java.io.IOException; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class BatchingExceptionTest { - - @Test - public void testBatchingException() { - Map failureCounts = new ConcurrentHashMap<>(); - failureCounts.put(RuntimeException.class, new AtomicInteger(6)); - failureCounts.put(IOException.class, new AtomicInteger(3)); - - Map statusCounts = new ConcurrentHashMap<>(); - statusCounts.put(FakeStatusCode.of(StatusCode.Code.UNIMPLEMENTED), new AtomicInteger(34)); - statusCounts.put(FakeStatusCode.of(StatusCode.Code.INVALID_ARGUMENT), new AtomicInteger(324)); - - BatchingException underTest = new BatchingException(10, failureCounts, statusCounts); - assertThat(underTest).isInstanceOf(RuntimeException.class); - assertThat(underTest.getTotalFailureCount()).isEqualTo(10); - assertThat(underTest.getFailureTypesCount()).isEqualTo(failureCounts); - assertThat(underTest.getFailureStatusCodeCount()).isEqualTo(statusCounts); - } -}