Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 171 additions & 0 deletions .github/workflows/sonarqube.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
name: SonarCloud Analysis

on:
pull_request:
branches:
- '*'

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
sonarcloud:
name: SonarCloud Scan
runs-on: ubuntu-latest
steps:
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # Shallow clones should be disabled for better relevancy of analysis

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 17

- name: Gradle cache
uses: gradle/actions/setup-gradle@v3

- name: Configure Gradle memory settings
run: |
echo "org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError" >> gradle.properties
echo "android.enableJetifier=false" >> gradle.properties
echo "android.enableR8.fullMode=false" >> gradle.properties
cat gradle.properties

- name: Build project and run tests with coverage
run: |
# Build the project
./gradlew assembleDebug --stacktrace

# Run tests with coverage - allow test failures
./gradlew testDebugUnitTest jacocoTestReport --stacktrace
TEST_RESULT=$?
if [ $TEST_RESULT -ne 0 ]; then
echo "Some tests failed, but continuing to check for coverage data..."
# Even if tests fail, JaCoCo should generate a report with partial coverage
# from the tests that did pass
fi

# Check if the report was generated with content
if [ -f build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml ]; then
# Use stat command compatible with both Linux and macOS
if [[ "$(uname)" == "Darwin" ]]; then
# macOS syntax
REPORT_SIZE=$(stat -f%z build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml)
else
# Linux syntax
REPORT_SIZE=$(stat -c%s build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml)
fi

if [ "$REPORT_SIZE" -lt 1000 ]; then
echo "JaCoCo report is too small ($REPORT_SIZE bytes), likely empty. Trying to generate from test execution data..."
# Try to generate the report directly from the exec file
if [ -f build/jacoco/testDebugUnitTest.exec ]; then
java -jar ~/.gradle/caches/modules-2/files-2.1/org.jacoco/org.jacoco.cli/0.8.8/*/org.jacoco.cli-0.8.8.jar report build/jacoco/testDebugUnitTest.exec \
--classfiles build/intermediates/runtime_library_classes_dir/debug \
--sourcefiles src/main/java \
--xml build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml

# Check if the report was successfully generated
if [[ "$(uname)" == "Darwin" ]]; then
# macOS syntax
NEW_REPORT_SIZE=$(stat -f%z build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml)
else
# Linux syntax
NEW_REPORT_SIZE=$(stat -c%s build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml)
fi

if [ "$NEW_REPORT_SIZE" -lt 1000 ]; then
echo "ERROR: Failed to generate a valid JaCoCo report with coverage data"
exit 1
else
echo "JaCoCo report successfully generated with size $NEW_REPORT_SIZE bytes"
fi
else
echo "ERROR: No JaCoCo execution data file found. Tests may not have run correctly."
exit 1
fi
else
echo "JaCoCo report generated successfully with size $REPORT_SIZE bytes"
fi
else
echo "ERROR: JaCoCo report file not found. Coverage data is missing."
exit 1
fi

- name: Prepare class files for SonarQube analysis
run: |
echo "Searching for compiled class files..."

# Create the target directory
mkdir -p build/intermediates/runtime_library_classes_dir/debug

# Find all directories containing class files
CLASS_DIRS=$(find build -name "*.class" -type f -exec dirname {} \; | sort -u)

if [ -z "$CLASS_DIRS" ]; then
echo "WARNING: No class files found in the build directory!"
else
echo "Found class files in the following directories:"
echo "$CLASS_DIRS"

# Copy classes from the first directory with classes
FIRST_CLASS_DIR=$(echo "$CLASS_DIRS" | head -1)
echo "Copying classes from $FIRST_CLASS_DIR to build/intermediates/runtime_library_classes_dir/debug"
cp -r $FIRST_CLASS_DIR/* build/intermediates/runtime_library_classes_dir/debug/ || echo "Failed to copy from $FIRST_CLASS_DIR"

# Verify the target directory now has class files
CLASS_COUNT=$(find build/intermediates/runtime_library_classes_dir/debug -name "*.class" | wc -l)
echo "Target directory now contains $CLASS_COUNT class files"
fi

# Update sonar-project.properties with all found class directories as a fallback
echo "\n# Additional binary paths found during build" >> sonar-project.properties
echo "sonar.java.binaries=build/intermediates/runtime_library_classes_dir/debug,$CLASS_DIRS" >> sonar-project.properties

echo "Checking for JaCoCo report files..."
find build -name "*.xml" | grep jacoco || echo "No XML files found"
find build -name "*.exec" | grep jacoco || echo "No exec files found"
echo "Contents of JaCoCo report directory:"
ls -la build/reports/jacoco/jacocoTestReport/ || echo "Directory not found"

echo "\nChecking test execution results:"
find build -name "TEST-*.xml" | xargs cat | grep -E 'tests|failures|errors|skipped' || echo "No test result files found"

echo "\nChecking JaCoCo report content:"
if [ -f build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml ]; then
echo "Report file size: $(wc -c < build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml) bytes"
echo "First 500 chars of report:"
head -c 500 build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml
echo "\n\nCounting coverage elements:"
grep -c "<package" build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml || echo "No packages found"
grep -c "<class" build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml || echo "No classes found"
grep -c "<method" build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml || echo "No methods found"
grep -c "<line" build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml || echo "No lines found"
grep -c 'ci="1"' build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml || echo "No covered lines found"
else
echo "JaCoCo report file not found"
fi

echo "\nChecking binary directories specified in sonar-project.properties:"
echo "build/intermediates/runtime_library_classes_dir/debug:"
ls -la build/intermediates/runtime_library_classes_dir/debug || echo "Directory not found"

echo "\nChecking all available class directories:"
find build -path "*/build/*" -name "*.class" | head -n 5 || echo "No class files found"

- name: SonarCloud Scan
uses: SonarSource/sonarqube-scan-action@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information
SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONARQUBE_HOST }}
25 changes: 25 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,24 @@ apply plugin: 'maven-publish'
apply plugin: 'signing'
apply plugin: 'kotlin-android'
apply from: 'spec.gradle'
apply from: 'jacoco.gradle'


ext {
splitVersion = '5.3.0'
jacocoVersion = '0.8.8'
}

// Define exclusions for JaCoCo coverage
def coverageExclusions = [
'**/R.class',
'**/R$*.class',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*Test*.*',
'android/**/*.*'
]

