CASSANDRASC-72: Split unit tests and integration tests in CircleCI config

As the number of integration tests continues to grow, it is desirable to split
unit tests and integration tests. It is also desirable to parallelize integration
tests, each integration test should be its own job. The goals of this improvement
are:

- Fail fast on checkstyle or minor errors
- Speed up test runtime by running integration tests in parallel
- Isolate failing tests to specific combinations of Cassandra

In this commit, we run unit tests individually for both java 8 and java 11. We split
integration tests into its own jobs, split per java version and cassandra version
combination.

Patch by Francisco Guerrero; Reviewed by Dinesh Joshi, Yifan Cai for CASSANDRASC-72
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 922e191..89cd80b 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -59,16 +59,45 @@
 
 jobs:
   # Runs java 8 tests on a docker image
-  java8:
+  unit_java8:
     docker:
-     - image: circleci/openjdk:8-jdk-stretch
-    resource_class: large
+     - image: cimg/openjdk:8.0
+    environment:
+      skipIntegrationTest: true
     steps:
      - checkout
+     - run: ./gradlew --info check -x integrationTest --stacktrace
 
-     - run: CASSANDRA_SHAS="d13b3ef61b9afbd04878c988c7b722507674228c 8666265521c97a5e726c9d38762028a14325e4dc" BRANCHES="cassandra-4.0 cassandra-4.1" scripts/build-dtest-jars.sh
-     - run: echo 'export DTEST_JAR=$(find dtest-jars -name 'dtest-4.1*.jar' -type f -maxdepth 1 -exec basename {} \;)' >> "$BASH_ENV"
-     - run: ./gradlew --info check --stacktrace -Dcassandra.sidecar.versions_to_test="4.0,4.1"
+     - store_artifacts:
+         path: build/reports
+         destination: test-reports
+
+     - store_test_results:
+         path: build/test-results/
+
+  integration_cassandra_40_java8:
+    docker:
+     - image: circleci/openjdk:8-jdk-stretch
+    steps:
+     - checkout
+     # Cassandra 4.0 jar seems to be missing some dependencies, so we use 4.1 here (this is what we currently do)
+     - run: BRANCHES="cassandra-4.0 cassandra-4.1" scripts/build-dtest-jars.sh
+     - run: ./gradlew -PdtestVersion=4.1.4 -Dcassandra.sidecar.versions_to_test="4.0" --info checkstyleIntegrationTest spotbugsIntegrationTest integrationTest --stacktrace
+
+     - store_artifacts:
+         path: build/reports
+         destination: test-reports
+
+     - store_test_results:
+         path: build/test-results/
+
+  integration_cassandra_41_java8:
+    docker:
+     - image: circleci/openjdk:8-jdk-stretch
+    steps:
+     - checkout
+     - run: BRANCHES="cassandra-4.1" scripts/build-dtest-jars.sh
+     - run: ./gradlew -PdtestVersion=4.1.4 -Dcassandra.sidecar.versions_to_test="4.1" --info checkstyleIntegrationTest spotbugsIntegrationTest integrationTest --stacktrace
 
      - store_artifacts:
          path: build/reports
@@ -78,16 +107,60 @@
          path: build/test-results/
 
   # Runs java 11 tests on a docker image
-  java11:
+  unit_java11:
     docker:
-      - image: circleci/openjdk:11-jdk-stretch
-    resource_class: large
+      - image: cimg/openjdk:11.0
+    environment:
+      skipIntegrationTest: true
     steps:
       - checkout
+      - run: ./gradlew --info check -x integrationTest --stacktrace
 
