Allow schema-loading from arbitrary url in tests (#374)

* Allow schema-loading from arbitrary url in tests

Server/Schema compatibility tests must be able to load different versions
of the SQL schema. This change allows test runners to override the
schema location using a system property.

Note: due to dependency-locking, we cannot manipulate the dependencies
closure in the build script to load different schema jars. The jars
must not be on the classpath.
This commit is contained in:
Weimin Yu 2019-11-20 12:22:48 -05:00 committed by GitHub
parent dbf1ff2096
commit bef21f80df
3 changed files with 111 additions and 1 deletions

View file

@ -660,6 +660,8 @@ task outcastTest(type: FilteringTest) {
// Dedicated test suite for schema-dependent tests. // Dedicated test suite for schema-dependent tests.
task sqlIntegrationTest(type: FilteringTest) { task sqlIntegrationTest(type: FilteringTest) {
systemProperties project.getProperties().subMap('sql_schema_resource_root')
excludeTestCases = false excludeTestCases = false
tests = ['google/registry/schema/integration/SqlIntegrationTestSuite.*'] tests = ['google/registry/schema/integration/SqlIntegrationTestSuite.*']
} }

View file

@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.joda.time.DateTimeZone.UTC; import static org.joda.time.DateTimeZone.UTC;
import static org.testcontainers.containers.PostgreSQLContainer.POSTGRESQL_PORT; import static org.testcontainers.containers.PostgreSQLContainer.POSTGRESQL_PORT;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
@ -32,6 +33,7 @@ import google.registry.testing.FakeClock;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.net.URL;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.sql.Connection; import java.sql.Connection;
@ -43,6 +45,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Properties; import java.util.Properties;
import javax.persistence.EntityManagerFactory; import javax.persistence.EntityManagerFactory;
import org.hibernate.cfg.Environment; import org.hibernate.cfg.Environment;
@ -60,6 +63,19 @@ import org.testcontainers.containers.PostgreSQLContainer;
* TransactionManagerFactory} with the {@link JpaTransactionManagerImpl} generated by the rule * TransactionManagerFactory} with the {@link JpaTransactionManagerImpl} generated by the rule
* itself, so that all SQL queries will be sent to the database instance created by {@link * itself, so that all SQL queries will be sent to the database instance created by {@link
* PostgreSQLContainer} to achieve test purpose. * PostgreSQLContainer} to achieve test purpose.
*
* <p>The location of the Nomulus golden schema may be overridden with the {@code
* "sql_schema_resource_root} system property. This feature is needed by the server/schema
* compatibility tests, which need to use different versions of the schema off the classpath.
*
* <p>If defined, the value of the {@code "sql_schema_resource_root} should be an URL string that
* points to the jar or resource root directory. Here are some examples:
*
* <ul>
* <li>Absolute path to local directory: [file://]/path/to/resources
* <li>Absolute path to local jar: [file://]/path/to/schema.jar
* <li>URL to remote jar: https://host/path/to/schema.jar
* </ul>
*/ */
public class JpaTransactionManagerRule extends ExternalResource { public class JpaTransactionManagerRule extends ExternalResource {
private static final String GOLDEN_SCHEMA_SQL_PATH = "sql/schema/nomulus.golden.sql"; private static final String GOLDEN_SCHEMA_SQL_PATH = "sql/schema/nomulus.golden.sql";
@ -68,6 +84,10 @@ public class JpaTransactionManagerRule extends ExternalResource {
private static final String MANAGEMENT_DB_NAME = "management"; private static final String MANAGEMENT_DB_NAME = "management";
private static final String POSTGRES_DB_NAME = "postgres"; private static final String POSTGRES_DB_NAME = "postgres";
// Name of the optional property that specifies the root path of the golden schema.
@VisibleForTesting
static final String GOLDEN_SCHEMA_RESOURCE_ROOT_PROP = "sql_schema_resource_root";
private final DateTime now = DateTime.now(UTC); private final DateTime now = DateTime.now(UTC);
private final FakeClock clock = new FakeClock(now); private final FakeClock clock = new FakeClock(now);
private final String initScriptPath; private final String initScriptPath;
@ -105,7 +125,7 @@ public class JpaTransactionManagerRule extends ExternalResource {
@Override @Override
public void before() throws Exception { public void before() throws Exception {
executeSql(MANAGEMENT_DB_NAME, readSqlInClassPath(DB_CLEANUP_SQL_PATH)); executeSql(MANAGEMENT_DB_NAME, readSqlInClassPath(DB_CLEANUP_SQL_PATH));
executeSql(POSTGRES_DB_NAME, readSqlInClassPath(initScriptPath)); executeSql(POSTGRES_DB_NAME, readInitialScript());
if (!extraEntityClasses.isEmpty()) { if (!extraEntityClasses.isEmpty()) {
File tempSqlFile = File.createTempFile("tempSqlFile", ".sql"); File tempSqlFile = File.createTempFile("tempSqlFile", ".sql");
tempSqlFile.deleteOnExit(); tempSqlFile.deleteOnExit();
@ -176,6 +196,38 @@ public class JpaTransactionManagerRule extends ExternalResource {
} }
} }
@VisibleForTesting
Optional<String> getInitScriptUrlOverride() {
String schemaRootPath = System.getProperty(GOLDEN_SCHEMA_RESOURCE_ROOT_PROP, "").trim();
if (schemaRootPath.isEmpty() || !initScriptPath.equals(GOLDEN_SCHEMA_SQL_PATH)) {
return Optional.empty();
}
if (schemaRootPath.startsWith("/")) {
schemaRootPath = "file://" + schemaRootPath;
}
if (schemaRootPath.endsWith(".jar") && !schemaRootPath.startsWith("jar:")) {
schemaRootPath = "jar:" + schemaRootPath;
}
if (schemaRootPath.endsWith(".jar")) {
schemaRootPath += "!/" + GOLDEN_SCHEMA_SQL_PATH;
} else {
schemaRootPath += "/" + GOLDEN_SCHEMA_SQL_PATH;
}
return Optional.of(schemaRootPath);
}
private String readInitialScript() {
Optional<String> schemaUrlOverride = getInitScriptUrlOverride();
if (!schemaUrlOverride.isPresent()) {
return readSqlInClassPath(initScriptPath);
}
try {
return Resources.toString(new URL(schemaUrlOverride.get()), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private void executeSql(String dbName, String sqlScript) { private void executeSql(String dbName, String sqlScript) {
try (Connection conn = createConnection(dbName); try (Connection conn = createConnection(dbName);
Statement statement = conn.createStatement()) { Statement statement = conn.createStatement()) {

View file

@ -15,10 +15,13 @@
package google.registry.model.transaction; package google.registry.model.transaction;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.model.transaction.JpaTransactionManagerRule.GOLDEN_SCHEMA_RESOURCE_ROOT_PROP;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm; import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.testing.JUnitBackports.assertThrows; import static google.registry.testing.JUnitBackports.assertThrows;
import google.registry.model.ImmutableObject; import google.registry.model.ImmutableObject;
import google.registry.testing.SystemPropertyRule;
import java.util.List; import java.util.List;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Id; import javax.persistence.Id;
@ -38,6 +41,8 @@ public class JpaTransactionManagerRuleTest {
.withEntityClass(TestEntity.class) .withEntityClass(TestEntity.class)
.build(); .build();
@Rule public final SystemPropertyRule systemPropertyRule = new SystemPropertyRule();
@Test @Test
public void verifiesRuleWorks() { public void verifiesRuleWorks() {
assertThrows( assertThrows(
@ -73,6 +78,57 @@ public class JpaTransactionManagerRuleTest {
assertThat(retrieved).isEqualTo(original); assertThat(retrieved).isEqualTo(original);
} }
@Test
public void testInitScriptUrl_noOverride() {
assertThat(jpaTmRule.getInitScriptUrlOverride()).isEmpty();
}
@Test
public void testInitScriptUrl_localDir_noProtocol() {
systemPropertyRule.setProperty(GOLDEN_SCHEMA_RESOURCE_ROOT_PROP, "/path/to/resources");
assertThat(jpaTmRule.getInitScriptUrlOverride())
.hasValue("file:///path/to/resources/sql/schema/nomulus.golden.sql");
}
@Test
public void testInitScriptUrl_localDir_hasProtocol() {
systemPropertyRule.setProperty(GOLDEN_SCHEMA_RESOURCE_ROOT_PROP, "file:///path/to/resources");
assertThat(jpaTmRule.getInitScriptUrlOverride())
.hasValue("file:///path/to/resources/sql/schema/nomulus.golden.sql");
}
@Test
public void testInitScriptUrl_localJar_noProtocol() {
systemPropertyRule.setProperty(
GOLDEN_SCHEMA_RESOURCE_ROOT_PROP, "/path/to/resources/schema.jar");
assertThat(jpaTmRule.getInitScriptUrlOverride())
.hasValue("jar:file:///path/to/resources/schema.jar!/sql/schema/nomulus.golden.sql");
}
@Test
public void testInitScriptUrl_localJar_hasPartialProtocol() {
systemPropertyRule.setProperty(
GOLDEN_SCHEMA_RESOURCE_ROOT_PROP, "file:///path/to/resources/schema.jar");
assertThat(jpaTmRule.getInitScriptUrlOverride())
.hasValue("jar:file:///path/to/resources/schema.jar!/sql/schema/nomulus.golden.sql");
}
@Test
public void testInitScriptUrl_localJar_hasFullProtocol() {
systemPropertyRule.setProperty(
GOLDEN_SCHEMA_RESOURCE_ROOT_PROP, "jar:file:///path/to/resources/schema.jar");
assertThat(jpaTmRule.getInitScriptUrlOverride())
.hasValue("jar:file:///path/to/resources/schema.jar!/sql/schema/nomulus.golden.sql");
}
@Test
public void testInitScriptUrl_remoteJar() {
systemPropertyRule.setProperty(
GOLDEN_SCHEMA_RESOURCE_ROOT_PROP, "http://host/path/to/resources/schema.jar");
assertThat(jpaTmRule.getInitScriptUrlOverride())
.hasValue("jar:http://host/path/to/resources/schema.jar!/sql/schema/nomulus.golden.sql");
}
@Entity(name = "TestEntity") // Specify name to avoid nested class naming issues. @Entity(name = "TestEntity") // Specify name to avoid nested class naming issues.
static class TestEntity extends ImmutableObject { static class TestEntity extends ImmutableObject {
@Id String key; @Id String key;