HBASE-27272: Enable code coverage reporting to SonarQube in hbase-connectors (#99)

Signed-off-by: Balazs Meszaros <meszibalu@apache.org>
diff --git a/dev-support/code-coverage/README.md b/dev-support/code-coverage/README.md
new file mode 100644
index 0000000..b6af086
--- /dev/null
+++ b/dev-support/code-coverage/README.md
@@ -0,0 +1,49 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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.
+-->
+
+# Code analysis
+
+The `run-coverage.sh` script runs maven with the coverage profile which generates the test coverage data for both java
+and scala classes.
+If the required parameters are given it also runs the sonar analysis and uploads the results to the given SonarQube
+Server.
+
+## Running code analysis
+
+After running the script the code coverage results are generated under the `test-reporting/target/code-coverage/`
+folder.
+The JaCoCo code coverage library generated reports can be found under the `jacoco-reports` folder and the SCoverage
+generated results can be found under the `scoverage-reports` folder.
+
+Here is how you can generate the code coverage reports:
+
+```./dev-support/code-coverage/run-coverage.sh```
+
+## Publishing coverage results to SonarQube
+
+The required parameters for publishing the results to SonarQube are:
+
+- host URL,
+- login credentials,
+- project key
+
+The project name is an optional parameter.
+
+Here is an example command for running and publishing the coverage data:
+
+```./dev-support/code-coverage/run-coverage.sh -l ProjectCredentials -u https://exampleserver.com -k Project_Key -n Project_Name```
\ No newline at end of file
diff --git a/dev-support/code-coverage/run-coverage.sh b/dev-support/code-coverage/run-coverage.sh
new file mode 100755
index 0000000..c56d846
--- /dev/null
+++ b/dev-support/code-coverage/run-coverage.sh
@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+
+usage() {
+  echo
+  echo "options:"
+  echo "-h     Display help"
+  echo "-u     SonarQube Host URL"
+  echo "-l     SonarQube Login Credentials"
+  echo "-k     SonarQube Project Key"
+  echo "-n     SonarQube Project Name"
+  echo
+  echo "Important:"
+  echo "    The required parameters for publishing the coverage results to SonarQube:"
+  echo "      - Host URL"
+  echo "      - Login Credentials"
+  echo "      - Project Key"
+  echo
+}
+
+execute() {
+SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
+MAIN_POM="${SCRIPT_DIR}/../../pom.xml"
+
+  mvn -B -e -f "$MAIN_POM" clean install -DskipTests -DskipShade -Pcoverage
+
+  mvn -B -e -f "$MAIN_POM" package -fn -Pcoverage
+
+  # If the required parameters are given, the code coverage results are uploaded to the SonarQube Server
+  if [ -n "$SONAR_LOGIN" ] && [ -n "$SONAR_PROJECT_KEY" ] && [ -n "$SONAR_URL" ]; then
+    mvn -B -e -Pcoverage sonar:sonar -Dsonar.clover.reportPath=./target/clover/clover.xml \
+      -Dsonar.host.url="$SONAR_URL" -Dsonar.login="$SONAR_LOGIN" -Dsonar.projectKey="$SONAR_PROJECT_KEY" -Dsonar.projectName="$SONAR_PROJECT_NAME"
+  fi
+}
+
+while getopts ":u:l:k:n:h" option; do
+  case $option in
+  u) SONAR_URL=${OPTARG:-} ;;
+  l) SONAR_LOGIN=${OPTARG:-} ;;
+  k) SONAR_PROJECT_KEY=${OPTARG:-} ;;
+  n) SONAR_PROJECT_NAME=${OPTARG:-} ;;
+  h) # Display usage
+    usage
+    exit
+    ;;
+  \?) # Invalid option
+    echo "Error: Invalid option"
+    exit
+    ;;
+  esac
+done
+
+# Start code analysis
+execute
diff --git a/pom.xml b/pom.xml
index 4d9bc56..ba889f6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -167,6 +167,10 @@
     <glassfish.el.version>3.0.1-b08</glassfish.el.version>
     <compat.module>hbase-hadoop2-compat</compat.module>
     <dependency.locations.enabled>false</dependency.locations.enabled>
+    <sonar-maven-plugin.version>3.9.1.2184</sonar-maven-plugin.version>
+    <scoverage.version>1.4.11</scoverage.version>
+    <sbt-compiler.version>1.0.0</sbt-compiler.version>
+    <jacoco.version>0.8.8</jacoco.version>
   </properties>
   <dependencyManagement>
     <dependencies>
