DATAMONGO-2414 - Introduce MongoServerCondition to replace JUnit 4 TestRules.

MongoServerCondition replaces the JUnit 4 TestRules (MongoVersionRule & ReplicaSet) with a JUnit Jupiter ExecutionCondition.

Original Pull Request: #807
This commit is contained in:
Christoph Strobl
2019-11-13 15:33:23 +01:00
parent f7e527e45c
commit d652b2bdb4
10 changed files with 264 additions and 46 deletions

View File

@@ -22,11 +22,10 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import com.mongodb.client.MongoClient;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
@@ -37,27 +36,29 @@ import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.test.util.EnableIfMongoServerVersion;
import org.springframework.data.mongodb.test.util.MongoServerCondition;
import org.springframework.data.mongodb.test.util.MongoTestUtils;
import org.springframework.data.mongodb.test.util.MongoVersionRule;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
/**
* @author Christoph Strobl
*/
@ExtendWith(MongoServerCondition.class)
@EnableIfMongoServerVersion(isGreaterThanEqual = "4.2")
public class MongoTemplateUpdateTests {
public static @ClassRule MongoVersionRule REQUIRES_AT_LEAST_4_2 = MongoVersionRule.REQUIRES_4_2;
static final String DB_NAME = "update-test";
MongoClient client;
MongoTemplate template;
@Before
public void setUp() {
@BeforeEach
void setUp() {
client = MongoTestUtils.replSetClient();
client = MongoTestUtils.client();
template = new MongoTemplate(new SimpleMongoClientDbFactory(client, DB_NAME));
MongoTestUtils.createOrReplaceCollection(DB_NAME, template.getCollectionName(Score.class), client);
@@ -247,7 +248,7 @@ public class MongoTemplateUpdateTests {
}
@Test // DATAMONGO-2331
@Ignore("https://jira.mongodb.org/browse/JAVA-3432")
@Disabled("https://jira.mongodb.org/browse/JAVA-3432")
public void findAndModifyAppliesAggregationUpdateCorrectly() {
Book one = new Book();

View File

@@ -25,10 +25,10 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
@@ -39,8 +39,9 @@ import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.test.util.EnableIfMongoServerVersion;
import org.springframework.data.mongodb.test.util.MongoServerCondition;
import org.springframework.data.mongodb.test.util.MongoTestUtils;
import org.springframework.data.mongodb.test.util.MongoVersionRule;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoCollection;
@@ -48,19 +49,19 @@ import com.mongodb.reactivestreams.client.MongoCollection;
/**
* @author Christoph Strobl
*/
@ExtendWith(MongoServerCondition.class)
@EnableIfMongoServerVersion(isGreaterThanEqual = "4.2")
public class ReactiveMongoTemplateUpdateTests {
public static @ClassRule MongoVersionRule REQUIRES_AT_LEAST_4_2 = MongoVersionRule.REQUIRES_4_2;
static final String DB_NAME = "reactive-update-test";
MongoClient client;
ReactiveMongoTemplate template;
@Before
public void setUp() {
@BeforeEach
void beforeEach() {
client = MongoTestUtils.reactiveReplSetClient();
client = MongoTestUtils.reactiveClient();
template = new ReactiveMongoTemplate(new SimpleReactiveMongoDatabaseFactory(client, DB_NAME));
MongoTestUtils.createOrReplaceCollection(DB_NAME, template.getCollectionName(Score.class), client).then()
@@ -239,7 +240,7 @@ public class ReactiveMongoTemplateUpdateTests {
}
@Test // DATAMONGO-2331
@Ignore("https://jira.mongodb.org/browse/JAVA-3432")
@Disabled("https://jira.mongodb.org/browse/JAVA-3432")
public void findAndModifyAppliesAggregationUpdateCorrectly() {
Book one = new Book();

View File

@@ -27,10 +27,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import org.bson.Document;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.dao.DataAccessException;
import org.springframework.data.annotation.Id;
@@ -38,9 +37,9 @@ import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDbFactory;
import org.springframework.data.mongodb.core.messaging.SubscriptionRequest.RequestOptions;
import org.springframework.data.mongodb.test.util.EnableIfReplicaSetAvailable;
import org.springframework.data.mongodb.test.util.MongoServerCondition;
import org.springframework.data.mongodb.test.util.MongoTestUtils;
import org.springframework.data.mongodb.test.util.ReplicaSet;
import org.springframework.test.annotation.IfProfileValue;
import org.springframework.util.ErrorHandler;
import com.mongodb.client.MongoCollection;
@@ -52,6 +51,7 @@ import com.mongodb.client.model.changestream.ChangeStreamDocument;
*
* @author Christoph Strobl
*/
@ExtendWith(MongoServerCondition.class)
public class DefaultMessageListenerContainerTests {
public static final String DATABASE_NAME = "change-stream-events";
@@ -60,8 +60,6 @@ public class DefaultMessageListenerContainerTests {
public static final Duration TIMEOUT = Duration.ofSeconds(2);
public @Rule TestRule replSet = ReplicaSet.none();
MongoDbFactory dbFactory;
MongoCollection<Document> collection;
MongoCollection<Document> collection2;
@@ -69,8 +67,8 @@ public class DefaultMessageListenerContainerTests {
private CollectingMessageListener<Object, Object> messageListener;
private MongoTemplate template;
@Before
public void setUp() {
@BeforeEach
void beforeEach() {
dbFactory = new SimpleMongoClientDbFactory(MongoTestUtils.client(), DATABASE_NAME);
template = new MongoTemplate(dbFactory);
@@ -85,7 +83,7 @@ public class DefaultMessageListenerContainerTests {
}
@Test // DATAMONGO-1803
@IfProfileValue(name = "replSet", value = "true")
@EnableIfReplicaSetAvailable
public void shouldCollectMappedChangeStreamMessagesCorrectly() throws InterruptedException {
MessageListenerContainer container = new DefaultMessageListenerContainer(template);
@@ -105,7 +103,7 @@ public class DefaultMessageListenerContainerTests {
}
@Test // DATAMONGO-2322
@IfProfileValue(name = "replSet", value = "true")
@EnableIfReplicaSetAvailable
public void shouldNotifyErrorHandlerOnErrorInListener() throws InterruptedException {
ErrorHandler errorHandler = mock(ErrorHandler.class);
@@ -136,7 +134,7 @@ public class DefaultMessageListenerContainerTests {
}
@Test // DATAMONGO-1803
@IfProfileValue(name = "replSet", value = "true")
@EnableIfReplicaSetAvailable
public void shouldNoLongerReceiveMessagesWhenContainerStopped() throws InterruptedException {
MessageListenerContainer container = new DefaultMessageListenerContainer(template);
@@ -161,7 +159,7 @@ public class DefaultMessageListenerContainerTests {
}
@Test // DATAMONGO-1803
@IfProfileValue(name = "replSet", value = "true")
@EnableIfReplicaSetAvailable
public void shouldReceiveMessagesWhenAddingRequestToAlreadyStartedContainer() throws InterruptedException {
MessageListenerContainer container = new DefaultMessageListenerContainer(template);
@@ -186,7 +184,7 @@ public class DefaultMessageListenerContainerTests {
}
@Test // DATAMONGO-1803
@IfProfileValue(name = "replSet", value = "true")
@EnableIfReplicaSetAvailable
public void shouldStartReceivingMessagesWhenContainerStarts() throws InterruptedException {
MessageListenerContainer container = new DefaultMessageListenerContainer(template);
@@ -224,8 +222,7 @@ public class DefaultMessageListenerContainerTests {
container.start();
awaitSubscription(
container.register(new TailableCursorRequest(messageListener, () -> COLLECTION_NAME), Document.class),
TIMEOUT);
container.register(new TailableCursorRequest(messageListener, () -> COLLECTION_NAME), Document.class), TIMEOUT);
collection.insertOne(new Document("_id", "id-2").append("value", "bar"));
@@ -245,8 +242,7 @@ public class DefaultMessageListenerContainerTests {
container.start();
awaitSubscription(
container.register(new TailableCursorRequest(messageListener, () -> COLLECTION_NAME), Document.class),
TIMEOUT);
container.register(new TailableCursorRequest(messageListener, () -> COLLECTION_NAME), Document.class), TIMEOUT);
collection.insertOne(new Document("_id", "id-1").append("value", "foo"));
collection.insertOne(new Document("_id", "id-2").append("value", "bar"));
@@ -317,7 +313,7 @@ public class DefaultMessageListenerContainerTests {
}
@Test // DATAMONGO-1803
@IfProfileValue(name = "replSet", value = "true")
@EnableIfReplicaSetAvailable
public void runsMoreThanOneTaskAtOnce() throws InterruptedException {
dbFactory.getDb().createCollection(COLLECTION_NAME,
@@ -349,7 +345,7 @@ public class DefaultMessageListenerContainerTests {
}
@Test // DATAMONGO-2012
@IfProfileValue(name = "replSet", value = "true")
@EnableIfReplicaSetAvailable
public void databaseLevelWatch() throws InterruptedException {
MessageListenerContainer container = new DefaultMessageListenerContainer(template);

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2019 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.mongodb.test.util;
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 Christoph Strobl
* @since 2.3
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Tag("version-specific")
public @interface EnableIfMongoServerVersion {
/**
* Inclusive lower bound of MongoDB server range.
*
* @return {@code 0.0.0} by default.
*/
String isGreaterThanEqual() default "0.0.0";
/**
* Exclusive upper bound of MongoDB server range.
*
* @return {@code 9999.9999.9999} by default.
*/
String isLessThan() default "9999.9999.9999";
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2019 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.mongodb.test.util;
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;
/**
* {@link EnableIfReplicaSetAvailable} marks a specific test class or method to be only executed against a server running in
* replicaSet mode. Intended to be used along with {@link MongoServerCondition}.
*
* @author Christoph Strobl
* @since 2.3
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Tag("replSet")
public @interface EnableIfReplicaSetAvailable {
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2019 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.mongodb.test.util;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.data.util.Version;
/**
* @author Christoph Strobl
*/
public class MongoServerCondition implements ExecutionCondition {
private static final Namespace NAMESPACE = Namespace.create("mongodb", "server");
private static final Version ANY = new Version(9999, 9999, 9999);
private static final Version DEFAULT_HIGH = ANY;
private static final Version DEFAULT_LOW = new Version(0, 0, 0);
@Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
if (context.getTags().contains("replSet")) {
if (!serverIsPartOfReplicaSet(context)) {
return ConditionEvaluationResult.disabled("Disabled for servers not running in replicaSet mode.");
}
}
if (context.getTags().contains("version-specific") && context.getElement().isPresent()) {
EnableIfMongoServerVersion version = AnnotatedElementUtils.findMergedAnnotation(context.getElement().get(),
EnableIfMongoServerVersion.class);
Version serverVersion = serverVersion(context);
if (version != null && !serverVersion.equals(ANY)) {
Version expectedMinVersion = Version.parse(version.isGreaterThanEqual());
if (!expectedMinVersion.equals(ANY) && !expectedMinVersion.equals(DEFAULT_LOW)) {
if (serverVersion.isLessThan(expectedMinVersion)) {
return ConditionEvaluationResult.disabled(String
.format("Disabled for server version %s. Requires at least %s.", serverVersion, expectedMinVersion));
}
}
Version expectedMaxVersion = Version.parse(version.isLessThan());
if (!expectedMaxVersion.equals(ANY) && !expectedMaxVersion.equals(DEFAULT_HIGH)) {
if (serverVersion.isGreaterThanOrEqualTo(expectedMaxVersion)) {
return ConditionEvaluationResult.disabled(String
.format("Disabled for server version %s. Only supported until %s.", serverVersion, expectedMaxVersion));
}
}
}
}
return ConditionEvaluationResult.enabled("Enabled by default");
}
private boolean serverIsPartOfReplicaSet(ExtensionContext context) {
return context.getStore(NAMESPACE).getOrComputeIfAbsent("--replSet", (key) -> MongoTestUtils.serverIsReplSet(),
Boolean.class);
}
private Version serverVersion(ExtensionContext context) {
return context.getStore(NAMESPACE).getOrComputeIfAbsent(Version.class, (key) -> MongoTestUtils.serverVersion(),
Version.class);
}
}

View File

@@ -19,8 +19,10 @@ import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.time.Duration;
import java.util.List;
import org.bson.Document;
import org.springframework.data.util.Version;
import com.mongodb.ReadPreference;
import com.mongodb.WriteConcern;
@@ -42,6 +44,8 @@ public class MongoTestUtils {
private static final String CONNECTION_STRING_PATTERN = "mongodb://%s:%s/";
private static final Version ANY = new Version(9999, 9999, 9999);
/**
* Create a new {@link com.mongodb.client.MongoClient} with defaults.
*
@@ -193,4 +197,35 @@ public class MongoTestUtils {
return MongoClients.create(CONNECTION_STRING);
}
/**
* @return the server version extracted from buildInfo.
* @since 2.3.0
*/
public static Version serverVersion() {
try (MongoClient client = client()) {
MongoDatabase database = client.getDatabase("test");
Document result = database.runCommand(new Document("buildInfo", 1));
return Version.parse(result.get("version", String.class));
} catch (Exception e) {
return ANY;
}
}
/**
* @return check if the server is running as part of a replica set.
* @since 2.3.0
*/
public static boolean serverIsReplSet() {
try (MongoClient client = MongoTestUtils.client()) {
return client.getDatabase("admin").runCommand(new Document("getCmdLineOpts", "1")).get("argv", List.class)
.contains("--replSet");
} catch (Exception e) {
return false;
}
}
}

View File

@@ -21,16 +21,21 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
/**
* {@link MongoVersion} allows specifying an version range of mongodb that is applicable for a specific test method. To
* be used along with {@link MongoVersionRule}.
* be used along with {@link MongoVersionRule} or {@link MongoServerCondition}.
*
* @author Christoph Strobl
* @since 2.1
* @deprecated Use {@link EnableIfMongoServerVersion} instead.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@EnableIfMongoServerVersion
@Deprecated
public @interface MongoVersion {
/**
@@ -38,6 +43,7 @@ public @interface MongoVersion {
*
* @return {@code 0.0.0} by default.
*/
@AliasFor(annotation = EnableIfMongoServerVersion.class, attribute = "isGreaterThanEqual")
String asOf() default "0.0.0";
/**
@@ -45,5 +51,6 @@ public @interface MongoVersion {
*
* @return {@code 9999.9999.9999} by default.
*/
@AliasFor(annotation = EnableIfMongoServerVersion.class, attribute = "isLessThan")
String until() default "9999.9999.9999";
}

View File

@@ -38,7 +38,9 @@ import com.mongodb.client.MongoDatabase;
* @author Christoph Strobl
* @author Mark Paluch
* @since 1.6
* @deprecated Use {@link MongoServerCondition} instead.
*/
@Deprecated
public class MongoVersionRule implements TestRule {
private static final Version ANY = new Version(9999, 9999, 9999);

View File

@@ -31,7 +31,9 @@ import com.mongodb.client.MongoClient;
* {@link TestRule} evaluating if MongoDB Server is running with {@code --replSet} flag.
*
* @author Christoph Strobl
* @deprecated Use {@link MongoServerCondition} with {@link EnableIfReplicaSetAvailable} instead.
*/
@Deprecated
public class ReplicaSet implements TestRule {
boolean required = false;