Make integration tests configurable to use different containers.

Original Pull Request: #1888 
Closes #1882
This commit is contained in:
Peter-Josef Meisch 2021-08-01 19:36:57 +02:00 committed by GitHub
parent d80d920a57
commit 1c8e0e03d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 214 additions and 46 deletions

56
pom.xml
View File

@ -25,6 +25,15 @@
<testcontainers>1.15.3</testcontainers> <testcontainers>1.15.3</testcontainers>
<blockhound-junit>1.0.6.RELEASE</blockhound-junit> <blockhound-junit>1.0.6.RELEASE</blockhound-junit>
<java-module-name>spring.data.elasticsearch</java-module-name> <java-module-name>spring.data.elasticsearch</java-module-name>
<!--
properties defining the maven phase for the tests and integration tests
set to "none" to disable the corresponding test execution (-Dmvn.unit-test.goal=none)
default execution for unit-test: "test", for the integration tests: "integration-test"
-->
<mvn.unit-test.goal>test</mvn.unit-test.goal>
<mvn.integration-test-elasticsearch.goal>integration-test</mvn.integration-test-elasticsearch.goal>
<mvn.integration-test-opensearch.goal>none</mvn.integration-test-opensearch.goal>
</properties> </properties>
<developers> <developers>
@ -264,25 +273,6 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!--
<dependency>
<groupId>org.apache.openwebbeans.test</groupId>
<artifactId>cditest-owb</artifactId>
<version>1.2.8</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jcdi_1.0_spec</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-atinject_1.0_spec</artifactId>
</exclusion>
</exclusions>
</dependency>
-->
<dependency> <dependency>
<groupId>org.skyscreamer</groupId> <groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId> <artifactId>jsonassert</artifactId>
@ -365,9 +355,6 @@
</resources> </resources>
<plugins> <plugins>
<!--
please do not remove this configuration for surefire - we need that to avoid issue with jar hell
-->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
@ -386,7 +373,7 @@
<!-- the default-test execution runs only the unit tests --> <!-- the default-test execution runs only the unit tests -->
<execution> <execution>
<id>default-test</id> <id>default-test</id>
<phase>test</phase> <phase>${mvn.unit-test.goal}</phase>
<goals> <goals>
<goal>test</goal> <goal>test</goal>
</goals> </goals>
@ -394,15 +381,32 @@
<excludedGroups>integration-test</excludedGroups> <excludedGroups>integration-test</excludedGroups>
</configuration> </configuration>
</execution> </execution>
<!-- execution to run the integration tests --> <!-- execution to run the integration tests against Elasticsearch -->
<execution> <execution>
<id>integration-test</id> <id>integration-test-elasticsearch</id>
<phase>integration-test</phase> <phase>${mvn.integration-test-elasticsearch.goal}</phase>
<goals> <goals>
<goal>test</goal> <goal>test</goal>
</goals> </goals>
<configuration> <configuration>
<groups>integration-test</groups> <groups>integration-test</groups>
<systemPropertyVariables>
<sde.integration-test.environment>elasticsearch</sde.integration-test.environment>
</systemPropertyVariables>
</configuration>
</execution>
<!-- execution to run the integration tests against Opensearch -->
<execution>
<id>integration-test-opensearch</id>
<phase>${mvn.integration-test-opensearch.goal}</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<groups>integration-test</groups>
<systemPropertyVariables>
<sde.integration-test.environment>opensearch</sde.integration-test.environment>
</systemPropertyVariables>
</configuration> </configuration>
</execution> </execution>
</executions> </executions>

View File

@ -0,0 +1,19 @@
package org.springframework.data.elasticsearch;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.Tag;
/**
* @author Peter-Josef Meisch
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Tag("foobar")
public @interface Foobar {
}

View File

@ -0,0 +1,28 @@
package org.springframework.data.elasticsearch;
import static org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
/**
* @author Peter-Josef Meisch
*/
@SpringIntegrationTest
@Foobar
public abstract class FoobarIntegrationTest {
private final Logger LOGGER = LoggerFactory.getLogger(getClass());
@Test
@DisplayName("should run test")
void shouldRunTest() {
int answer = 42;
assertThat(answer).isEqualTo(42);
}
}