@@ -713,5 +717,77 @@
         </plugins>
       </build>
     </profile>
+    <!-- this profile runs unit test code coverage analysis -->
+    <profile>
+      <id>coverage</id>
+      <activation>
+        <activeByDefault>false</activeByDefault>
+      </activation>
+      <modules>
+        <module>test-reporting</module>
+      </modules>
+      <properties>
+        <sonar.projectBaseDir>.</sonar.projectBaseDir>
+        <sonar.exclusions>test-reporting/**/*</sonar.exclusions>
+        <sonar.coverage.exclusions>**/example/**/*,**/DumpToStringListener*,**/KafkaProxy*</sonar.coverage.exclusions>
+        <sonar.java.binaries>**/target/classes</sonar.java.binaries>
+        <sonar.surefire.reportsPath>${project.build.directory}/surefire-reports</sonar.surefire.reportsPath>
+        <sonar.coverage.jacoco.xmlReportPaths>
+          ${sonar.projectBaseDir}/test-reporting/target/site/jacoco-aggregate/jacoco.xml
+        </sonar.coverage.jacoco.xmlReportPaths>
+        <sonar.scala.coverage.reportPaths>
+          ${sonar.projectBaseDir}/spark/hbase-spark/target/scoverage.xml
+        </sonar.scala.coverage.reportPaths>
+        <argLine />
+        <main.basedir>${project.basedir}</main.basedir>
+        <codeCoverageReportRootDir>${main.basedir}/test-reporting/target/code-coverage</codeCoverageReportRootDir>
+        <jacocoDestFile>${codeCoverageReportRootDir}/jacoco.exec</jacocoDestFile>
+        <jacocoReportDir>${codeCoverageReportRootDir}/jacoco-reports</jacocoReportDir>
+        <scoverageReportDir>${codeCoverageReportRootDir}/scoverage-reports</scoverageReportDir>
+      </properties>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.jacoco</groupId>
+            <artifactId>jacoco-maven-plugin</artifactId>
+          </plugin>
+          <plugin>
+            <groupId>org.sonarsource.scanner.maven</groupId>
+            <artifactId>sonar-maven-plugin</artifactId>
+            <version>${sonar-maven-plugin.version}</version>
+          </plugin>
+        </plugins>
+
+        <pluginManagement>
+          <plugins>
+            <plugin>
+              <groupId>org.scoverage</groupId>
+              <artifactId>scoverage-maven-plugin</artifactId>
+              <version>${scoverage.version}</version>
+              <configuration>
+                <excludedPackages>src/test/java</excludedPackages>
+                <excludedFiles>IntegrationTest*</excludedFiles>
+              </configuration>
+            </plugin>
+            <plugin>
+              <groupId>org.jacoco</groupId>
+              <artifactId>jacoco-maven-plugin</artifactId>
+              <version>${jacoco.version}</version>
+              <configuration>
+                <excludes>**/example/**/*</excludes>
+              </configuration>
+              <executions>
+                <execution>
+                  <id>prepare-agent</id>
+                  <goals>
+                    <goal>prepare-agent</goal>
+                  </goals>
+                </execution>
+              </executions>
+            </plugin>
+          </plugins>
+        </pluginManagement>
+      </build>
+    </profile>
   </profiles>
 </project>
diff --git a/spark/hbase-spark/pom.xml b/spark/hbase-spark/pom.xml
index 26c5c70..7ac4828 100644
--- a/spark/hbase-spark/pom.xml
+++ b/spark/hbase-spark/pom.xml
@@ -509,6 +509,98 @@
         </plugins>
       </build>
     </profile>
