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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions .github/workflows/reusable-build-and-publish-v3s.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
# run: gradle shadowJar jacocoTestReport jacocoTestCoverageVerification
shell: bash
run: |
gradle cleanAndTest
gradle cleanAndTest jacocoAggregatedTestReport
- name: Upload Test Report (HTML)
if: success() || failure()
uses: actions/upload-artifact@v4
Expand All @@ -48,20 +48,20 @@ jobs:
if: success() || failure() # always run even if the previous step fails
with:
report_paths: '**/build/test-results/jvmTest/TEST-*.xml'
# - name: Publish code coverage report as PR comment
# id: jacoco
# uses: madrapps/jacoco-report@v1.6.1
# with:
# paths: '**/build/reports/jacoco/test/jacocoTestReport.xml'
# token: ${{ secrets.GITHUB_TOKEN }}
# min-coverage-overall: $MIN_COVERAGE_OVERALL
# min-coverage-changed-files: $MIN_COVERAGE_CHANGED_FILES
# title: Code Coverage
# - name: Fail when coverage of changed files is too low
# run: |
# CHANGED_FILES_FAILED=$(echo '${{ steps.jacoco.outputs.coverage-changed-files }} < ${{ env.MIN_COVERAGE_CHANGED_FILES }}' | bc)
# [[ $CHANGED_FILES_FAILED -ne 0 ]] && echo 'Changed files coverage ${{ steps.jacoco.outputs.coverage-changed-files }}% is smaller than required ${{ env.MIN_COVERAGE_CHANGED_FILES }}%'
# [[ $CHANGED_FILES_FAILED -ne 0 ]] && exit 1 || exit 0
- name: Publish code coverage report as PR comment
id: jacoco
uses: madrapps/jacoco-report@v1.7.2
with:
paths: ${{ github.workspace }}/build/reports/jacoco/jacocoAggregatedTestReport/jacocoAggregatedTestReport.xml
token: ${{ secrets.GITHUB_TOKEN }}
min-coverage-overall: $MIN_COVERAGE_OVERALL
min-coverage-changed-files: $MIN_COVERAGE_CHANGED_FILES
title: Code Coverage
- name: Fail when coverage of changed files is too low
run: |
CHANGED_FILES_FAILED=$(echo '${{ steps.jacoco.outputs.coverage-changed-files }} < ${{ env.MIN_COVERAGE_CHANGED_FILES }}' | bc)
[[ $CHANGED_FILES_FAILED -ne 0 ]] && echo 'Changed files coverage ${{ steps.jacoco.outputs.coverage-changed-files }}% is smaller than required ${{ env.MIN_COVERAGE_CHANGED_FILES }}%'
[[ $CHANGED_FILES_FAILED -ne 0 ]] && exit 1 || exit 0
# - name: List generated artifacts
# run: |
# ls -l $SERVICE_JAR_DIR/*
Expand Down
23 changes: 13 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,36 +243,39 @@ The service will respond with the inserted geo features:

# Testing locally

To run tests locally run Gradle `test` task:
To run tests locally run Gradle `cleanAndTest` task:
```bash
./gradlew test
./gradlew cleanAndTest
```

