Skip to content

Commit db5e2b5

Browse files
CASL-927 - Configure code coverage in V3 (#520)
* CASL-927: Migrate aggr code coverage changes to v3 Signed-off-by: mlisak <[email protected]> * CASL-927 - Configure code coverage in V3 Signed-off-by: mlisak <[email protected]> * feat: add jacoco Signed-off-by: Bartosz Czyż <[email protected]> * build: refactor Signed-off-by: Bartosz Czyż <[email protected]> * build: refactor Signed-off-by: Bartosz Czyż <[email protected]> * fix: jacoco plugin Signed-off-by: Bartosz Czyż <[email protected]> * docs: fix Signed-off-by: Bartosz Czyż <[email protected]> --------- Signed-off-by: mlisak <[email protected]> Signed-off-by: Bartosz Czyż <[email protected]> Co-authored-by: Bartosz Czyż <[email protected]>
1 parent f9c0be0 commit db5e2b5

File tree

5 files changed

+158
-26
lines changed

5 files changed

+158
-26
lines changed

.github/workflows/reusable-build-and-publish-v3s.yml

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
# run: gradle shadowJar jacocoTestReport jacocoTestCoverageVerification
3737
shell: bash
3838
run: |
39-
gradle cleanAndTest
39+
gradle cleanAndTest jacocoAggregatedTestReport
4040
- name: Upload Test Report (HTML)
4141
if: success() || failure()
4242
uses: actions/upload-artifact@v4
@@ -48,20 +48,20 @@ jobs:
4848
if: success() || failure() # always run even if the previous step fails
4949
with:
5050
report_paths: '**/build/test-results/jvmTest/TEST-*.xml'
51-
# - name: Publish code coverage report as PR comment
52-
# id: jacoco
53-
# uses: madrapps/jacoco-report@v1.6.1
54-
# with:
55-
# paths: '**/build/reports/jacoco/test/jacocoTestReport.xml'
56-
# token: ${{ secrets.GITHUB_TOKEN }}
57-
# min-coverage-overall: $MIN_COVERAGE_OVERALL
58-
# min-coverage-changed-files: $MIN_COVERAGE_CHANGED_FILES
59-
# title: Code Coverage
60-
# - name: Fail when coverage of changed files is too low
61-
# run: |
62-
# CHANGED_FILES_FAILED=$(echo '${{ steps.jacoco.outputs.coverage-changed-files }} < ${{ env.MIN_COVERAGE_CHANGED_FILES }}' | bc)
63-
# [[ $CHANGED_FILES_FAILED -ne 0 ]] && echo 'Changed files coverage ${{ steps.jacoco.outputs.coverage-changed-files }}% is smaller than required ${{ env.MIN_COVERAGE_CHANGED_FILES }}%'
64-
# [[ $CHANGED_FILES_FAILED -ne 0 ]] && exit 1 || exit 0
51+
- name: Publish code coverage report as PR comment
52+
id: jacoco
53+
uses: madrapps/jacoco-report@v1.7.2
54+
with:
55+
paths: ${{ github.workspace }}/build/reports/jacoco/jacocoAggregatedTestReport/jacocoAggregatedTestReport.xml
56+
token: ${{ secrets.GITHUB_TOKEN }}
57+
min-coverage-overall: $MIN_COVERAGE_OVERALL
58+
min-coverage-changed-files: $MIN_COVERAGE_CHANGED_FILES
59+
title: Code Coverage
60+
- name: Fail when coverage of changed files is too low
61+
run: |
62+
CHANGED_FILES_FAILED=$(echo '${{ steps.jacoco.outputs.coverage-changed-files }} < ${{ env.MIN_COVERAGE_CHANGED_FILES }}' | bc)
63+
[[ $CHANGED_FILES_FAILED -ne 0 ]] && echo 'Changed files coverage ${{ steps.jacoco.outputs.coverage-changed-files }}% is smaller than required ${{ env.MIN_COVERAGE_CHANGED_FILES }}%'
64+
[[ $CHANGED_FILES_FAILED -ne 0 ]] && exit 1 || exit 0
6565
# - name: List generated artifacts
6666
# run: |
6767
# ls -l $SERVICE_JAR_DIR/*

README.md

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -243,36 +243,39 @@ The service will respond with the inserted geo features:
243243
244244
# Testing locally
245245
246-
To run tests locally run Gradle `test` task:
246+
To run tests locally run Gradle `cleanAndTest` task:
247247
```bash
248-
./gradlew test
248+
./gradlew cleanAndTest
249249
```
250250
251251
Code coverage report is generated with use of [jacoco](https://www.jacoco.org/)
252252
253253
To generate **subproject** level coverage, use Gradle task `jacocoTestReport`:
254254
255255
```bash
256-
./gradlew test jacocoTestReport
256+
./gradlew :<module-name>:jacocoTestReport
257257
```
258258
259-
Outputs for each subproject will be stored in `/[module]/build/reports/jacoco/test/html/index.html`
259+
Outputs for each subproject will be stored in `/[module]/build/reports/jacocoTestReport/html/index.html`
260260
261-
To generate **root** level aggregated coverage, use additional Gradle task `testCodeCoverageReport`:
261+
To generate **root** level aggregated coverage, use Gradle task `jacocoAggregatedTestReport`:
262262
263263
```bash
264-
./gradlew test jacocoTestReport testCodeCoverageReport
264+
./gradlew jacocoAggregatedTestReport
265265
```
266266
267-
Outputs will be stored in `/build/reports/jacoco/testCodeCoverageReport/html/index.html`
267+
Outputs will be stored in `/build/reports/jacoco/jacocoAggregatedTestReport/html/index.html`
268268
269-
To validate test coverage, run `jacocoTestCoverageVerification` Gradle task:
269+
To validate test coverage, run `jacocoAggreagetedTestCoverageVerification` Gradle task:
270270
```bash
271-
./gradlew test jacocoTestReport jacocoTestCoverageVerification
271+
./gradlew jacocoAggreagetedTestCoverageVerification
272272
```
273273
274+
You can also validate test coverage for **subproject**, use Gradle task `jacocoTestCoverageVerification`:
274275
275-
276+
```bash
277+
./gradlew :<module-name>:jacocoTestCoverageVerification
278+
```
276279
277280
# Acknowledgements
278281

build.gradle.kts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import com.vanniktech.maven.publish.SonatypeHost
2+
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
23
import java.net.URI
34

45
plugins {
@@ -11,6 +12,7 @@ plugins {
1112
// Only need within root
1213
// see: https://github.com/johnrengelman/shadow
1314
alias(libs.plugins.shadow) apply false
15+
id("jacoco")
1416
}
1517

1618
//configurations.implementation {
@@ -202,6 +204,50 @@ allprojects {
202204
}
203205
}
204206

207+
jacoco {
208+
toolVersion = rootProject.libs.versions.jacoco.get()
209+
reportsDirectory = layout.buildDirectory.dir("reports/jacoco")
210+
}
211+
212+
subprojects {
213+
if(allModules[name]?.first == CleanAndTest.KOTLIN) {
214+
apply(plugin = "jacoco")
215+
216+
jacoco {
217+
toolVersion = rootProject.libs.versions.jacoco.get()
218+
reportsDirectory = layout.buildDirectory.dir("reports/jacoco")
219+
}
220+
221+
tasks {
222+
val jacocoTestReport by registering(JacocoReport::class) {
223+
group = "jacoco"
224+
225+
dependsOn("jvmTest")
226+
configureJacocoForKmp(project)
227+
reports {
228+
xml.required = true
229+
}
230+
}
231+
232+
val jacocoTestCoverageVerification by registering(JacocoCoverageVerification::class) {
233+
group = "jacoco"
234+
dependsOn(jacocoTestReport)
235+
val reportTask = jacocoTestReport.get()
236+
sourceDirectories.setFrom(reportTask.sourceDirectories)
237+
classDirectories.setFrom(reportTask.classDirectories)
238+
executionData.setFrom(reportTask.executionData)
239+
violationRules {
240+
rule {
241+
limit {
242+
minimum = getOverallCoverage().toBigDecimal()
243+
}
244+
}
245+
}
246+
}
247+
}
248+
}
249+
}
250+
205251
// Helper, run as `gradle cleanAndTestAll`
206252
fun Task.configureCleanAndTestTasks() {
207253
allModules.forEach {
@@ -217,6 +263,45 @@ fun Task.configureCleanAndTestTasks() {
217263
}
218264
tasks.register("cleanAndTestAll") { configureCleanAndTestTasks() }
219265

266+
tasks.register<JacocoReport>("jacocoAggregatedTestReport") {
267+
group = "jacoco"
268+
269+
val modulesWithTestsEnabled = allModules.filter {(_, moduleInfo) -> moduleInfo.first == CleanAndTest.KOTLIN }
270+
modulesWithTestsEnabled.forEach {(moduleName, _) ->
271+
dependsOn(":${moduleName}:jvmTest")
272+
}
273+
modulesWithTestsEnabled
274+
.map { (moduleName, _) -> project(":${moduleName}")
275+
.extractJacocoProjectDirs() }
276+
.merge()
277+
.let {
278+
sourceDirectories.setFrom(it.sourceDirectories)
279+
classDirectories.setFrom(it.classDirectories)
280+
executionData.setFrom(it.executionData)
281+
}
282+
283+
reports {
284+
xml.required = true
285+
}
286+
}
287+
288+
tasks.register<JacocoCoverageVerification>("jacocoAggreagetedTestCoverageVerification") {
289+
group = "jacoco"
290+
291+
val reportTask = tasks.named<JacocoReport>("jacocoAggregatedTestReport").get()
292+
dependsOn(reportTask)
293+
sourceDirectories.setFrom(reportTask.sourceDirectories)
294+
classDirectories.setFrom(reportTask.classDirectories)
295+
executionData.setFrom(reportTask.executionData)
296+
violationRules {
297+
rule {
298+
limit {
299+
minimum = getOverallCoverage().toBigDecimal()
300+
}
301+
}
302+
}
303+
}
304+
220305
// Helper, run as `gradle publishToLocal`
221306
fun Task.publishToLocal() {
222307
allModules.forEach {
@@ -295,3 +380,46 @@ tasks.register("publishToCentral") { publishToCentral() }
295380
tasks.register("shadowJar") {
296381
dependsOn(":here-naksha-app-service:shadowJar")
297382
}
383+
384+
data class JacocoProjectDirs(
385+
val sourceDirectories: ConfigurableFileCollection,
386+
val classDirectories: ConfigurableFileCollection,
387+
val executionData: ConfigurableFileCollection
388+
)
389+
390+
fun Project.extractJacocoProjectDirs(): JacocoProjectDirs {
391+
val kotlinExtension = requireNotNull(
392+
extensions.findByType(KotlinMultiplatformExtension::class.java)
393+
) { "KotlinMultiplatformExtension not found in project '$name'" }
394+
val sourceSets = kotlinExtension.sourceSets
395+
val commonSrcDirs = sourceSets.getByName("commonMain").kotlin.srcDirs
396+
val jvmSrcDirs = sourceSets.getByName("jvmMain").kotlin.srcDirs
397+
val buildDirectory = layout.buildDirectory
398+
val classesDirs = kotlinExtension.jvm().compilations.getByName("main").output.classesDirs
399+
val buildData = buildDirectory.files("jacoco/jvmTest.exec")
400+
return JacocoProjectDirs(
401+
sourceDirectories = files(commonSrcDirs + jvmSrcDirs),
402+
classDirectories = classesDirs,
403+
executionData = files(buildData)
404+
)
405+
}
406+
407+
fun JacocoReportBase.configureJacocoForKmp(project: Project) {
408+
project.extractJacocoProjectDirs().let {
409+
sourceDirectories.setFrom(it.sourceDirectories)
410+
classDirectories.setFrom(it.classDirectories)
411+
executionData.setFrom(it.executionData)
412+
}
413+
}
414+
415+
fun List<JacocoProjectDirs>.merge(): JacocoProjectDirs {
416+
val allSourceDirs = flatMap { it.sourceDirectories }
417+
val allClassDirs = flatMap { it.classDirectories }
418+
val allExecData = flatMap { it.executionData }
419+
420+
return JacocoProjectDirs(
421+
sourceDirectories = files(allSourceDirs),
422+
classDirectories = files(allClassDirs),
423+
executionData = files(allExecData)
424+
)
425+
}

gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ log4j = "2.25.1"
1010
junit = "5.9.2"
1111
test_containers = "1.20.6"
1212
kotlin = "2.1.20"
13+
jacoco = "0.8.14"
1314
fastdouble = "2.0.1"
1415
jmh = "1.37"
1516
gson = "2.13.2" # https://github.com/google/gson

here-naksha-app-service/src/jvmTest/POSTGRES_SETUP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ In tests of Naksha service, it relies on custom container image (which is simply
4848

4949
#### Prerequisites:
5050
- `Docker` or some equivalent (we suggest `Podman` for those without `Docker` license) available on host machine
51-
- Environment variable `NAKSHA_LOCAL_TEST_CONTEXT` must be set to `TEST_CONTAINERS`
51+
- Environment variable `NAKSHA_APP_SERVICE_TEST_CONTEXT` must be set to `TEST_CONTAINERS`
5252
- Port `5432` must be available (it is possible for TestContainers to utilize any other port but the majority of tests that were written before supporting this approach rely on strict port mapping - that is likely to change in the future)
5353
- To build image locally (ie when you don't have access to the registry to pull it), run:
5454
```

0 commit comments

Comments
 (0)