mirror of
https://github.com/google/nomulus.git
synced 2025-05-23 04:39:35 +02:00
Add JUnit5 extension to run test twice against different databases (#588)
* Add JUnit5 extension to run test against different databases * Fix typos * Add some explanation
This commit is contained in:
parent
5e596bb389
commit
a0f4013d53
7 changed files with 254 additions and 63 deletions
|
@ -880,6 +880,9 @@ task standardTest(type: FilteringTest) {
|
||||||
// forkEvery 1
|
// forkEvery 1
|
||||||
|
|
||||||
// Sets the maximum number of test executors that may exist at the same time.
|
// Sets the maximum number of test executors that may exist at the same time.
|
||||||
|
// Also, Gradle executes tests in 1 thread and some of our test infrastructures
|
||||||
|
// depend on that, e.g. DualDatabaseTestInvocationContextProvider injects
|
||||||
|
// different implementation of TransactionManager into TransactionManagerFactory.
|
||||||
maxParallelForks 5
|
maxParallelForks 5
|
||||||
|
|
||||||
systemProperty 'test.projectRoot', rootProject.projectRootDir
|
systemProperty 'test.projectRoot', rootProject.projectRootDir
|
||||||
|
|
|
@ -16,6 +16,7 @@ package google.registry.persistence.transaction;
|
||||||
|
|
||||||
import com.google.appengine.api.utils.SystemProperty;
|
import com.google.appengine.api.utils.SystemProperty;
|
||||||
import com.google.appengine.api.utils.SystemProperty.Environment.Value;
|
import com.google.appengine.api.utils.SystemProperty.Environment.Value;
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Suppliers;
|
import com.google.common.base.Suppliers;
|
||||||
import google.registry.model.ofy.DatastoreTransactionManager;
|
import google.registry.model.ofy.DatastoreTransactionManager;
|
||||||
import google.registry.persistence.DaggerPersistenceComponent;
|
import google.registry.persistence.DaggerPersistenceComponent;
|
||||||
|
@ -26,7 +27,9 @@ import java.util.function.Supplier;
|
||||||
// TODO: Rename this to PersistenceFactory and move to persistence package.
|
// TODO: Rename this to PersistenceFactory and move to persistence package.
|
||||||
public class TransactionManagerFactory {
|
public class TransactionManagerFactory {
|
||||||
|
|
||||||
private static final TransactionManager TM = createTransactionManager();
|
private static final DatastoreTransactionManager ofyTm = createTransactionManager();
|
||||||
|
|
||||||
|
@NonFinalForTesting private static TransactionManager tm = ofyTm;
|
||||||
|
|
||||||
/** Supplier for jpaTm so that it is initialized only once, upon first usage. */
|
/** Supplier for jpaTm so that it is initialized only once, upon first usage. */
|
||||||
@NonFinalForTesting
|
@NonFinalForTesting
|
||||||
|
@ -45,10 +48,7 @@ public class TransactionManagerFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TransactionManager createTransactionManager() {
|
private static DatastoreTransactionManager createTransactionManager() {
|
||||||
// TODO: Determine how to provision TransactionManager after the dual-write. During the
|
|
||||||
// dual-write transitional phase, we need the TransactionManager for both Datastore and Cloud
|
|
||||||
// SQL, and this method returns the one for Datastore.
|
|
||||||
return new DatastoreTransactionManager(null);
|
return new DatastoreTransactionManager(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ public class TransactionManagerFactory {
|
||||||
|
|
||||||
/** Returns {@link TransactionManager} instance. */
|
/** Returns {@link TransactionManager} instance. */
|
||||||
public static TransactionManager tm() {
|
public static TransactionManager tm() {
|
||||||
return TM;
|
return tm;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns {@link JpaTransactionManager} instance. */
|
/** Returns {@link JpaTransactionManager} instance. */
|
||||||
|
@ -75,8 +75,20 @@ public class TransactionManagerFactory {
|
||||||
return jpaTm.get();
|
return jpaTm.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns {@link DatastoreTransactionManager} instance. */
|
||||||
|
@VisibleForTesting
|
||||||
|
public static DatastoreTransactionManager ofyTm() {
|
||||||
|
return ofyTm;
|
||||||
|
}
|
||||||
|
|
||||||
/** Sets the return of {@link #jpaTm()} to the given instance of {@link JpaTransactionManager}. */
|
/** Sets the return of {@link #jpaTm()} to the given instance of {@link JpaTransactionManager}. */
|
||||||
public static void setJpaTm(JpaTransactionManager newJpaTm) {
|
public static void setJpaTm(JpaTransactionManager newJpaTm) {
|
||||||
jpaTm = Suppliers.ofInstance(newJpaTm);
|
jpaTm = Suppliers.ofInstance(newJpaTm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Sets the return of {@link #tm()} to the given instance of {@link TransactionManager}. */
|
||||||
|
@VisibleForTesting
|
||||||
|
public static void setTm(TransactionManager newTm) {
|
||||||
|
tm = newTm;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,13 @@ import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.JUnit4;
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
/** Unit tests for {@link JpaTransactionManagerImpl}. */
|
/**
|
||||||
|
* Unit tests for SQL only APIs defined in {@link JpaTransactionManagerImpl}. Note that the tests
|
||||||
|
* for common APIs in {@link TransactionManager} are added in {@link TransactionManagerTest}.
|
||||||
|
*
|
||||||
|
* <p>TODO(shicong): Remove duplicate tests that covered by TransactionManagerTest by refactoring
|
||||||
|
* the test schema.
|
||||||
|
*/
|
||||||
@RunWith(JUnit4.class)
|
@RunWith(JUnit4.class)
|
||||||
public class JpaTransactionManagerImplTest {
|
public class JpaTransactionManagerImplTest {
|
||||||
|
|
||||||
|
@ -62,29 +68,6 @@ public class JpaTransactionManagerImplTest {
|
||||||
.withEntityClass(TestEntity.class, TestCompoundIdEntity.class)
|
.withEntityClass(TestEntity.class, TestCompoundIdEntity.class)
|
||||||
.buildUnitTestRule();
|
.buildUnitTestRule();
|
||||||
|
|
||||||
@Test
|
|
||||||
public void inTransaction_returnsCorrespondingResult() {
|
|
||||||
assertThat(jpaTm().inTransaction()).isFalse();
|
|
||||||
jpaTm().transact(() -> assertThat(jpaTm().inTransaction()).isTrue());
|
|
||||||
assertThat(jpaTm().inTransaction()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void assertInTransaction_throwsExceptionWhenNotInTransaction() {
|
|
||||||
assertThrows(IllegalStateException.class, () -> jpaTm().assertInTransaction());
|
|
||||||
jpaTm().transact(() -> jpaTm().assertInTransaction());
|
|
||||||
assertThrows(IllegalStateException.class, () -> jpaTm().assertInTransaction());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getTransactionTime_throwsExceptionWhenNotInTransaction() {
|
|
||||||
FakeClock txnClock = fakeClock;
|
|
||||||
txnClock.advanceOneMilli();
|
|
||||||
assertThrows(IllegalStateException.class, () -> jpaTm().getTransactionTime());
|
|
||||||
jpaTm().transact(() -> assertThat(jpaTm().getTransactionTime()).isEqualTo(txnClock.nowUtc()));
|
|
||||||
assertThrows(IllegalStateException.class, () -> jpaTm().getTransactionTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void transact_succeeds() {
|
public void transact_succeeds() {
|
||||||
assertPersonEmpty();
|
assertPersonEmpty();
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package google.registry.model.ofy;
|
package google.registry.persistence.transaction;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||||
|
@ -23,16 +23,24 @@ import com.googlecode.objectify.Key;
|
||||||
import com.googlecode.objectify.annotation.Entity;
|
import com.googlecode.objectify.annotation.Entity;
|
||||||
import com.googlecode.objectify.annotation.Id;
|
import com.googlecode.objectify.annotation.Id;
|
||||||
import google.registry.model.ImmutableObject;
|
import google.registry.model.ImmutableObject;
|
||||||
|
import google.registry.model.ofy.DatastoreTransactionManager;
|
||||||
|
import google.registry.model.ofy.Ofy;
|
||||||
import google.registry.persistence.VKey;
|
import google.registry.persistence.VKey;
|
||||||
import google.registry.testing.AppEngineRule;
|
import google.registry.testing.AppEngineRule;
|
||||||
|
import google.registry.testing.DualDatabaseTest;
|
||||||
import google.registry.testing.FakeClock;
|
import google.registry.testing.FakeClock;
|
||||||
import google.registry.testing.InjectRule;
|
import google.registry.testing.InjectRule;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.TestTemplate;
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
|
||||||
public class DatastoreTransactionManagerTest {
|
/**
|
||||||
|
* Unit tests for common APIs in {@link DatastoreTransactionManager} and {@link
|
||||||
|
* JpaTransactionManagerImpl}.
|
||||||
|
*/
|
||||||
|
@DualDatabaseTest
|
||||||
|
public class TransactionManagerTest {
|
||||||
|
|
||||||
private final FakeClock fakeClock = new FakeClock();
|
private final FakeClock fakeClock = new FakeClock();
|
||||||
|
|
||||||
|
@ -51,38 +59,31 @@ public class DatastoreTransactionManagerTest {
|
||||||
.withClock(fakeClock)
|
.withClock(fakeClock)
|
||||||
.withDatastoreAndCloudSql()
|
.withDatastoreAndCloudSql()
|
||||||
.withOfyTestEntities(TestEntity.class)
|
.withOfyTestEntities(TestEntity.class)
|
||||||
|
.withJpaUnitTestEntities(TestEntity.class)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public DatastoreTransactionManagerTest() {}
|
public TransactionManagerTest() {}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
inject.setStaticField(Ofy.class, "clock", fakeClock);
|
inject.setStaticField(Ofy.class, "clock", fakeClock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(mmuller): The tests below are just copy-pasted from JpaTransactionManagerImplTest
|
@TestTemplate
|
||||||
// (excluding the CompoundId tests and native query tests, which are not relevant to datastore,
|
|
||||||
// and the test methods using "count" which doesn't work for datastore, as well as tests for
|
|
||||||
// functionality that doesn't exist in datastore, like failures based on whether a newly saved or
|
|
||||||
// updated object exists or not). We need to merge these into a single test suite, but first we
|
|
||||||
// should move the JpaUnitTestRule functionality into AppEngineTest and migrate the whole thing
|
|
||||||
// to junit5.
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void inTransaction_returnsCorrespondingResult() {
|
public void inTransaction_returnsCorrespondingResult() {
|
||||||
assertThat(tm().inTransaction()).isFalse();
|
assertThat(tm().inTransaction()).isFalse();
|
||||||
tm().transact(() -> assertThat(tm().inTransaction()).isTrue());
|
tm().transact(() -> assertThat(tm().inTransaction()).isTrue());
|
||||||
assertThat(tm().inTransaction()).isFalse();
|
assertThat(tm().inTransaction()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestTemplate
|
||||||
public void assertInTransaction_throwsExceptionWhenNotInTransaction() {
|
public void assertInTransaction_throwsExceptionWhenNotInTransaction() {
|
||||||
assertThrows(IllegalStateException.class, () -> tm().assertInTransaction());
|
assertThrows(IllegalStateException.class, () -> tm().assertInTransaction());
|
||||||
tm().transact(() -> tm().assertInTransaction());
|
tm().transact(() -> tm().assertInTransaction());
|
||||||
assertThrows(IllegalStateException.class, () -> tm().assertInTransaction());
|
assertThrows(IllegalStateException.class, () -> tm().assertInTransaction());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestTemplate
|
||||||
public void getTransactionTime_throwsExceptionWhenNotInTransaction() {
|
public void getTransactionTime_throwsExceptionWhenNotInTransaction() {
|
||||||
FakeClock txnClock = fakeClock;
|
FakeClock txnClock = fakeClock;
|
||||||
txnClock.advanceOneMilli();
|
txnClock.advanceOneMilli();
|
||||||
|
@ -91,7 +92,7 @@ public class DatastoreTransactionManagerTest {
|
||||||
assertThrows(IllegalStateException.class, () -> tm().getTransactionTime());
|
assertThrows(IllegalStateException.class, () -> tm().getTransactionTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestTemplate
|
||||||
public void transact_hasNoEffectWithPartialSuccess() {
|
public void transact_hasNoEffectWithPartialSuccess() {
|
||||||
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -106,7 +107,7 @@ public class DatastoreTransactionManagerTest {
|
||||||
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestTemplate
|
||||||
public void transact_reusesExistingTransaction() {
|
public void transact_reusesExistingTransaction() {
|
||||||
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
||||||
fakeClock.advanceOneMilli();
|
fakeClock.advanceOneMilli();
|
||||||
|
@ -115,7 +116,7 @@ public class DatastoreTransactionManagerTest {
|
||||||
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isTrue();
|
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestTemplate
|
||||||
public void saveNew_succeeds() {
|
public void saveNew_succeeds() {
|
||||||
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
||||||
fakeClock.advanceOneMilli();
|
fakeClock.advanceOneMilli();
|
||||||
|
@ -126,7 +127,7 @@ public class DatastoreTransactionManagerTest {
|
||||||
assertThat(tm().transact(() -> tm().load(theEntity.key()))).isEqualTo(theEntity);
|
assertThat(tm().transact(() -> tm().load(theEntity.key()))).isEqualTo(theEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestTemplate
|
||||||
public void saveAllNew_succeeds() {
|
public void saveAllNew_succeeds() {
|
||||||
moreEntities.forEach(
|
moreEntities.forEach(
|
||||||
entity -> assertThat(tm().transact(() -> tm().checkExists(entity))).isFalse());
|
entity -> assertThat(tm().transact(() -> tm().checkExists(entity))).isFalse());
|
||||||
|
@ -137,7 +138,7 @@ public class DatastoreTransactionManagerTest {
|
||||||
entity -> assertThat(tm().transact(() -> tm().checkExists(entity))).isTrue());
|
entity -> assertThat(tm().transact(() -> tm().checkExists(entity))).isTrue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestTemplate
|
||||||
public void saveNewOrUpdate_persistsNewEntity() {
|
public void saveNewOrUpdate_persistsNewEntity() {
|
||||||
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
||||||
fakeClock.advanceOneMilli();
|
fakeClock.advanceOneMilli();
|
||||||
|
@ -148,7 +149,7 @@ public class DatastoreTransactionManagerTest {
|
||||||
assertThat(tm().transact(() -> tm().load(theEntity.key()))).isEqualTo(theEntity);
|
assertThat(tm().transact(() -> tm().load(theEntity.key()))).isEqualTo(theEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestTemplate
|
||||||
public void saveNewOrUpdate_updatesExistingEntity() {
|
public void saveNewOrUpdate_updatesExistingEntity() {
|
||||||
fakeClock.advanceOneMilli();
|
fakeClock.advanceOneMilli();
|
||||||
tm().transact(() -> tm().saveNew(theEntity));
|
tm().transact(() -> tm().saveNew(theEntity));
|
||||||
|
@ -163,7 +164,7 @@ public class DatastoreTransactionManagerTest {
|
||||||
assertThat(persisted.data).isEqualTo("bar");
|
assertThat(persisted.data).isEqualTo("bar");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestTemplate
|
||||||
public void saveNewOrUpdateAll_succeeds() {
|
public void saveNewOrUpdateAll_succeeds() {
|
||||||
moreEntities.forEach(
|
moreEntities.forEach(
|
||||||
entity -> assertThat(tm().transact(() -> tm().checkExists(entity))).isFalse());
|
entity -> assertThat(tm().transact(() -> tm().checkExists(entity))).isFalse());
|
||||||
|
@ -174,13 +175,16 @@ public class DatastoreTransactionManagerTest {
|
||||||
entity -> assertThat(tm().transact(() -> tm().checkExists(entity))).isTrue());
|
entity -> assertThat(tm().transact(() -> tm().checkExists(entity))).isTrue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestTemplate
|
||||||
public void update_succeeds() {
|
public void update_succeeds() {
|
||||||
fakeClock.advanceOneMilli();
|
fakeClock.advanceOneMilli();
|
||||||
tm().transact(() -> tm().saveNew(theEntity));
|
tm().transact(() -> tm().saveNew(theEntity));
|
||||||
fakeClock.advanceOneMilli();
|
fakeClock.advanceOneMilli();
|
||||||
TestEntity persisted =
|
TestEntity persisted =
|
||||||
tm().transact(() -> tm().load(VKey.createOfy(TestEntity.class, Key.create(theEntity))));
|
tm().transact(
|
||||||
|
() ->
|
||||||
|
tm().load(
|
||||||
|
VKey.create(TestEntity.class, theEntity.name, Key.create(theEntity))));
|
||||||
fakeClock.advanceOneMilli();
|
fakeClock.advanceOneMilli();
|
||||||
assertThat(persisted.data).isEqualTo("foo");
|
assertThat(persisted.data).isEqualTo("foo");
|
||||||
theEntity.data = "bar";
|
theEntity.data = "bar";
|
||||||
|
@ -190,7 +194,7 @@ public class DatastoreTransactionManagerTest {
|
||||||
assertThat(persisted.data).isEqualTo("bar");
|
assertThat(persisted.data).isEqualTo("bar");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestTemplate
|
||||||
public void load_succeeds() {
|
public void load_succeeds() {
|
||||||
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
||||||
fakeClock.advanceOneMilli();
|
fakeClock.advanceOneMilli();
|
||||||
|
@ -201,7 +205,7 @@ public class DatastoreTransactionManagerTest {
|
||||||
assertThat(persisted.data).isEqualTo("foo");
|
assertThat(persisted.data).isEqualTo("foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestTemplate
|
||||||
public void load_throwsOnMissingElement() {
|
public void load_throwsOnMissingElement() {
|
||||||
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
||||||
fakeClock.advanceOneMilli();
|
fakeClock.advanceOneMilli();
|
||||||
|
@ -209,7 +213,7 @@ public class DatastoreTransactionManagerTest {
|
||||||
NoSuchElementException.class, () -> tm().transact(() -> tm().load(theEntity.key())));
|
NoSuchElementException.class, () -> tm().transact(() -> tm().load(theEntity.key())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestTemplate
|
||||||
public void maybeLoad_succeeds() {
|
public void maybeLoad_succeeds() {
|
||||||
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
||||||
fakeClock.advanceOneMilli();
|
fakeClock.advanceOneMilli();
|
||||||
|
@ -220,14 +224,14 @@ public class DatastoreTransactionManagerTest {
|
||||||
assertThat(persisted.data).isEqualTo("foo");
|
assertThat(persisted.data).isEqualTo("foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestTemplate
|
||||||
public void maybeLoad_nonExistentObject() {
|
public void maybeLoad_nonExistentObject() {
|
||||||
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
||||||
fakeClock.advanceOneMilli();
|
fakeClock.advanceOneMilli();
|
||||||
assertThat(tm().transact(() -> tm().maybeLoad(theEntity.key())).isPresent()).isFalse();
|
assertThat(tm().transact(() -> tm().maybeLoad(theEntity.key())).isPresent()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestTemplate
|
||||||
public void delete_succeeds() {
|
public void delete_succeeds() {
|
||||||
fakeClock.advanceOneMilli();
|
fakeClock.advanceOneMilli();
|
||||||
tm().transact(() -> tm().saveNew(theEntity));
|
tm().transact(() -> tm().saveNew(theEntity));
|
||||||
|
@ -239,7 +243,7 @@ public class DatastoreTransactionManagerTest {
|
||||||
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestTemplate
|
||||||
public void delete_returnsZeroWhenNoEntity() {
|
public void delete_returnsZeroWhenNoEntity() {
|
||||||
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
|
||||||
fakeClock.advanceOneMilli();
|
fakeClock.advanceOneMilli();
|
||||||
|
@ -249,8 +253,9 @@ public class DatastoreTransactionManagerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity(name = "TestEntity")
|
@Entity(name = "TestEntity")
|
||||||
|
@javax.persistence.Entity(name = "TestEntity")
|
||||||
private static class TestEntity extends ImmutableObject {
|
private static class TestEntity extends ImmutableObject {
|
||||||
@Id private String name;
|
@Id @javax.persistence.Id private String name;
|
||||||
|
|
||||||
private String data;
|
private String data;
|
||||||
|
|
||||||
|
@ -262,7 +267,7 @@ public class DatastoreTransactionManagerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public VKey<TestEntity> key() {
|
public VKey<TestEntity> key() {
|
||||||
return VKey.createOfy(TestEntity.class, Key.create(this));
|
return VKey.create(TestEntity.class, name, Key.create(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -17,6 +17,8 @@ package google.registry.testing;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static com.google.common.truth.Truth.assertWithMessage;
|
import static com.google.common.truth.Truth.assertWithMessage;
|
||||||
import static google.registry.testing.DatastoreHelper.persistSimpleResources;
|
import static google.registry.testing.DatastoreHelper.persistSimpleResources;
|
||||||
|
import static google.registry.testing.DualDatabaseTestInvocationContextProvider.injectTmForDualDatabaseTest;
|
||||||
|
import static google.registry.testing.DualDatabaseTestInvocationContextProvider.restoreTmAfterDualDatabaseTest;
|
||||||
import static google.registry.util.ResourceUtils.readResourceUtf8;
|
import static google.registry.util.ResourceUtils.readResourceUtf8;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static org.json.XML.toJSONObject;
|
import static org.json.XML.toJSONObject;
|
||||||
|
@ -45,6 +47,7 @@ import google.registry.persistence.transaction.JpaTestRules;
|
||||||
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationTestRule;
|
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationTestRule;
|
||||||
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension;
|
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension;
|
||||||
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageRule;
|
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageRule;
|
||||||
|
import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestRule;
|
||||||
import google.registry.util.Clock;
|
import google.registry.util.Clock;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -118,8 +121,11 @@ public final class AppEngineRule extends ExternalResource
|
||||||
*/
|
*/
|
||||||
JpaIntegrationWithCoverageExtension jpaIntegrationWithCoverageExtension = null;
|
JpaIntegrationWithCoverageExtension jpaIntegrationWithCoverageExtension = null;
|
||||||
|
|
||||||
|
JpaUnitTestRule jpaUnitTestRule;
|
||||||
|
|
||||||
private boolean withDatastoreAndCloudSql;
|
private boolean withDatastoreAndCloudSql;
|
||||||
private boolean enableJpaEntityCoverageCheck;
|
private boolean enableJpaEntityCoverageCheck;
|
||||||
|
private boolean withJpaUnitTest;
|
||||||
private boolean withLocalModules;
|
private boolean withLocalModules;
|
||||||
private boolean withTaskQueue;
|
private boolean withTaskQueue;
|
||||||
private boolean withUserService;
|
private boolean withUserService;
|
||||||
|
@ -131,12 +137,14 @@ public final class AppEngineRule extends ExternalResource
|
||||||
|
|
||||||
// Test Objectify entity classes to be used with this AppEngineRule instance.
|
// Test Objectify entity classes to be used with this AppEngineRule instance.
|
||||||
private ImmutableList<Class<?>> ofyTestEntities;
|
private ImmutableList<Class<?>> ofyTestEntities;
|
||||||
|
private ImmutableList<Class<?>> jpaTestEntities;
|
||||||
|
|
||||||
/** Builder for {@link AppEngineRule}. */
|
/** Builder for {@link AppEngineRule}. */
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
private AppEngineRule rule = new AppEngineRule();
|
private AppEngineRule rule = new AppEngineRule();
|
||||||
private ImmutableList.Builder<Class<?>> ofyTestEntities = new ImmutableList.Builder();
|
private ImmutableList.Builder<Class<?>> ofyTestEntities = new ImmutableList.Builder<>();
|
||||||
|
private ImmutableList.Builder<Class<?>> jpaTestEntities = new ImmutableList.Builder<>();
|
||||||
|
|
||||||
/** Turn on the Datastore service and the Cloud SQL service. */
|
/** Turn on the Datastore service and the Cloud SQL service. */
|
||||||
public Builder withDatastoreAndCloudSql() {
|
public Builder withDatastoreAndCloudSql() {
|
||||||
|
@ -205,11 +213,24 @@ public final class AppEngineRule extends ExternalResource
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder withJpaUnitTestEntities(Class<?>... entities) {
|
||||||
|
jpaTestEntities.add(entities);
|
||||||
|
rule.withJpaUnitTest = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public AppEngineRule build() {
|
public AppEngineRule build() {
|
||||||
checkState(
|
checkState(
|
||||||
!rule.enableJpaEntityCoverageCheck || rule.withDatastoreAndCloudSql,
|
!rule.enableJpaEntityCoverageCheck || rule.withDatastoreAndCloudSql,
|
||||||
"withJpaEntityCoverageCheck enabled without Cloud SQL");
|
"withJpaEntityCoverageCheck enabled without Cloud SQL");
|
||||||
|
checkState(
|
||||||
|
!rule.withJpaUnitTest || rule.withDatastoreAndCloudSql,
|
||||||
|
"withJpaUnitTestEntities enabled without Cloud SQL");
|
||||||
|
checkState(
|
||||||
|
!rule.withJpaUnitTest || !rule.enableJpaEntityCoverageCheck,
|
||||||
|
"withJpaUnitTestEntities cannot be set when enableJpaEntityCoverageCheck");
|
||||||
rule.ofyTestEntities = this.ofyTestEntities.build();
|
rule.ofyTestEntities = this.ofyTestEntities.build();
|
||||||
|
rule.jpaTestEntities = this.jpaTestEntities.build();
|
||||||
return rule;
|
return rule;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -328,11 +349,18 @@ public final class AppEngineRule extends ExternalResource
|
||||||
if (enableJpaEntityCoverageCheck) {
|
if (enableJpaEntityCoverageCheck) {
|
||||||
jpaIntegrationWithCoverageExtension = builder.buildIntegrationWithCoverageExtension();
|
jpaIntegrationWithCoverageExtension = builder.buildIntegrationWithCoverageExtension();
|
||||||
jpaIntegrationWithCoverageExtension.beforeEach(context);
|
jpaIntegrationWithCoverageExtension.beforeEach(context);
|
||||||
|
} else if (withJpaUnitTest) {
|
||||||
|
jpaUnitTestRule =
|
||||||
|
builder
|
||||||
|
.withEntityClass(jpaTestEntities.toArray(new Class[jpaTestEntities.size()]))
|
||||||
|
.buildUnitTestRule();
|
||||||
|
jpaUnitTestRule.before();
|
||||||
} else {
|
} else {
|
||||||
jpaIntegrationTestRule = builder.buildIntegrationTestRule();
|
jpaIntegrationTestRule = builder.buildIntegrationTestRule();
|
||||||
jpaIntegrationTestRule.before();
|
jpaIntegrationTestRule.before();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
injectTmForDualDatabaseTest(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Called after each test method. JUnit 5 only. */
|
/** Called after each test method. JUnit 5 only. */
|
||||||
|
@ -341,11 +369,14 @@ public final class AppEngineRule extends ExternalResource
|
||||||
if (withDatastoreAndCloudSql) {
|
if (withDatastoreAndCloudSql) {
|
||||||
if (enableJpaEntityCoverageCheck) {
|
if (enableJpaEntityCoverageCheck) {
|
||||||
jpaIntegrationWithCoverageExtension.afterEach(context);
|
jpaIntegrationWithCoverageExtension.afterEach(context);
|
||||||
|
} else if (withJpaUnitTest) {
|
||||||
|
jpaUnitTestRule.after();
|
||||||
} else {
|
} else {
|
||||||
jpaIntegrationTestRule.after();
|
jpaIntegrationTestRule.after();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
after();
|
after();
|
||||||
|
restoreTmAfterDualDatabaseTest(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -560,4 +591,8 @@ public final class AppEngineRule extends ExternalResource
|
||||||
makeRegistrarContact2(),
|
makeRegistrarContact2(),
|
||||||
makeRegistrarContact3()));
|
makeRegistrarContact3()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isWithDatastoreAndCloudSql() {
|
||||||
|
return withDatastoreAndCloudSql;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// 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 google.registry.testing;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.TYPE;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
|
/** Annotation to add {@link DualDatabaseTestInvocationContextProvider} for the annotated test. */
|
||||||
|
@Target({TYPE})
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@ExtendWith(DualDatabaseTestInvocationContextProvider.class)
|
||||||
|
public @interface DualDatabaseTest {}
|
|
@ -0,0 +1,125 @@
|
||||||
|
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// 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 google.registry.testing;
|
||||||
|
|
||||||
|
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||||
|
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import google.registry.persistence.transaction.TransactionManager;
|
||||||
|
import google.registry.persistence.transaction.TransactionManagerFactory;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import org.junit.jupiter.api.TestTemplate;
|
||||||
|
import org.junit.jupiter.api.extension.Extension;
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
|
||||||
|
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
|
||||||
|
import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
|
||||||
|
import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link TestTemplateInvocationContextProvider} to execute tests against
|
||||||
|
* different database. The test annotated with {@link TestTemplate} will be executed twice against
|
||||||
|
* Datastore and PostgresQL respectively.
|
||||||
|
*/
|
||||||
|
class DualDatabaseTestInvocationContextProvider implements TestTemplateInvocationContextProvider {
|
||||||
|
private static final Namespace NAMESPACE =
|
||||||
|
Namespace.create(DualDatabaseTestInvocationContextProvider.class);
|
||||||
|
private static final String INJECTED_TM_SUPPLIER_KEY = "injected_tm_supplier_key";
|
||||||
|
private static final String ORIGINAL_TM_KEY = "original_tm_key";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsTestTemplate(ExtensionContext context) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(
|
||||||
|
ExtensionContext context) {
|
||||||
|
return Stream.of(
|
||||||
|
createInvocationContext("Test Datastore", TransactionManagerFactory::ofyTm),
|
||||||
|
createInvocationContext("Test PostgreSQL", TransactionManagerFactory::jpaTm));
|
||||||
|
}
|
||||||
|
|
||||||
|
private TestTemplateInvocationContext createInvocationContext(
|
||||||
|
String name, Supplier<? extends TransactionManager> tmSupplier) {
|
||||||
|
return new TestTemplateInvocationContext() {
|
||||||
|
@Override
|
||||||
|
public String getDisplayName(int invocationIndex) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Extension> getAdditionalExtensions() {
|
||||||
|
return ImmutableList.of(new DatabaseSwitchInvocationContext(tmSupplier));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DatabaseSwitchInvocationContext implements TestInstancePostProcessor {
|
||||||
|
|
||||||
|
private Supplier<? extends TransactionManager> tmSupplier;
|
||||||
|
|
||||||
|
private DatabaseSwitchInvocationContext(Supplier<? extends TransactionManager> tmSupplier) {
|
||||||
|
this.tmSupplier = tmSupplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postProcessTestInstance(Object testInstance, ExtensionContext context)
|
||||||
|
throws Exception {
|
||||||
|
List<Field> appEngineRuleFields =
|
||||||
|
Stream.of(testInstance.getClass().getFields())
|
||||||
|
.filter(field -> field.getType().isAssignableFrom(AppEngineRule.class))
|
||||||
|
.collect(toImmutableList());
|
||||||
|
if (appEngineRuleFields.size() != 1) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"@DualDatabaseTest test must have only 1 AppEngineRule field");
|
||||||
|
}
|
||||||
|
appEngineRuleFields.get(0).setAccessible(true);
|
||||||
|
AppEngineRule appEngineRule = (AppEngineRule) appEngineRuleFields.get(0).get(testInstance);
|
||||||
|
if (!appEngineRule.isWithDatastoreAndCloudSql()) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"AppEngineRule in @DualDatabaseTest test must set withDatastoreAndCloudSql()");
|
||||||
|
}
|
||||||
|
context.getStore(NAMESPACE).put(INJECTED_TM_SUPPLIER_KEY, tmSupplier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void injectTmForDualDatabaseTest(ExtensionContext context) {
|
||||||
|
if (isDualDatabaseTest(context)) {
|
||||||
|
context.getStore(NAMESPACE).put(ORIGINAL_TM_KEY, tm());
|
||||||
|
Supplier<? extends TransactionManager> tmSupplier =
|
||||||
|
(Supplier<? extends TransactionManager>)
|
||||||
|
context.getStore(NAMESPACE).get(INJECTED_TM_SUPPLIER_KEY);
|
||||||
|
TransactionManagerFactory.setTm(tmSupplier.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void restoreTmAfterDualDatabaseTest(ExtensionContext context) {
|
||||||
|
if (isDualDatabaseTest(context)) {
|
||||||
|
TransactionManager original =
|
||||||
|
(TransactionManager) context.getStore(NAMESPACE).get(ORIGINAL_TM_KEY);
|
||||||
|
TransactionManagerFactory.setTm(original);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isDualDatabaseTest(ExtensionContext context) {
|
||||||
|
Object testInstance = context.getTestInstance().orElseThrow(RuntimeException::new);
|
||||||
|
return testInstance.getClass().isAnnotationPresent(DualDatabaseTest.class);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue