diff --git a/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java b/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java index 3e85937bf..b96fd2003 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java +++ b/gax/src/main/java/com/google/api/gax/retrying/BasicRetryingFuture.java @@ -176,9 +176,10 @@ void handleAttempt(Throwable throwable, ResponseT response) { Level.FINEST, "Retrying with:\n{0}\n{1}\n{2}\n{3}", new Object[] { - "enclosingMethod: " + callable.getClass().getEnclosingMethod() != null - ? callable.getClass().getEnclosingMethod().getName() - : "", + "enclosingMethod: " + + (callable.getClass().getEnclosingMethod() != null + ? callable.getClass().getEnclosingMethod().getName() + : ""), "attemptCount: " + attemptSettings.getAttemptCount(), "delay: " + attemptSettings.getRetryDelay(), "retriableException: " + throwable diff --git a/gax/src/test/java/com/google/api/gax/retrying/BasicRetryingFutureTest.java b/gax/src/test/java/com/google/api/gax/retrying/BasicRetryingFutureTest.java new file mode 100644 index 000000000..72c753915 --- /dev/null +++ b/gax/src/test/java/com/google/api/gax/retrying/BasicRetryingFutureTest.java @@ -0,0 +1,106 @@ +/* + * Copyright 2020 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.retrying; + +import static org.mockito.Mockito.mock; + +import com.google.api.gax.tracing.ApiTracer; +import java.lang.reflect.Field; +import java.util.concurrent.Callable; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import org.threeten.bp.Duration; + +@RunWith(JUnit4.class) +public class BasicRetryingFutureTest { + private Level logLevel; + + @Before + public void setUp() throws Exception { + logLevel = getLoggerInstance().getLevel(); + } + + @After + public void tearDown() throws Exception { + getLoggerInstance().setLevel(logLevel); + } + + @Test + public void testHandleAttemptDoesNotThrowNPEWhenLogLevelLowerThanFiner() throws Exception { + @SuppressWarnings("unchecked") + Callable callable = mock(Callable.class); + @SuppressWarnings("unchecked") + RetryAlgorithm retryAlgorithm = mock(RetryAlgorithm.class); + RetryingContext retryingContext = mock(RetryingContext.class); + ApiTracer tracer = mock(ApiTracer.class); + TimedAttemptSettings timedAttemptSettings = mock(TimedAttemptSettings.class); + + Mockito.when(retryingContext.getTracer()).thenReturn(tracer); + + Mockito.when(retryAlgorithm.createFirstAttempt()).thenReturn(timedAttemptSettings); + Mockito.when( + retryAlgorithm.createNextAttempt( + ArgumentMatchers.any(), + ArgumentMatchers.any(), + ArgumentMatchers.any())) + .thenReturn(timedAttemptSettings); + Mockito.when( + retryAlgorithm.shouldRetry( + ArgumentMatchers.any(), + ArgumentMatchers.any(), + ArgumentMatchers.any())) + .thenReturn(true); + + getLoggerInstance().setLevel(Level.FINEST); + + BasicRetryingFuture future = + new BasicRetryingFuture<>(callable, retryAlgorithm, retryingContext); + + future.handleAttempt(null, null); + + Mockito.verify(tracer) + .attemptFailed(ArgumentMatchers.any(), ArgumentMatchers.any()); + Mockito.verifyNoMoreInteractions(tracer); + } + + private Logger getLoggerInstance() throws NoSuchFieldException, IllegalAccessException { + Field logger = BasicRetryingFuture.class.getDeclaredField("LOG"); + logger.setAccessible(true); + + return (Logger) logger.get(BasicRetryingFuture.class); + } +}