diff --git a/core/src/test/java/google/registry/model/registry/label/PremiumListTest.java b/core/src/test/java/google/registry/model/registry/label/PremiumListTest.java index c0e24098e..047bc29c0 100644 --- a/core/src/test/java/google/registry/model/registry/label/PremiumListTest.java +++ b/core/src/test/java/google/registry/model/registry/label/PremiumListTest.java @@ -29,16 +29,14 @@ import google.registry.model.registry.label.PremiumList.PremiumListEntry; import google.registry.model.registry.label.PremiumList.PremiumListRevision; import google.registry.testing.AppEngineRule; import org.joda.money.Money; -import org.junit.Rule; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport; +import org.junit.jupiter.api.extension.RegisterExtension; /** Unit tests for {@link PremiumList}. */ -@EnableRuleMigrationSupport public class PremiumListTest { - @Rule + @RegisterExtension public final AppEngineRule appEngine = AppEngineRule.builder().withDatastoreAndCloudSql().build(); @BeforeEach diff --git a/core/src/test/java/google/registry/persistence/transaction/JpaEntityCoverage.java b/core/src/test/java/google/registry/persistence/transaction/JpaEntityCoverage.java index ba28ca2d7..b0ad2044b 100644 --- a/core/src/test/java/google/registry/persistence/transaction/JpaEntityCoverage.java +++ b/core/src/test/java/google/registry/persistence/transaction/JpaEntityCoverage.java @@ -27,6 +27,7 @@ import java.sql.SQLException; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; import javax.persistence.Entity; import org.junit.rules.ExternalResource; @@ -51,15 +52,15 @@ public class JpaEntityCoverage extends ExternalResource { private static final Map testsJpaEntities = Maps.newHashMap(); // Provides class name of the test being executed. - private final TestCaseWatcher watcher; + private final Supplier currTestClassNameSupplier; - public JpaEntityCoverage(TestCaseWatcher watcher) { - this.watcher = watcher; + public JpaEntityCoverage(Supplier currTestClassNameSupplier) { + this.currTestClassNameSupplier = currTestClassNameSupplier; } @Override public void before() { - testsJpaEntities.putIfAbsent(watcher.getTestClass(), false); + testsJpaEntities.putIfAbsent(currTestClassNameSupplier.get(), false); } @Override @@ -69,7 +70,7 @@ public class JpaEntityCoverage extends ExternalResource { .forEach( entity -> { allCoveredJpaEntities.add(entity); - testsJpaEntities.put(watcher.getTestClass(), true); + testsJpaEntities.put(currTestClassNameSupplier.get(), true); }); } diff --git a/core/src/test/java/google/registry/persistence/transaction/JpaTestRules.java b/core/src/test/java/google/registry/persistence/transaction/JpaTestRules.java index c4ecd1ea1..fc392f302 100644 --- a/core/src/test/java/google/registry/persistence/transaction/JpaTestRules.java +++ b/core/src/test/java/google/registry/persistence/transaction/JpaTestRules.java @@ -29,6 +29,9 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import org.joda.time.DateTime; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; import org.junit.runner.Description; @@ -88,7 +91,7 @@ public class JpaTestRules { this.ruleChain = RuleChain.outerRule(watcher) .around(integrationTestRule) - .around(new JpaEntityCoverage(watcher)); + .around(new JpaEntityCoverage(watcher::getTestClass)); } @Override @@ -97,6 +100,40 @@ public class JpaTestRules { } } + /** + * JUnit extension for member classes of {@link + * google.registry.schema.integration.SqlIntegrationTestSuite}. In addition to providing a + * database through {@link JpaIntegrationTestRule}, it also keeps track of the test coverage of + * the declared JPA entities (in persistence.xml). Per-class statistics are stored in static + * variables. The SqlIntegrationTestSuite inspects the cumulative statistics after all test + * classes have run. + */ + public static final class JpaIntegrationWithCoverageExtension + implements BeforeEachCallback, AfterEachCallback { + private String currentTestClassName = null; + private final JpaEntityCoverage jpaEntityCoverage = + new JpaEntityCoverage(() -> this.currentTestClassName); + private final JpaIntegrationTestRule integrationTestRule; + + JpaIntegrationWithCoverageExtension(JpaIntegrationTestRule integrationTestRule) { + this.integrationTestRule = integrationTestRule; + } + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + this.currentTestClassName = context.getRequiredTestClass().getName(); + integrationTestRule.before(); + jpaEntityCoverage.before(); + } + + @Override + public void afterEach(ExtensionContext context) throws Exception { + jpaEntityCoverage.after(); + integrationTestRule.after(); + this.currentTestClassName = null; + } + } + /** Builder of test rules that provide {@link JpaTransactionManager}. */ public static class Builder { private String initScript; @@ -149,6 +186,15 @@ public class JpaTestRules { return new JpaIntegrationWithCoverageRule(buildIntegrationTestRule()); } + /** + * JUnit extension that adapts {@link JpaIntegrationTestRule} for JUnit 5 and also checks test + * coverage of JPA entity classes. + */ + public JpaIntegrationWithCoverageExtension buildIntegrationWithCoverageExtension() { + checkState(initScript == null, "Integration tests do not accept initScript"); + return new JpaIntegrationWithCoverageExtension(buildIntegrationTestRule()); + } + /** Builds a {@link JpaUnitTestRule} instance. */ public JpaUnitTestRule buildUnitTestRule() { checkState( diff --git a/core/src/test/java/google/registry/testing/AppEngineRule.java b/core/src/test/java/google/registry/testing/AppEngineRule.java index a5fc89d42..622911bc8 100644 --- a/core/src/test/java/google/registry/testing/AppEngineRule.java +++ b/core/src/test/java/google/registry/testing/AppEngineRule.java @@ -14,6 +14,7 @@ package google.registry.testing; +import static com.google.common.base.Preconditions.checkState; import static com.google.common.truth.Truth.assertWithMessage; import static google.registry.testing.DatastoreHelper.persistSimpleResources; import static google.registry.util.ResourceUtils.readResourceUtf8; @@ -40,6 +41,7 @@ import google.registry.model.registrar.Registrar.State; import google.registry.model.registrar.RegistrarAddress; import google.registry.model.registrar.RegistrarContact; import google.registry.persistence.transaction.JpaTestRules; +import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension; import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageRule; import google.registry.util.Clock; import java.io.ByteArrayInputStream; @@ -53,6 +55,9 @@ import javax.annotation.Nullable; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.rules.ExternalResource; import org.junit.rules.TemporaryFolder; import org.junit.runner.Description; @@ -66,9 +71,15 @@ import org.junit.runners.model.Statement; * *

This rule also resets global Objectify for the current thread. * + *

This class works with both JUnit 4 and JUnit 5. With JUnit 4, the test runner calls {@link + * #apply(Statement, Description)}, which in turns calls {@link #before()} on entry and {@link + * #after()} on exit. With JUnit 5, the test runner calls {@link #beforeEach(ExtensionContext)} and + * {@link #afterEach(ExtensionContext)}. + * * @see org.junit.rules.ExternalResource */ -public final class AppEngineRule extends ExternalResource { +public final class AppEngineRule extends ExternalResource + implements BeforeEachCallback, AfterEachCallback { public static final String NEW_REGISTRAR_GAE_USER_ID = "666"; public static final String THE_REGISTRAR_GAE_USER_ID = "31337"; @@ -93,6 +104,8 @@ public final class AppEngineRule extends ExternalResource { /** A rule-within-a-rule to provide a temporary folder for AppEngineRule's internal temp files. */ TemporaryFolder temporaryFolder = new TemporaryFolder(); + // Sets up a SQL database when running on JUnit 5. + JpaIntegrationWithCoverageExtension jpaIntegrationWithCoverageExtension = null; private boolean withDatastoreAndCloudSql; private boolean withLocalModules; @@ -257,14 +270,38 @@ public final class AppEngineRule extends ExternalResource { .build(); } + /** Called before every test method. JUnit 5 only. */ + @Override + public void beforeEach(ExtensionContext context) throws Exception { + before(); + if (withDatastoreAndCloudSql) { + JpaTestRules.Builder builder = new JpaTestRules.Builder(); + if (clock != null) { + builder.withClock(clock); + } + jpaIntegrationWithCoverageExtension = builder.buildIntegrationWithCoverageExtension(); + jpaIntegrationWithCoverageExtension.beforeEach(context); + } + } + + /** Called after each test method. JUnit 5 only. */ + @Override + public void afterEach(ExtensionContext context) throws Exception { + if (withDatastoreAndCloudSql) { + checkState( + jpaIntegrationWithCoverageExtension != null, "Null jpaIntegrationWithCoverageExtension"); + jpaIntegrationWithCoverageExtension.afterEach(context); + } + after(); + } + /** * Hack to make sure AppEngineRule is always wrapped in a {@link JpaIntegrationWithCoverageRule}. + * JUnit 4 only. */ // Note: Even with @EnableRuleMigrationSupport, JUnit5 runner does not call this method. - // Note 2: Do not migrate any members of SqlIntegrationTestSuite to JUnit5 before - // calls to JpaIntegrationWithCoverageRule can be made elsewhere. - // TODO(weiminyu): make JpaIntegrationWithCoverageRule implement ExternaResource and invoke it in - // before() and after(), then drop this method. + // Note 2: Do not migrate members of SqlIntegrationTestSuite to JUnit5 individually. + // TODO(weiminyu): migrate SqlIntegrationTestSuite in one go. @Override public Statement apply(Statement base, Description description) { Statement statement = base;