Skip to content

Commit

Permalink
test: fix Spanner R2DBC flaky integration tests
Browse files Browse the repository at this point in the history
Create a new database for each test for isolation.

Fixes: #2832.
Fixes: #2314.
  • Loading branch information
meltsufin committed May 12, 2024
1 parent 535de07 commit 27bd9f2
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 325 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright 2024-2024 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
*
* https://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.r2dbc.springdata.it;

import com.google.cloud.ServiceOptions;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.r2dbc.springdata.SpannerR2dbcDialect;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.AfterEach;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.annotation.UserConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;

/**
* Abstract base for Spanner R2DBC integration tests of {@link SpannerR2dbcDialect}.
*/
abstract class AbstractBaseSpannerR2dbcIntegrationTest {
private static final Logger log =
LoggerFactory.getLogger(AbstractBaseSpannerR2dbcIntegrationTest.class);
protected static final String PROJECT_NAME =
System.getProperty("gcp.project", ServiceOptions.getDefaultProjectId());
protected static final String DRIVER_NAME = "spanner";
protected static final String TEST_INSTANCE =
System.getProperty("spanner.instance", "reactivetest");
protected String testDatabase;
protected Spanner spanner;
protected ApplicationContextRunner contextRunner;

/** Initializes the integration test environment for the Spanner R2DBC dialect. */
void initializeTestEnvironment(Class configurationClass, String tableDdl) {
this.testDatabase = "testdb_" + UUID.randomUUID().toString().replace('-', '_').substring(0, 20);

this.spanner = SpannerOptions.newBuilder().setProjectId(PROJECT_NAME).build().getService();

if (configurationClass != null) {
this.contextRunner =
new ApplicationContextRunner()
.withPropertyValues("testDatabase=" + testDatabase)
.withConfiguration(
UserConfigurations.of(
configurationClass));
}

log.info("Creating database {}", testDatabase);
spanner.getDatabaseAdminClient().createDatabase(
TEST_INSTANCE, testDatabase, List.of(tableDdl));
log.info("Done creating database {}", testDatabase);
}

void initializeTestEnvironment(String tableDdl) {
initializeTestEnvironment(null, tableDdl);
}

@AfterEach
void cleanupTestInstanceAfterTest() {
log.info("Deleting database {}", testDatabase);
this.spanner.getDatabaseAdminClient().dropDatabase(TEST_INSTANCE, testDatabase);
log.info("Done deleting database {}", testDatabase);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,10 @@
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.util.Arrays;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
Expand All @@ -52,87 +46,26 @@
import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration;
import org.springframework.data.r2dbc.convert.R2dbcCustomConversions;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

/**
* Integration tests verifying the {@link Timestamp},{@link Date} support of {@link
* SpannerR2dbcDialect}.
*/
@EnabledIfSystemProperty(named = "it.spanner", matches = "true")
@ExtendWith(SpringExtension.class)
@ContextConfiguration(
classes = SpannerR2dbcDialectDateTimeBindingIntegrationTest.TestConfiguration.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SpannerR2dbcDialectDateTimeBindingIntegrationTest {

private static final Logger log =
LoggerFactory.getLogger(SpannerR2dbcDialectDateTimeBindingIntegrationTest.class);

private static final String PROJECT_NAME =
System.getProperty("gcp.project", ServiceOptions.getDefaultProjectId());
private static final String DRIVER_NAME = "spanner";

private static final String TEST_INSTANCE =
System.getProperty("spanner.instance", "reactivetest");

private static final String TEST_DATABASE = System.getProperty("spanner.database", "testdb");

private static final ConnectionFactory connectionFactory =
ConnectionFactories.get(
ConnectionFactoryOptions.builder()
.option(Option.valueOf("project"), ServiceOptions.getDefaultProjectId())
.option(PROJECT, PROJECT_NAME)
.option(DRIVER, DRIVER_NAME)
.option(INSTANCE, TEST_INSTANCE)
.option(DATABASE, TEST_DATABASE)
.build());

@Autowired private R2dbcEntityTemplate r2dbcEntityTemplate;

class SpannerR2dbcDialectDateTimeBindingIntegrationTest extends AbstractBaseSpannerR2dbcIntegrationTest {
/** Initializes the integration test environment for the Spanner R2DBC dialect. */
@BeforeAll
static void initializeTestEnvironment() {
Mono.from(connectionFactory.create())
.flatMap(
c ->
Mono.from(c.createStatement("drop table card").execute())
.doOnSuccess(x -> log.info("Table drop completed."))
.doOnError(
x -> {
if (!x.getMessage().contains("Table not found")) {
log.info("Table drop failed. {}", x.getMessage());
}
})
.onErrorResume(x -> Mono.empty())
.thenReturn(c))
.flatMap(
c ->
Mono.from(
c.createStatement(
"create table card ("
+ " id int64 not null,"
+ " expiry_year int64 not null,"
+ " expiry_month int64 not null,"
+ " issue_date date not null,"
+ " requested_at timestamp not null"
+ ") primary key (id)")
.execute())
.doOnSuccess(x -> log.info("Table creation completed.")))
.block();
}

@AfterAll
static void cleanupTableAfterTest() {
Mono.from(connectionFactory.create())
.flatMap(
c ->
Mono.from(c.createStatement("drop table card").execute())
.doOnSuccess(x -> log.info("Table drop completed."))
.doOnError(x -> log.info("Table drop failed.")))
.block();
@BeforeEach
void initializeTestEnvironment() {
super.initializeTestEnvironment(
SpannerR2dbcDialectDateTimeBindingIntegrationTest.TestConfiguration.class,
"create table card ("
+ " id int64 not null,"
+ " expiry_year int64 not null,"
+ " expiry_month int64 not null,"
+ " issue_date date not null,"
+ " requested_at timestamp not null"
+ ") primary key (id)");
}

@Test
Expand All @@ -145,36 +78,48 @@ void shouldReadWriteDateAndTimestampTypes() {
LocalDate.parse("2021-12-31"),
LocalDateTime.parse("2021-12-15T21:30:10"));

this.r2dbcEntityTemplate
.insert(Card.class)
.using(card)
.then()
.as(StepVerifier::create)
.verifyComplete();

this.r2dbcEntityTemplate
.select(Card.class)
.first()
.as(StepVerifier::create)
.expectNextMatches(
c ->
c.getId() == 1L
&& c.getExpiryYear() == 2022
&& c.getExpiryMonth() == 12
&& c.getIssueDate().equals(LocalDate.parse("2021-12-31"))
&& c.getRequestedAt().equals(LocalDateTime.parse("2021-12-15T21:30:10")))
.verifyComplete();
this.contextRunner.run(ctx -> {
R2dbcEntityTemplate r2dbcEntityTemplate = ctx.getBean(R2dbcEntityTemplate.class);

r2dbcEntityTemplate
.insert(Card.class)
.using(card)
.then()
.as(StepVerifier::create)
.verifyComplete();

r2dbcEntityTemplate
.select(Card.class)
.first()
.as(StepVerifier::create)
.expectNextMatches(
c ->
c.getId() == 1L
&& c.getExpiryYear() == 2022
&& c.getExpiryMonth() == 12
&& c.getIssueDate().equals(LocalDate.parse("2021-12-31"))
&& c.getRequestedAt().equals(LocalDateTime.parse("2021-12-15T21:30:10")))
.verifyComplete();
});
}

/** Register custom converters. */
@Configuration
static class TestConfiguration extends AbstractR2dbcConfiguration {

@Autowired ApplicationContext applicationContext;
@Value("${testDatabase}")
private String testDatabase;

@Override
@Bean
public ConnectionFactory connectionFactory() {
return connectionFactory;
return ConnectionFactories.get(
ConnectionFactoryOptions.builder()
.option(Option.valueOf("project"), ServiceOptions.getDefaultProjectId())
.option(PROJECT, PROJECT_NAME)
.option(DRIVER, DRIVER_NAME)
.option(INSTANCE, TEST_INSTANCE)
.option(DATABASE, testDatabase)
.build());
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,19 @@
import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE;
import static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER;

import com.google.cloud.ServiceOptions;
import com.google.cloud.spanner.r2dbc.springdata.it.entities.President;
import io.r2dbc.spi.Connection;
import io.r2dbc.spi.ConnectionFactories;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.ConnectionFactoryOptions;
import io.r2dbc.spi.Option;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.data.relational.core.query.Query;
import org.springframework.r2dbc.core.DatabaseClient;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

/**
Expand All @@ -51,66 +43,28 @@
* `spanner.database` system properties.
*/
@EnabledIfSystemProperty(named = "it.spanner", matches = "true")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SpannerR2dbcDialectIntegrationTest {

private static final Logger logger =
LoggerFactory.getLogger(SpannerR2dbcDialectIntegrationTest.class);

private static final String DRIVER_NAME = "spanner";

private static final String TEST_INSTANCE =
System.getProperty("spanner.instance", "reactivetest");

private static final String TEST_DATABASE =
System.getProperty("spanner.database", "testdb");

private static final ConnectionFactory connectionFactory =
ConnectionFactories.get(ConnectionFactoryOptions.builder()
.option(Option.valueOf("project"), ServiceOptions.getDefaultProjectId())
.option(DRIVER, DRIVER_NAME)
.option(INSTANCE, TEST_INSTANCE)
.option(DATABASE, TEST_DATABASE)
.build());

private DatabaseClient databaseClient;

class SpannerR2dbcDialectIntegrationTest extends AbstractBaseSpannerR2dbcIntegrationTest {
private ConnectionFactory connectionFactory;
private R2dbcEntityTemplate r2dbcEntityTemplate;

/**
* Initializes the integration test environment for the Spanner R2DBC dialect.
*/
@BeforeAll
@BeforeEach
public void initializeTestEnvironment() {
Connection connection = Mono.from(connectionFactory.create()).block();
initializeTestEnvironment("CREATE TABLE PRESIDENT ("
+ " NAME STRING(256) NOT NULL,"
+ " START_YEAR INT64 NOT NULL"
+ ") PRIMARY KEY (NAME)");

this.connectionFactory = ConnectionFactories.get(ConnectionFactoryOptions.builder()
.option(Option.valueOf("project"), PROJECT_NAME)
.option(DRIVER, DRIVER_NAME)
.option(INSTANCE, TEST_INSTANCE)
.option(DATABASE, testDatabase)
.build());

this.r2dbcEntityTemplate = new R2dbcEntityTemplate(connectionFactory);
this.databaseClient = this.r2dbcEntityTemplate.getDatabaseClient();

if (SpannerTestUtils.tableExists(connection, "PRESIDENT")) {
this.databaseClient.sql("DROP TABLE PRESIDENT")
.fetch()
.rowsUpdated()
.block();
}

this.databaseClient.sql(
"CREATE TABLE PRESIDENT ("
+ " NAME STRING(256) NOT NULL,"
+ " START_YEAR INT64 NOT NULL"
+ ") PRIMARY KEY (NAME)")
.fetch()
.rowsUpdated()
.block();
}

@AfterEach
public void cleanupTableAfterTest() {
this.databaseClient
.sql("DELETE FROM PRESIDENT where NAME is not null")
.fetch()
.rowsUpdated()
.block();
}

@Test
Expand Down

0 comments on commit 27bd9f2

Please sign in to comment.