Code coverage report is generated with use of [jacoco](https://www.jacoco.org/)

To generate **subproject** level coverage, use Gradle task `jacocoTestReport`:

```bash
./gradlew test jacocoTestReport
./gradlew :<module-name>:jacocoTestReport
```

Outputs for each subproject will be stored in `/[module]/build/reports/jacoco/test/html/index.html`
Outputs for each subproject will be stored in `/[module]/build/reports/jacocoTestReport/html/index.html`

To generate **root** level aggregated coverage, use additional Gradle task `testCodeCoverageReport`:
To generate **root** level aggregated coverage, use Gradle task `jacocoAggregatedTestReport`:

```bash
./gradlew test jacocoTestReport testCodeCoverageReport
./gradlew jacocoAggregatedTestReport
```

Outputs will be stored in `/build/reports/jacoco/testCodeCoverageReport/html/index.html`
Outputs will be stored in `/build/reports/jacoco/jacocoAggregatedTestReport/html/index.html`

To validate test coverage, run `jacocoTestCoverageVerification` Gradle task:
To validate test coverage, run `jacocoAggreagetedTestCoverageVerification` Gradle task:
```bash
./gradlew test jacocoTestReport jacocoTestCoverageVerification
./gradlew jacocoAggreagetedTestCoverageVerification
```

You can also validate test coverage for **subproject**, use Gradle task `jacocoTestCoverageVerification`:


```bash
./gradlew :<module-name>:jacocoTestCoverageVerification
```

# Acknowledgements

Expand Down
128 changes: 128 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import com.vanniktech.maven.publish.SonatypeHost
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import java.net.URI

plugins {
Expand All @@ -11,6 +12,7 @@ plugins {
// Only need within root
// see: https://github.com/johnrengelman/shadow
alias(libs.plugins.shadow) apply false
id("jacoco")
}

//configurations.implementation {
Expand Down Expand Up @@ -202,6 +204,50 @@ allprojects {
}
}

jacoco {
toolVersion = rootProject.libs.versions.jacoco.get()
reportsDirectory = layout.buildDirectory.dir("reports/jacoco")
}

subprojects {
if(allModules[name]?.first == CleanAndTest.KOTLIN) {
apply(plugin = "jacoco")

jacoco {
toolVersion = rootProject.libs.versions.jacoco.get()
reportsDirectory = layout.buildDirectory.dir("reports/jacoco")
}

tasks {
val jacocoTestReport by registering(JacocoReport::class) {
group = "jacoco"

dependsOn("jvmTest")
configureJacocoForKmp(project)
reports {
xml.required = true
}
}

val jacocoTestCoverageVerification by registering(JacocoCoverageVerification::class) {
group = "jacoco"
dependsOn(jacocoTestReport)
val reportTask = jacocoTestReport.get()
sourceDirectories.setFrom(reportTask.sourceDirectories)
classDirectories.setFrom(reportTask.classDirectories)
executionData.setFrom(reportTask.executionData)
violationRules {
rule {
limit {
minimum = getOverallCoverage().toBigDecimal()
}
}
}
}
}
}
}

// Helper, run as `gradle cleanAndTestAll`
fun Task.configureCleanAndTestTasks() {
allModules.forEach {
Expand All @@ -217,6 +263,45 @@ fun Task.configureCleanAndTestTasks() {
}
tasks.register("cleanAndTestAll") { configureCleanAndTestTasks() }

tasks.register<JacocoReport>("jacocoAggregatedTestReport") {
group = "jacoco"

val modulesWithTestsEnabled = allModules.filter {(_, moduleInfo) -> moduleInfo.first == CleanAndTest.KOTLIN }
modulesWithTestsEnabled.forEach {(moduleName, _) ->
dependsOn(":${moduleName}:jvmTest")
}
modulesWithTestsEnabled
.map { (moduleName, _) -> project(":${moduleName}")
.extractJacocoProjectDirs() }
.merge()
.let {
sourceDirectories.setFrom(it.sourceDirectories)
classDirectories.setFrom(it.classDirectories)
executionData.setFrom(it.executionData)
}

reports {
xml.required = true
}
}

tasks.register<JacocoCoverageVerification>("jacocoAggreagetedTestCoverageVerification") {
group = "jacoco"

val reportTask = tasks.named<JacocoReport>("jacocoAggregatedTestReport").get()
dependsOn(reportTask)
sourceDirectories.setFrom(reportTask.sourceDirectories)
classDirectories.setFrom(reportTask.classDirectories)
executionData.setFrom(reportTask.executionData)
violationRules {
rule {
limit {
minimum = getOverallCoverage().toBigDecimal()
}
}
}
}

// Helper, run as `gradle publishToLocal`
fun Task.publishToLocal() {
allModules.forEach {
Expand Down Expand Up @@ -295,3 +380,46 @@ tasks.register("publishToCentral") { publishToCentral() }
tasks.register("shadowJar") {
dependsOn(":here-naksha-app-service:shadowJar")
}

data class JacocoProjectDirs(
val sourceDirectories: ConfigurableFileCollection,
val classDirectories: ConfigurableFileCollection,
val executionData: ConfigurableFileCollection
)

fun Project.extractJacocoProjectDirs(): JacocoProjectDirs {
val kotlinExtension = requireNotNull(
extensions.findByType(KotlinMultiplatformExtension::class.java)
) { "KotlinMultiplatformExtension not found in project '$name'" }
val sourceSets = kotlinExtension.sourceSets
val commonSrcDirs = sourceSets.getByName("commonMain").kotlin.srcDirs
val jvmSrcDirs = sourceSets.getByName("jvmMain").kotlin.srcDirs
val buildDirectory = layout.buildDirectory
val classesDirs = kotlinExtension.jvm().compilations.getByName("main").output.classesDirs
val buildData = buildDirectory.files("jacoco/jvmTest.exec")
return JacocoProjectDirs(
sourceDirectories = files(commonSrcDirs + jvmSrcDirs),
classDirectories = classesDirs,
executionData = files(buildData)
)
}

fun JacocoReportBase.configureJacocoForKmp(project: Project) {
project.extractJacocoProjectDirs().let {
sourceDirectories.setFrom(it.sourceDirectories)
classDirectories.setFrom(it.classDirectories)
executionData.setFrom(it.executionData)
}
}

fun List<JacocoProjectDirs>.merge(): JacocoProjectDirs {
val allSourceDirs = flatMap { it.sourceDirectories }
val allClassDirs = flatMap { it.classDirectories }
val allExecData = flatMap { it.executionData }

return JacocoProjectDirs(
sourceDirectories = files(allSourceDirs),
classDirectories = files(allClassDirs),
executionData = files(allExecData)
)
}
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ log4j = "2.25.1"
junit = "5.9.2"
test_containers = "1.20.6"
kotlin = "2.1.20"
jacoco = "0.8.14"
fastdouble = "2.0.1"
jmh = "1.37"
gson = "2.13.2" # https://github.com/google/gson
Expand Down
2 changes: 1 addition & 1 deletion here-naksha-app-service/src/jvmTest/POSTGRES_SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ In tests of Naksha service, it relies on custom container image (which is simply

#### Prerequisites:
- `Docker` or some equivalent (we suggest `Podman` for those without `Docker` license) available on host machine
- Environment variable `NAKSHA_LOCAL_TEST_CONTEXT` must be set to `TEST_CONTAINERS`
- Environment variable `NAKSHA_APP_SERVICE_TEST_CONTEXT` must be set to `TEST_CONTAINERS`
- 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)
- To build image locally (ie when you don't have access to the registry to pull it), run:
```
Expand Down
Loading