Skip to content

Commit

Permalink
Integrate with Jacoco
Browse files Browse the repository at this point in the history
  • Loading branch information
pmendelski authored Oct 13, 2024
1 parent 3797778 commit 1afdd9c
Show file tree
Hide file tree
Showing 15 changed files with 212 additions and 58 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @coditory/reviewers
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jobs:
&& 'generate-and-submit' || 'disabled' }}
- name: Build
if: steps.skipper.outputs.should_skip != 'true'
run: ./gradlew build

- name: Publish Coverage Report
Expand Down
16 changes: 8 additions & 8 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ cp scripts/git/pre-commit .git/hooks/pre-commit
```

## Commit messages

Before writing a commit message read [this article](https://chris.beams.io/posts/git-commit/).

## Build

Before pushing any changes make sure project builds without errors with:

```
./gradlew build
```
Expand Down Expand Up @@ -52,9 +55,8 @@ There are few ways for testing locally a gradle plugin:

- Publish plugin to your local maven repository (`$HOME/.m2`) with:
```sh
./gradlew publishToMavenLocal
./gradlew publishToMavenLocal -Pversion="<SOME_VERSION>" && ls -la ~/.m2/repository/com/coditory/gradle/integration-test-plugin
```
It will publish the plugin with `unspecified` version.
- Add section to `settings.gradle.kts`:
```kt
// Instruct a sample project to use maven local to find the plugin
Expand All @@ -68,20 +70,18 @@ There are few ways for testing locally a gradle plugin:
- Add dependency:
```kt
plugins {
id("com.coditory.integration-test") version "unspecified"
id("com.coditory.integration-test") version "<SOME_VERSION>"
}
```
- You can skip last two steps and just use https://github.com/coditory/gradle-integration-test-plugin-sample and change
plugin version to `unspecified`.

**Import plugin jar**
Add plugin jar to the sample project (that uses the tested plugin):

