Refactor test framework infrastructure classes.
Resolves gh-1115.
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user