From e500548cbddfbc41be4c825997880b16c118843c Mon Sep 17 00:00:00 2001 From: AR Abdul Azeez Date: Mon, 13 Oct 2025 13:08:28 -0500 Subject: [PATCH 1/5] adding test coverage --- OneSignalSDK/build.gradle | 3 + OneSignalSDK/jacoco.gradle | 252 +++++++++++++++++++++++++++++++++++++ 2 files changed, 255 insertions(+) create mode 100644 OneSignalSDK/jacoco.gradle diff --git a/OneSignalSDK/build.gradle b/OneSignalSDK/build.gradle index 977c232ed..b6e79bff2 100644 --- a/OneSignalSDK/build.gradle +++ b/OneSignalSDK/build.gradle @@ -56,3 +56,6 @@ allprojects { maven { url 'https://developer.huawei.com/repo/' } } } + +// Apply JaCoCo configuration from separate file +apply from: 'jacoco.gradle' diff --git a/OneSignalSDK/jacoco.gradle b/OneSignalSDK/jacoco.gradle new file mode 100644 index 000000000..a25233c2d --- /dev/null +++ b/OneSignalSDK/jacoco.gradle @@ -0,0 +1,252 @@ +// JaCoCo Test Coverage Configuration +// This file contains all JaCoCo-related configuration for test coverage reporting + +subprojects { + // Apply JaCoCo to all modules with tests + plugins.withId("com.android.library") { + apply plugin: 'jacoco' + + jacoco { + toolVersion = "0.8.11" + } + + android { + buildTypes { + debug { + testCoverageEnabled = true + } + } + } + + def coverageExcludes = [ + // Android generated + '**/R.class', + '**/R$*.class', + '**/BuildConfig.*', + '**/Manifest*.*', + 'android/**/*.*', + + // Test files (just in case) + '**/*Test*.*', + '**/*Mock*.*', + '**/test/**/*.*', + '**/androidTest/**/*.*', + + // View binding & injection + '**/*$ViewInjector*.*', + '**/*$ViewBinder*.*', + '**/*Binding.*', + + // Kotlin lambdas & synthetic + '**/Lambda$*.class', + '**/Lambda.class', + '**/*Lambda.class', + '**/*Lambda*.class', + '**/*$inlined$*.class', + + // Dagger/Hilt generated + '**/*_MembersInjector.class', + '**/Dagger*.*', + '**/*_Factory.*', + '**/*_Provide*Factory.*', + '**/*Module.*', + '**/*Module_*.*', + '**/*Component.*', + '**/*Component$*.*', + '**/*Subcomponent*.*', + '**/Hilt_*.*', + '**/*_HiltModules*.*', + + // Data classes & enums (often no logic to test) + '**/*$Companion.class', + + // Sealed classes + '**/*$WhenMappings.class' + ] + + tasks.register('jacocoTestReport', JacocoReport) { + dependsOn 'testDebugUnitTest' + + reports { + xml.required = true + html.required = true + csv.required = false + } + + def javaClasses = fileTree(dir: "$buildDir/intermediates/javac/debug", excludes: coverageExcludes) + def kotlinClasses = fileTree(dir: "$buildDir/tmp/kotlin-classes/debug", excludes: coverageExcludes) + + classDirectories.from = files([javaClasses, kotlinClasses]) + + sourceDirectories.from = files([ + "$projectDir/src/main/java", + "$projectDir/src/main/kotlin" + ]) + + executionData.from = fileTree(dir: buildDir, includes: [ + 'outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec', + 'jacoco/testDebugUnitTest.exec' + ]) + } + + tasks.register('jacocoTestCoverageVerification', JacocoCoverageVerification) { + dependsOn 'jacocoTestReport' + + def javaClasses = fileTree(dir: "$buildDir/intermediates/javac/debug", excludes: coverageExcludes) + def kotlinClasses = fileTree(dir: "$buildDir/tmp/kotlin-classes/debug", excludes: coverageExcludes) + + classDirectories.from = files([javaClasses, kotlinClasses]) + + sourceDirectories.from = files([ + "$projectDir/src/main/java", + "$projectDir/src/main/kotlin" + ]) + + executionData.from = fileTree(dir: buildDir, includes: [ + 'outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec', + 'jacoco/testDebugUnitTest.exec' + ]) + + violationRules { + rule { + // Start with baseline - we'll gradually increase these thresholds + enabled = false // Disabled for now - just generate reports, don't enforce + element = 'CLASS' + + limit { + counter = 'LINE' + value = 'COVEREDRATIO' + minimum = 0.00 // Baseline: 0% - increase this as coverage improves + } + + limit { + counter = 'BRANCH' + value = 'COVEREDRATIO' + minimum = 0.00 // Baseline: 0% - increase this as coverage improves + } + } + } + } + } +} + +// Task to run coverage on all modules +tasks.register('jacocoTestReportAll') { + group = 'verification' + description = 'Generate JaCoCo test coverage reports for all modules' + + subprojects.each { subproject -> + subproject.plugins.withId('com.android.library') { + dependsOn "${subproject.path}:jacocoTestReport" + } + } +} + +// Task to verify coverage on all modules +tasks.register('jacocoTestCoverageVerificationAll') { + group = 'verification' + description = 'Verify JaCoCo test coverage is above threshold for all modules' + + subprojects.each { subproject -> + subproject.plugins.withId('com.android.library') { + dependsOn "${subproject.path}:jacocoTestCoverageVerification" + } + } +} + +// Task to print coverage summary for CI/CD +tasks.register('jacocoTestReportSummary') { + group = 'verification' + description = 'Print JaCoCo test coverage summary for all modules (useful for CI/CD)' + + dependsOn 'jacocoTestReportAll' + + doLast { + def totalInstructions = 0 + def coveredInstructions = 0 + def totalBranches = 0 + def coveredBranches = 0 + def totalLines = 0 + def coveredLines = 0 + + println("\n" + "=".multiply(80)) + println("JaCoCo Test Coverage Summary") + println("=".multiply(80)) + + subprojects.each { subproject -> + subproject.plugins.withId('com.android.library') { + def reportFile = file("${subproject.buildDir}/reports/jacoco/jacocoTestReport/jacocoTestReport.xml") + + if (reportFile.exists()) { + def parser = new XmlSlurper() + parser.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false) + parser.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false) + def report = parser.parse(reportFile) + def moduleName = subproject.name + + def instructions = report.counter.find { it.@type == 'INSTRUCTION' } + def branches = report.counter.find { it.@type == 'BRANCH' } + def lines = report.counter.find { it.@type == 'LINE' } + + if (instructions) { + def missed = instructions.@missed.toInteger() + def covered = instructions.@covered.toInteger() + def total = missed + covered + def percentage = total > 0 ? (covered * 100.0 / total) : 0 + + totalInstructions += total + coveredInstructions += covered + + println("\nModule: ${moduleName}") + println(" Instructions: ${covered}/${total} (${String.format('%.2f', percentage)}%)") + } + + if (branches) { + def missed = branches.@missed.toInteger() + def covered = branches.@covered.toInteger() + def total = missed + covered + def percentage = total > 0 ? (covered * 100.0 / total) : 0 + + totalBranches += total + coveredBranches += covered + + println(" Branches: ${covered}/${total} (${String.format('%.2f', percentage)}%)") + } + + if (lines) { + def missed = lines.@missed.toInteger() + def covered = lines.@covered.toInteger() + def total = missed + covered + def percentage = total > 0 ? (covered * 100.0 / total) : 0 + + totalLines += total + coveredLines += covered + + println(" Lines: ${covered}/${total} (${String.format('%.2f', percentage)}%)") + } + + println(" Report: file://${reportFile.parentFile}/html/index.html") + } + } + } + + if (totalInstructions > 0) { + def overallInstructionPercent = (coveredInstructions * 100.0 / totalInstructions) + def overallBranchPercent = totalBranches > 0 ? (coveredBranches * 100.0 / totalBranches) : 0 + def overallLinePercent = totalLines > 0 ? (coveredLines * 100.0 / totalLines) : 0 + + println("\n" + "=".multiply(80)) + println("Overall Coverage") + println("=".multiply(80)) + println("Instructions: ${coveredInstructions}/${totalInstructions} (${String.format('%.2f', overallInstructionPercent)}%)") + println("Branches: ${coveredBranches}/${totalBranches} (${String.format('%.2f', overallBranchPercent)}%)") + println("Lines: ${coveredLines}/${totalLines} (${String.format('%.2f', overallLinePercent)}%)") + println("=".multiply(80) + "\n") + + // CI-friendly output + println("::set-output name=coverage::${String.format('%.2f', overallLinePercent)}") + println("COVERAGE_PERCENTAGE=${String.format('%.2f', overallLinePercent)}") + } + } +} + From 3f76a160dfa8b2a7c1f702a7b3a9ce63abd10a03 Mon Sep 17 00:00:00 2001 From: AR Abdul Azeez Date: Thu, 30 Oct 2025 13:17:04 -0400 Subject: [PATCH 2/5] jacoco and diff --- .github/actions/project-setup/action.yml | 30 +++++++++++++++ .github/workflows/ci.yml | 23 +++++------ .github/workflows/diff-coverage.yml | 45 ++++++++++++++++++++++ OneSignalSDK/jacoco.gradle | 49 ++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 14 deletions(-) create mode 100644 .github/actions/project-setup/action.yml create mode 100644 .github/workflows/diff-coverage.yml diff --git a/.github/actions/project-setup/action.yml b/.github/actions/project-setup/action.yml new file mode 100644 index 000000000..18bceda33 --- /dev/null +++ b/.github/actions/project-setup/action.yml @@ -0,0 +1,30 @@ +runs: + using: composite + steps: + - name: Checkout (full history) + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + with: + cmdline-tools-version: '10406996' + log-accepted-android-sdk-licenses: false + + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/gradle/libs.versions.toml') }} + restore-keys: | + ${{ runner.os }}-gradle- + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4643b9215..8fbf53fdf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Test +name: Build and Test SDK on: pull_request: @@ -8,21 +8,16 @@ jobs: build: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 - - name: "[Setup] Java" - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - - name: "[Setup] Android" - uses: android-actions/setup-android@v3 - with: - cmdline-tools-version: 10406996 - log-accepted-android-sdk-licenses: false - - name: "[Test] Linting" + - name: "[Setup] Project" + uses: ./.github/actions/project-setup + - name: "[Code Formatting] Spotless" + working-directory: OneSignalSDK + run: | + ./gradlew spotlessCheck --console=plain + - name: "[Static Code Analysis] Detekt" working-directory: OneSignalSDK run: | - ./gradlew ktlintCheck --console=plain + ./gradlew detekt --console=plain - name: "[Test] SDK Unit Tests" working-directory: OneSignalSDK run: | diff --git a/.github/workflows/diff-coverage.yml b/.github/workflows/diff-coverage.yml new file mode 100644 index 000000000..7201c1bff --- /dev/null +++ b/.github/workflows/diff-coverage.yml @@ -0,0 +1,45 @@ +name: Diff Coverage + +on: + pull_request: + branches: "**" + workflow_dispatch: + +env: + DIFF_COVERAGE_THRESHOLD: '80' + +jobs: + diff-coverage: + name: Run Diff Coverage Check + runs-on: ubuntu-latest + steps: + - name: "[Setup] Project" + uses: ./.github/actions/project-setup + + - name: Run Unit Tests with Coverage + working-directory: OneSignalSDK + run: | + ./gradlew --no-daemon clean testDebugUnitTest jacocoTestReportAll jacocoMergedReport + + - name: Diff lines coverage + uses: zgosalvez/github-actions-diff-lines-coverage@v0.2.0 + with: + jacoco-paths: | + OneSignalSDK/build/reports/jacoco/merged/jacocoMergedReport.xml + min-coverage: ${{ env.DIFF_COVERAGE_THRESHOLD }} + # Set to false to not fail the job but still post results; we want failure on miss + fail-under-coverage: true + + - name: Upload Diff Coverage Report + uses: actions/upload-artifact@v4 + with: + name: diff-coverage-report + path: OneSignalSDK/diff_coverage.html + + - name: Upload JaCoCo Reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: jacoco-full-report + path: OneSignalSDK/build/reports/jacoco/ + diff --git a/OneSignalSDK/jacoco.gradle b/OneSignalSDK/jacoco.gradle index a25233c2d..c7b551de2 100644 --- a/OneSignalSDK/jacoco.gradle +++ b/OneSignalSDK/jacoco.gradle @@ -154,6 +154,55 @@ tasks.register('jacocoTestCoverageVerificationAll') { } } +// Merged XML report across all Android library modules for CI tools (e.g., diff-cover) +tasks.register('jacocoMergedReport', JacocoReport) { + group = 'verification' + description = 'Generate a single merged JaCoCo XML report across all modules' + + dependsOn 'jacocoTestReportAll' + + reports { + xml.required = true + html.required = false + csv.required = false + xml.outputLocation = file("${buildDir}/reports/jacoco/merged/jacocoMergedReport.xml") + } + + def allClassDirs = [] + def allSourceDirs = [] + def allExecData = [] + + subprojects.each { subproject -> + subproject.plugins.withId('com.android.library') { + // Class and source directories + def javaClasses = subproject.fileTree(dir: "${subproject.buildDir}/intermediates/javac/debug", excludes: [ + '**/R.class','**/R$*.class','**/BuildConfig.*','**/Manifest*.*','android/**/*.*', + '**/*Test*.*','**/*Mock*.*','**/test/**/*.*','**/androidTest/**/*.*', + '**/*$ViewInjector*.*','**/*$ViewBinder*.*','**/*Binding.*', + '**/Lambda$*.class','**/Lambda.class','**/*Lambda.class','**/*Lambda*.class','**/*$inlined$*.class', + '**/*_MembersInjector.class','**/Dagger*.*','**/*_Factory.*','**/*_Provide*Factory.*','**/*Module.*','**/*Module_*.*','**/*Component.*','**/*Component$*.*','**/*Subcomponent*.*','**/Hilt_*.*','**/*_HiltModules*.*', + '**/*$Companion.class','**/*$WhenMappings.class' + ]) + def kotlinClasses = subproject.fileTree(dir: "${subproject.buildDir}/tmp/kotlin-classes/debug", excludes: javaClasses.excludes) + allClassDirs << javaClasses + allClassDirs << kotlinClasses + + allSourceDirs << subproject.file("${subproject.projectDir}/src/main/java") + allSourceDirs << subproject.file("${subproject.projectDir}/src/main/kotlin") + + // Exec data from unit tests + allExecData << subproject.fileTree(dir: subproject.buildDir, includes: [ + 'outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec', + 'jacoco/testDebugUnitTest.exec' + ]) + } + } + + classDirectories.from = files(allClassDirs) + sourceDirectories.from = files(allSourceDirs) + executionData.from = files(allExecData) +} + // Task to print coverage summary for CI/CD tasks.register('jacocoTestReportSummary') { group = 'verification' From 30d50db1f77a48248ab55fd6c90c4352b3b14975 Mon Sep 17 00:00:00 2001 From: AR Abdul Azeez Date: Thu, 30 Oct 2025 13:34:22 -0400 Subject: [PATCH 3/5] using custom diff --- .github/workflows/diff-coverage.yml | 43 +++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/.github/workflows/diff-coverage.yml b/.github/workflows/diff-coverage.yml index 7201c1bff..85383b8fc 100644 --- a/.github/workflows/diff-coverage.yml +++ b/.github/workflows/diff-coverage.yml @@ -21,20 +21,33 @@ jobs: run: | ./gradlew --no-daemon clean testDebugUnitTest jacocoTestReportAll jacocoMergedReport - - name: Diff lines coverage - uses: zgosalvez/github-actions-diff-lines-coverage@v0.2.0 + - name: Set up Python + uses: actions/setup-python@v5 with: - jacoco-paths: | - OneSignalSDK/build/reports/jacoco/merged/jacocoMergedReport.xml - min-coverage: ${{ env.DIFF_COVERAGE_THRESHOLD }} - # Set to false to not fail the job but still post results; we want failure on miss - fail-under-coverage: true + python-version: '3.x' - - name: Upload Diff Coverage Report - uses: actions/upload-artifact@v4 - with: - name: diff-coverage-report - path: OneSignalSDK/diff_coverage.html + - name: Install diff-cover + run: | + python -m pip install --upgrade pip diff-cover + + - name: Diff Coverage (fail under threshold) + working-directory: OneSignalSDK + run: | + REPORT=build/reports/jacoco/merged/jacocoMergedReport.xml + test -f "$REPORT" || { echo "Merged JaCoCo report not found at $REPORT" >&2; exit 1; } + python -m diff_cover.diff_cover_tool "$REPORT" \ + --compare-branch=origin/main \ + --fail-under=$DIFF_COVERAGE_THRESHOLD + + - name: Generate HTML Diff Report + if: always() + working-directory: OneSignalSDK + run: | + REPORT=build/reports/jacoco/merged/jacocoMergedReport.xml + test -f "$REPORT" || { echo "Merged JaCoCo report not found at $REPORT" >&2; exit 1; } + python -m diff_cover.diff_cover_tool "$REPORT" \ + --compare-branch=origin/main \ + --html-report diff_coverage.html || true - name: Upload JaCoCo Reports if: always() @@ -43,3 +56,9 @@ jobs: name: jacoco-full-report path: OneSignalSDK/build/reports/jacoco/ + - name: Upload Diff Coverage Report + if: always() + uses: actions/upload-artifact@v4 + with: + name: diff-coverage-report + path: OneSignalSDK/diff_coverage.html \ No newline at end of file From 39ad4d24390331ce6d4c595031e43e3d4ea51b50 Mon Sep 17 00:00:00 2001 From: AR Abdul Azeez Date: Thu, 30 Oct 2025 13:38:37 -0400 Subject: [PATCH 4/5] coverage with checkout --- .github/workflows/ci.yml | 2 ++ .github/workflows/diff-coverage.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ecb40ea94..d6e09b3bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,8 @@ jobs: build: runs-on: ubuntu-24.04 steps: + - name: "[Checkout] Repo" + uses: actions/checkout@v4 - name: "[Setup] Project" uses: ./.github/actions/project-setup - name: "[Code Formatting] Spotless" diff --git a/.github/workflows/diff-coverage.yml b/.github/workflows/diff-coverage.yml index 85383b8fc..bb15af163 100644 --- a/.github/workflows/diff-coverage.yml +++ b/.github/workflows/diff-coverage.yml @@ -13,6 +13,8 @@ jobs: name: Run Diff Coverage Check runs-on: ubuntu-latest steps: + - name: "[Checkout] Repo" + uses: actions/checkout@v4 - name: "[Setup] Project" uses: ./.github/actions/project-setup From cb2e853a34ae8134f70c6fed5f4e7faf958bb10e Mon Sep 17 00:00:00 2001 From: AR Abdul Azeez Date: Thu, 30 Oct 2025 13:53:16 -0400 Subject: [PATCH 5/5] diff cover in one workflow --- .github/workflows/ci.yml | 35 +++++++++++++-- .github/workflows/diff-coverage.yml | 66 ----------------------------- 2 files changed, 31 insertions(+), 70 deletions(-) delete mode 100644 .github/workflows/diff-coverage.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d6e09b3bd..1ba3736d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,9 @@ on: pull_request: branches: "**" +env: + DIFF_COVERAGE_THRESHOLD: '80' + jobs: build: runs-on: ubuntu-24.04 @@ -17,10 +20,6 @@ jobs: run: | ./gradlew spotlessCheck --console=plain - name: "[Static Code Analysis] Detekt" - working-directory: OneSignalSDK - run: | - ./gradlew spotlessCheck --console=plain - - name: "[Test] Detekt" working-directory: OneSignalSDK run: | ./gradlew detekt --console=plain @@ -28,6 +27,34 @@ jobs: working-directory: OneSignalSDK run: | ./gradlew testReleaseUnitTest --console=plain --continue + - name: "[Coverage] Generate JaCoCo merged XML" + working-directory: OneSignalSDK + run: | + ./gradlew jacocoTestReportAll jacocoMergedReport --console=plain --continue + - name: "[Setup] Python" + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: "[Diff Coverage] Install diff-cover" + run: | + python -m pip install --upgrade pip diff-cover + - name: "[Diff Coverage] Check and HTML report" + working-directory: OneSignalSDK + run: | + REPORT=build/reports/jacoco/merged/jacocoMergedReport.xml + test -f "$REPORT" || { echo "Merged JaCoCo report not found at $REPORT" >&2; exit 1; } + python -m diff_cover.diff_cover_tool "$REPORT" \ + --compare-branch=origin/main \ + --fail-under=$DIFF_COVERAGE_THRESHOLD + python -m diff_cover.diff_cover_tool "$REPORT" \ + --compare-branch=origin/main \ + --html-report diff_coverage.html || true + - name: Upload diff coverage HTML + if: always() + uses: actions/upload-artifact@v4 + with: + name: diff-coverage-report + path: OneSignalSDK/diff_coverage.html - name: Unit tests results if: failure() uses: actions/upload-artifact@v4 diff --git a/.github/workflows/diff-coverage.yml b/.github/workflows/diff-coverage.yml deleted file mode 100644 index bb15af163..000000000 --- a/.github/workflows/diff-coverage.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: Diff Coverage - -on: - pull_request: - branches: "**" - workflow_dispatch: - -env: - DIFF_COVERAGE_THRESHOLD: '80' - -jobs: - diff-coverage: - name: Run Diff Coverage Check - runs-on: ubuntu-latest - steps: - - name: "[Checkout] Repo" - uses: actions/checkout@v4 - - name: "[Setup] Project" - uses: ./.github/actions/project-setup - - - name: Run Unit Tests with Coverage - working-directory: OneSignalSDK - run: | - ./gradlew --no-daemon clean testDebugUnitTest jacocoTestReportAll jacocoMergedReport - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.x' - - - name: Install diff-cover - run: | - python -m pip install --upgrade pip diff-cover - - - name: Diff Coverage (fail under threshold) - working-directory: OneSignalSDK - run: | - REPORT=build/reports/jacoco/merged/jacocoMergedReport.xml - test -f "$REPORT" || { echo "Merged JaCoCo report not found at $REPORT" >&2; exit 1; } - python -m diff_cover.diff_cover_tool "$REPORT" \ - --compare-branch=origin/main \ - --fail-under=$DIFF_COVERAGE_THRESHOLD - - - name: Generate HTML Diff Report - if: always() - working-directory: OneSignalSDK - run: | - REPORT=build/reports/jacoco/merged/jacocoMergedReport.xml - test -f "$REPORT" || { echo "Merged JaCoCo report not found at $REPORT" >&2; exit 1; } - python -m diff_cover.diff_cover_tool "$REPORT" \ - --compare-branch=origin/main \ - --html-report diff_coverage.html || true - - - name: Upload JaCoCo Reports - if: always() - uses: actions/upload-artifact@v4 - with: - name: jacoco-full-report - path: OneSignalSDK/build/reports/jacoco/ - - - name: Upload Diff Coverage Report - if: always() - uses: actions/upload-artifact@v4 - with: - name: diff-coverage-report - path: OneSignalSDK/diff_coverage.html \ No newline at end of file