diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractMockServerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractMockServerTest.java new file mode 100644 index 0000000000..760f4e8a65 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractMockServerTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner; + +import com.google.api.gax.grpc.testing.LocalChannelProvider; +import com.google.cloud.NoCredentials; +import io.grpc.Server; +import io.grpc.inprocess.InProcessServerBuilder; +import java.io.IOException; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; + +abstract class AbstractMockServerTest { + protected static MockSpannerServiceImpl mockSpanner; + protected static Server server; + protected static LocalChannelProvider channelProvider; + + private Spanner spanner; + + @BeforeClass + public static void startMockServer() throws IOException { + mockSpanner = new MockSpannerServiceImpl(); + mockSpanner.setAbortProbability(0.0D); // We don't want any unpredictable aborted transactions. + + String uniqueName = InProcessServerBuilder.generateName(); + server = InProcessServerBuilder.forName(uniqueName).addService(mockSpanner).build().start(); + channelProvider = LocalChannelProvider.create(uniqueName); + } + + @AfterClass + public static void stopMockServer() throws InterruptedException { + server.shutdown(); + server.awaitTermination(); + } + + @Before + public void createSpannerInstance() { + spanner = + SpannerOptions.newBuilder() + .setProjectId("test-project") + .setChannelProvider(channelProvider) + .setCredentials(NoCredentials.getInstance()) + .setSessionPoolOption(SessionPoolOptions.newBuilder().setFailOnSessionLeak().build()) + .build() + .getService(); + } + + @After + public void cleanup() { + spanner.close(); + mockSpanner.reset(); + mockSpanner.removeAllExecutionTimes(); + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java index 686efd3aff..029ea471cd 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java @@ -48,7 +48,6 @@ import com.google.cloud.spanner.AbstractResultSet.GrpcStreamIterator; import com.google.cloud.spanner.AsyncResultSet.CallbackResponse; import com.google.cloud.spanner.AsyncTransactionManager.TransactionContextFuture; -import com.google.cloud.spanner.BaseSessionPoolTest.FakeClock; import com.google.cloud.spanner.MockSpannerServiceImpl.SimulatedExecutionTime; import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult; import com.google.cloud.spanner.Options.RpcPriority; diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SamplesMockServerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SamplesMockServerTest.java new file mode 100644 index 0000000000..d8dcfc10e0 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SamplesMockServerTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.rpc.StatusCode; +import com.google.cloud.NoCredentials; +import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.threeten.bp.Duration; + +/** Tests for samples that use an in-mem mock server instead of running on real Cloud Spanner. */ +@RunWith(JUnit4.class) +public class SamplesMockServerTest extends AbstractMockServerTest { + + @Test + public void testSampleRetrySettings() { + String sql = + "INSERT INTO Singers (SingerId, FirstName, LastName)\n" + + "VALUES (20, 'George', 'Washington')"; + mockSpanner.putStatementResult(StatementResult.update(Statement.of(sql), 1L)); + + SpannerOptions.Builder builder = + SpannerOptions.newBuilder() + .setProjectId("p") + .setCredentials(NoCredentials.getInstance()) + .setChannelProvider(channelProvider); + // Set a timeout value for the ExecuteSql RPC that is so low that it will always be triggered. + // This should cause the RPC to fail with a DEADLINE_EXCEEDED error. + builder + .getSpannerStubSettingsBuilder() + .executeSqlSettings() + .setRetryableCodes(StatusCode.Code.UNAVAILABLE) + .setRetrySettings( + RetrySettings.newBuilder() + .setInitialRetryDelay(Duration.ofMillis(500)) + .setMaxRetryDelay(Duration.ofSeconds(16)) + .setRetryDelayMultiplier(1.5) + .setInitialRpcTimeout(Duration.ofNanos(1L)) + .setMaxRpcTimeout(Duration.ofNanos(1L)) + .setRpcTimeoutMultiplier(1.0) + .setTotalTimeout(Duration.ofNanos(1L)) + .build()); + // Create a Spanner client using the custom retry and timeout settings. + try (Spanner spanner = builder.build().getService()) { + DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d")); + SpannerException exception = + assertThrows( + SpannerException.class, + () -> + client + .readWriteTransaction() + .run(transaction -> transaction.executeUpdate(Statement.of(sql)))); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, exception.getErrorCode()); + } + } +} diff --git a/samples/snippets/src/main/java/com/example/spanner/CustomTimeoutAndRetrySettingsExample.java b/samples/snippets/src/main/java/com/example/spanner/CustomTimeoutAndRetrySettingsExample.java index 7a4a806fde..27051e299c 100644 --- a/samples/snippets/src/main/java/com/example/spanner/CustomTimeoutAndRetrySettingsExample.java +++ b/samples/snippets/src/main/java/com/example/spanner/CustomTimeoutAndRetrySettingsExample.java @@ -49,7 +49,7 @@ static void executeSqlWithCustomTimeoutAndRetrySettings( .getSpannerStubSettingsBuilder() .executeSqlSettings() // Configure which errors should be retried. - .setRetryableCodes(Code.DEADLINE_EXCEEDED, Code.UNAVAILABLE) + .setRetryableCodes(Code.UNAVAILABLE) .setRetrySettings( RetrySettings.newBuilder() // Configure retry delay settings. @@ -57,7 +57,7 @@ static void executeSqlWithCustomTimeoutAndRetrySettings( .setInitialRetryDelay(Duration.ofMillis(500)) // The maximum amount of time to wait before retrying. I.e. after this value is // reached, the wait time will not increase further by the multiplier. - .setMaxRetryDelay(Duration.ofSeconds(64)) + .setMaxRetryDelay(Duration.ofSeconds(16)) // The previous wait time is multiplied by this multiplier to come up with the next // wait time, until the max is reached. .setRetryDelayMultiplier(1.5)