From 493f4bdc4294c46ca336affe38c0647035ba2365 Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Fri, 12 Jul 2019 14:17:31 +0200 Subject: [PATCH] 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. --- .../integration/imperative/AuditingIT.java | 2 +- .../integration/imperative/CallbacksIT.java | 2 +- .../imperative/IdGeneratorsIT.java | 2 +- .../integration/imperative/RepositoryIT.java | 2 +- .../data/integration/kotlin/KotlinIT.java | 2 +- .../reactive/ReactiveAuditingIT.java | 5 +- .../reactive/ReactiveCallbacksIT.java | 6 +- .../reactive/ReactiveIdGeneratorsIT.java | 5 +- .../reactive/ReactiveRepositoryIT.java | 7 +- .../data/test/Neo4jExtension.java | 164 ++++++++++++++++-- 10 files changed, 170 insertions(+), 27 deletions(-) diff --git a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/imperative/AuditingIT.java b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/imperative/AuditingIT.java index 08a266ecc..17e68894c 100644 --- a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/imperative/AuditingIT.java +++ b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/imperative/AuditingIT.java @@ -98,7 +98,7 @@ class AuditingIT extends AuditingITBase { @Bean public Driver driver() { - return neo4jConnectionSupport.openConnection(); + return neo4jConnectionSupport.getDriver(); } @Override diff --git a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/imperative/CallbacksIT.java b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/imperative/CallbacksIT.java index 5f2c31186..721e36025 100644 --- a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/imperative/CallbacksIT.java +++ b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/imperative/CallbacksIT.java @@ -93,7 +93,7 @@ class CallbacksIT extends CallbacksITBase { @Bean public Driver driver() { - return neo4jConnectionSupport.openConnection(); + return neo4jConnectionSupport.getDriver(); } @Override diff --git a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/imperative/IdGeneratorsIT.java b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/imperative/IdGeneratorsIT.java index f9feb4162..16a7e0a51 100644 --- a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/imperative/IdGeneratorsIT.java +++ b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/imperative/IdGeneratorsIT.java @@ -131,7 +131,7 @@ class IdGeneratorsIT extends IdGeneratorsITBase { @Bean public Driver driver() { - return neo4jConnectionSupport.openConnection(); + return neo4jConnectionSupport.getDriver(); } @Override diff --git a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/imperative/RepositoryIT.java b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/imperative/RepositoryIT.java index 65562fc24..8f5d33c30 100644 --- a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/imperative/RepositoryIT.java +++ b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/imperative/RepositoryIT.java @@ -1025,7 +1025,7 @@ class RepositoryIT { @Bean public Driver driver() { - return neo4jConnectionSupport.openConnection(); + return neo4jConnectionSupport.getDriver(); } @Override diff --git a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/kotlin/KotlinIT.java b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/kotlin/KotlinIT.java index 8e87609a1..5f6ef6da5 100644 --- a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/kotlin/KotlinIT.java +++ b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/kotlin/KotlinIT.java @@ -84,7 +84,7 @@ class KotlinIT { @Bean public Driver driver() { - return neo4jConnectionSupport.openConnection(); + return neo4jConnectionSupport.getDriver(); } public Collection getMappingBasePackages() { diff --git a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/reactive/ReactiveAuditingIT.java b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/reactive/ReactiveAuditingIT.java index f8e0d822d..3b1a33ee1 100644 --- a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/reactive/ReactiveAuditingIT.java +++ b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/reactive/ReactiveAuditingIT.java @@ -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 diff --git a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/reactive/ReactiveCallbacksIT.java b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/reactive/ReactiveCallbacksIT.java index 0322202af..1cebd24bc 100644 --- a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/reactive/ReactiveCallbacksIT.java +++ b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/reactive/ReactiveCallbacksIT.java @@ -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 diff --git a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/reactive/ReactiveIdGeneratorsIT.java b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/reactive/ReactiveIdGeneratorsIT.java index 72de53449..da46380c0 100644 --- a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/reactive/ReactiveIdGeneratorsIT.java +++ b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/reactive/ReactiveIdGeneratorsIT.java @@ -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 diff --git a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/reactive/ReactiveRepositoryIT.java b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/reactive/ReactiveRepositoryIT.java index 011d64680..63daf023d 100644 --- a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/reactive/ReactiveRepositoryIT.java +++ b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/integration/reactive/ReactiveRepositoryIT.java @@ -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 diff --git a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/test/Neo4jExtension.java b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/test/Neo4jExtension.java index feb83b636..28c472669 100644 --- a/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/test/Neo4jExtension.java +++ b/spring-data-neo4j/src/test/java/org/neo4j/springframework/data/test/Neo4jExtension.java @@ -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 COMMUNITY_EDITION_INDICATOR = Collections.singleton("community"); + + private static Set 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 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) { + } } }