```kt
buildscript {
dependencies {
classpath(files("<PLUGIN_PROJECT_PATH>/build/libs/integration-test-plugin.jar"))
}
dependencies {
classpath(files("<PLUGIN_PROJECT_PATH>/build/libs/integration-test-plugin.jar"))
}
}
apply(plugin = "com.coditory.build")
Expand Down
27 changes: 9 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
- Makes integration classpath extend test classpath and main classpath (in this order).
- Makes sure IntelliJ idea treats `src/integrationTest/*` as test sources.
- Exposes kotlin internal scope (from main and test module) to integration tests.
- Integrates with [Jacoco](https://docs.gradle.org/current/userguide/jacoco_plugin.html)
and [Kover](https://github.com/Kotlin/kotlinx-kover).

## Using the plugin

Expand Down Expand Up @@ -161,12 +163,12 @@ Skipping tests:
# Skip all tests
./gradlew clean build -x test integrationTest
# ...or skipTests=true/false
./gradlew clean build -PskipTests
./gradlew clean build -PskipTest

# Skip tests from /src/test
./gradlew clean build -x test
# ...or skipUnitTests=true/false
./gradlew clean build -PskipUnitTests
./gradlew clean build -PskipUnitTest

# Skip tests from /src/integration
./gradlew clean build -x integrationTest
Expand All @@ -180,30 +182,19 @@ Skipping tests:
./gradlew iT --tests com.coditory.SampleTest.shouldWork
```

Creating a single [Jacoco](https://docs.gradle.org/current/userguide/jacoco_plugin.html) report for unit and integration
tests:

```gradle
jacocoTestReport {
executionData(fileTree(project.buildDir).include("jacoco/*.exec"))
reports {
xml.enabled = true
html.enabled = true
}
}
```

## The no-plugin alternative

If you're against adding plugins to your build file, simply copy-paste the configuration from:

- [Java + Junit5 (no plugin)](https://github.com/coditory/gradle-integration-test-plugin-sample/tree/master/java-junit5-no-plugin/build.gradle)
- [Kotlin + Junit5 (no plugin)](https://github.com/coditory/gradle-integration-test-plugin-sample/tree/master/kotlin-junit5-no-plugin/build.gradle.kts)

...though it's not a one-liner, be aware of the obfuscation
...though mind the boilerplate

## Migrating from 1.x.x to 2.x.x

- Integration test source folder changed from `src/integration` to `src/integrationTest`
- Skipping flags changed from `skipTests`, `skipUnitTests`, `skipIntegrationTests`
to `skipTest`, `skipUnitTest`, `skipIntegrationTest`
- In `build.gradle` file in `dependencies {}` section use `integrationTestImplementation(...)` instead of
`integrationImplementation(...)`
- Skipping flags changed names. Use `skipTests`, `skipUnitTests`, `skipIntegrationTests`
instead of `skipTest`, `skipUnitTest`, `skipIntegrationTest`
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ open class IntegrationTestPlugin : Plugin<Project> {
}
TestSuitesConfiguration.apply(project)
TestAllTaskConfiguration.apply(project)
JacocoTaskConfiguration.apply(project)
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.coditory.gradle.integration

import com.coditory.gradle.integration.IntegrationTestPlugin.Companion.INTEGRATION_TEST
import org.gradle.api.Project
import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
import org.gradle.testing.jacoco.tasks.JacocoCoverageVerification
import org.gradle.testing.jacoco.tasks.JacocoReport

internal object JacocoTaskConfiguration {
fun apply(project: Project) {
if (project.pluginManager.hasPlugin("jacoco")) {
var dstFile: String? = null
project.tasks.named(INTEGRATION_TEST) { task ->
val jacocoTaskExtension = task.extensions.getByType(JacocoTaskExtension::class.java)
dstFile = jacocoTaskExtension.destinationFile?.path
}
if (dstFile != null) {
project.tasks.withType(JacocoReport::class.java) { task ->
task.executionData(dstFile)
task.mustRunAfter(INTEGRATION_TEST)
}
project.tasks.withType(JacocoCoverageVerification::class.java) { task ->
task.mustRunAfter(INTEGRATION_TEST)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.coditory.gradle.integration

import com.coditory.gradle.integration.base.TestProjectBuilder.Companion.project
import com.coditory.gradle.integration.base.toBuildPath
import org.assertj.core.api.Assertions.assertThat
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPlugin
import org.gradle.testing.jacoco.plugins.JacocoPlugin
import org.gradle.testing.jacoco.tasks.JacocoReport
import org.junit.jupiter.api.Test

class JacocoConfigurationTest {
private val project: Project = project()
.withPlugins(JavaPlugin::class, JacocoPlugin::class, IntegrationTestPlugin::class)
.build()

@Test
fun `should register integrationTest exec files in jacoco report task`() {
val jacocoReportTask = project.tasks.getByName("jacocoTestReport") as JacocoReport
val executionData = jacocoReportTask.executionData
assertThat(executionData).isNotNull()
assertThat(executionData.asPath).isEqualTo(
project.toBuildPath(
"jacoco/test.exec",
"jacoco/integrationTest.exec",
),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.coditory.gradle.integration

import com.coditory.gradle.integration.IntegrationTestPlugin.Companion.INTEGRATION_TEST
import com.coditory.gradle.integration.base.TestProjectBuilder.Companion.createProject
import com.coditory.gradle.integration.base.toBuildPath
import org.assertj.core.api.Assertions.assertThat
import org.gradle.api.Project
import org.gradle.api.plugins.JavaBasePlugin
Expand All @@ -10,7 +11,6 @@ import org.gradle.api.tasks.SourceSet
import org.gradle.language.base.plugins.LifecycleBasePlugin.VERIFICATION_GROUP
import org.gradle.testing.base.TestingExtension
import org.junit.jupiter.api.Test
import java.io.File
import org.gradle.api.tasks.testing.Test as TestTask

class TestTaskConfigurationTest {
Expand All @@ -20,21 +20,19 @@ class TestTaskConfigurationTest {
fun `should configure integration source sets`() {
val sourceSet = getSourceSet()
assertThat(sourceSet).isNotNull
assertThat(sourceSet.output.classesDirs.asPath).isEqualTo(toBuildPath("classes/java/integrationTest"))
assertThat(sourceSet.output.resourcesDir.toString()).isEqualTo(toBuildPath("resources/integrationTest"))
// TODO: Fix it. Tried is all. It's failing with Could not find org.gradle.internal.impldep.org.junit.jupiter:junit-jupiter:5.8.2
assertThat(sourceSet.output.classesDirs.asPath).isEqualTo(project.toBuildPath("classes/java/integrationTest"))
assertThat(sourceSet.output.resourcesDir.toString()).isEqualTo(project.toBuildPath("resources/integrationTest"))
// TODO: Fix it. Tried it all. It's failing with Could not find org.gradle.internal.impldep.org.junit.jupiter:junit-jupiter:5.8.2
// Tried: adding repositories to test project, defining tests to use junit platform etc - did not help...
// assertThat(sourceSet.runtimeClasspath.asPath)
// .isEqualTo(
// toBuildPath(
// listOf(
// "classes/java/integrationTest",
// "resources/integrationTest",
// "classes/java/test",
// "resources/test",
// "classes/java/main",
// "resources/main",
// ),
// project.toBuildPath(
// "classes/java/integrationTest",
// "resources/integrationTest",
// "classes/java/test",
// "resources/test",
// "classes/java/main",
// "resources/main",
// ),
// )
}
Expand Down Expand Up @@ -63,16 +61,6 @@ class TestTaskConfigurationTest {
return project.tasks.getByName(INTEGRATION_TEST) as TestTask
}

private fun toBuildPath(path: String, project: Project = this.project): String {
return toBuildPath(listOf(path), project)
}

private fun toBuildPath(paths: List<String>, project: Project = this.project): String {
return paths.joinToString(File.pathSeparator) {
"${project.layout.buildDirectory.get()}${File.separator}${it.replace("/", File.separator)}"
}
}

@Suppress("UnstableApiUsage")
private fun getSourceSet(project: Project = this.project): SourceSet {
return project.extensions.getByType(TestingExtension::class.java).suites
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class CommandLineAcceptanceTest {
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.11.0"
}
test {
tasks.withType(Test) {
useJUnitPlatform()
testLogging {
events("passed", "failed", "skipped")
Expand Down Expand Up @@ -77,10 +77,6 @@ class CommandLineAcceptanceTest {
).build()
}

companion object {
const val ABC = ""
}

@ParameterizedTest(name = "should run unit tests and integration tests on check command for gradle {0}")
@ValueSource(strings = [GRADLE_MAX_SUPPORTED_VERSION, GRADLE_MIN_SUPPORTED_VERSION])
fun `should run unit tests and integration tests on check command`(gradleVersion: String?) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package com.coditory.gradle.integration.acceptance

import com.coditory.gradle.integration.base.GradleTestVersions.GRADLE_MAX_SUPPORTED_VERSION
import com.coditory.gradle.integration.base.TestProjectBuilder.Companion.project
import com.coditory.gradle.integration.base.TestProjectRunner.runGradle
import com.coditory.gradle.integration.base.readFileFromBuildDir
import org.assertj.core.api.Assertions.assertThat
import org.gradle.api.Project
import org.gradle.testkit.runner.TaskOutcome
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource

class JacocoBasedAcceptanceTest {
private val project = createProject()

private fun createProject(): Project {
val commonImports =
"""
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
""".trimIndent()
return project("sample-project")
.withBuildGradle(
"""
plugins {
id 'jacoco'
id 'com.coditory.integration-test'
}
repositories {
mavenCentral()
}
dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:5.11.0"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.11.0"
}
tasks.withType(Test) {
useJUnitPlatform()
testLogging {
events("passed", "failed", "skipped")
setExceptionFormat("full")
}
}
jacocoTestReport {
reports {
xml.required = true
}
}
""",
).withFile(
"src/main/java/Calculator.java",
"""
public class Calculator {
public static int add(int a, int b) {
return a + b;
}
public static int subtract(int a, int b) {
return a - b;
}
}
""",
).withFile(
"src/integrationTest/java/TestIntgSpec.java",
"""
$commonImports
public class TestIntgSpec {
@Test
public void shouldSubtract() {
assertEquals(3, Calculator.subtract(6, 3));
}
}
""",
).withFile(
"src/test/java/TestUnitSpec.java",
"""
$commonImports
public class TestUnitSpec {
@Test
public void shouldAdd() {
assertEquals(9, Calculator.add(6, 3));
}
}
""",
)
.build()
}

@ParameterizedTest(name = "should aggregate coverage from unit and integration tests when using Jacoco {0}")
// @ValueSource(strings = [GRADLE_MAX_SUPPORTED_VERSION, GRADLE_MIN_SUPPORTED_VERSION])
@ValueSource(strings = [GRADLE_MAX_SUPPORTED_VERSION])
fun `should aggregate coverage from unit and integration tests when using Jacoco`(gradleVersion: String?) {
// when
val result =
runGradle(project, listOf("check", "jacocoTestReport"), gradleVersion)
// then
assertThat(result.task(":test")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
assertThat(result.task(":integrationTest")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
assertThat(project.readFileFromBuildDir("reports/jacoco/test/jacocoTestReport.xml"))
// missed method is the init
.contains("<counter type=\"METHOD\" missed=\"1\" covered=\"2\"/>")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class Junit4BasedAcceptanceTest {
integrationTestImplementation "org.slf4j:slf4j-api:2.0.16"
}
test {
tasks.withType(Test) {
testLogging {
events("passed", "failed", "skipped")
setExceptionFormat("full")
Expand Down
Loading

0 comments on commit 1afdd9c

Please sign in to comment.