diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ea110064..843f63901 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ We use [semantic versioning](http://semver.org/): - [fix] _agent_: With `duplicates=FAIL`, the error for a duplicate, non-identical class now names the class and how to resolve the problem. - [fix] _agent_, _teamscale-gradle-plugin_, _teamscale-maven-plugin_: A corrupt class file whose class name cannot be read is skipped with a warning instead of failing the coverage report. - [fix] _impacted-test-engine_: A test that a failing assumption aborts is reported as skipped instead of as an error. +- [fix] _teamscale-gradle-plugin_: The plugin now works when the build sets `jacoco.excludes` or the JUnit Platform `excludeEngines` of a test task to a read-only collection (for example, `listOf(...)` or `setOf(...)` in the Kotlin DSL). Previously, applying the plugin or running the test task failed with an `UnsupportedOperationException`. - [fix] _teamscale-gradle-plugin_: The default excludes now match Mockito mocks (e.g. `com.company.IFoo$MockitoMock$123`) and Guice fast classes (e.g. `com.company.Foo$$FastClassByGuice$$abc`). Previously the profiler instrumented them, and the testwise coverage report warned that it found coverage for classes that were not provided. # 39.0.0 diff --git a/teamscale-gradle-plugin/src/main/kotlin/com/teamscale/TeamscalePlugin.kt b/teamscale-gradle-plugin/src/main/kotlin/com/teamscale/TeamscalePlugin.kt index 2393c41f2..76e181d26 100755 --- a/teamscale-gradle-plugin/src/main/kotlin/com/teamscale/TeamscalePlugin.kt +++ b/teamscale-gradle-plugin/src/main/kotlin/com/teamscale/TeamscalePlugin.kt @@ -141,7 +141,7 @@ class TeamscalePlugin : Plugin { AgentPortGenerator::class.java ) {} project.tasks.withType { - jacoco.excludes?.addAll(DEFAULT_EXCLUDES) + jacoco.excludes?.let { buildExcludes -> jacoco.excludes = buildExcludes + DEFAULT_EXCLUDES } val extension = this.extensions.create( TEAMSCALE_EXTENSION_NAME, teamscaleJacocoAgentConfiguration, jacoco diff --git a/teamscale-gradle-plugin/src/main/kotlin/com/teamscale/TestImpactConfigurationAction.kt b/teamscale-gradle-plugin/src/main/kotlin/com/teamscale/TestImpactConfigurationAction.kt index 3efac7ce8..842a9652a 100644 --- a/teamscale-gradle-plugin/src/main/kotlin/com/teamscale/TestImpactConfigurationAction.kt +++ b/teamscale-gradle-plugin/src/main/kotlin/com/teamscale/TestImpactConfigurationAction.kt @@ -53,7 +53,7 @@ class TestImpactConfigurationAction( writeProperty("excludedEngines", testFrameworkOptions.excludeEngines.joinToString(",")) testFrameworkOptions.includeEngines = setOf(IMPACTED_TEST_ENGINE_ID) } else { - testFrameworkOptions.excludeEngines.add(IMPACTED_TEST_ENGINE_ID) + testFrameworkOptions.excludeEngines = testFrameworkOptions.excludeEngines + IMPACTED_TEST_ENGINE_ID } } diff --git a/teamscale-gradle-plugin/src/test/kotlin/com/teamscale/TeamscalePluginBuildExcludesTest.kt b/teamscale-gradle-plugin/src/test/kotlin/com/teamscale/TeamscalePluginBuildExcludesTest.kt new file mode 100644 index 000000000..6a9354cd3 --- /dev/null +++ b/teamscale-gradle-plugin/src/test/kotlin/com/teamscale/TeamscalePluginBuildExcludesTest.kt @@ -0,0 +1,64 @@ +package com.teamscale + +import org.assertj.core.api.Assertions.assertThat +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPlugin +import org.gradle.api.tasks.testing.Test as TestTask +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.getByType +import org.gradle.kotlin.dsl.named +import org.gradle.kotlin.dsl.withType +import org.gradle.testfixtures.ProjectBuilder +import org.gradle.testing.jacoco.plugins.JacocoPlugin +import org.gradle.testing.jacoco.plugins.JacocoTaskExtension +import org.junit.jupiter.api.Test + +/** Tests how the plugin combines its default excludes with the JaCoCo excludes that the build sets. */ +class TeamscalePluginBuildExcludesTest { + + private val buildExclude = "*\$MockitoMock\$*" + + @Test + fun `plugin appends its default excludes to read-only excludes of the build`() { + val excludes = testTaskExcludesAfterApplyingPlugin(listOf(buildExclude)) + + assertThat(excludes) + .`as`("The excludes should be the excludes of the build followed by the default excludes") + .containsExactlyElementsOf(listOf(buildExclude) + defaultExcludes()) + } + + @Test + fun `plugin appends its default excludes to mutable excludes of the build`() { + val excludes = testTaskExcludesAfterApplyingPlugin(mutableListOf(buildExclude)) + + assertThat(excludes) + .`as`("The excludes should be the excludes of the build followed by the default excludes") + .containsExactlyElementsOf(listOf(buildExclude) + defaultExcludes()) + } + + private fun testTaskExcludesAfterApplyingPlugin(buildExcludes: List): List? { + val project = ProjectBuilder.builder().build() + project.pluginManager.apply(JavaPlugin::class.java) + project.pluginManager.apply(JacocoPlugin::class.java) + project.tasks.withType().configureEach { + extensions.configure { excludes = buildExcludes } + } + + project.pluginManager.apply(TeamscalePlugin::class.java) + return testTaskExcludesOf(project) + } + + private fun defaultExcludes(): List { + val project = ProjectBuilder.builder().build() + project.pluginManager.apply(TeamscalePlugin::class.java) + val defaultExcludes = testTaskExcludesOf(project).orEmpty() + + assertThat(defaultExcludes) + .`as`("The plugin should add default excludes to a test task without excludes of the build") + .isNotEmpty() + return defaultExcludes + } + + private fun testTaskExcludesOf(project: Project) = + project.tasks.named("test").get().extensions.getByType().excludes +} diff --git a/teamscale-gradle-plugin/src/test/kotlin/com/teamscale/TestImpactConfigurationActionTest.kt b/teamscale-gradle-plugin/src/test/kotlin/com/teamscale/TestImpactConfigurationActionTest.kt new file mode 100644 index 000000000..87be68c8e --- /dev/null +++ b/teamscale-gradle-plugin/src/test/kotlin/com/teamscale/TestImpactConfigurationActionTest.kt @@ -0,0 +1,35 @@ +package com.teamscale + +import com.teamscale.extension.TeamscalePluginExtension +import com.teamscale.extension.TeamscaleTaskExtension +import org.assertj.core.api.Assertions.assertThat +import org.gradle.api.tasks.testing.Test as TestTask +import org.gradle.api.tasks.testing.junitplatform.JUnitPlatformOptions +import org.gradle.kotlin.dsl.getByType +import org.gradle.kotlin.dsl.named +import org.gradle.testfixtures.ProjectBuilder +import org.junit.jupiter.api.Test + +/** Tests how [TestImpactConfigurationAction] configures the JUnit Platform engines of a test task. */ +class TestImpactConfigurationActionTest { + + @Test + fun `plugin adds the impacted test engine to read-only engine excludes of the build`() { + val project = ProjectBuilder.builder().build() + project.pluginManager.apply(TeamscalePlugin::class.java) + val testTask = project.tasks.named("test").get() + testTask.useJUnitPlatform { excludeEngines = setOf("junit-vintage") } + + runTestImpactConfigurationAction(testTask) + + assertThat((testTask.options as JUnitPlatformOptions).excludeEngines) + .`as`("The test task should exclude the engine of the build and the impacted test engine") + .containsExactlyInAnyOrder("junit-vintage", "teamscale-test-impacted") + } + + private fun runTestImpactConfigurationAction(testTask: TestTask) { + val pluginExtension = testTask.project.extensions.getByType() + val taskExtension = testTask.extensions.getByType() + TestImpactConfigurationAction(pluginExtension, taskExtension).execute(testTask) + } +}