Introduce JUnit 5 tags for various types of tests.

This introduces some constants to mark tests as tests that need reactive
support and to distinguish between tests that run only on the commercial
or community edition.

The Neo4jExtension assumes on the requirements of the tags, so those
tags have a dual use:

They can be used to filter for tests, i.e. setup a pipeline that does
only reactive tests on community edition.

They make sure the project is easy to build for external contributors
and people who might not have access to latest edition by assuming on
the requirements and prevent the tests from failing in conditions where
the tests are bound to fail.
This commit is contained in:
Michael Simons
2019-07-12 14:17:31 +02:00
parent 92291eb22a
commit 493f4bdc42
10 changed files with 170 additions and 27 deletions

View File

@@ -98,7 +98,7 @@ class AuditingIT extends AuditingITBase {
@Bean
public Driver driver() {
return neo4jConnectionSupport.openConnection();
return neo4jConnectionSupport.getDriver();
}
@Override

View File

@@ -93,7 +93,7 @@ class CallbacksIT extends CallbacksITBase {
@Bean
public Driver driver() {
return neo4jConnectionSupport.openConnection();
return neo4jConnectionSupport.getDriver();
}
@Override

View File

@@ -131,7 +131,7 @@ class IdGeneratorsIT extends IdGeneratorsITBase {
@Bean
public Driver driver() {
return neo4jConnectionSupport.openConnection();
return neo4jConnectionSupport.getDriver();
}
@Override

View File

@@ -1025,7 +1025,7 @@ class RepositoryIT {
@Bean
public Driver driver() {
return neo4jConnectionSupport.openConnection();
return neo4jConnectionSupport.getDriver();
}
@Override

View File

@@ -84,7 +84,7 @@ class KotlinIT {
@Bean
public Driver driver() {
return neo4jConnectionSupport.openConnection();
return neo4jConnectionSupport.getDriver();
}
public Collection<String> getMappingBasePackages() {

View File

@@ -19,6 +19,7 @@
package org.neo4j.springframework.data.integration.reactive;
import static org.assertj.core.api.Assertions.*;
import static org.neo4j.springframework.data.test.Neo4jExtension.*;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@@ -29,6 +30,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.neo4j.driver.Driver;
import org.neo4j.springframework.data.config.AbstractReactiveNeo4jConfig;
@@ -50,6 +52,7 @@ import org.springframework.transaction.reactive.TransactionalOperator;
/**
* @author Michael J. Simons
*/
@Tag(NEEDS_REACTIVE_SUPPORT)
@ContextConfiguration(classes = ReactiveAuditingIT.Config.class)
class ReactiveAuditingIT extends AuditingITBase {
@@ -125,7 +128,7 @@ class ReactiveAuditingIT extends AuditingITBase {
@Bean
public Driver driver() {
return neo4jConnectionSupport.openConnection();
return neo4jConnectionSupport.getDriver();
}
@Override

View File

@@ -18,6 +18,8 @@
*/
package org.neo4j.springframework.data.integration.reactive;
import static org.neo4j.springframework.data.test.Neo4jExtension.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@@ -28,6 +30,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.neo4j.driver.Driver;
import org.neo4j.springframework.data.config.AbstractReactiveNeo4jConfig;
@@ -47,6 +50,7 @@ import org.springframework.transaction.reactive.TransactionalOperator;
/**
* @author Michael J. Simons
*/
@Tag(NEEDS_REACTIVE_SUPPORT)
@ContextConfiguration(classes = ReactiveCallbacksIT.Config.class)
class ReactiveCallbacksIT extends CallbacksITBase {
@@ -148,7 +152,7 @@ class ReactiveCallbacksIT extends CallbacksITBase {
@Bean
public Driver driver() {
return neo4jConnectionSupport.openConnection();
return neo4jConnectionSupport.getDriver();
}
@Override

View File

@@ -20,6 +20,7 @@ package org.neo4j.springframework.data.integration.reactive;
import static java.util.stream.Collectors.*;
import static org.assertj.core.api.Assertions.*;
import static org.neo4j.springframework.data.test.Neo4jExtension.*;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@@ -32,6 +33,7 @@ import java.util.List;
import java.util.Set;
import java.util.stream.IntStream;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.neo4j.driver.Driver;
import org.neo4j.springframework.data.config.AbstractReactiveNeo4jConfig;
@@ -52,6 +54,7 @@ import org.springframework.transaction.reactive.TransactionalOperator;
/**
* @author Michael J. Simons
*/
@Tag(NEEDS_REACTIVE_SUPPORT)
@ContextConfiguration(classes = ReactiveIdGeneratorsIT.Config.class)
class ReactiveIdGeneratorsIT extends IdGeneratorsITBase {
@@ -171,7 +174,7 @@ class ReactiveIdGeneratorsIT extends IdGeneratorsITBase {
@Bean
public Driver driver() {
return neo4jConnectionSupport.openConnection();
return neo4jConnectionSupport.getDriver();
}
@Override

View File

@@ -19,6 +19,7 @@
package org.neo4j.springframework.data.integration.reactive;
import static org.neo4j.driver.Values.*;
import static org.neo4j.springframework.data.test.Neo4jExtension.*;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
@@ -34,6 +35,7 @@ import java.util.List;
import java.util.stream.IntStream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.neo4j.driver.Driver;
@@ -47,7 +49,7 @@ import org.neo4j.springframework.data.integration.shared.PersonWithAllConstructo
import org.neo4j.springframework.data.integration.shared.ThingWithAssignedId;
import org.neo4j.springframework.data.repository.config.EnableReactiveNeo4jRepositories;
import org.neo4j.springframework.data.test.Neo4jExtension;
import org.neo4j.springframework.data.test.Neo4jExtension.Neo4jConnectionSupport;
import org.neo4j.springframework.data.test.Neo4jExtension.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -64,6 +66,7 @@ import org.springframework.transaction.reactive.TransactionalOperator;
* @author Gerrit Meier
* @author Michael J. Simons
*/
@Tag(NEEDS_REACTIVE_SUPPORT)
@ExtendWith(Neo4jExtension.class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ReactiveRepositoryIT.Config.class)
@@ -725,7 +728,7 @@ class ReactiveRepositoryIT {
@Bean
public Driver driver() {
return neo4jConnectionSupport.openConnection();
return neo4jConnectionSupport.getDriver();
}
@Override

View File

@@ -18,21 +18,33 @@
*/
package org.neo4j.springframework.data.test;
import static org.assertj.core.api.Assumptions.*;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.platform.commons.util.ReflectionUtils;
import org.neo4j.driver.AccessMode;
import org.neo4j.driver.AuthToken;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Config;
import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;
import org.neo4j.driver.Logging;
import org.neo4j.driver.Session;
import org.neo4j.driver.internal.SessionConfig;
import org.neo4j.driver.internal.util.ServerVersion;
import org.testcontainers.containers.Neo4jContainer;
/**
@@ -45,12 +57,21 @@ import org.testcontainers.containers.Neo4jContainer;
* @since 1.0
*/
@Slf4j
public class Neo4jExtension implements BeforeAllCallback {
public class Neo4jExtension implements BeforeAllCallback, BeforeEachCallback {
public final static String NEEDS_REACTIVE_SUPPORT = "reactiveTest";
public final static String COMMUNITY_EDITION_ONLY = "communityEdition";
public final static String COMMERCIAL_EDITION_ONLY = "commercialEdition";
private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace
.create(Neo4jExtension.class);
private static final String KEY = "neo4j.standalone";
private static final String KEY_NEO4J_INSTANCE = "neo4j.standalone";
private static final String KEY_DRIVER_INSTANCE = "neo4j.driver";
private static Set<String> COMMUNITY_EDITION_INDICATOR = Collections.singleton("community");
private static Set<String> COMMERCIAL_EDITION_INDICATOR = new HashSet<>(Arrays.asList("commercial", "enterprise"));
@Override
public void beforeAll(ExtensionContext context) throws Exception {
@@ -66,30 +87,65 @@ public class Neo4jExtension implements BeforeAllCallback {
String neo4jUrl = Optional.ofNullable(System.getenv("SDN_RX_NEO4J_URL")).orElse("");
String neo4jPassword = Optional.ofNullable(System.getenv("SDN_RX_NEO4J_PASSWORD")).orElse("");
Neo4jConnectionSupport neo4jConnectionSupport;
if (!(neo4jUrl.isEmpty() || neo4jPassword.isEmpty())) {
log.warn("Using Neo4j instance at {}.", neo4jUrl);
neo4jConnectionSupport = new Neo4jConnectionSupport(neo4jUrl,
AuthTokens.basic("neo4j", neo4jPassword));
} else {
log.warn("Using Neo4j test container.");
ExtensionContext.Store store = context.getStore(NAMESPACE);
ContainerAdapter adapter = store
.getOrComputeIfAbsent(KEY, key -> new Neo4jExtension.ContainerAdapter(), ContainerAdapter.class);
adapter.start();
neo4jConnectionSupport = new Neo4jConnectionSupport(adapter.getBoltUrl(), AuthTokens.none());
ExtensionContext.Store contextStore = context.getStore(NAMESPACE);
Neo4jConnectionSupport neo4jConnectionSupport = contextStore
.get(KEY_DRIVER_INSTANCE, Neo4jConnectionSupport.class);
if (neo4jConnectionSupport == null) {
if (!(neo4jUrl.isEmpty() || neo4jPassword.isEmpty())) {
log.warn("Using Neo4j instance at {}.", neo4jUrl);
neo4jConnectionSupport = new Neo4jConnectionSupport(neo4jUrl, AuthTokens.basic("neo4j", neo4jPassword));
} else {
log.warn("Using Neo4j test container.");
ContainerAdapter adapter = contextStore
.getOrComputeIfAbsent(KEY_NEO4J_INSTANCE, key -> new Neo4jExtension.ContainerAdapter(),
ContainerAdapter.class);
adapter.start();
neo4jConnectionSupport = new Neo4jConnectionSupport(adapter.getBoltUrl(), AuthTokens.none());
}
contextStore.put(KEY_DRIVER_INSTANCE, neo4jConnectionSupport);
}
checkRequiredFeatures(neo4jConnectionSupport, context.getTags());
Field field = injectableFields.get(0);
field.setAccessible(true);
field.set(null, neo4jConnectionSupport);
}
@Override
public void beforeEach(ExtensionContext context) throws Exception {
ExtensionContext.Store contextStore = context.getStore(NAMESPACE);
Neo4jConnectionSupport neo4jConnectionSupport = contextStore
.get(KEY_DRIVER_INSTANCE, Neo4jConnectionSupport.class);
checkRequiredFeatures(neo4jConnectionSupport, context.getTags());
}
private void checkRequiredFeatures(Neo4jConnectionSupport neo4jConnectionSupport, Set<String> tags) {
if (tags.contains(NEEDS_REACTIVE_SUPPORT)) {
assumeThat(neo4jConnectionSupport.getServerVersion().greaterThanOrEqual(ServerVersion.v4_0_0))
.describedAs("This test requires at least Neo4j 4.0 for reactive database connectivity.")
.isTrue();
}
if (tags.contains(COMMUNITY_EDITION_ONLY)) {
assumeThat(neo4jConnectionSupport.isCommunityEdition())
.describedAs("This test should be run on the community edition only")
.isTrue();
}
if (tags.contains(COMMERCIAL_EDITION_ONLY)) {
assumeThat(neo4jConnectionSupport.isCommercialEdition())
.describedAs("This test should be run on the commercial edition only")
.isTrue();
}
}
/**
* Support class that holds the connection information and opens a new connection on demand.
* @since 1.0
*/
public static class Neo4jConnectionSupport {
public static class Neo4jConnectionSupport implements ExtensionContext.Store.CloseableResource {
public final String url;
@@ -97,14 +153,88 @@ public class Neo4jExtension implements BeforeAllCallback {
public final Config config;
public volatile ServerVersion cachedServerVersion;
/**
* Shared instance of the standard (non-routing) driver.
*/
public volatile Driver driverInstance;
public Neo4jConnectionSupport(String url, AuthToken authToken) {
this.url = url;
this.authToken = authToken;
this.config = Config.builder().withLogging(Logging.slf4j()).build();
}
public Driver openConnection() {
return GraphDatabase.driver(url, authToken, config);
/**
* @return A possible shared driver instance, connected to either a database running inside test containers or
* running locally.
*/
public Driver getDriver() {
Driver driver = this.driverInstance;
if (driver == null) {
synchronized (this) {
driver = this.driverInstance;
if (driver == null) {
this.driverInstance = GraphDatabase.driver(url, authToken, config);
driver = this.driverInstance;
}
}
}
return driver;
}
ServerVersion getServerVersion() {
ServerVersion serverVersion = this.cachedServerVersion;
if (serverVersion == null) {
synchronized (this) {
serverVersion = this.cachedServerVersion;
if (serverVersion == null) {
this.cachedServerVersion = ServerVersion.version(getDriver());
serverVersion = this.cachedServerVersion;
}
}
}
return serverVersion;
}
String getEdition() {
String edition = "n/a";
SessionConfig sessionConfig = SessionConfig.builder().withDefaultAccessMode(AccessMode.READ).build();
try (Session session = getDriver().session(
sessionConfig)) {
edition = session.run("call dbms.components() yield edition").single().get("edition").asString();
}
return edition.toLowerCase(Locale.ENGLISH);
}
boolean isCommunityEdition() {
return COMMUNITY_EDITION_INDICATOR.contains(getEdition());
}
boolean isCommercialEdition() {
return COMMERCIAL_EDITION_INDICATOR.contains(getEdition());
}
@Override
public void close() {
// Don't open up a driver for just closing it
if (driverInstance == null) {
return;
}
// Catch all the things... The driver has been closed maybe by a Spring Context already
try {
log.debug("Closing Neo4j connection support.");
driverInstance.close();
} catch (Exception e) {
}
}
}