-      - run: CASSANDRA_SHAS="d13b3ef61b9afbd04878c988c7b722507674228c cbaef9094e83364e6812c65b8411ff7dbffaf9c6" BRANCHES="cassandra-4.0 trunk" CASSANDRA_USE_JDK11=true scripts/build-dtest-jars.sh
-      - run: echo 'export DTEST_JAR=$(find dtest-jars -name 'dtest-5.*.jar' -type f -maxdepth 1 -exec basename {} \;)' >> "$BASH_ENV"
-      - run: ./gradlew --info check --stacktrace
+      - store_artifacts:
+          path: build/reports
+          destination: test-reports
+
+      - store_test_results:
+          path: build/test-results/
+
+  integration_cassandra_40_java11:
+    docker:
+      - image: circleci/openjdk:11-jdk-stretch
+    steps:
+      - checkout
+      # Cassandra 4.0 jar seems to be missing some dependencies, so we use 4.1 here (this is what we currently do)
+      - run: BRANCHES="cassandra-4.0 cassandra-4.1" CASSANDRA_USE_JDK11=true scripts/build-dtest-jars.sh
+      - run: ./gradlew -PdtestVersion=4.1.4 -Dcassandra.sidecar.versions_to_test="4.0" --info checkstyleIntegrationTest spotbugsIntegrationTest integrationTest --stacktrace
+
+      - store_artifacts:
+          path: build/reports
+          destination: test-reports
+
+      - store_test_results:
+          path: build/test-results/
+
+  integration_cassandra_50_java11:
+    docker:
+      - image: circleci/openjdk:11-jdk-stretch
+    steps:
+      - checkout
+      - run: BRANCHES="cassandra-5.0" scripts/build-dtest-jars.sh
+      - run: ./gradlew -PdtestVersion=5.0-alpha1 -Dcassandra.sidecar.versions_to_test="5.0" --info checkstyleIntegrationTest spotbugsIntegrationTest integrationTest --stacktrace
+
+      - store_artifacts:
+          path: build/reports
+          destination: test-reports
+
+      - store_test_results:
+          path: build/test-results/
+
+  integration_cassandra_trunk_java11:
+    docker:
+      - image: circleci/openjdk:11-jdk-stretch
+    steps:
+      - checkout
+      - run: BRANCHES="trunk" scripts/build-dtest-jars.sh
+      - run: ./gradlew -PdtestVersion=5.1 -Dcassandra.sidecar.versions_to_test="5.1" --info checkstyleIntegrationTest spotbugsIntegrationTest integrationTest --stacktrace
 
       - store_artifacts:
           path: build/reports
@@ -138,25 +211,65 @@
   version: 2
   build-and-test:
     jobs:
-      - java8
-      - java11
+      - unit_java8
+      - integration_cassandra_40_java8:
+          requires:
+            - unit_java8
+      - integration_cassandra_41_java8:
+          requires:
+            - unit_java8
+      - unit_java11
+      - integration_cassandra_40_java11:
+          requires:
+            - unit_java11
+      - integration_cassandra_50_java11:
+          requires:
+            - unit_java11
+      - integration_cassandra_trunk_java11:
+          requires:
+            - unit_java11
       - docs_build:
           requires:
-            - java8
-            - java11
+            - unit_java8
+            - integration_cassandra_40_java8
+            - integration_cassandra_41_java8
+            - unit_java11
+            - integration_cassandra_40_java11
+            - integration_cassandra_50_java11
+            - integration_cassandra_trunk_java11
       - docker_build:
           requires:
-            - java8
-            - java11
+            - unit_java8
+            - integration_cassandra_40_java8
+            - integration_cassandra_41_java8
+            - unit_java11
+            - integration_cassandra_40_java11
+            - integration_cassandra_50_java11
+            - integration_cassandra_trunk_java11
       - rpm_build_install:
           requires:
-            - java8
-            - java11
+            - unit_java8
+            - integration_cassandra_40_java8
+            - integration_cassandra_41_java8
+            - unit_java11
+            - integration_cassandra_40_java11
+            - integration_cassandra_50_java11
+            - integration_cassandra_trunk_java11
       - deb_build_install:
           requires:
-            - java8
-            - java11
+            - unit_java8
+            - integration_cassandra_40_java8
+            - integration_cassandra_41_java8
+            - unit_java11
+            - integration_cassandra_40_java11
+            - integration_cassandra_50_java11
+            - integration_cassandra_trunk_java11
       - docker_build:
           requires:
-            - java8
-            - java11
+            - unit_java8
+            - integration_cassandra_40_java8
+            - integration_cassandra_41_java8
+            - unit_java11
+            - integration_cassandra_40_java11
+            - integration_cassandra_50_java11
+            - integration_cassandra_trunk_java11
diff --git a/CHANGES.txt b/CHANGES.txt
index f7d0831..a703c40 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,6 @@
 1.0.0
 -----
+ * Split unit tests and integration tests in CircleCI config (CASSANDRASC-72)
  * Allow configuring permissions for uploaded SSTables (CASSANDRASC-71)
  * Refactor Sidecar configuration (CASSANDRASC-69)
  * Add Client Methods for Obtaining Sidecar and Cassandra Health (CASSANDRASC-70)
diff --git a/build.gradle b/build.gradle
index abbe555..9f55443 100644
--- a/build.gradle
+++ b/build.gradle
@@ -52,9 +52,7 @@
     id("org.nosphere.apache.rat") version "0.8.0"
 }
 
