Refactor test framework infrastructure classes.

Resolves gh-1115.
This commit is contained in:
John Blum
2021-04-09 16:01:20 -07:00
parent 4492e1a06a
commit 8fb8041445
4 changed files with 110 additions and 100 deletions

View File

@@ -24,6 +24,7 @@ import java.util.List;
import java.util.Properties;
import org.springframework.core.convert.converter.Converter;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
/**
@@ -31,79 +32,75 @@ import org.springframework.util.Assert;
* the build and can be override using system properties.
*
* @author Mark Paluch
* @author John Blum
*/
@SuppressWarnings("serial")
@SuppressWarnings("unused")
public class CassandraConnectionProperties extends Properties {
private final static List<WeakReference<CassandraConnectionProperties>> instances = new ArrayList<>();
private String resourceName;
private final String resourceName;
/**
* Create a new {@link CassandraConnectionProperties} using properties from
* {@code config/cassandra-connection.properties}.
* Construct a new instance of {@link CassandraConnectionProperties} using properties
* from {@code config/cassandra-connection.properties}.
*/
public CassandraConnectionProperties() {
this("/config/cassandra-connection.properties");
}
private CassandraConnectionProperties(@NonNull String resourceName) {
this.resourceName = resourceName;
loadProperties();
instances.add(new WeakReference<>(this));
}
public void update() {
try {
// Caution: Rewriting properties during initialization.
File file = new File(getClass().getResource(resourceName).toURI());
File file = new File(getClass().getResource(this.resourceName).toURI());
try (FileOutputStream fos = new FileOutputStream(file)) {
store(fos, "");
try (FileOutputStream out = new FileOutputStream(file)) {
store(out, "");
}
reload();
} catch (Exception e) {
e.printStackTrace();
throw new IllegalStateException(e);
}
catch (Exception cause) {
cause.printStackTrace();
throw new IllegalStateException(cause);
}
}
private static void reload() {
for (WeakReference<CassandraConnectionProperties> ref : instances) {
CassandraConnectionProperties properties = ref.get();
if (properties != null) {
properties.loadProperties();
}
}
}
private CassandraConnectionProperties(String resourceName) {
this.resourceName = resourceName;
loadProperties();
instances.add(new WeakReference<>(this));
}
private void loadProperties() {
loadProperties(this.resourceName);
}
private void loadProperties(String resourceName) {
InputStream in = null;
try {
in = getClass().getResourceAsStream(resourceName);
try (InputStream in = getClass().getResourceAsStream(resourceName)){
if (in != null) {
load(in);
}
} catch (Exception cause) {
}
catch (Exception cause) {
throw new RuntimeException(cause);
} finally {
if (in != null) {
try {
in.close();
} catch (Exception ignore) {}
}
}
}
@@ -111,6 +108,7 @@ public class CassandraConnectionProperties extends Properties {
public String getProperty(String key) {
String value = super.getProperty(key);
if (value == null) {
value = System.getProperty(key);
}
@@ -153,13 +151,6 @@ public class CassandraConnectionProperties extends Properties {
return getInt("build.cassandra.rpc_port");
}
/**
* @return the Cassandra Storage port
*/
public int getCassandraStoragePort() {
return getInt("build.cassandra.storage_port");
}
/**
* @return the Cassandra SSL Storage port
*/
@@ -167,6 +158,13 @@ public class CassandraConnectionProperties extends Properties {
return getInt("build.cassandra.ssl_storage_port");
}
/**
* @return the Cassandra Storage port
*/
public int getCassandraStoragePort() {
return getInt("build.cassandra.storage_port");
}
/**
* @return the Cassandra type (Embedded or External)
*/
@@ -174,12 +172,9 @@ public class CassandraConnectionProperties extends Properties {
String cassandraType = getProperty("build.cassandra.mode");
if (CassandraType.TESTCONTAINERS.name().equalsIgnoreCase(cassandraType)) {
return CassandraType.TESTCONTAINERS;
}
return CassandraType.EXTERNAL.name().equalsIgnoreCase(cassandraType) ? CassandraType.EXTERNAL
: CassandraType.EMBEDDED;
return CassandraType.TESTCONTAINERS.name().equalsIgnoreCase(cassandraType) ? CassandraType.TESTCONTAINERS
: CassandraType.EXTERNAL.name().equalsIgnoreCase(cassandraType) ? CassandraType.EXTERNAL
: CassandraType.EMBEDDED;
}
/**
@@ -229,6 +224,6 @@ public class CassandraConnectionProperties extends Properties {
}
public enum CassandraType {
EMBEDDED, EXTERNAL, TESTCONTAINERS;
EMBEDDED, EXTERNAL, TESTCONTAINERS
}
}

View File

@@ -22,15 +22,16 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.testcontainers.containers.CassandraContainer;
import org.springframework.data.cassandra.core.cql.SessionCallback;
import org.springframework.data.cassandra.support.CassandraConnectionProperties;
import org.springframework.data.cassandra.support.CqlDataSet;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import org.springframework.util.SocketUtils;
import org.springframework.util.StringUtils;
import org.testcontainers.containers.CassandraContainer;
import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.CqlSessionBuilder;
@@ -48,22 +49,25 @@ import com.datastax.oss.driver.api.core.metadata.Node;
* @author Mark Paluch
* @author John Blum
* @author Tomasz Lelek
* @see org.springframework.data.cassandra.support.CassandraConnectionProperties
* @see com.datastax.oss.driver.api.core.CqlSessionBuilder
* @see com.datastax.oss.driver.api.core.CqlSession
* @since 1.5
* @see CassandraConnectionProperties
*/
class CassandraDelegate {
private static ResourceHolder resourceHolder;
private static CassandraContainer<?> container;
private static ResourceHolder resourceHolder;
private final long startupTimeout;
@SuppressWarnings("all") private final CassandraConnectionProperties properties = new CassandraConnectionProperties();
private CqlSession system;
private final CassandraConnectionProperties properties = new CassandraConnectionProperties();
private CqlSession session;
private CqlSession system;
private CqlSessionBuilder sessionBuilder;
private Integer cassandraPort;
@@ -72,16 +76,16 @@ class CassandraDelegate {
private final Map<SessionCallback<?>, InvocationMode> invocationModeMap = new HashMap<>();
private CqlSessionBuilder sessionBuilder;
private final String configurationFilename;
/**
* Create a new {@link CassandraDelegate} and allows the use of a config file.
* Create a new {@link CassandraDelegate} allowing the use of a config file.
*
* @param yamlConfigurationResource name of the configuration resource, must not be {@literal null} and not empty
* @param yamlConfigurationResource {@link String name} of the configuration resource;
* must not be {@literal null} or {@literal empty}.
* @see #CassandraDelegate(String, long)
*/
public CassandraDelegate(String yamlConfigurationResource) {
public CassandraDelegate(@NonNull String yamlConfigurationResource) {
this(yamlConfigurationResource, EmbeddedCassandraServerHelper.DEFAULT_STARTUP_TIMEOUT_MS);
}
@@ -232,6 +236,7 @@ class CassandraDelegate {
private void startCassandraIfNeeded() throws Exception {
if (isStartNeeded()) {
configureRemoteJmxPort();
if (isEmbedded()) {
@@ -263,12 +268,9 @@ class CassandraDelegate {
private void runTestcontainerCassandra() {
if (container == null) {
String cassandra_version = System.getenv("CASSANDRA_VERSION");
if (StringUtils.hasText(cassandra_version)) {
container = new CassandraContainer<>("cassandra:" + cassandra_version);
} else {
container = new CassandraContainer<>();
}
container = getCassandraDockerImageName().map(CassandraContainer::new)
.orElseGet(CassandraContainer::new);
container.start();
@@ -278,6 +280,13 @@ class CassandraDelegate {
}
}
private Optional<String> getCassandraDockerImageName() {
return Optional.ofNullable(System.getenv("CASSANDRA_VERSION"))
.filter(StringUtils::hasText)
.map(cassandraVersion -> String.format("cassandra:%s", cassandraVersion));
}
private synchronized void initializeConnection() {
this.cassandraPort = resolvePort();
@@ -420,7 +429,8 @@ class CassandraDelegate {
/**
* Create a {@link CqlSession} object.
*
* @return
* @return a new {@link CqlSession}.
* @see com.datastax.oss.driver.api.core.CqlSession
*/
CqlSession createSession() {
return sessionBuilder.build();

View File

@@ -19,7 +19,6 @@ import java.lang.reflect.Field;
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
@@ -28,6 +27,8 @@ import org.junit.platform.commons.util.AnnotationUtils;
import org.junit.platform.commons.util.ReflectionUtils;
import org.junit.platform.commons.util.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.data.cassandra.support.RandomKeyspaceName;
import org.springframework.util.Assert;
@@ -62,16 +63,6 @@ public class CassandraExtension implements BeforeAllCallback, AfterAllCallback,
private final CassandraDelegate delegate = new CassandraDelegate("embedded-cassandra.yaml");
/**
* Retrieve the system {@link CqlSession} that is associated with the current {@link Thread}.
*
* @return the system {@link CqlSession}.
* @throws IllegalStateException if no system session was associated with the current {@link Thread}.
*/
public static CqlSession currentSystemSession() {
return getResources().systemSession;
}
/**
* Retrieve the keyspace {@link CqlSession} that is associated with the current {@link Thread}.
*
@@ -82,28 +73,36 @@ public class CassandraExtension implements BeforeAllCallback, AfterAllCallback,
CqlSession cqlSession = getResources().cqlSession;
if (cqlSession == null) {
throw new IllegalStateException(
"No keyspace-bound session. Make sure to annotate your test class with @TestKeyspaceName");
}
Assert.state(cqlSession != null,
"No keyspace-bound session. Make sure to annotate your test class with @TestKeyspaceName");
return cqlSession;
}
/**
* Retrieve the system {@link CqlSession} that is associated with the current {@link Thread}.
*
* @return the system {@link CqlSession}.
* @throws IllegalStateException if no system session was associated with the current {@link Thread}.
*/
public static CqlSession currentSystemSession() {
return getResources().systemSession;
}
public static Resources getResources() {
Resources resources = TEST_RESOURCES.get();
if (resources == null) {
throw new IllegalStateException(
"No test in progress. Did you annotate your test class with @ExtendWith(CassandraExtension.class)?");
}
Assert.state(resources != null,
"No test in progress. Did you annotate your test class with @ExtendWith(CassandraExtension.class)?");
return resources;
}
@Override
public void beforeAll(ExtensionContext context) throws Exception {
Optional<String> keyspaceName = getKeyspaceName(context.getTestClass());
Optional<String> keyspaceName = getKeyspaceName(context);
ExtensionContext.Store store = context.getStore(CASSANDRA);
delegate.before();
@@ -115,7 +114,6 @@ public class CassandraExtension implements BeforeAllCallback, AfterAllCallback,
if (keyspaceName.isPresent()) {
keyspaceName.ifPresent(name -> {
store.put("KEYSPACE", name);
TestKeyspaceDelegate.before(session, name);
store.put(CqlSession.class, session);
@@ -129,6 +127,7 @@ public class CassandraExtension implements BeforeAllCallback, AfterAllCallback,
TEST_RESOURCES.remove();
ExtensionContext.Store store = context.getStore(CASSANDRA);
String keyspaceName = store.getOrDefault("KEYSPACE", String.class, null);
if (keyspaceName != null) {
@@ -148,14 +147,16 @@ public class CassandraExtension implements BeforeAllCallback, AfterAllCallback,
@Override
public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
ExtensionContext.Store store = context.getStore(CASSANDRA);
List<Field> fields = ReflectionUtils.findFields(testInstance.getClass(),
field -> AnnotationUtils.isAnnotated(field, TestKeyspace.class),
ReflectionUtils.HierarchyTraversalMode.TOP_DOWN);
if (!fields.isEmpty()) {
ExtensionContext.Store store = context.getStore(CASSANDRA);
String keyspaceName = store.get("KEYSPACE", String.class);
CqlSession cqlSession = store.get(CqlSession.class, CqlSession.class);
Assert.state(!cqlSession.isClosed(), "CQL Session closed");
@@ -172,14 +173,16 @@ public class CassandraExtension implements BeforeAllCallback, AfterAllCallback,
}
}
private Optional<String> getKeyspaceName(Optional<Class<?>> instance) {
private Optional<String> getKeyspaceName(ExtensionContext context) {
return context.getTestClass().map(it -> {
return instance.map(it -> {
Optional<TestKeyspaceName> annotation = AnnotationUtils.findAnnotation(it, TestKeyspaceName.class);
if (annotation.isPresent()) {
return annotation.map(TestKeyspaceName::value).filter(StringUtils::isNotBlank)
.orElseGet(RandomKeyspaceName::create);
return annotation.map(TestKeyspaceName::value)
.filter(StringUtils::isNotBlank)
.orElseGet(RandomKeyspaceName::create);
}
return null;

View File

@@ -44,9 +44,10 @@ import org.springframework.util.FileSystemUtils;
*
* @author Mark Paluch
*/
@SuppressWarnings("unused")
class EmbeddedCassandraServerHelper {
private static Logger log = LoggerFactory.getLogger(EmbeddedCassandraServerHelper.class);
private static final Logger log = LoggerFactory.getLogger(EmbeddedCassandraServerHelper.class);
public static final long DEFAULT_STARTUP_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(20);
private static final String DEFAULT_TMP_DIR = "target/embeddedCassandra";
@@ -95,9 +96,10 @@ class EmbeddedCassandraServerHelper {
/**
* Start an embedded Cassandra instance.
*
* @param yamlResource
* @param timeout
* @throws Exception
* @param yamlResource {@link String location} of a YAML file to configure Cassandra.
* @param timeout {@link Long} value specifying the timeout used to wait for the Cassandra server to startup.
* @throws Exception if the Cassandra server fails to start.
* @see #startEmbeddedCassandra(String, String, long)
*/
public static void startEmbeddedCassandra(String yamlResource, long timeout) throws Exception {
startEmbeddedCassandra(yamlResource, DEFAULT_TMP_DIR, timeout);
@@ -106,10 +108,10 @@ class EmbeddedCassandraServerHelper {
/**
* Start an embedded Cassandra instance.
*
* @param yamlResource
* @param tmpDir
* @param timeout
* @throws Exception
* @param yamlResource {@link String resource location} of a YAML file to configure Cassandra.
* @param tmpDir {@link String filesystem location} used by the embedded Cassandra instance.
* @param timeout {@link Long} value specifying the timeout used to wait for the Cassandra server to startup.
* @throws Exception if the Cassandra server fails to start.
*/
private static void startEmbeddedCassandra(String yamlResource, String tmpDir, long timeout) throws Exception {