Merge branch '4.x' into JAVA-3065
diff --git a/integration-tests/src/test/java/com/datastax/dse/driver/api/core/auth/EmbeddedAdsRule.java b/integration-tests/src/test/java/com/datastax/dse/driver/api/core/auth/EmbeddedAdsRule.java
index cbec842..0903eb9 100644
--- a/integration-tests/src/test/java/com/datastax/dse/driver/api/core/auth/EmbeddedAdsRule.java
+++ b/integration-tests/src/test/java/com/datastax/dse/driver/api/core/auth/EmbeddedAdsRule.java
@@ -20,12 +20,14 @@
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.Version;
import com.datastax.oss.driver.api.core.config.DefaultDriverOption;
-import com.datastax.oss.driver.api.testinfra.DseRequirement;
import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge;
import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule;
+import com.datastax.oss.driver.api.testinfra.requirement.BackendType;
+import com.datastax.oss.driver.api.testinfra.requirement.VersionRequirement;
import com.datastax.oss.driver.api.testinfra.session.SessionUtils;
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap;
import java.io.File;
+import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.junit.AssumptionViolatedException;
@@ -151,50 +153,25 @@
}
}
- private Statement buildErrorStatement(
- Version requirement, Version actual, String description, boolean lessThan) {
- return new Statement() {
-
- @Override
- public void evaluate() {
- throw new AssumptionViolatedException(
- String.format(
- "Test requires %s %s %s but %s is configured. Description: %s",
- lessThan ? "less than" : "at least", "DSE", requirement, actual, description));
- }
- };
- }
-
@Override
public Statement apply(Statement base, Description description) {
- DseRequirement dseRequirement = description.getAnnotation(DseRequirement.class);
- if (dseRequirement != null) {
- if (!CcmBridge.DSE_ENABLEMENT) {
- return new Statement() {
- @Override
- public void evaluate() {
- throw new AssumptionViolatedException("Test Requires DSE but C* is configured.");
- }
- };
- } else {
- Version dseVersion = CcmBridge.VERSION;
- if (!dseRequirement.min().isEmpty()) {
- Version minVersion = Version.parse(dseRequirement.min());
- if (minVersion.compareTo(dseVersion) > 0) {
- return buildErrorStatement(dseVersion, dseVersion, dseRequirement.description(), false);
- }
- }
+ BackendType backend = CcmBridge.DSE_ENABLEMENT ? BackendType.DSE : BackendType.CASSANDRA;
+ Version version = CcmBridge.VERSION;
- if (!dseRequirement.max().isEmpty()) {
- Version maxVersion = Version.parse(dseRequirement.max());
+ Collection<VersionRequirement> requirements = VersionRequirement.fromAnnotations(description);
- if (maxVersion.compareTo(dseVersion) <= 0) {
- return buildErrorStatement(dseVersion, dseVersion, dseRequirement.description(), true);
- }
+ if (VersionRequirement.meetsAny(requirements, backend, version)) {
+ return super.apply(base, description);
+ } else {
+ // requirements not met, throw reasoning assumption to skip test
+ return new Statement() {
+ @Override
+ public void evaluate() {
+ throw new AssumptionViolatedException(
+ VersionRequirement.buildReasonString(requirements, backend, version));
}
- }
+ };
}
- return super.apply(base, description);
}
@Override
diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/PreparedStatementIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/PreparedStatementIT.java
index fe2df25..c3494a6 100644
--- a/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/PreparedStatementIT.java
+++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/PreparedStatementIT.java
@@ -16,7 +16,7 @@
package com.datastax.oss.driver.core.cql;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.catchThrowable;
import com.codahale.metrics.Gauge;
@@ -36,6 +36,8 @@
import com.datastax.oss.driver.api.core.type.DataTypes;
import com.datastax.oss.driver.api.testinfra.CassandraRequirement;
import com.datastax.oss.driver.api.testinfra.ccm.CcmRule;
+import com.datastax.oss.driver.api.testinfra.requirement.BackendRequirement;
+import com.datastax.oss.driver.api.testinfra.requirement.BackendType;
import com.datastax.oss.driver.api.testinfra.session.SessionRule;
import com.datastax.oss.driver.api.testinfra.session.SessionUtils;
import com.datastax.oss.driver.categories.ParallelizableTests;
@@ -54,6 +56,7 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import junit.framework.TestCase;
+import org.assertj.core.api.AbstractThrowableAssert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -425,13 +428,11 @@
}
/**
- * This test relies on CASSANDRA-15252 to reproduce the error condition. If the bug gets fixed in
- * Cassandra, we'll need to add a version restriction.
+ * This method reproduces CASSANDRA-15252 which is fixed in 3.0.26/3.11.12/4.0.2.
*
* @see <a href="https://issues.apache.org/jira/browse/CASSANDRA-15252">CASSANDRA-15252</a>
*/
- @Test
- public void should_fail_fast_if_id_changes_on_reprepare() {
+ private AbstractThrowableAssert<?, ? extends Throwable> assertableReprepareAfterIdChange() {
try (CqlSession session = SessionUtils.newSession(ccmRule)) {
PreparedStatement preparedStatement =
session.prepare(
@@ -444,12 +445,42 @@
executeDdl("DROP TABLE prepared_statement_test");
executeDdl("CREATE TABLE prepared_statement_test (a int PRIMARY KEY, b int, c int)");
- assertThatThrownBy(() -> session.execute(preparedStatement.bind(1)))
- .isInstanceOf(IllegalStateException.class)
- .hasMessageContaining("ID mismatch while trying to reprepare");
+ return assertThatCode(() -> session.execute(preparedStatement.bind(1)));
}
}
+ // Add version bounds to the DSE requirement if there is a version containing fix for
+ // CASSANDRA-15252
+ @BackendRequirement(
+ type = BackendType.DSE,
+ description = "No DSE version contains fix for CASSANDRA-15252")
+ @BackendRequirement(type = BackendType.CASSANDRA, minInclusive = "3.0.0", maxExclusive = "3.0.26")
+ @BackendRequirement(
+ type = BackendType.CASSANDRA,
+ minInclusive = "3.11.0",
+ maxExclusive = "3.11.12")
+ @BackendRequirement(type = BackendType.CASSANDRA, minInclusive = "4.0.0", maxExclusive = "4.0.2")
+ @Test
+ public void should_fail_fast_if_id_changes_on_reprepare() {
+ assertableReprepareAfterIdChange()
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining("ID mismatch while trying to reprepare");
+ }
+
+ @BackendRequirement(
+ type = BackendType.CASSANDRA,
+ minInclusive = "3.0.26",
+ maxExclusive = "3.11.0")
+ @BackendRequirement(
+ type = BackendType.CASSANDRA,
+ minInclusive = "3.11.12",
+ maxExclusive = "4.0.0")
+ @BackendRequirement(type = BackendType.CASSANDRA, minInclusive = "4.0.2")
+ @Test
+ public void handle_id_changes_on_reprepare() {
+ assertableReprepareAfterIdChange().doesNotThrowAnyException();
+ }
+
private void invalidationResultSetTest(Consumer<CqlSession> createFn) {
try (CqlSession session = sessionWithCacheSizeMetric()) {
diff --git a/osgi-tests/src/test/java/com/datastax/oss/driver/internal/osgi/support/CcmPaxExam.java b/osgi-tests/src/test/java/com/datastax/oss/driver/internal/osgi/support/CcmPaxExam.java
index 8697a0d..4a17006 100644
--- a/osgi-tests/src/test/java/com/datastax/oss/driver/internal/osgi/support/CcmPaxExam.java
+++ b/osgi-tests/src/test/java/com/datastax/oss/driver/internal/osgi/support/CcmPaxExam.java
@@ -18,10 +18,9 @@
import static com.datastax.oss.driver.internal.osgi.support.CcmStagedReactor.CCM_BRIDGE;
import com.datastax.oss.driver.api.core.Version;
-import com.datastax.oss.driver.api.testinfra.CassandraRequirement;
-import com.datastax.oss.driver.api.testinfra.DseRequirement;
-import java.util.Objects;
-import java.util.Optional;
+import com.datastax.oss.driver.api.testinfra.requirement.BackendType;
+import com.datastax.oss.driver.api.testinfra.requirement.VersionRequirement;
+import java.util.Collection;
import org.junit.AssumptionViolatedException;
import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
@@ -38,69 +37,20 @@
@Override
public void run(RunNotifier notifier) {
Description description = getDescription();
- CassandraRequirement cassandraRequirement =
- description.getAnnotation(CassandraRequirement.class);
- if (cassandraRequirement != null) {
- if (!cassandraRequirement.min().isEmpty()) {
- Version minVersion = Objects.requireNonNull(Version.parse(cassandraRequirement.min()));
- if (minVersion.compareTo(CCM_BRIDGE.getCassandraVersion()) > 0) {
- fireRequirementsNotMet(notifier, description, cassandraRequirement.min(), false, false);
- return;
- }
- }
- if (!cassandraRequirement.max().isEmpty()) {
- Version maxVersion = Objects.requireNonNull(Version.parse(cassandraRequirement.max()));
- if (maxVersion.compareTo(CCM_BRIDGE.getCassandraVersion()) <= 0) {
- fireRequirementsNotMet(notifier, description, cassandraRequirement.max(), true, false);
- return;
- }
- }
- }
- DseRequirement dseRequirement = description.getAnnotation(DseRequirement.class);
- if (dseRequirement != null) {
- Optional<Version> dseVersionOption = CCM_BRIDGE.getDseVersion();
- if (!dseVersionOption.isPresent()) {
- notifier.fireTestAssumptionFailed(
- new Failure(
- description,
- new AssumptionViolatedException("Test Requires DSE but C* is configured.")));
- return;
- } else {
- Version dseVersion = dseVersionOption.get();
- if (!dseRequirement.min().isEmpty()) {
- Version minVersion = Objects.requireNonNull(Version.parse(dseRequirement.min()));
- if (minVersion.compareTo(dseVersion) > 0) {
- fireRequirementsNotMet(notifier, description, dseRequirement.min(), false, true);
- return;
- }
- }
- if (!dseRequirement.max().isEmpty()) {
- Version maxVersion = Objects.requireNonNull(Version.parse(dseRequirement.max()));
- if (maxVersion.compareTo(dseVersion) <= 0) {
- fireRequirementsNotMet(notifier, description, dseRequirement.min(), true, true);
- return;
- }
- }
- }
- }
- super.run(notifier);
- }
+ BackendType backend =
+ CCM_BRIDGE.getDseVersion().isPresent() ? BackendType.DSE : BackendType.CASSANDRA;
+ Version version = CCM_BRIDGE.getDseVersion().orElseGet(CCM_BRIDGE::getCassandraVersion);
- private void fireRequirementsNotMet(
- RunNotifier notifier,
- Description description,
- String requirement,
- boolean lessThan,
- boolean dse) {
- AssumptionViolatedException e =
- new AssumptionViolatedException(
- String.format(
- "Test requires %s %s %s but %s is configured. Description: %s",
- lessThan ? "less than" : "at least",
- dse ? "DSE" : "C*",
- requirement,
- dse ? CCM_BRIDGE.getDseVersion().orElse(null) : CCM_BRIDGE.getCassandraVersion(),
- description));
- notifier.fireTestAssumptionFailed(new Failure(description, e));
+ Collection<VersionRequirement> requirements =
+ VersionRequirement.fromAnnotations(getDescription());
+ if (VersionRequirement.meetsAny(requirements, backend, version)) {
+ super.run(notifier);
+ } else {
+ // requirements not met, throw reasoning assumption to skip test
+ AssumptionViolatedException e =
+ new AssumptionViolatedException(
+ VersionRequirement.buildReasonString(requirements, backend, version));
+ notifier.fireTestAssumptionFailed(new Failure(description, e));
+ }
}
}
diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java
index c902434..d4830dd 100644
--- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java
+++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java
@@ -18,9 +18,10 @@
import com.datastax.oss.driver.api.core.DefaultProtocolVersion;
import com.datastax.oss.driver.api.core.ProtocolVersion;
import com.datastax.oss.driver.api.core.Version;
-import com.datastax.oss.driver.api.testinfra.CassandraRequirement;
import com.datastax.oss.driver.api.testinfra.CassandraResourceRule;
-import com.datastax.oss.driver.api.testinfra.DseRequirement;
+import com.datastax.oss.driver.api.testinfra.requirement.BackendType;
+import com.datastax.oss.driver.api.testinfra.requirement.VersionRequirement;
+import java.util.Collection;
import java.util.Optional;
import org.junit.AssumptionViolatedException;
import org.junit.runner.Description;
@@ -55,80 +56,26 @@
ccmBridge.remove();
}
- private Statement buildErrorStatement(
- Version requirement, String description, boolean lessThan, boolean dse) {
- return new Statement() {
-
- @Override
- public void evaluate() {
- throw new AssumptionViolatedException(
- String.format(
- "Test requires %s %s %s but %s is configured. Description: %s",
- lessThan ? "less than" : "at least",
- dse ? "DSE" : "C*",
- requirement,
- dse ? ccmBridge.getDseVersion().orElse(null) : ccmBridge.getCassandraVersion(),
- description));
- }
- };
- }
-
@Override
public Statement apply(Statement base, Description description) {
- // If test is annotated with CassandraRequirement or DseRequirement, ensure configured CCM
- // cluster meets those requirements.
- CassandraRequirement cassandraRequirement =
- description.getAnnotation(CassandraRequirement.class);
+ BackendType backend =
+ ccmBridge.getDseVersion().isPresent() ? BackendType.DSE : BackendType.CASSANDRA;
+ Version version = ccmBridge.getDseVersion().orElseGet(ccmBridge::getCassandraVersion);
- if (cassandraRequirement != null) {
- // if the configured cassandra cassandraRequirement exceeds the one being used skip this test.
- if (!cassandraRequirement.min().isEmpty()) {
- Version minVersion = Version.parse(cassandraRequirement.min());
- if (minVersion.compareTo(ccmBridge.getCassandraVersion()) > 0) {
- return buildErrorStatement(minVersion, cassandraRequirement.description(), false, false);
+ Collection<VersionRequirement> requirements = VersionRequirement.fromAnnotations(description);
+
+ if (VersionRequirement.meetsAny(requirements, backend, version)) {
+ return super.apply(base, description);
+ } else {
+ // requirements not met, throw reasoning assumption to skip test
+ return new Statement() {
+ @Override
+ public void evaluate() {
+ throw new AssumptionViolatedException(
+ VersionRequirement.buildReasonString(requirements, backend, version));
}
- }
-
- if (!cassandraRequirement.max().isEmpty()) {
- // if the test version exceeds the maximum configured one, fail out.
- Version maxVersion = Version.parse(cassandraRequirement.max());
-
- if (maxVersion.compareTo(ccmBridge.getCassandraVersion()) <= 0) {
- return buildErrorStatement(maxVersion, cassandraRequirement.description(), true, false);
- }
- }
+ };
}
-
- DseRequirement dseRequirement = description.getAnnotation(DseRequirement.class);
- if (dseRequirement != null) {
- Optional<Version> dseVersionOption = ccmBridge.getDseVersion();
- if (!dseVersionOption.isPresent()) {
- return new Statement() {
-
- @Override
- public void evaluate() {
- throw new AssumptionViolatedException("Test Requires DSE but C* is configured.");
- }
- };
- } else {
- Version dseVersion = dseVersionOption.get();
- if (!dseRequirement.min().isEmpty()) {
- Version minVersion = Version.parse(dseRequirement.min());
- if (minVersion.compareTo(dseVersion) > 0) {
- return buildErrorStatement(minVersion, dseRequirement.description(), false, true);
- }
- }
-
- if (!dseRequirement.max().isEmpty()) {
- Version maxVersion = Version.parse(dseRequirement.max());
-
- if (maxVersion.compareTo(dseVersion) <= 0) {
- return buildErrorStatement(maxVersion, dseRequirement.description(), true, true);
- }
- }
- }
- }
- return super.apply(base, description);
}
public Version getCassandraVersion() {
diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/requirement/BackendRequirement.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/requirement/BackendRequirement.java
new file mode 100644
index 0000000..ec034db
--- /dev/null
+++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/requirement/BackendRequirement.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright DataStax, Inc.
+ *
+ * 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
+ *
+ * http://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 com.datastax.oss.driver.api.testinfra.requirement;
+
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Annotation for a Class or Method that defines a database backend Version requirement. If the
+ * type/version in use does not meet the requirement, the test is skipped.
+ */
+@Repeatable(BackendRequirements.class)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface BackendRequirement {
+ BackendType type();
+
+ String minInclusive() default "";
+
+ String maxExclusive() default "";
+
+ String description() default "";
+}
diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/requirement/BackendRequirements.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/requirement/BackendRequirements.java
new file mode 100644
index 0000000..09786c1
--- /dev/null
+++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/requirement/BackendRequirements.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright DataStax, Inc.
+ *
+ * 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
+ *
+ * http://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 com.datastax.oss.driver.api.testinfra.requirement;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Annotation to allow @BackendRequirement to be repeatable. */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface BackendRequirements {
+ BackendRequirement[] value();
+}
diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/requirement/BackendType.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/requirement/BackendType.java
new file mode 100644
index 0000000..eae7067
--- /dev/null
+++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/requirement/BackendType.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright DataStax, Inc.
+ *
+ * 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
+ *
+ * http://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 com.datastax.oss.driver.api.testinfra.requirement;
+
+public enum BackendType {
+ CASSANDRA("C*"),
+ DSE("Dse"),
+ ;
+
+ final String friendlyName;
+
+ BackendType(String friendlyName) {
+ this.friendlyName = friendlyName;
+ }
+
+ public String getFriendlyName() {
+ return friendlyName;
+ }
+}
diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/requirement/VersionRequirement.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/requirement/VersionRequirement.java
new file mode 100644
index 0000000..28a72bc
--- /dev/null
+++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/requirement/VersionRequirement.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright DataStax, Inc.
+ *
+ * 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
+ *
+ * http://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 com.datastax.oss.driver.api.testinfra.requirement;
+
+import com.datastax.oss.driver.api.core.Version;
+import com.datastax.oss.driver.api.testinfra.CassandraRequirement;
+import com.datastax.oss.driver.api.testinfra.DseRequirement;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import org.junit.runner.Description;
+
+/**
+ * Used to unify the requirements specified by
+ * annotations @CassandraRequirement, @DseRequirment, @BackendRequirement
+ */
+public class VersionRequirement {
+ final BackendType backendType;
+ final Optional<Version> minInclusive;
+ final Optional<Version> maxExclusive;
+ final String description;
+
+ public VersionRequirement(
+ BackendType backendType, String minInclusive, String maxExclusive, String description) {
+ this.backendType = backendType;
+ this.minInclusive =
+ minInclusive.isEmpty() ? Optional.empty() : Optional.of(Version.parse(minInclusive));
+ this.maxExclusive =
+ maxExclusive.isEmpty() ? Optional.empty() : Optional.of(Version.parse(maxExclusive));
+ this.description = description;
+ }
+
+ public BackendType getBackendType() {
+ return backendType;
+ }
+
+ public Optional<Version> getMinInclusive() {
+ return minInclusive;
+ }
+
+ public Optional<Version> getMaxExclusive() {
+ return maxExclusive;
+ }
+
+ public String readableString() {
+ final String versionRange;
+ if (minInclusive.isPresent() && maxExclusive.isPresent()) {
+ versionRange =
+ String.format("%s or greater, but less than %s", minInclusive.get(), maxExclusive.get());
+ } else if (minInclusive.isPresent()) {
+ versionRange = String.format("%s or greater", minInclusive.get());
+ } else if (maxExclusive.isPresent()) {
+ versionRange = String.format("less than %s", maxExclusive.get());
+ } else {
+ versionRange = "any version";
+ }
+
+ if (!description.isEmpty()) {
+ return String.format("%s %s [%s]", backendType.getFriendlyName(), versionRange, description);
+ } else {
+ return String.format("%s %s", backendType.getFriendlyName(), versionRange);
+ }
+ }
+
+ public static VersionRequirement fromBackendRequirement(BackendRequirement requirement) {
+ return new VersionRequirement(
+ requirement.type(),
+ requirement.minInclusive(),
+ requirement.maxExclusive(),
+ requirement.description());
+ }
+
+ public static VersionRequirement fromCassandraRequirement(CassandraRequirement requirement) {
+ return new VersionRequirement(
+ BackendType.CASSANDRA, requirement.min(), requirement.max(), requirement.description());
+ }
+
+ public static VersionRequirement fromDseRequirement(DseRequirement requirement) {
+ return new VersionRequirement(
+ BackendType.DSE, requirement.min(), requirement.max(), requirement.description());
+ }
+
+ public static Collection<VersionRequirement> fromAnnotations(Description description) {
+ // collect all requirement annotation types
+ CassandraRequirement cassandraRequirement =
+ description.getAnnotation(CassandraRequirement.class);
+ DseRequirement dseRequirement = description.getAnnotation(DseRequirement.class);
+ BackendRequirements backendRequirements = description.getAnnotation(BackendRequirements.class);
+
+ // build list of required versions
+ Collection<VersionRequirement> requirements = new ArrayList<>();
+ if (cassandraRequirement != null) {
+ requirements.add(VersionRequirement.fromCassandraRequirement(cassandraRequirement));
+ }
+ if (dseRequirement != null) {
+ requirements.add(VersionRequirement.fromDseRequirement(dseRequirement));
+ }
+ if (backendRequirements != null) {
+ Arrays.stream(backendRequirements.value())
+ .forEach(r -> requirements.add(VersionRequirement.fromBackendRequirement(r)));
+ }
+ return requirements;
+ }
+
+ public static boolean meetsAny(
+ Collection<VersionRequirement> requirements,
+ BackendType configuredBackend,
+ Version configuredVersion) {
+ // special case: if there are no requirements then any backend/version is sufficient
+ if (requirements.isEmpty()) {
+ return true;
+ }
+
+ return requirements.stream()
+ .anyMatch(
+ requirement -> {
+ // requirement is different db type
+ if (requirement.getBackendType() != configuredBackend) {
+ return false;
+ }
+
+ // configured version is less than requirement min
+ if (requirement.getMinInclusive().isPresent()) {
+ if (requirement.getMinInclusive().get().compareTo(configuredVersion) > 0) {
+ return false;
+ }
+ }
+
+ // configured version is greater than or equal to requirement max
+ if (requirement.getMaxExclusive().isPresent()) {
+ if (requirement.getMaxExclusive().get().compareTo(configuredVersion) <= 0) {
+ return false;
+ }
+ }
+
+ // backend type and version range match
+ return true;
+ });
+ }
+
+ public static String buildReasonString(
+ Collection<VersionRequirement> requirements, BackendType backend, Version version) {
+ return String.format(
+ "Test requires one of:\n%s\nbut configuration is %s %s.",
+ requirements.stream()
+ .map(req -> String.format(" - %s", req.readableString()))
+ .collect(Collectors.joining("\n")),
+ backend.getFriendlyName(),
+ version);
+ }
+}
diff --git a/test-infra/src/test/java/com/datastax/oss/driver/api/testinfra/requirement/VersionRequirementTest.java b/test-infra/src/test/java/com/datastax/oss/driver/api/testinfra/requirement/VersionRequirementTest.java
new file mode 100644
index 0000000..51a362d
--- /dev/null
+++ b/test-infra/src/test/java/com/datastax/oss/driver/api/testinfra/requirement/VersionRequirementTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright DataStax, Inc.
+ *
+ * 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
+ *
+ * http://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 com.datastax.oss.driver.api.testinfra.requirement;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.datastax.oss.driver.api.core.Version;
+import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+
+public class VersionRequirementTest {
+ // backend aliases
+ private static BackendType CASSANDRA = BackendType.CASSANDRA;
+ private static BackendType DSE = BackendType.DSE;
+
+ // version numbers
+ private static Version V_0_0_0 = Version.parse("0.0.0");
+ private static Version V_0_1_0 = Version.parse("0.1.0");
+ private static Version V_1_0_0 = Version.parse("1.0.0");
+ private static Version V_1_0_1 = Version.parse("1.0.1");
+ private static Version V_1_1_0 = Version.parse("1.1.0");
+ private static Version V_2_0_0 = Version.parse("2.0.0");
+ private static Version V_2_0_1 = Version.parse("2.0.1");
+ private static Version V_3_0_0 = Version.parse("3.0.0");
+ private static Version V_3_1_0 = Version.parse("3.1.0");
+ private static Version V_4_0_0 = Version.parse("4.0.0");
+
+ // requirements
+ private static VersionRequirement CASSANDRA_ANY = new VersionRequirement(CASSANDRA, "", "", "");
+ private static VersionRequirement CASSANDRA_FROM_1_0_0 =
+ new VersionRequirement(CASSANDRA, "1.0.0", "", "");
+ private static VersionRequirement CASSANDRA_TO_1_0_0 =
+ new VersionRequirement(CASSANDRA, "", "1.0.0", "");
+ private static VersionRequirement CASSANDRA_FROM_1_0_0_TO_2_0_0 =
+ new VersionRequirement(CASSANDRA, "1.0.0", "2.0.0", "");
+ private static VersionRequirement CASSANDRA_FROM_1_1_0 =
+ new VersionRequirement(CASSANDRA, "1.1.0", "", "");
+ private static VersionRequirement CASSANDRA_FROM_3_0_0_TO_3_1_0 =
+ new VersionRequirement(CASSANDRA, "3.0.0", "3.1.0", "");
+ private static VersionRequirement DSE_ANY = new VersionRequirement(DSE, "", "", "");
+
+ @Test
+ public void empty_requirements() {
+ List<VersionRequirement> req = Collections.emptyList();
+
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_0_0_0)).isTrue();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_0_0)).isTrue();
+ assertThat(VersionRequirement.meetsAny(req, DSE, V_0_0_0)).isTrue();
+ assertThat(VersionRequirement.meetsAny(req, DSE, V_1_0_0)).isTrue();
+ }
+
+ @Test
+ public void single_requirement_any_version() {
+ List<VersionRequirement> anyCassandra = Collections.singletonList(CASSANDRA_ANY);
+ List<VersionRequirement> anyDse = Collections.singletonList(DSE_ANY);
+
+ assertThat(VersionRequirement.meetsAny(anyCassandra, CASSANDRA, V_0_0_0)).isTrue();
+ assertThat(VersionRequirement.meetsAny(anyCassandra, CASSANDRA, V_1_0_0)).isTrue();
+ assertThat(VersionRequirement.meetsAny(anyDse, DSE, V_0_0_0)).isTrue();
+ assertThat(VersionRequirement.meetsAny(anyDse, DSE, V_1_0_0)).isTrue();
+
+ assertThat(VersionRequirement.meetsAny(anyDse, CASSANDRA, V_0_0_0)).isFalse();
+ assertThat(VersionRequirement.meetsAny(anyDse, CASSANDRA, V_1_0_0)).isFalse();
+ assertThat(VersionRequirement.meetsAny(anyCassandra, DSE, V_0_0_0)).isFalse();
+ assertThat(VersionRequirement.meetsAny(anyCassandra, DSE, V_1_0_0)).isFalse();
+ }
+
+ @Test
+ public void single_requirement_min_only() {
+ List<VersionRequirement> req = Collections.singletonList(CASSANDRA_FROM_1_0_0);
+
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_0_0)).isTrue();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_0_1)).isTrue();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_1_0)).isTrue();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_2_0_0)).isTrue();
+
+ assertThat(VersionRequirement.meetsAny(req, DSE, V_1_0_0)).isFalse();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_0_0_0)).isFalse();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_0_1_0)).isFalse();
+ }
+
+ @Test
+ public void single_requirement_max_only() {
+ List<VersionRequirement> req = Collections.singletonList(CASSANDRA_TO_1_0_0);
+
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_0_0_0)).isTrue();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_0_1_0)).isTrue();
+
+ assertThat(VersionRequirement.meetsAny(req, DSE, V_0_0_0)).isFalse();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_0_0)).isFalse();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_0_1)).isFalse();
+ }
+
+ @Test
+ public void single_requirement_min_and_max() {
+ List<VersionRequirement> req = Collections.singletonList(CASSANDRA_FROM_1_0_0_TO_2_0_0);
+
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_0_0)).isTrue();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_0_1)).isTrue();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_1_0)).isTrue();
+
+ assertThat(VersionRequirement.meetsAny(req, DSE, V_1_0_0)).isFalse();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_0_0_0)).isFalse();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_0_1_0)).isFalse();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_2_0_0)).isFalse();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_2_0_1)).isFalse();
+ }
+
+ @Test
+ public void multi_requirement_any_version() {
+ List<VersionRequirement> req = ImmutableList.of(CASSANDRA_ANY, DSE_ANY);
+
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_0_0)).isTrue();
+ assertThat(VersionRequirement.meetsAny(req, DSE, V_1_0_0)).isTrue();
+ }
+
+ @Test
+ public void multi_db_requirement_min_one_any_other() {
+ List<VersionRequirement> req = ImmutableList.of(CASSANDRA_FROM_1_0_0, DSE_ANY);
+
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_0_0)).isTrue();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_2_0_0)).isTrue();
+ assertThat(VersionRequirement.meetsAny(req, DSE, V_0_0_0)).isTrue();
+ assertThat(VersionRequirement.meetsAny(req, DSE, V_1_0_0)).isTrue();
+
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_0_0_0)).isFalse();
+ }
+
+ @Test
+ public void multi_requirement_two_ranges() {
+ List<VersionRequirement> req =
+ ImmutableList.of(CASSANDRA_FROM_1_0_0_TO_2_0_0, CASSANDRA_FROM_3_0_0_TO_3_1_0);
+
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_0_0)).isTrue();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_1_0)).isTrue();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_3_0_0)).isTrue();
+
+ assertThat(VersionRequirement.meetsAny(req, DSE, V_1_0_0)).isFalse();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_0_0_0)).isFalse();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_2_0_0)).isFalse();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_3_1_0)).isFalse();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_4_0_0)).isFalse();
+ }
+
+ @Test
+ public void multi_requirement_overlapping() {
+ List<VersionRequirement> req =
+ ImmutableList.of(CASSANDRA_FROM_1_0_0_TO_2_0_0, CASSANDRA_FROM_1_1_0);
+
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_0_0)).isTrue();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_1_0)).isTrue();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_2_0_0)).isTrue();
+
+ assertThat(VersionRequirement.meetsAny(req, DSE, V_1_0_0)).isFalse();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_0_0_0)).isFalse();
+ }
+
+ @Test
+ public void multi_requirement_not_range() {
+ List<VersionRequirement> req = ImmutableList.of(CASSANDRA_TO_1_0_0, CASSANDRA_FROM_1_1_0);
+
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_0_0_0)).isTrue();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_1_0)).isTrue();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_2_0_0)).isTrue();
+
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_0_0)).isFalse();
+ assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_0_1)).isFalse();
+ }
+}