View File

@ -0,0 +1,17 @@
package org.springframework.data.elasticsearch;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
import org.springframework.data.elasticsearch.junit.jupiter.IntegrationtestEnvironment;
import org.springframework.test.context.ContextConfiguration;
/**
* This class should only run when the cluster is an Elasticsearch cluster.
*
* @author Peter-Josef Meisch
*/
@EnabledIfSystemProperty(named = IntegrationtestEnvironment.SYSTEM_PROPERTY, matches = "(?i)elasticsearch")
@ContextConfiguration(classes = { ElasticsearchTemplateConfiguration.class })
@DisplayName("foobar integration with transport client")
public class TransportFoobarIntegrationTest extends FoobarIntegrationTest {}

View File

@ -73,7 +73,7 @@ public abstract class AuditingIntegrationTest {
assertThat(entity.getCreatedBy()).isEqualTo("Auditor 1"); assertThat(entity.getCreatedBy()).isEqualTo("Auditor 1");
assertThat(entity.getModifiedBy()).isEqualTo("Auditor 1"); assertThat(entity.getModifiedBy()).isEqualTo("Auditor 1");
Thread.sleep(10); Thread.sleep(50);
entity = callbacks.callback(BeforeConvertCallback.class, entity, IndexCoordinates.of("index")); entity = callbacks.callback(BeforeConvertCallback.class, entity, IndexCoordinates.of("index"));

View File

@ -24,9 +24,9 @@ import java.util.Properties;
import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.data.elasticsearch.support.VersionInfo;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.testcontainers.elasticsearch.ElasticsearchContainer; import org.testcontainers.elasticsearch.ElasticsearchContainer;
import org.testcontainers.utility.DockerImageName;
/** /**
* This class manages the connection to an Elasticsearch Cluster, starting a containerized one if necessary. The * This class manages the connection to an Elasticsearch Cluster, starting a containerized one if necessary. The
@ -40,9 +40,10 @@ public class ClusterConnection implements ExtensionContext.Store.CloseableResour
private static final Logger LOGGER = LoggerFactory.getLogger(ClusterConnection.class); private static final Logger LOGGER = LoggerFactory.getLogger(ClusterConnection.class);
private static final String SDE_TESTCONTAINER_IMAGE_NAME = "sde.testcontainers.image-name";
private static final String SDE_TESTCONTAINER_IMAGE_VERSION = "sde.testcontainers.image-version";
private static final int ELASTICSEARCH_DEFAULT_PORT = 9200; private static final int ELASTICSEARCH_DEFAULT_PORT = 9200;
private static final int ELASTICSEARCH_DEFAULT_TRANSPORT_PORT = 9300; private static final int ELASTICSEARCH_DEFAULT_TRANSPORT_PORT = 9300;
private static final String ELASTICSEARCH_DEFAULT_IMAGE = "docker.elastic.co/elasticsearch/elasticsearch";
private static final ThreadLocal<ClusterConnectionInfo> clusterConnectionInfoThreadLocal = new ThreadLocal<>(); private static final ThreadLocal<ClusterConnectionInfo> clusterConnectionInfoThreadLocal = new ThreadLocal<>();
@ -78,20 +79,27 @@ public class ClusterConnection implements ExtensionContext.Store.CloseableResour
@Nullable @Nullable
private ClusterConnectionInfo startElasticsearchContainer() { private ClusterConnectionInfo startElasticsearchContainer() {
LOGGER.debug("Starting Elasticsearch Container"); LOGGER.info("Starting Elasticsearch Container...");
try { try {
String elasticsearchVersion = VersionInfo.versionProperties() IntegrationtestEnvironment integrationtestEnvironment = IntegrationtestEnvironment.get();
.getProperty(VersionInfo.VERSION_ELASTICSEARCH_CLIENT); LOGGER.info("Integration test environment: {}", integrationtestEnvironment);
Map<String, String> elasticsearchProperties = elasticsearchProperties(); if (integrationtestEnvironment == IntegrationtestEnvironment.UNDEFINED) {
throw new IllegalArgumentException(IntegrationtestEnvironment.SYSTEM_PROPERTY + " property not set");
}
String dockerImageName = ELASTICSEARCH_DEFAULT_IMAGE + ':' + elasticsearchVersion; String testcontainersConfiguration = integrationtestEnvironment.name().toLowerCase();
LOGGER.debug("Docker image: {}", dockerImageName); Map<String, String> testcontainersProperties = testcontainersProperties(
"testcontainers-" + testcontainersConfiguration + ".properties");
ElasticsearchContainer elasticsearchContainer = new ElasticsearchContainer(dockerImageName); DockerImageName dockerImageName = getDockerImageName(testcontainersProperties);
elasticsearchContainer.withEnv(elasticsearchProperties);
ElasticsearchContainer elasticsearchContainer = new ElasticsearchContainer(dockerImageName)
.withEnv(testcontainersProperties);
elasticsearchContainer.start(); elasticsearchContainer.start();
return ClusterConnectionInfo.builder() // return ClusterConnectionInfo.builder() //
.withIntegrationtestEnvironment(integrationtestEnvironment)
.withHostAndPort(elasticsearchContainer.getHost(), .withHostAndPort(elasticsearchContainer.getHost(),
elasticsearchContainer.getMappedPort(ELASTICSEARCH_DEFAULT_PORT)) // elasticsearchContainer.getMappedPort(ELASTICSEARCH_DEFAULT_PORT)) //
.withTransportPort(elasticsearchContainer.getMappedPort(ELASTICSEARCH_DEFAULT_TRANSPORT_PORT)) // .withTransportPort(elasticsearchContainer.getMappedPort(ELASTICSEARCH_DEFAULT_TRANSPORT_PORT)) //
@ -104,9 +112,32 @@ public class ClusterConnection implements ExtensionContext.Store.CloseableResour
return null; return null;
} }
private Map<String, String> elasticsearchProperties() { private DockerImageName getDockerImageName(Map<String, String> testcontainersProperties) {
String imageName = testcontainersProperties.get(SDE_TESTCONTAINER_IMAGE_NAME);
String imageVersion = testcontainersProperties.get(SDE_TESTCONTAINER_IMAGE_VERSION);
if (imageName == null) {
throw new IllegalArgumentException("property " + SDE_TESTCONTAINER_IMAGE_NAME + " not configured");
}
testcontainersProperties.remove(SDE_TESTCONTAINER_IMAGE_NAME);
if (imageVersion == null) {
throw new IllegalArgumentException("property " + SDE_TESTCONTAINER_IMAGE_VERSION + " not configured");
}
testcontainersProperties.remove(SDE_TESTCONTAINER_IMAGE_VERSION);
String configuredImageName = imageName + ':' + imageVersion;
DockerImageName dockerImageName = DockerImageName.parse(configuredImageName)
.asCompatibleSubstituteFor("docker.elastic.co/elasticsearch/elasticsearch");
LOGGER.info("Docker image: {}", dockerImageName);
return dockerImageName;
}
private Map<String, String> testcontainersProperties(String propertiesFile) {
LOGGER.info("load configuration from {}", propertiesFile);
String propertiesFile = "testcontainers-elasticsearch.properties";
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(propertiesFile)) { try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(propertiesFile)) {
Properties props = new Properties(); Properties props = new Properties();

View File

@ -25,10 +25,11 @@ import org.testcontainers.elasticsearch.ElasticsearchContainer;
* with a rest client for both a local started cluster and for one defined by the cluster URL when creating the * with a rest client for both a local started cluster and for one defined by the cluster URL when creating the
* {@link ClusterConnection}.<br/> * {@link ClusterConnection}.<br/>
* The object must be created by using a {@link ClusterConnectionInfo.Builder}. * The object must be created by using a {@link ClusterConnectionInfo.Builder}.
* *
* @author Peter-Josef Meisch * @author Peter-Josef Meisch
*/ */
public final class ClusterConnectionInfo { public final class ClusterConnectionInfo {
private final IntegrationtestEnvironment integrationtestEnvironment;
private final boolean useSsl; private final boolean useSsl;
private final String host; private final String host;
private final int httpPort; private final int httpPort;
@ -40,8 +41,9 @@ public final class ClusterConnectionInfo {
return new Builder(); return new Builder();
} }
private ClusterConnectionInfo(String host, int httpPort, boolean useSsl, int transportPort, private ClusterConnectionInfo(IntegrationtestEnvironment integrationtestEnvironment, String host, int httpPort,
@Nullable ElasticsearchContainer elasticsearchContainer) { boolean useSsl, int transportPort, @Nullable ElasticsearchContainer elasticsearchContainer) {
this.integrationtestEnvironment = integrationtestEnvironment;
this.host = host; this.host = host;
this.httpPort = httpPort; this.httpPort = httpPort;
this.useSsl = useSsl; this.useSsl = useSsl;
@ -53,7 +55,8 @@ public final class ClusterConnectionInfo {
@Override @Override
public String toString() { public String toString() {
return "ClusterConnectionInfo{" + // return "ClusterConnectionInfo{" + //
"useSsl=" + useSsl + // "configuration: " + integrationtestEnvironment + //
", useSsl=" + useSsl + //
", host='" + host + '\'' + // ", host='" + host + '\'' + //
", httpPort=" + httpPort + // ", httpPort=" + httpPort + //
", transportPort=" + transportPort + // ", transportPort=" + transportPort + //
@ -86,14 +89,22 @@ public final class ClusterConnectionInfo {
} }
public static class Builder { public static class Builder {
boolean useSsl = false; private IntegrationtestEnvironment integrationtestEnvironment;
private boolean useSsl = false;
private String host; private String host;
private int httpPort; private int httpPort;
private int transportPort; private int transportPort;
@Nullable private ElasticsearchContainer elasticsearchContainer; @Nullable private ElasticsearchContainer elasticsearchContainer;
public Builder withIntegrationtestEnvironment(IntegrationtestEnvironment integrationtestEnvironment) {
this.integrationtestEnvironment = integrationtestEnvironment;
return this;
}
public Builder withHostAndPort(String host, int httpPort) { public Builder withHostAndPort(String host, int httpPort) {
Assert.hasLength(host, "host must not be empty"); Assert.hasLength(host, "host must not be empty");
this.host = host; this.host = host;
this.httpPort = httpPort; this.httpPort = httpPort;
return this; return this;
@ -115,7 +126,8 @@ public final class ClusterConnectionInfo {
} }
public ClusterConnectionInfo build() { public ClusterConnectionInfo build() {
return new ClusterConnectionInfo(host, httpPort, useSsl, transportPort, elasticsearchContainer); return new ClusterConnectionInfo(integrationtestEnvironment, host, httpPort, useSsl, transportPort,
elasticsearchContainer);
} }
} }
} }

View File

@ -0,0 +1,39 @@
/*
* Copyright 2021 the original author or authors.
*
* 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
*
* https://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 org.springframework.data.elasticsearch.junit.jupiter;
/**
* @author Peter-Josef Meisch
*/
public enum IntegrationtestEnvironment {
ELASTICSEARCH, OPENSEARCH, UNDEFINED;
public static final String SYSTEM_PROPERTY = "sde.integration-test.environment";
public static IntegrationtestEnvironment get() {
String property = System.getProperty(SYSTEM_PROPERTY, "elasticsearch");
switch (property.toUpperCase()) {
case "ELASTICSEARCH":
return ELASTICSEARCH;
case "OPENSEARCH":
return OPENSEARCH;
default:
return UNDEFINED;
}
}
}

View File

@ -1,2 +1,10 @@
# needed as we do a DELETE /* at the end of the tests, will be requited from 8.0 on, produces a warning since 7.13 #
# properties defining the image, these are not passed to the container on startup
#
sde.testcontainers.image-name=docker.elastic.co/elasticsearch/elasticsearch
sde.testcontainers.image-version=7.13.3
#
#
# needed as we do a DELETE /* at the end of the tests, will be required from 8.0 on, produces a warning since 7.13
#
action.destructive_requires_name=false action.destructive_requires_name=false

View File

@ -0,0 +1,10 @@
#
# properties defining the image, these are not passed to the container on startup
#
sde.testcontainers.image-name=opensearchproject/opensearch
sde.testcontainers.image-version=1.0.0
#
#
# Opensearch as default has TLS and Basic auth enabled, we do not want this here, Testcontainers cannot ignore self signed certificates
#
plugins.security.disabled=true