-
-ext.dtestJar = System.getenv("DTEST_JAR") ?: "dtest-5.1.jar" // trunk is currently 5.1.jar - update when trunk moves
-println("Using DTest jar: ${ext.dtestJar}")
+println("Using DTest jar: ${dtestVersion}")
 
 // Force checkstyle, rat, and spotBugs to run before test tasks for faster feedback
 def codeCheckTasks = task("codeCheckTasks")
@@ -66,6 +64,9 @@
 
     repositories {
         mavenCentral()
+
+        // for dtest jar
+        mavenLocal()
     }
 
     checkstyle {
@@ -213,8 +214,8 @@
     testFixturesImplementation("io.vertx:vertx-web:${project.vertxVersion}") {
         exclude group: 'junit', module: 'junit'
     }
-    integrationTestImplementation(files("dtest-jars/${dtestJar}"))
-    integrationTestImplementation("org.apache.cassandra:dtest-api:0.0.16")
+    integrationTestImplementation(group: 'org.apache.cassandra', name: "${dtestDependencyName}", version: "${dtestVersion}")
+    integrationTestImplementation(group: 'org.apache.cassandra', name: 'dtest-api', version: "${dtestApiVersion}")
     // Needed by the Cassandra dtest framework
     integrationTestImplementation("org.junit.vintage:junit-vintage-engine:${junitVersion}")
 }
@@ -425,8 +426,9 @@
     reportDir.set(file("build/reports/rat"))
 }
 
-integrationTest.onlyIf { "true" != System.getenv("skipIntegrationTest") }
 compileIntegrationTestJava.onlyIf { "true" != System.getenv("skipIntegrationTest") }
+checkstyleIntegrationTest.onlyIf { "true" != System.getenv("skipIntegrationTest") }
+spotbugsIntegrationTest.onlyIf { "true" != System.getenv("skipIntegrationTest") }
 
 // copyDist gets called on every build
 copyDist.dependsOn installDist, copyJolokia
diff --git a/gradle.properties b/gradle.properties
index ae298ce..481aa45 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -22,3 +22,7 @@
 guavaVersion=27.0.1-jre
 slf4jVersion=1.7.36
 jacksonVersion=2.14.3
+dtestApiVersion=0.0.16
+# trunk is currently 5.1 - update when trunk moves
+dtestVersion=5.1
+dtestDependencyName=cassandra-dtest-local-all
diff --git a/scripts/build-dtest-jars.sh b/scripts/build-dtest-jars.sh
index 07742ac..9d9118d 100755
--- a/scripts/build-dtest-jars.sh
+++ b/scripts/build-dtest-jars.sh
@@ -18,12 +18,24 @@
 #
 
 set -xe
-BRANCHES=( ${BRANCHES:-cassandra-4.0 trunk} )
-CASSANDRA_SHAS_ARRAY=( ${CASSANDRA_SHAS} )
+CANDIDATE_BRANCHES=(
+  "cassandra-4.0:d13b3ef61b9afbd04878c988c7b722507674228c"
+  "cassandra-4.1:8666265521c97a5e726c9d38762028a14325e4dc"
+  "cassandra-5.0:410018ab165b54c378648d52fb4ec815c557e80e"
+  "trunk:cbaef9094e83364e6812c65b8411ff7dbffaf9c6"
+)
+BRANCHES=( ${BRANCHES:-cassandra-4.0 cassandra-4.1 cassandra-5.0 trunk} )
+echo ${BRANCHES[*]}
 REPO=${REPO:-"https://github.com/apache/cassandra.git"}
 SCRIPT_DIR=$( dirname -- "$( readlink -f -- "$0"; )"; )
 DTEST_JAR_DIR="$(dirname "${SCRIPT_DIR}/")/dtest-jars"
 BUILD_DIR="${DTEST_JAR_DIR}/build"
+
+if [[ "x$CLEAN" != "x" ]]; then
+  echo "Clean up $DTEST_JAR_DIR"
+  rm -rf $DTEST_JAR_DIR
+fi
+
 source "$SCRIPT_DIR/functions.sh"
 mkdir -p "${BUILD_DIR}"
 
@@ -32,10 +44,17 @@
 REPO_HOST=$(get_hostname "${REPO}")
 ssh-keyscan "${REPO_HOST}" >> ~/.ssh/known_hosts || true
 
