From 371167f335cb55e8ca446c35565a0851d85b8750 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 2 Jun 2025 09:45:02 -0300 Subject: [PATCH 01/21] Sonar config --- .github/workflows/sonarqube.yml | 83 +++++++++++++++++++++++++++++++++ build.gradle | 69 +++++++++++++++++++++++++++ sonar-project.properties | 23 +++++++++ 3 files changed, 175 insertions(+) create mode 100644 .github/workflows/sonarqube.yml create mode 100644 sonar-project.properties diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml new file mode 100644 index 000000000..aedfd1125 --- /dev/null +++ b/.github/workflows/sonarqube.yml @@ -0,0 +1,83 @@ +name: SonarCloud Analysis + +on: + push: + branches: + - '*' + 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 + cache: 'gradle' + + - name: Gradle cache + uses: gradle/actions/setup-gradle@v3 + + - name: Run unit tests with coverage (continue on error) + run: ./gradlew test jacocoTestReport --continue + continue-on-error: true + + - name: AVD cache + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-28 + + - name: Create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 28 + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + script: echo "Generated AVD snapshot for caching." + + - name: Assemble debug AndroidTest + run: ./gradlew assembleDebugAndroidTest + + - name: Run instrumented tests with coverage (continue on error) + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 28 + profile: Galaxy Nexus + script: ./gradlew connectedDebugAndroidTest jacocoAndroidTestReport --continue + continue-on-error: true + + - name: Generate combined coverage report + run: ./gradlew jacocoCombinedReport + + - 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 }} diff --git a/build.gradle b/build.gradle index 7646953c5..8ef1c4bcc 100644 --- a/build.gradle +++ b/build.gradle @@ -14,10 +14,12 @@ apply plugin: 'com.android.library' apply plugin: 'maven-publish' apply plugin: 'signing' apply plugin: 'kotlin-android' +apply plugin: 'jacoco' apply from: 'spec.gradle' ext { splitVersion = '5.3.0' + jacocoVersion = '0.8.8' } android { @@ -80,6 +82,7 @@ android { debug { buildConfigField("String", "SPLIT_VERSION_NAME", "\"${splitVersion}\"") buildConfigField("String", "FLAGS_SPEC", "\"${flagsSpec}\"") + testCoverageEnabled true } release { buildConfigField("String", "SPLIT_VERSION_NAME", "\"${splitVersion}\"") @@ -334,3 +337,69 @@ tasks.withType(Test) { forkEvery = 100 maxHeapSize = "1024m" } + +// JaCoCo configuration +jacoco { + toolVersion = jacocoVersion +} + +// Unit test coverage report task +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) + + sourceDirectories.from(files(['src/main/java'])) + classDirectories.from(files([debugTree])) + executionData.from(fileTree(dir: "$buildDir", includes: [ + 'jacoco/testDebugUnitTest.exec' + ])) +} + +// Android instrumented test coverage report task +tasks.register('jacocoAndroidTestReport', JacocoReport) { + dependsOn 'connectedDebugAndroidTest' + + 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) + + sourceDirectories.from(files(['src/main/java'])) + classDirectories.from(files([debugTree])) + executionData.from(fileTree(dir: "$buildDir", includes: [ + 'outputs/code_coverage/debugAndroidTest/connected/*coverage.ec' + ])) +} + +// Combined coverage report task +tasks.register('jacocoCombinedReport', JacocoReport) { + dependsOn 'testDebugUnitTest', 'connectedDebugAndroidTest' + + 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) + + sourceDirectories.from(files(['src/main/java'])) + classDirectories.from(files([debugTree])) + executionData.from(fileTree(dir: "$buildDir", includes: [ + 'jacoco/testDebugUnitTest.exec', + 'outputs/code_coverage/debugAndroidTest/connected/*coverage.ec' + ])) +} diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 000000000..6af192bd7 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,23 @@ +# Required metadata +sonar.projectKey=splitio_android-client +sonar.projectName=android-client + +# Path to source directories +sonar.sources=src/main/java +sonar.java.binaries=build/intermediates/javac/debug/classes + +# 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 +sonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml,build/reports/jacoco/jacocoCombinedReport/jacocoCombinedReport.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 From 20663f0dcb59084cc0566bb47581b6ccea928729 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 30 May 2025 16:46:30 -0300 Subject: [PATCH 02/21] Try to avoid skipped tasks --- build.gradle | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/build.gradle b/build.gradle index 8ef1c4bcc..f1c72a1fb 100644 --- a/build.gradle +++ b/build.gradle @@ -361,6 +361,9 @@ tasks.register('jacocoTestReport', JacocoReport) { executionData.from(fileTree(dir: "$buildDir", includes: [ 'jacoco/testDebugUnitTest.exec' ])) + + // Ensure this task always runs and doesn't get skipped + outputs.upToDateWhen { false } } // Android instrumented test coverage report task @@ -381,6 +384,9 @@ tasks.register('jacocoAndroidTestReport', JacocoReport) { executionData.from(fileTree(dir: "$buildDir", includes: [ 'outputs/code_coverage/debugAndroidTest/connected/*coverage.ec' ])) + + // Ensure this task always runs and doesn't get skipped + outputs.upToDateWhen { false } } // Combined coverage report task @@ -402,4 +408,7 @@ tasks.register('jacocoCombinedReport', JacocoReport) { 'jacoco/testDebugUnitTest.exec', 'outputs/code_coverage/debugAndroidTest/connected/*coverage.ec' ])) + + // Ensure this task always runs and doesn't get skipped + outputs.upToDateWhen { false } } From 0229ae0ccb8f84c17f337750b230236dcd763f91 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 30 May 2025 18:09:21 -0300 Subject: [PATCH 03/21] WIP --- .github/workflows/sonarqube.yml | 57 +++++++++++++++++---------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index aedfd1125..b07589261 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -42,38 +42,39 @@ jobs: run: ./gradlew test jacocoTestReport --continue continue-on-error: true - - name: AVD cache - uses: actions/cache@v4 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-28 + # Commented out Android instrumented test part to focus on unit test flow + # - name: AVD cache + # uses: actions/cache@v4 + # id: avd-cache + # with: + # path: | + # ~/.android/avd/* + # ~/.android/adb* + # key: avd-28 - - name: Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: 28 - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - script: echo "Generated AVD snapshot for caching." + # - name: Create AVD and generate snapshot for caching + # if: steps.avd-cache.outputs.cache-hit != 'true' + # uses: reactivecircus/android-emulator-runner@v2 + # with: + # api-level: 28 + # force-avd-creation: false + # emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + # disable-animations: true + # script: echo "Generated AVD snapshot for caching." - - name: Assemble debug AndroidTest - run: ./gradlew assembleDebugAndroidTest + # - name: Assemble debug AndroidTest + # run: ./gradlew assembleDebugAndroidTest - - name: Run instrumented tests with coverage (continue on error) - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: 28 - profile: Galaxy Nexus - script: ./gradlew connectedDebugAndroidTest jacocoAndroidTestReport --continue - continue-on-error: true + # - name: Run instrumented tests with coverage (continue on error) + # uses: reactivecircus/android-emulator-runner@v2 + # with: + # api-level: 28 + # profile: Galaxy Nexus + # script: ./gradlew connectedDebugAndroidTest jacocoAndroidTestReport --continue + # continue-on-error: true - - name: Generate combined coverage report - run: ./gradlew jacocoCombinedReport + # - name: Generate combined coverage report + # run: ./gradlew jacocoCombinedReport - name: SonarCloud Scan uses: SonarSource/sonarqube-scan-action@v5 From 67b93e3e6d76416924378bbc0d3539848c195e09 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 30 May 2025 18:30:02 -0300 Subject: [PATCH 04/21] Report path --- .github/workflows/sonarqube.yml | 10 +++++++++- build.gradle | 28 ++++++++++++++++++++++++++++ sonar-project.properties | 2 +- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index b07589261..1e2401e29 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -39,8 +39,16 @@ jobs: uses: gradle/actions/setup-gradle@v3 - name: Run unit tests with coverage (continue on error) - run: ./gradlew test jacocoTestReport --continue + run: ./gradlew test jacocoTestReport generateJacocoXmlReport --continue continue-on-error: true + + - name: Debug - Check for JaCoCo report files + run: | + 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" # Commented out Android instrumented test part to focus on unit test flow # - name: AVD cache diff --git a/build.gradle b/build.gradle index f1c72a1fb..7c86f6c97 100644 --- a/build.gradle +++ b/build.gradle @@ -343,6 +343,34 @@ jacoco { toolVersion = jacocoVersion } +// Enable JaCoCo for the test task +tasks.withType(Test) { + jacoco.includeNoLocationClasses = true + jacoco.excludes = ['jdk.internal.*'] +} + +// 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 = "\n\n" + 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 + } + } +} + // Unit test coverage report task tasks.register('jacocoTestReport', JacocoReport) { dependsOn 'testDebugUnitTest' diff --git a/sonar-project.properties b/sonar-project.properties index 6af192bd7..b9ebef8b5 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -13,7 +13,7 @@ sonar.tests=src/test/java,src/androidTest/java,src/sharedTest/java sonar.sourceEncoding=UTF-8 # Include test coverage reports -sonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml,build/reports/jacoco/jacocoCombinedReport/jacocoCombinedReport.xml +sonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml,build/reports/jacoco/jacocoCombinedReport/jacocoCombinedReport.xml,build/reports/jacoco/test/jacocoTestReport.xml,target/site/jacoco/jacoco.xml # Exclusions sonar.exclusions=**/R.class,**/R$*.class,**/BuildConfig.*,**/Manifest*.*,**/*Test*.*,android/**/*.* From 5576837e90f65d879cb255f8b754b6a3b21131a6 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 2 Jun 2025 09:59:25 -0300 Subject: [PATCH 05/21] Debug cov --- .github/workflows/sonarqube.yml | 20 +++++++++- build.gradle | 69 ++++++++++++++++++++++++++++++--- 2 files changed, 82 insertions(+), 7 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 1e2401e29..88e3e20a7 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -39,7 +39,7 @@ jobs: uses: gradle/actions/setup-gradle@v3 - name: Run unit tests with coverage (continue on error) - run: ./gradlew test jacocoTestReport generateJacocoXmlReport --continue + run: ./gradlew clean test jacocoTestReport generateJacocoXmlReport --debug continue-on-error: true - name: Debug - Check for JaCoCo report files @@ -49,6 +49,24 @@ jobs: 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 " 0) { + def xmlContent = reportFile.text + println "First 500 chars of report: ${xmlContent.take(500)}..." + + // Count packages, classes, and methods + def packageCount = (xmlContent =~ / + if (!file.exists()) { + logger.warn("JaCoCo execution data file not found: $file") + } else { + logger.lifecycle("Found JaCoCo execution data file: $file") + } + } + } } // Android instrumented test coverage report task From 5d46d7f9595979bd4aa7907cb51a68bf93746091 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 2 Jun 2025 10:15:03 -0300 Subject: [PATCH 06/21] Debug 2 --- .github/workflows/sonarqube.yml | 10 +++++++++- sonar-project.properties | 5 ++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 88e3e20a7..1c2700ca8 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -42,7 +42,7 @@ jobs: run: ./gradlew clean test jacocoTestReport generateJacocoXmlReport --debug continue-on-error: true - - name: Debug - Check for JaCoCo report files + - name: Debug - Check for JaCoCo report files and binary directories run: | echo "Checking for JaCoCo report files..." find build -name "*.xml" | grep jacoco || echo "No XML files found" @@ -67,6 +67,14 @@ jobs: else echo "JaCoCo report file not found" fi + + echo "\nChecking binary directories specified in sonar-project.properties:" + echo "build/intermediates/javac/debug/classes:" + ls -la build/intermediates/javac/debug/classes || echo "Directory not found" + echo "\nbuild/intermediates/javac/debugUnitTest/classes:" + ls -la build/intermediates/javac/debugUnitTest/classes || echo "Directory not found" + echo "\nbuild/intermediates/compile_library_classes_jar/debug/:" + ls -la build/intermediates/compile_library_classes_jar/debug/ || echo "Directory not found" # Commented out Android instrumented test part to focus on unit test flow # - name: AVD cache diff --git a/sonar-project.properties b/sonar-project.properties index b9ebef8b5..4e77f118d 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -4,7 +4,10 @@ sonar.projectName=android-client # Path to source directories sonar.sources=src/main/java -sonar.java.binaries=build/intermediates/javac/debug/classes + +# Path to compiled classes +sonar.java.binaries=build/intermediates/javac/debug/classes,build/intermediates/javac/debugUnitTest/classes +sonar.java.libraries=build/intermediates/compile_library_classes_jar/debug/*.jar # Path to test directories sonar.tests=src/test/java,src/androidTest/java,src/sharedTest/java From 074ff63c2bf1ccb71961722d330962adaa31d07e Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 2 Jun 2025 10:57:50 -0300 Subject: [PATCH 07/21] Add binaries dir --- sonar-project.properties | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sonar-project.properties b/sonar-project.properties index 4e77f118d..13326687c 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -6,8 +6,7 @@ sonar.projectName=android-client sonar.sources=src/main/java # Path to compiled classes -sonar.java.binaries=build/intermediates/javac/debug/classes,build/intermediates/javac/debugUnitTest/classes -sonar.java.libraries=build/intermediates/compile_library_classes_jar/debug/*.jar +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 From a83423e8c03263b6f917383aa496fa974ff333fd Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 2 Jun 2025 11:09:34 -0300 Subject: [PATCH 08/21] Fix --- .github/workflows/sonarqube.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 1c2700ca8..9d75867c1 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -38,8 +38,8 @@ jobs: - name: Gradle cache uses: gradle/actions/setup-gradle@v3 - - name: Run unit tests with coverage (continue on error) - run: ./gradlew clean test jacocoTestReport generateJacocoXmlReport --debug + - name: Build project and run tests with coverage + run: ./gradlew clean assembleDebug test jacocoTestReport generateJacocoXmlReport continue-on-error: true - name: Debug - Check for JaCoCo report files and binary directories @@ -69,12 +69,11 @@ jobs: fi echo "\nChecking binary directories specified in sonar-project.properties:" - echo "build/intermediates/javac/debug/classes:" - ls -la build/intermediates/javac/debug/classes || echo "Directory not found" - echo "\nbuild/intermediates/javac/debugUnitTest/classes:" - ls -la build/intermediates/javac/debugUnitTest/classes || echo "Directory not found" - echo "\nbuild/intermediates/compile_library_classes_jar/debug/:" - ls -la build/intermediates/compile_library_classes_jar/debug/ || echo "Directory not found" + 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" # Commented out Android instrumented test part to focus on unit test flow # - name: AVD cache From 0357bd60b22b15dfea8fa8df9a72e73c4a9f3354 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 2 Jun 2025 11:18:26 -0300 Subject: [PATCH 09/21] Test --- .github/workflows/sonarqube.yml | 7 +++++-- sonar-project.properties | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 9d75867c1..d5c891427 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -38,8 +38,11 @@ jobs: - name: Gradle cache uses: gradle/actions/setup-gradle@v3 - - name: Build project and run tests with coverage - run: ./gradlew clean assembleDebug test jacocoTestReport generateJacocoXmlReport + - name: Build project + run: ./gradlew assembleDebug --stacktrace + + - name: Run tests with coverage + run: ./gradlew test jacocoTestReport generateJacocoXmlReport --stacktrace continue-on-error: true - name: Debug - Check for JaCoCo report files and binary directories diff --git a/sonar-project.properties b/sonar-project.properties index 13326687c..01c1176dd 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -5,8 +5,8 @@ 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 compiled classes - using wildcards to be more resilient +sonar.java.binaries=**/*.class # Path to test directories sonar.tests=src/test/java,src/androidTest/java,src/sharedTest/java From 1048975bd3eb05a79c70d7bf1655de5a1e4cb819 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 2 Jun 2025 11:45:34 -0300 Subject: [PATCH 10/21] Fix --- build.gradle | 56 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/build.gradle b/build.gradle index 90323f5b1..6e3a0ef41 100644 --- a/build.gradle +++ b/build.gradle @@ -349,8 +349,6 @@ tasks.withType(Test) { jacoco { includeNoLocationClasses = true excludes = ['jdk.internal.*'] - // Ensure JaCoCo agent is always attached - append = true } finalizedBy jacocoTestReport } @@ -429,23 +427,25 @@ tasks.register('jacocoTestReport', JacocoReport) { // Include all class files except excluded ones classDirectories.from = files([debugTree]) - // Include all execution data files - executionData.from = files(fileTree(dir: "$buildDir", includes: [ + // Include execution data files + executionData.from(fileTree(dir: "$buildDir", includes: [ 'jacoco/testDebugUnitTest.exec', - 'outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec', - '**/*.exec' + 'outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec' ])) // Ensure this task always runs and doesn't get skipped outputs.upToDateWhen { false } doFirst { - // Ensure we have execution data files - executionData.from.each { file -> - if (!file.exists()) { - logger.warn("JaCoCo execution data file not found: $file") - } else { - logger.lifecycle("Found JaCoCo execution data file: $file") + // 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") + } } } } @@ -462,7 +462,7 @@ tasks.register('jacocoAndroidTestReport', JacocoReport) { } def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*'] - def debugTree = fileTree(dir: "${buildDir}/intermediates/javac/debug/classes", excludes: fileFilter) + def debugTree = fileTree(dir: "${buildDir}/intermediates/runtime_library_classes_dir/debug", excludes: fileFilter) sourceDirectories.from(files(['src/main/java'])) classDirectories.from(files([debugTree])) @@ -472,6 +472,20 @@ tasks.register('jacocoAndroidTestReport', JacocoReport) { // Ensure this task always runs and doesn't get skipped 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") + } + } + } + } } // Combined coverage report task @@ -485,7 +499,7 @@ tasks.register('jacocoCombinedReport', JacocoReport) { } def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*'] - def debugTree = fileTree(dir: "${buildDir}/intermediates/javac/debug/classes", excludes: fileFilter) + def debugTree = fileTree(dir: "${buildDir}/intermediates/runtime_library_classes_dir/debug", excludes: fileFilter) sourceDirectories.from(files(['src/main/java'])) classDirectories.from(files([debugTree])) @@ -496,4 +510,18 @@ tasks.register('jacocoCombinedReport', JacocoReport) { // Ensure this task always runs and doesn't get skipped 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") + } + } + } + } } From 43646d10e1efa369ca0b6e340c6a4d14d072a537 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 2 Jun 2025 12:03:58 -0300 Subject: [PATCH 11/21] Handle report error --- .github/workflows/sonarqube.yml | 49 +++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index d5c891427..81f8665e4 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -38,12 +38,49 @@ jobs: - name: Gradle cache uses: gradle/actions/setup-gradle@v3 - - name: Build project - run: ./gradlew assembleDebug --stacktrace - - - name: Run tests with coverage - run: ./gradlew test jacocoTestReport generateJacocoXmlReport --stacktrace - continue-on-error: true + - 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 || { + 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 + } + + # Check if the report was generated with content + if [ -f build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml ]; then + REPORT_SIZE=$(stat -f%z build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml) + 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 + NEW_REPORT_SIZE=$(stat -f%z build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml) + 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: Debug - Check for JaCoCo report files and binary directories run: | From a8318df6948d20e1b3243943140ce66b61b0e6db Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 2 Jun 2025 12:19:24 -0300 Subject: [PATCH 12/21] Stat --- .github/workflows/sonarqube.yml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 81f8665e4..1feee0fea 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -52,7 +52,15 @@ jobs: # Check if the report was generated with content if [ -f build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml ]; then - REPORT_SIZE=$(stat -f%z build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml) + # 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 @@ -63,7 +71,14 @@ jobs: --xml build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml # Check if the report was successfully generated - NEW_REPORT_SIZE=$(stat -f%z build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml) + 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 From 5e255d6eb1dd81d7df80a4e2fe89d24ac2fecac0 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 2 Jun 2025 12:44:55 -0300 Subject: [PATCH 13/21] Specific path --- .github/workflows/sonarqube.yml | 30 +++++++++++++++++++++++++++++- sonar-project.properties | 4 ++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 1feee0fea..6528bb9b4 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -97,8 +97,36 @@ jobs: exit 1 fi - - name: Debug - Check for JaCoCo report files and binary directories + - 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" diff --git a/sonar-project.properties b/sonar-project.properties index 01c1176dd..13326687c 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -5,8 +5,8 @@ sonar.projectName=android-client # Path to source directories sonar.sources=src/main/java -# Path to compiled classes - using wildcards to be more resilient -sonar.java.binaries=**/*.class +# 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 From b8fb2a7247cd0533db92d49d8a2158edbf94a2ba Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 2 Jun 2025 16:33:25 -0300 Subject: [PATCH 14/21] Test --- .github/workflows/sonarqube.yml | 167 ++++++++++++++++++++++++++------ build.gradle | 118 ++++++++++++++++++++-- sonar-project.properties | 4 +- 3 files changed, 251 insertions(+), 38 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 6528bb9b4..4aaf693b9 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -158,39 +158,146 @@ jobs: echo "\nChecking all available class directories:" find build -path "*/build/*" -name "*.class" | head -n 5 || echo "No class files found" - # Commented out Android instrumented test part to focus on unit test flow - # - name: AVD cache - # uses: actions/cache@v4 - # id: avd-cache - # with: - # path: | - # ~/.android/avd/* - # ~/.android/adb* - # key: avd-28 - - # - name: Create AVD and generate snapshot for caching - # if: steps.avd-cache.outputs.cache-hit != 'true' - # uses: reactivecircus/android-emulator-runner@v2 - # with: - # api-level: 28 - # force-avd-creation: false - # emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - # disable-animations: true - # script: echo "Generated AVD snapshot for caching." + # Android instrumented test section for comprehensive coverage + - name: AVD cache + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-28 - # - name: Assemble debug AndroidTest - # run: ./gradlew assembleDebugAndroidTest + - name: Create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 28 + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + script: echo "Generated AVD snapshot for caching." + + - name: Assemble debug AndroidTest + run: ./gradlew assembleDebugAndroidTest - # - name: Run instrumented tests with coverage (continue on error) - # uses: reactivecircus/android-emulator-runner@v2 - # with: - # api-level: 28 - # profile: Galaxy Nexus - # script: ./gradlew connectedDebugAndroidTest jacocoAndroidTestReport --continue - # continue-on-error: true + - name: Run instrumented tests with coverage + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 28 + profile: Galaxy Nexus + script: | + # Run instrumented tests with coverage - allow test failures + # Use a more direct approach to ensure coverage is enabled + ./gradlew assembleDebugAndroidTest + + # Run only a single test class with coverage explicitly enabled + adb shell am instrument -w -e coverage true -e class tests.database.DatabaseInitializationTest io.split.android.android_client.test/androidx.test.runner.AndroidJUnitRunner || { + echo "Some instrumented tests failed, but continuing to check for coverage data..." + } + + # Create directory for coverage files + mkdir -p build/outputs/code_coverage/debugAndroidTest/connected/ + + # Try to find and pull coverage files from various possible locations + echo "Searching for coverage files..." + + # Check app-specific data directory + adb shell run-as io.split.android.android_client find /data/data/io.split.android.android_client -name "*.ec" | while read -r file; do + echo "Found coverage file: $file" + filename=$(basename "$file") + adb shell run-as io.split.android.android_client cat "$file" > "build/outputs/code_coverage/debugAndroidTest/connected/$filename" + echo "Pulled coverage file to build/outputs/code_coverage/debugAndroidTest/connected/$filename" + done + + # Also check sdcard location + adb pull /sdcard/coverage.ec build/outputs/code_coverage/debugAndroidTest/connected/ || echo "No coverage file at /sdcard/coverage.ec" + + # List all coverage files to verify + find build -name "*.ec" -o -name "*.exec" + + # Run the JaCoCo report task + ./gradlew jacocoAndroidTestReport || { + echo "Failed to generate Android test coverage report, but continuing..." + } + + # Check if the Android test report was generated with content + if [ -f build/reports/jacoco/jacocoAndroidTestReport/jacocoAndroidTestReport.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/jacocoAndroidTestReport/jacocoAndroidTestReport.xml) + else + # Linux syntax + REPORT_SIZE=$(stat -c%s build/reports/jacoco/jacocoAndroidTestReport/jacocoAndroidTestReport.xml) + fi + + echo "Android test JaCoCo report size: $REPORT_SIZE bytes" + + if [ "$REPORT_SIZE" -lt 1000 ]; then + echo "WARNING: Android test JaCoCo report is too small, likely empty" + else + echo "Android test JaCoCo report generated successfully" + fi + else + echo "WARNING: Android test JaCoCo report file not found" + fi + continue-on-error: true - # - name: Generate combined coverage report - # run: ./gradlew jacocoCombinedReport + - name: Generate combined coverage report + run: | + # Generate combined report - allow failures + ./gradlew jacocoCombinedReport || { + echo "Failed to generate combined report, but continuing..." + } + + # Check if the combined report was generated with content + if [ -f build/reports/jacoco/jacocoCombinedReport/jacocoCombinedReport.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/jacocoCombinedReport/jacocoCombinedReport.xml) + else + # Linux syntax + REPORT_SIZE=$(stat -c%s build/reports/jacoco/jacocoCombinedReport/jacocoCombinedReport.xml) + fi + + echo "Combined JaCoCo report size: $REPORT_SIZE bytes" + + if [ "$REPORT_SIZE" -lt 1000 ]; then + echo "WARNING: Combined JaCoCo report is too small, likely empty" + # Try to manually combine the reports + mkdir -p build/reports/jacoco/jacocoCombinedReport/ + cp build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml build/reports/jacoco/jacocoCombinedReport/jacocoCombinedReport.xml + else + echo "Combined JaCoCo report generated successfully" + fi + else + echo "WARNING: Combined JaCoCo report file not found, using unit test report as fallback" + mkdir -p build/reports/jacoco/jacocoCombinedReport/ + cp build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml build/reports/jacoco/jacocoCombinedReport/jacocoCombinedReport.xml + fi + continue-on-error: true + + - name: Check combined coverage report + run: | + echo "Checking for all coverage data files:" + find build -name "*.exec" -o -name "*.ec" | sort + + echo "\nChecking combined JaCoCo report content:" + if [ -f build/reports/jacoco/jacocoCombinedReport/jacocoCombinedReport.xml ]; then + echo "Report file size: $(wc -c < build/reports/jacoco/jacocoCombinedReport/jacocoCombinedReport.xml) bytes" + echo "First 500 chars of report:" + head -c 500 build/reports/jacoco/jacocoCombinedReport/jacocoCombinedReport.xml + echo "\n\nCounting coverage elements:" + grep -c " + if (file.exists()) { + logger.lifecycle("Found JaCoCo execution data file: $file") + } + } + } + } +} + +// Keep the original tasks for backward compatibility tasks.register('jacocoAndroidTestReport', JacocoReport) { dependsOn 'connectedDebugAndroidTest' @@ -461,13 +564,14 @@ tasks.register('jacocoAndroidTestReport', JacocoReport) { csv.required = false } - def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*'] - def debugTree = fileTree(dir: "${buildDir}/intermediates/runtime_library_classes_dir/debug", excludes: fileFilter) + def debugTree = fileTree(dir: "${buildDir}/intermediates/runtime_library_classes_dir/debug", excludes: coverageExclusions) sourceDirectories.from(files(['src/main/java'])) classDirectories.from(files([debugTree])) executionData.from(fileTree(dir: "$buildDir", includes: [ - 'outputs/code_coverage/debugAndroidTest/connected/*coverage.ec' + 'outputs/code_coverage/debugAndroidTest/connected/*coverage.ec', + '**/*.exec', + '**/*.ec' ])) // Ensure this task always runs and doesn't get skipped @@ -505,7 +609,9 @@ tasks.register('jacocoCombinedReport', JacocoReport) { classDirectories.from(files([debugTree])) executionData.from(fileTree(dir: "$buildDir", includes: [ 'jacoco/testDebugUnitTest.exec', - 'outputs/code_coverage/debugAndroidTest/connected/*coverage.ec' + 'outputs/code_coverage/debugAndroidTest/connected/*coverage.ec', + '**/*.exec', + '**/*.ec' ])) // Ensure this task always runs and doesn't get skipped diff --git a/sonar-project.properties b/sonar-project.properties index 13326687c..8e6d67f94 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -14,8 +14,8 @@ sonar.tests=src/test/java,src/androidTest/java,src/sharedTest/java # Encoding of the source code sonar.sourceEncoding=UTF-8 -# Include test coverage reports -sonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml,build/reports/jacoco/jacocoCombinedReport/jacocoCombinedReport.xml,build/reports/jacoco/test/jacocoTestReport.xml,target/site/jacoco/jacoco.xml +# Include test coverage reports - prioritize combined report +sonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/jacocoCombinedReport/jacocoCombinedReport.xml,build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml,build/reports/jacoco/jacocoAndroidTestReport/jacocoAndroidTestReport.xml,build/reports/jacoco/test/jacocoTestReport.xml,target/site/jacoco/jacoco.xml # Exclusions sonar.exclusions=**/R.class,**/R$*.class,**/BuildConfig.*,**/Manifest*.*,**/*Test*.*,android/**/*.* From f70e2df1a9aef964f594e85d3f0857cdd8488bec Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 2 Jun 2025 17:18:48 -0300 Subject: [PATCH 15/21] Fix syntax error --- .github/workflows/sonarqube.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 4aaf693b9..b8c795cca 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -44,11 +44,13 @@ jobs: ./gradlew assembleDebug --stacktrace # Run tests with coverage - allow test failures - ./gradlew testDebugUnitTest jacocoTestReport --stacktrace || { + ./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 @@ -192,9 +194,11 @@ jobs: ./gradlew assembleDebugAndroidTest # Run only a single test class with coverage explicitly enabled - adb shell am instrument -w -e coverage true -e class tests.database.DatabaseInitializationTest io.split.android.android_client.test/androidx.test.runner.AndroidJUnitRunner || { + adb shell am instrument -w -e coverage true -e class tests.database.DatabaseInitializationTest io.split.android.android_client.test/androidx.test.runner.AndroidJUnitRunner + TEST_RESULT=$? + if [ $TEST_RESULT -ne 0 ]; then echo "Some instrumented tests failed, but continuing to check for coverage data..." - } + fi # Create directory for coverage files mkdir -p build/outputs/code_coverage/debugAndroidTest/connected/ @@ -217,9 +221,11 @@ jobs: find build -name "*.ec" -o -name "*.exec" # Run the JaCoCo report task - ./gradlew jacocoAndroidTestReport || { + ./gradlew jacocoAndroidTestReport + REPORT_RESULT=$? + if [ $REPORT_RESULT -ne 0 ]; then echo "Failed to generate Android test coverage report, but continuing..." - } + fi # Check if the Android test report was generated with content if [ -f build/reports/jacoco/jacocoAndroidTestReport/jacocoAndroidTestReport.xml ]; then From 890c1f67339264ea528f1cb2e79186823ff34481 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 2 Jun 2025 18:00:55 -0300 Subject: [PATCH 16/21] Autodetect package --- .github/workflows/sonarqube.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index b8c795cca..acb45bc93 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -193,8 +193,12 @@ jobs: # Use a more direct approach to ensure coverage is enabled ./gradlew assembleDebugAndroidTest + # First, let's determine the correct test package name + TEST_PACKAGE=$(adb shell pm list instrumentation | grep split | cut -d' ' -f1 | cut -d: -f2) + echo "Found test instrumentation package: $TEST_PACKAGE" + # Run only a single test class with coverage explicitly enabled - adb shell am instrument -w -e coverage true -e class tests.database.DatabaseInitializationTest io.split.android.android_client.test/androidx.test.runner.AndroidJUnitRunner + adb shell am instrument -w -e coverage true -e class tests.database.DatabaseInitializationTest $TEST_PACKAGE TEST_RESULT=$? if [ $TEST_RESULT -ne 0 ]; then echo "Some instrumented tests failed, but continuing to check for coverage data..." From 647f5959b7311379a6cac354745227eda021cec1 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 3 Jun 2025 10:41:47 -0300 Subject: [PATCH 17/21] Debug logs --- .github/workflows/sonarqube.yml | 71 ++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 6 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index acb45bc93..f9cec8282 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -194,23 +194,60 @@ jobs: ./gradlew assembleDebugAndroidTest # First, let's determine the correct test package name + echo "\n[DEBUG] Listing all available instrumentation packages:" + adb shell pm list instrumentation + TEST_PACKAGE=$(adb shell pm list instrumentation | grep split | cut -d' ' -f1 | cut -d: -f2) - echo "Found test instrumentation package: $TEST_PACKAGE" + echo "\n[DEBUG] Found test instrumentation package: $TEST_PACKAGE" + + # Check if the test APK is installed properly + echo "\n[DEBUG] Checking installed packages:" + adb shell pm list packages | grep split + + # Verify test class exists + echo "\n[DEBUG] Checking if test class exists:" + ./gradlew -q listTestClasses | grep DatabaseInitializationTest || echo "Test class not found in Gradle test classes" # Run only a single test class with coverage explicitly enabled + echo "\n[DEBUG] Running instrumentation with explicit coverage flag:" adb shell am instrument -w -e coverage true -e class tests.database.DatabaseInitializationTest $TEST_PACKAGE TEST_RESULT=$? if [ $TEST_RESULT -ne 0 ]; then echo "Some instrumented tests failed, but continuing to check for coverage data..." + else + echo "\n[DEBUG] Instrumentation completed successfully" fi # Create directory for coverage files mkdir -p build/outputs/code_coverage/debugAndroidTest/connected/ # Try to find and pull coverage files from various possible locations - echo "Searching for coverage files..." + echo "\n[DEBUG] Searching for coverage files..." + + # Check if coverage is enabled in the app manifest + echo "\n[DEBUG] Checking test AndroidManifest.xml for coverage settings:" + adb shell pm dump $TEST_PACKAGE | grep -i coverage - # Check app-specific data directory + # Check for files in external storage + echo "\n[DEBUG] Checking external storage for coverage files:" + adb shell ls -la /sdcard/ | grep -i coverage || echo "No coverage files found in /sdcard/" + + # Check app-specific data directory - try both package names + echo "\n[DEBUG] Checking app-specific data directory for coverage files:" + APP_PACKAGE=$(adb shell pm list packages | grep split | cut -d: -f2 | head -1) + echo "\n[DEBUG] App package name: $APP_PACKAGE" + + # Try with the detected app package + echo "\n[DEBUG] Searching in /data/data/$APP_PACKAGE/:" + adb shell run-as $APP_PACKAGE find /data/data/$APP_PACKAGE -name "*.ec" | while read -r file; do + echo "Found coverage file: $file" + filename=$(basename "$file") + adb shell run-as $APP_PACKAGE cat "$file" > "build/outputs/code_coverage/debugAndroidTest/connected/$filename" + echo "Pulled coverage file to build/outputs/code_coverage/debugAndroidTest/connected/$filename" + done + + # Also try with the hardcoded package name as fallback + echo "\n[DEBUG] Searching in /data/data/io.split.android.android_client/:" adb shell run-as io.split.android.android_client find /data/data/io.split.android.android_client -name "*.ec" | while read -r file; do echo "Found coverage file: $file" filename=$(basename "$file") @@ -219,16 +256,38 @@ jobs: done # Also check sdcard location + echo "\n[DEBUG] Checking /sdcard/ location:" adb pull /sdcard/coverage.ec build/outputs/code_coverage/debugAndroidTest/connected/ || echo "No coverage file at /sdcard/coverage.ec" + # Try alternative locations + echo "\n[DEBUG] Checking alternative locations for coverage files:" + adb pull /data/local/tmp/coverage.ec build/outputs/code_coverage/debugAndroidTest/connected/ || echo "No coverage file at /data/local/tmp/coverage.ec" + # List all coverage files to verify + echo "\n[DEBUG] Listing all found coverage files:" find build -name "*.ec" -o -name "*.exec" - # Run the JaCoCo report task - ./gradlew jacocoAndroidTestReport + # Run the JaCoCo report task with debug info + echo "\n[DEBUG] Running JaCoCo Android test report task with debug info:" + ./gradlew jacocoAndroidTestReport --debug > jacoco_debug.log REPORT_RESULT=$? + + # Save the important parts of the debug log + echo "\n[DEBUG] Extracting coverage-related info from debug log:" + grep -A 10 -B 2 "JaCoCo" jacoco_debug.log > jacoco_important.log || echo "No JaCoCo info found in debug log" + grep -A 5 -B 2 "coverage" jacoco_debug.log >> jacoco_important.log || echo "No coverage info found in debug log" + grep -A 3 -B 1 "exec" jacoco_debug.log >> jacoco_important.log || echo "No exec info found in debug log" + grep -A 3 -B 1 "ec" jacoco_debug.log >> jacoco_important.log || echo "No .ec info found in debug log" + + echo "\n[DEBUG] Important JaCoCo debug info:" + cat jacoco_important.log + if [ $REPORT_RESULT -ne 0 ]; then - echo "Failed to generate Android test coverage report, but continuing..." + echo "\n[DEBUG] Failed to generate Android test coverage report, but continuing..." + echo "\n[DEBUG] Last 20 lines of debug log:" + tail -20 jacoco_debug.log + else + echo "\n[DEBUG] JaCoCo Android test report generated successfully" fi # Check if the Android test report was generated with content From ec0148b0ecad9f6d39590ee580f9160854322b58 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 3 Jun 2025 10:46:33 -0300 Subject: [PATCH 18/21] Fixed script --- .github/workflows/sonarqube.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index f9cec8282..7d931b100 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -210,7 +210,7 @@ jobs: # Run only a single test class with coverage explicitly enabled echo "\n[DEBUG] Running instrumentation with explicit coverage flag:" - adb shell am instrument -w -e coverage true -e class tests.database.DatabaseInitializationTest $TEST_PACKAGE + adb shell "am instrument -w -e coverage true -e class tests.database.DatabaseInitializationTest $TEST_PACKAGE" TEST_RESULT=$? if [ $TEST_RESULT -ne 0 ]; then echo "Some instrumented tests failed, but continuing to check for coverage data..." @@ -316,9 +316,11 @@ jobs: - name: Generate combined coverage report run: | # Generate combined report - allow failures - ./gradlew jacocoCombinedReport || { + ./gradlew jacocoCombinedReport + COMBINED_RESULT=$? + if [ $COMBINED_RESULT -ne 0 ]; then echo "Failed to generate combined report, but continuing..." - } + fi # Check if the combined report was generated with content if [ -f build/reports/jacoco/jacocoCombinedReport/jacocoCombinedReport.xml ]; then From 38c02edae264b6aa10a8641f28a9191dcb8a9c7c Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 3 Jun 2025 11:01:03 -0300 Subject: [PATCH 19/21] Fix --- .github/workflows/sonarqube.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 7d931b100..6619f78d2 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -316,7 +316,7 @@ jobs: - name: Generate combined coverage report run: | # Generate combined report - allow failures - ./gradlew jacocoCombinedReport + ./gradlew jacocoCodeCoverageReport COMBINED_RESULT=$? if [ $COMBINED_RESULT -ne 0 ]; then echo "Failed to generate combined report, but continuing..." From 6bf9b5d63cdb48a8b7d3013f9f9b36dee5338721 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 3 Jun 2025 11:53:45 -0300 Subject: [PATCH 20/21] Remove dependency on connectedCheck --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index e912f5d99..7c9b5564e 100644 --- a/build.gradle +++ b/build.gradle @@ -511,8 +511,8 @@ tasks.register('jacocoCodeCoverageReport', JacocoReport) { group = 'Reporting' description = 'Generate Jacoco coverage report for both unit and instrumented tests' - // Depend on both unit and instrumented tests - dependsOn 'testDebugUnitTest', 'connectedDebugAndroidTest' + // Only depend on unit tests - instrumented tests should be run separately + dependsOn 'testDebugUnitTest' reports { xml.required = true From bd629de626648e177a999191cf3f315486c97899 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 3 Jun 2025 14:11:25 -0300 Subject: [PATCH 21/21] Increase memory --- .github/workflows/sonarqube.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 6619f78d2..fcd96beab 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -37,6 +37,13 @@ jobs: - 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: |