android {
compileSdk 33
targetCompatibility = '1.8'
Expand Down Expand Up @@ -63,6 +76,14 @@ android {

testOptions {
unitTests.returnDefaultValues = true

// Configure JaCoCo for all test tasks
unitTests.all {
jacoco {
includeNoLocationClasses = true
excludes = ['jdk.internal.*']
}
}
}

sourceSets {
Expand Down Expand Up @@ -334,3 +355,7 @@ tasks.withType(Test) {
forkEvery = 100
maxHeapSize = "1024m"
}




115 changes: 115 additions & 0 deletions jacoco.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Jacoco configuration extracted from build.gradle

apply plugin: 'jacoco'

jacoco {
toolVersion = '0.8.8'
}

// Enable JaCoCo for the test task
tasks.withType(Test) {
// Enable JaCoCo coverage
jacoco {
includeNoLocationClasses = true
excludes = ['jdk.internal.*']
}
finalizedBy jacocoTestReport
}

// Unit test coverage report task
// This task generates Jacoco coverage reports for unit tests only
tasks.register('jacocoTestReport', JacocoReport) {
dependsOn 'testDebugUnitTest'

reports {
xml.required = true
html.required = true
csv.required = false
}

def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
def debugTree = fileTree(dir: "${buildDir}/intermediates/javac/debug/classes", excludes: fileFilter)

// Make sure to include all source directories
sourceDirectories.from = files(['src/main/java'])

// Include all class files except excluded ones
classDirectories.from = files([debugTree])

// Include execution data files
executionData.from(fileTree(dir: "$buildDir", includes: [
'jacoco/testDebugUnitTest.exec',
'outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec'
]))

outputs.upToDateWhen { false }

doFirst {
// Log a warning if no execution data files exist
def execFiles = executionData.files
if (execFiles.isEmpty() || !execFiles.any { it.exists() }) {
logger.warn("JaCoCo will not generate coverage report because no execution data files were found")
} else {
execFiles.each { file ->
if (file.exists()) {
logger.lifecycle("Found JaCoCo execution data file: $file")
}
}
}
}
}

// Task to ensure JaCoCo XML report is created in the exact location SonarQube expects
task generateJacocoXmlReport {
doLast {
// Create the directory if it doesn't exist
def reportDir = file("${buildDir}/reports/jacoco/jacocoTestReport")
reportDir.mkdirs()

// Check if we have a JaCoCo exec file
def execFiles = fileTree(dir: "${buildDir}", includes: ['**/*.exec'])
if (execFiles.isEmpty()) {
// Create an empty report file if no exec files found
def reportFile = new File(reportDir, "jacocoTestReport.xml")
reportFile.text = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<report name=\"android-client\">\n</report>"
println "Created empty JaCoCo report at ${reportFile.absolutePath}"
} else {
println "Found JaCoCo exec files: ${execFiles.files}"
// If we have exec files but no report, we could use JaCoCo's ant task to generate one
// This is a simplified version - in a real scenario you'd use the JaCoCo ant task
}

// Check if the report exists and log its content
def reportFile = new File(reportDir, "jacocoTestReport.xml")
if (reportFile.exists()) {
println "\n==== JaCoCo Report Content ===="
println "Report file size: ${reportFile.length()} bytes"

if (reportFile.length() > 0) {
def xmlContent = reportFile.text
println "First 500 chars of report: ${xmlContent.take(500)}..."

// Count packages, classes, and methods
def packageCount = (xmlContent =~ /<package/).count
def classCount = (xmlContent =~ /<class/).count
def methodCount = (xmlContent =~ /<method/).count
def lineCount = (xmlContent =~ /<line/).count

println "Report contains:\n - ${packageCount} packages\n - ${classCount} classes\n - ${methodCount} methods\n - ${lineCount} lines"

// Check for covered lines
def coveredLineCount = (xmlContent =~ /ci=\"1\"/).count
println " - ${coveredLineCount} covered lines"

if (coveredLineCount == 0) {
println "WARNING: No covered lines found in the report!"
}
} else {
println "Report file is empty!"
}
println "===========================\n"
} else {
println "Report file does not exist at ${reportFile.absolutePath}"
}
}
}
25 changes: 25 additions & 0 deletions sonar-project.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Required metadata
sonar.projectKey=splitio_android-client
sonar.projectName=android-client

# Path to source directories
sonar.sources=src/main/java

# Path to compiled classes
sonar.java.binaries=build/intermediates/runtime_library_classes_dir/debug

# Path to test directories
sonar.tests=src/test/java,src/androidTest/java,src/sharedTest/java

# Encoding of the source code
sonar.sourceEncoding=UTF-8

# Include test coverage reports - prioritize combined report
sonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml

# Exclusions
sonar.exclusions=**/R.class,**/R$*.class,**/BuildConfig.*,**/Manifest*.*,**/*Test*.*,android/**/*.*

# Java specific configuration
sonar.java.source=1.8
sonar.java.target=1.8
Loading