-for index in "${!BRANCHES[@]}"; do
+for index in "${!CANDIDATE_BRANCHES[@]}"; do
   cd "${BUILD_DIR}"
-  branch=${BRANCHES[$index]}
-  sha=${CASSANDRA_SHAS_ARRAY[$index]}
+  branchSha=(${CANDIDATE_BRANCHES[$index]//:/ })
+  branch=${branchSha[0]}
+  sha=${branchSha[1]}
+
+  if ! [[ "${BRANCHES[@]}" =~ "$branch" ]]; then
+    echo "branch ${branch} is not selected to build. The selected branches are ${BRANCHES[*]}"
+    continue
+  fi
+
   echo "index ${index} branch ${branch} sha ${sha}"
   # check out the correct cassandra version:
   if [ ! -d "${branch}" ] ; then
diff --git a/scripts/build-shaded-dtest-jar-local.sh b/scripts/build-shaded-dtest-jar-local.sh
index 9d00ff0..66401ba 100755
--- a/scripts/build-shaded-dtest-jar-local.sh
+++ b/scripts/build-shaded-dtest-jar-local.sh
@@ -20,7 +20,7 @@
 set -xe
 
 ARTIFACT_NAME=cassandra-dtest
-REPO_DIR="$(pwd)/out"
+REPO_DIR="${M2_HOME:-${HOME}/.m2/repository}"
 SCRIPT_DIR=$( dirname -- "$( readlink -f -- "$0"; )"; )
 CASSANDRA_VERSION=$(cat build.xml | grep 'property name="base.version"' | awk -F "\"" '{print $4}')
 GIT_HASH=$(git rev-parse --short HEAD)
@@ -54,4 +54,14 @@
                      -Drelocation.prefix="shaded-${GIT_HASH}"                           \
                      --no-snapshot-updates --update-snapshots
 
+# Install the shaded version
+"${SCRIPT_DIR}/mvnw" install:install-file                                     \
+                     -Dfile="${DTEST_JAR_DIR}/dtest-${CASSANDRA_VERSION}.jar" \
+                     -DgroupId=org.apache.cassandra                           \
+                     -DartifactId="${DTEST_ARTIFACT_ID}-all"                  \
+                     -Dversion="${CASSANDRA_VERSION}"                         \
+                     -Dpackaging=jar                                          \
+                     -DgeneratePom=true                                       \
+                     -DlocalRepositoryPath="${REPO_DIR}"
+
 set +xe
diff --git a/src/test/integration/org/apache/cassandra/testing/TestVersionSupplier.java b/src/test/integration/org/apache/cassandra/testing/TestVersionSupplier.java
index 1f69472..9264c93 100644
--- a/src/test/integration/org/apache/cassandra/testing/TestVersionSupplier.java
+++ b/src/test/integration/org/apache/cassandra/testing/TestVersionSupplier.java
@@ -21,6 +21,9 @@
 import java.util.Arrays;
 import java.util.stream.Stream;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 /**
  * Generates the list of versions we're going to test against.
  * We will run the same module (trunk for example) against multiple versions of Cassandra.
@@ -32,10 +35,13 @@
  */
 public class TestVersionSupplier
 {
+    private static final Logger LOGGER = LoggerFactory.getLogger(TestVersionSupplier.class);
+
     Stream<TestVersion> testVersions()
     {
         // By default, we test 2 versions that will exercise oldest and newest supported versions
         String versions = System.getProperty("cassandra.sidecar.versions_to_test", "4.0,5.1");
+        LOGGER.info("Testing with versions={}", versions);
         return Arrays.stream(versions.split(",")).map(String::trim).map(TestVersion::new);
     }
 }
diff --git a/src/test/java/org/apache/cassandra/sidecar/config/yaml/SSTableUploadConfigurationImplTest.java b/src/test/java/org/apache/cassandra/sidecar/config/yaml/SSTableUploadConfigurationImplTest.java
index 6a2c604..9cca38f 100644
--- a/src/test/java/org/apache/cassandra/sidecar/config/yaml/SSTableUploadConfigurationImplTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/config/yaml/SSTableUploadConfigurationImplTest.java
@@ -43,7 +43,7 @@
     {
         assertThatIllegalArgumentException()
         .isThrownBy(() -> new SSTableUploadConfigurationImpl(value))
-        .withMessage("Invalid filePermissions configuration=\"" + value + "\"");
+        .withMessage("Invalid file_permissions configuration=\"" + value + "\"");
     }
 
     @ParameterizedTest(name = "{index} => valid permission string \"{0}\"")