+
+    <profile>
+      <id>coverage</id>
+      <activation>
+        <activeByDefault>false</activeByDefault>
+      </activation>
+      <properties>
+        <sonar.sources>src/main/</sonar.sources>
+        <main.basedir>${project.parent.parent.basedir}</main.basedir>
+      </properties>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>net.alchim31.maven</groupId>
+            <artifactId>scala-maven-plugin</artifactId>
+            <configuration>
+              <testSourceDir>src/test/scala</testSourceDir>
+              <sourceDir>src/main/scala</sourceDir>
+            </configuration>
+            <executions>
+              <execution>
+                <id>default-sbt-compile</id>
+                <goals>
+                  <goal>compile</goal>
+                  <goal>testCompile</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-compiler-plugin</artifactId>
+            <configuration>
+              <skipMain>true</skipMain>
+              <skip>true</skip>
+            </configuration>
+          </plugin>
+          <plugin>
+            <groupId>org.scoverage</groupId>
+            <artifactId>scoverage-maven-plugin</artifactId>
+            <configuration>
+              <scalacPluginVersion>1.4.11</scalacPluginVersion>
+              <highlighting>true</highlighting>
+              <aggregate>true</aggregate>
+              <failOnError>false</failOnError>
+              <encoding>${project.build.sourceEncoding}</encoding>
+              <outputDirectory>${scoverageReportDir}</outputDirectory>
+              <xmlOutputDirectory>${scoverageReportDir}</xmlOutputDirectory>
+            </configuration>
+            <executions>
+              <execution>
+                <id>instrument</id>
+                <goals>
+                  <goal>pre-compile</goal>
+                  <goal>post-compile</goal>
+                </goals>
+              </execution>
+              <execution>
+                <id>package</id>
+                <goals>
+                  <goal>package</goal>
+                  <goal>report</goal>
+                </goals>
+              </execution>
+              <execution>
+                <id>scoverage-report</id>
+                <goals>
+                  <!-- Needed for Sonar -->
+                  <goal>report-only</goal>
+                </goals>
+                <phase>prepare-package</phase>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+      <reporting>
+        <plugins>
+          <plugin>
+            <groupId>org.scoverage</groupId>
+            <artifactId>scoverage-maven-plugin</artifactId>
+            <reportSets>
+              <reportSet>
+                <reports>
+                  <report>report-only</report>
+                </reports>
+              </reportSet>
+            </reportSets>
+          </plugin>
+        </plugins>
+      </reporting>
+    </profile>
   </profiles>
 
 </project>
diff --git a/test-reporting/pom.xml b/test-reporting/pom.xml
new file mode 100644
index 0000000..0a5f5df
--- /dev/null
+++ b/test-reporting/pom.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <!--
+  /**
+   * Licensed to the Apache Software Foundation (ASF) under one
+   * or more contributor license agreements.  See the NOTICE file
+   * distributed with this work for additional information
+   * regarding copyright ownership.  The ASF licenses this file
+   * to you 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.
+   */
+  -->
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <artifactId>hbase-connectors</artifactId>
+    <groupId>org.apache.hbase.connectors</groupId>
+    <version>${revision}</version>
+  </parent>
+
+  <artifactId>test-reporting</artifactId>
+  <packaging>pom</packaging>
+  <version>${revision}</version>
+  <name>Test Reporting</name>
+  <description>Test Reporting for Apache HBase Connectors</description>
+
+  <properties>
+    <argLine/>
+    <main.basedir>${project.parent.basedir}</main.basedir>
+  </properties>
+
+  <dependencies>
+    <!-- module dependencies where coverage is needed -->
+    <dependency>
+      <groupId>org.apache.hbase.connectors.kafka</groupId>
+      <artifactId>hbase-kafka-proxy</artifactId>
+      <version>${revision}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.hbase.connectors.spark</groupId>
+      <artifactId>hbase-spark</artifactId>
+      <version>${revision}</version>
+      <exclusions>
+        <exclusion>
+          <!-- make sure wrong scala version is not pulled in -->
+          <groupId>org.scala-lang</groupId>
+          <artifactId>scala-library</artifactId>
+        </exclusion>
+        <exclusion>
+          <!-- make sure wrong scala version is not pulled in -->
+          <groupId>org.scala-lang</groupId>
+          <artifactId>scalap</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>com.google.code.findbugs</groupId>
+          <artifactId>jsr305</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <version>${surefire.version}</version>
+        <configuration>
+          <argLine>${argLine} -Xms256m -Xmx2048m</argLine>
+          <forkCount>1</forkCount>
+          <runOrder>random</runOrder>
+        </configuration>
+      </plugin>
+    </plugins>
+
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.jacoco</groupId>
+          <artifactId>jacoco-maven-plugin</artifactId>
+          <executions>
+            <execution>
+              <id>report</id>
+              <goals>
+                <goal>report-aggregate</goal>
+              </goals>
+              <phase>package</phase>
+              <configuration>
+                <outputDirectory>${jacocoReportDir}</outputDirectory>
+                <sourceEncoding>${project.build.sourceEncoding}</sourceEncoding>
+                <outputEncoding>${project.reporting.outputEncoding}</outputEncoding>
+              </configuration>
+            </execution>
+          </executions>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+</project>
\ No newline at end of file