diff --git a/.github/workflows/gradle-tests.yml b/.github/workflows/gradle-tests.yml index f2afccba..d34d56ca 100644 --- a/.github/workflows/gradle-tests.yml +++ b/.github/workflows/gradle-tests.yml @@ -2,9 +2,6 @@ name: 🧪 Unit Tests on: push: - branches: - - main - pull_request: permissions: contents: read @@ -83,6 +80,14 @@ jobs: working-directory: ./ run: chmod +x gradlew && ./gradlew clean && make cov + - name: Upload LCOV coverage report + if: ${{ !cancelled() && matrix.java-version == '21' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-java-${{ matrix.java-version }}-${{ matrix.distribution }} + path: agent_api/build/jacoco/test/lcov.info + if-no-files-found: error + - name: Upload coverage report to Codecov if: matrix.java-version == '21' # Only upload coverage for Java 21 uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 @@ -91,3 +96,27 @@ jobs: files: ./agent_api/build/jacoco/test/jacocoTestReport.xml token: ${{ secrets.CODECOV_TOKEN }} verbose: true + + upload-coverage: + needs: test-and-coverage + if: github.event_name == 'push' + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + steps: + - name: Download LCOV artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + path: coverage-reports + pattern: coverage-java-* + merge-multiple: false + + - name: Upload coverage to Aikido + uses: AikidoSec/code-coverage-github-action@660c7844f246c44570d9c035f5b51ffe7f266a79 # v1.0.0 + with: + lcov-file-paths: | + coverage-reports/coverage-java-21-adopt/lcov.info + coverage-reports/coverage-java-21-corretto/lcov.info + coverage-reports/coverage-java-21-oracle/lcov.info + fail-on-error: false diff --git a/Makefile b/Makefile index 71e00e07..8699fcd8 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ test: AIKIDO_LOG_LEVEL="error" AIKIDO_TOKEN="token" ./gradlew test cov: - AIKIDO_LOG_LEVEL="error" AIKIDO_TOKEN="token" ./gradlew test --rerun-tasks -PcoverageRun jacocoTestReport jacocoTestCoverageVerification + AIKIDO_LOG_LEVEL="error" AIKIDO_TOKEN="token" ./gradlew test --rerun-tasks -PcoverageRun jacocoTestReport jacocoToLcov jacocoTestCoverageVerification # Automatic versioning for releases : diff --git a/agent_api/build.gradle b/agent_api/build.gradle index 88620e51..f1c50aa9 100644 --- a/agent_api/build.gradle +++ b/agent_api/build.gradle @@ -151,6 +151,56 @@ jacocoTestReport { dependsOn test } +def jacocoReportFile = layout.buildDirectory.file('jacoco/test/jacocoTestReport.xml') +def lcovReportFile = layout.buildDirectory.file('jacoco/test/lcov.info') + +tasks.register('jacocoToLcov') { + group = 'verification' + description = 'Converts JaCoCo XML coverage report to LCOV format' + dependsOn jacocoTestReport + inputs.file(jacocoReportFile) + outputs.file(lcovReportFile) + + doLast { + def reportFile = jacocoReportFile.get().asFile + if (!reportFile.exists()) { + throw new GradleException("JaCoCo report not found: ${reportFile}") + } + + def parser = new groovy.xml.XmlSlurper(false, false, true) + parser.setFeature('http://xml.org/sax/features/external-general-entities', false) + parser.setFeature('http://xml.org/sax/features/external-parameter-entities', false) + parser.setFeature('http://apache.org/xml/features/nonvalidating/load-external-dtd', false) + def xml = parser.parse(reportFile) + def sourceRoots = sourceSets.main.java.srcDirs + + def out = lcovReportFile.get().asFile + out.parentFile.mkdirs() + out.withWriter('UTF-8') { writer -> + writer.println('TN:') + xml.package.each { packageNode -> + packageNode.sourcefile.each { sourceFile -> + def relativePath = "${packageNode.@name.text()}/${sourceFile.@name.text()}" + def path = relativePath + for (root in sourceRoots) { + def candidate = new File(root, relativePath) + if (candidate.exists()) { + path = candidate.path + break + } + } + writer.println("SF:${path}") + sourceFile.line.each { line -> + def hits = line.@ci.text().toInteger() > 0 ? 1 : 0 + writer.println("DA:${line.@nr.text()},${hits}") + } + writer.println('end_of_record') + } + } + } + } +} + jacocoTestCoverageVerification { classDirectories.setFrom(jacocoClassDirectories) violationRules {