Skip to content

Commit 62313cf

Browse files
committed
Introduce DirectoryProvider interface to collect source/class dirs
Different integrations provide their own implementation of this interface in order to be recognized by JUnit 5 tasks & companions. The current state uses a Default (Java) implementation as well as the opt-in Kotlin provider, applied to projects that also use the Kotlin plugin. The Jacoco integration also profits from this new abstraction, as it is now able to operate on _all_ source & class directories regardless of language, which allows the respective Task to not worry about the construction of which directories to scan anymore.
1 parent 6563d53 commit 62313cf

File tree

8 files changed

+178
-75
lines changed

8 files changed

+178
-75
lines changed

android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/Plugin.kt

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package de.mannodermaus.gradle.plugins.junit5
22

3-
import de.mannodermaus.gradle.plugins.junit5.providers.KotlinTestRootDirectoryProvider
4-
import de.mannodermaus.gradle.plugins.junit5.providers.TestRootDirectoryProvider
3+
import com.android.build.gradle.api.BaseVariant
4+
import de.mannodermaus.gradle.plugins.junit5.providers.JavaDirectoryProvider
5+
import de.mannodermaus.gradle.plugins.junit5.providers.DirectoryProvider
6+
import de.mannodermaus.gradle.plugins.junit5.providers.KotlinDirectoryProvider
57
import de.mannodermaus.gradle.plugins.junit5.tasks.AndroidJUnit5JacocoReport
68
import de.mannodermaus.gradle.plugins.junit5.tasks.AndroidJUnit5UnitTest
79
import org.gradle.api.Plugin
@@ -95,22 +97,31 @@ class AndroidJUnitPlatformPlugin : Plugin<Project> {
9597
// and connect a Code Coverage report to it if Jacoco is enabled.
9698
val testVariants = projectConfig.unitTestVariants
9799
val isJacocoApplied = projectConfig.jacocoPluginApplied
98-
val isKotlinApplied = projectConfig.kotlinPluginApplied
99100

100101
testVariants.forEach { variant ->
101-
// Aggregate non-standard test root directories
102-
val rootProviders = mutableSetOf<TestRootDirectoryProvider>()
103-
if (isKotlinApplied) {
104-
rootProviders += KotlinTestRootDirectoryProvider(this, variant)
105-
}
102+
val directoryProviders = collectDirectoryProviders(variant)
106103

107104
// Create JUnit 5 test task
108-
val testTask = AndroidJUnit5UnitTest.create(this, variant, rootProviders)
105+
val testTask = AndroidJUnit5UnitTest.create(this, variant, directoryProviders)
109106

110107
if (isJacocoApplied) {
111108
// Create a Jacoco friend task
112-
AndroidJUnit5JacocoReport.create(this, testTask)
109+
AndroidJUnit5JacocoReport.create(this, testTask, directoryProviders)
113110
}
114111
}
115112
}
113+
114+
private fun Project.collectDirectoryProviders(variant: BaseVariant): Collection<DirectoryProvider> {
115+
val providers = mutableSetOf<DirectoryProvider>()
116+
117+
// Default JUnit 5 directories
118+
providers += JavaDirectoryProvider(variant)
119+
120+
// Kotlin Integration
121+
if (projectConfig.kotlinPluginApplied) {
122+
providers += KotlinDirectoryProvider(project, variant)
123+
}
124+
125+
return providers
126+
}
116127
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package de.mannodermaus.gradle.plugins.junit5.providers
2+
3+
import java.io.File
4+
5+
/**
6+
* General interface for providers of class & source directories
7+
* towards the construction of JUnit 5 tasks and its companions.
8+
*
9+
* Registered through the plugin, integrations with different languages
10+
* and frameworks can provide their own collection of directories.
11+
* The most prominent example consists of the opt-in Kotlin support,
12+
* which provides the "/kotlin" directories to each JUnit 5 task,
13+
* allowing Kotlin classes to be used for test detection & execution.
14+
*/
15+
interface DirectoryProvider {
16+
/**
17+
* The locations of compiled class files
18+
*/
19+
fun mainClassDirectories(): Set<File>
20+
21+
/**
22+
* The locations of compiled test class files
23+
*/
24+
fun testClassDirectories(): Set<File>
25+
26+
/**
27+
* The combined locations of all compiled class files
28+
*/
29+
fun classDirectories() = mainClassDirectories() + testClassDirectories()
30+
31+
/**
32+
* The locations of source files
33+
*/
34+
fun mainSourceDirectories(): Set<File>
35+
36+
/**
37+
* The locations of test source files
38+
*/
39+
fun testSourceDirectories(): Set<File>
40+
41+
/**
42+
* The combined locations of all source files
43+
*/
44+
fun sourceDirectories() = mainSourceDirectories() + testSourceDirectories()
45+
}
46+
47+
/* Extensions */
48+
49+
fun Iterable<DirectoryProvider>.classDirectories() = flatMap { it.classDirectories() }.distinct()
50+
fun Iterable<DirectoryProvider>.sourceDirectories() = flatMap { it.sourceDirectories() }.distinct()
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package de.mannodermaus.gradle.plugins.junit5.providers
2+
3+
import com.android.build.gradle.api.BaseVariant
4+
import de.mannodermaus.gradle.plugins.junit5.unitTestVariant
5+
import de.mannodermaus.gradle.plugins.junit5.variantData
6+
7+
/**
8+
* Default Provider implementation for Java-based test root directories.
9+
* This will look up the main & test root directories
10+
* of the variant connected to a given JUnit 5 task.
11+
*/
12+
class JavaDirectoryProvider(private val variant: BaseVariant) : DirectoryProvider {
13+
14+
override fun mainSourceDirectories() =
15+
javaSourceFoldersOf(variant)
16+
17+
override fun mainClassDirectories() =
18+
javaClassFoldersOf(variant)
19+
20+
override fun testSourceDirectories() =
21+
javaSourceFoldersOf(variant.unitTestVariant)
22+
23+
override fun testClassDirectories() =
24+
javaClassFoldersOf(variant.unitTestVariant)
25+
26+
/* Private */
27+
28+
private fun javaSourceFoldersOf(variant: BaseVariant) =
29+
variant.sourceSets
30+
.flatMap { it.javaDirectories }
31+
.toSet()
32+
33+
private fun javaClassFoldersOf(variant: BaseVariant) =
34+
setOf(variant.variantData.scope.javaOutputDir)
35+
}
Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,66 @@
11
package de.mannodermaus.gradle.plugins.junit5.providers
22

33
import com.android.build.gradle.api.BaseVariant
4+
import com.android.builder.model.SourceProvider
45
import de.mannodermaus.gradle.plugins.junit5.unitTestVariant
56
import org.gradle.api.Project
7+
import org.gradle.api.file.SourceDirectorySet
8+
import org.gradle.api.internal.HasConvention
9+
import org.jetbrains.kotlin.gradle.plugin.KOTLIN_DSL_NAME
10+
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
611
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
712
import java.io.File
813

14+
/* Types */
15+
916
/**
1017
* Provides test root directories for Kotlin sources,
1118
* with which a JUnit 5 Task can be enhanced.
19+
*
20+
* Note: The resulting Sets might be empty for modules powered by AGP2:
21+
* The legacy Kotlin integration automatically copies over Kotlin classes
22+
* into the Java directories, which renders dedicated Gradle tasks useless.
1223
*/
13-
class KotlinTestRootDirectoryProvider(
24+
class KotlinDirectoryProvider(
1425
private val project: Project,
15-
private val variant: BaseVariant) : TestRootDirectoryProvider {
16-
17-
override fun testRootDirectories(): Set<File> {
18-
// Hook in the Kotlin destination directories to the JUnit 5 Task.
19-
// Note: The resulting Set might be empty for modules powered by AGP2:
20-
// The legacy Kotlin integration automatically copies over Kotlin classes
21-
// into the Java directories, which renders dedicated Gradle tasks useless.
22-
val kotlinTaskNames = listOf(
23-
kotlinTaskName(variant),
24-
kotlinTaskName(variant.unitTestVariant))
25-
26-
return kotlinTaskNames
27-
.map { project.tasks.findByName(it) }
28-
.filter { it != null }
29-
.map { it as KotlinCompile }
30-
.map { it.destinationDir }
31-
.toSet()
32-
}
26+
private val variant: BaseVariant) : DirectoryProvider {
27+
28+
override fun mainSourceDirectories() =
29+
kotlinSourceFoldersOf(variant)
30+
31+
override fun mainClassDirectories() =
32+
kotlinClassFoldersOf(variant)
33+
34+
override fun testSourceDirectories() =
35+
kotlinSourceFoldersOf(variant.unitTestVariant)
36+
37+
override fun testClassDirectories() =
38+
kotlinClassFoldersOf(variant.unitTestVariant)
39+
40+
/* Private */
3341

34-
private fun kotlinTaskName(variant: BaseVariant) =
35-
"compile${variant.name.capitalize()}Kotlin"
42+
private fun kotlinSourceFoldersOf(variant: BaseVariant) =
43+
variant.sourceSets
44+
.flatMap { it.kotlin.srcDirs }
45+
.toSet()
46+
47+
private fun kotlinClassFoldersOf(variant: BaseVariant): Set<File> {
48+
val kotlinTask = project.tasks.findByName(variant.kotlinTaskName) ?: return emptySet()
49+
return setOf((kotlinTask as KotlinCompile).destinationDir)
50+
}
3651
}
52+
53+
/* Extensions */
54+
55+
private val BaseVariant.kotlinTaskName
56+
get() = "compile${this.name.capitalize()}Kotlin"
57+
58+
private val SourceProvider.kotlin: SourceDirectorySet
59+
get() {
60+
if (this !is HasConvention) {
61+
throw IllegalArgumentException("Argument doesn't have Conventions: $this")
62+
}
63+
64+
val kotlinConvention = this.convention.plugins[KOTLIN_DSL_NAME] as KotlinSourceSet
65+
return kotlinConvention.kotlin
66+
}

android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/providers/TestRootDirectoryProvider.kt

Lines changed: 0 additions & 7 deletions
This file was deleted.

android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/tasks/Jacoco.kt

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ package de.mannodermaus.gradle.plugins.junit5.tasks
33
import de.mannodermaus.gradle.plugins.junit5.jacoco
44
import de.mannodermaus.gradle.plugins.junit5.logInfo
55
import de.mannodermaus.gradle.plugins.junit5.maybeCreate
6+
import de.mannodermaus.gradle.plugins.junit5.providers.DirectoryProvider
7+
import de.mannodermaus.gradle.plugins.junit5.providers.classDirectories
8+
import de.mannodermaus.gradle.plugins.junit5.providers.sourceDirectories
69
import org.gradle.api.Project
710
import org.gradle.testing.jacoco.tasks.JacocoReport
811

@@ -16,8 +19,10 @@ private const val GROUP_REPORTING = "reporting"
1619
open class AndroidJUnit5JacocoReport : JacocoReport() {
1720

1821
companion object {
19-
fun create(project: Project, testTask: AndroidJUnit5UnitTest): AndroidJUnit5JacocoReport {
20-
val configAction = ConfigAction(project, testTask)
22+
fun create(project: Project,
23+
testTask: AndroidJUnit5UnitTest,
24+
directoryProviders: Collection<DirectoryProvider>): AndroidJUnit5JacocoReport {
25+
val configAction = ConfigAction(project, testTask, directoryProviders)
2126
return project.tasks.create(configAction.name, configAction.type, configAction)
2227
}
2328
}
@@ -39,7 +44,8 @@ open class AndroidJUnit5JacocoReport : JacocoReport() {
3944
*/
4045
private class ConfigAction(
4146
project: Project,
42-
testTask: AndroidJUnit5UnitTest
47+
testTask: AndroidJUnit5UnitTest,
48+
private val directoryProviders: Collection<DirectoryProvider>
4349
) : JUnit5TaskConfigAction<AndroidJUnit5JacocoReport>(project, testTask) {
4450

4551
override fun getName(): String = scope.getTaskName(TASK_NAME_DEFAULT)
@@ -58,11 +64,8 @@ open class AndroidJUnit5JacocoReport : JacocoReport() {
5864
// Task-level Configuration
5965
val taskJacoco = testTask.jacoco
6066
reportTask.executionData = project.files(taskJacoco.destinationFile.path)
61-
reportTask.classDirectories = project.files(scope.javaOutputDir)
62-
reportTask.sourceDirectories = project.files(variant.sourceSets
63-
.map { it.javaDirectories }
64-
.flatten()
65-
.map { it.path })
67+
reportTask.classDirectories = project.files(directoryProviders.classDirectories())
68+
reportTask.sourceDirectories = project.files(directoryProviders.sourceDirectories())
6669

6770
// Apply JUnit 5 configuration parameters
6871
val junit5Jacoco = junit5.jacoco
@@ -72,7 +75,7 @@ open class AndroidJUnit5JacocoReport : JacocoReport() {
7275
xml.isEnabled = junit5Jacoco.xmlReport
7376
}
7477

75-
project.logInfo("Assembled Jacoco Code Coverage for JUnit 5 Task '$testTask.name':")
78+
project.logInfo("Assembled Jacoco Code Coverage for JUnit 5 Task '${testTask.name}':")
7679
project.logInfo("|__ Execution Data: ${reportTask.executionData.asPath}")
7780
project.logInfo("|__ Source Dirs: ${reportTask.sourceDirectories.asPath}")
7881
project.logInfo("|__ Class Dirs: ${reportTask.classDirectories.asPath}")

android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/tasks/UnitTest.kt

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import com.android.build.gradle.internal.scope.VariantScope
66
import com.android.build.gradle.tasks.factory.AndroidUnitTest
77
import com.android.builder.core.VariantType
88
import de.mannodermaus.gradle.plugins.junit5.AndroidJUnitPlatformExtension
9-
import de.mannodermaus.gradle.plugins.junit5.providers.TestRootDirectoryProvider
109
import de.mannodermaus.gradle.plugins.junit5.engines
1110
import de.mannodermaus.gradle.plugins.junit5.filters
1211
import de.mannodermaus.gradle.plugins.junit5.getExcludeClassNamePatterns
@@ -15,9 +14,10 @@ import de.mannodermaus.gradle.plugins.junit5.isEmpty
1514
import de.mannodermaus.gradle.plugins.junit5.junit5
1615
import de.mannodermaus.gradle.plugins.junit5.logInfo
1716
import de.mannodermaus.gradle.plugins.junit5.packages
17+
import de.mannodermaus.gradle.plugins.junit5.providers.DirectoryProvider
18+
import de.mannodermaus.gradle.plugins.junit5.providers.classDirectories
1819
import de.mannodermaus.gradle.plugins.junit5.selectors
1920
import de.mannodermaus.gradle.plugins.junit5.tags
20-
import de.mannodermaus.gradle.plugins.junit5.unitTestVariant
2121
import de.mannodermaus.gradle.plugins.junit5.variantData
2222
import org.gradle.api.Project
2323
import org.gradle.api.file.FileCollection
@@ -43,8 +43,8 @@ open class AndroidJUnit5UnitTest : JavaExec() {
4343
fun create(
4444
project: Project,
4545
variant: BaseVariant,
46-
additionalRoots: Set<TestRootDirectoryProvider>): AndroidJUnit5UnitTest {
47-
val configAction = ConfigAction(project, variant, additionalRoots)
46+
directoryProviders: Collection<DirectoryProvider>): AndroidJUnit5UnitTest {
47+
val configAction = ConfigAction(project, variant, directoryProviders)
4848
return project.tasks.create(configAction.name, configAction.type, configAction)
4949
}
5050
}
@@ -61,28 +61,13 @@ open class AndroidJUnit5UnitTest : JavaExec() {
6161
@Optional
6262
var assetsCollection: FileCollection? = null
6363

64-
/**
65-
* Default Provider implementation for test root directories.
66-
* This will look up the main & test root directories
67-
* of the variant connected to a given JUnit 5 task.
68-
*/
69-
private class DefaultTestRootDirectoryProvider(
70-
private val variant: BaseVariant) : TestRootDirectoryProvider {
71-
override fun testRootDirectories() = setOf(
72-
// e.g. "build/intermediates/classes/debug/..."
73-
variant.variantData.scope.javaOutputDir,
74-
// e.g. "build/intermediates/classes/test/debug/..."
75-
variant.unitTestVariant.variantData.scope.javaOutputDir
76-
)
77-
}
78-
7964
/**
8065
* Configuration closure for an Android JUnit5 test task.
8166
*/
8267
private class ConfigAction(
8368
val project: Project,
8469
val variant: BaseVariant,
85-
val additionalRoots: Set<TestRootDirectoryProvider>
70+
val directoryProviders: Collection<DirectoryProvider>
8671
) : TaskConfigAction<AndroidJUnit5UnitTest> {
8772

8873
private val scope: VariantScope = variant.variantData.scope
@@ -114,13 +99,10 @@ open class AndroidJUnit5UnitTest : JavaExec() {
11499
task.classpath = getDefaultUnitTestTask().classpath +
115100
project.configurations.getByName("junitPlatform")
116101

117-
// Aggregate test root directories, starting with the default set of folders.
118-
// (Usually, the unit test variant's folders should be enough,
119-
// however we aggregate the main scope's output as well)
120-
val defaultProvider = DefaultTestRootDirectoryProvider(variant)
121-
val testRootDirs = defaultProvider.testRootDirectories() + additionalRoots.flatMap { it.testRootDirectories() }
102+
// Aggregate test root directories from the given providers
103+
val testRootDirs = directoryProviders.classDirectories()
122104

123-
project.logInfo("Assembled JUnit 5 Task '$task.name':")
105+
project.logInfo("Assembled JUnit 5 Task '${task.name}':")
124106
testRootDirs.forEach { project.logInfo("|__ $it") }
125107

126108
// Configure main class & arguments
@@ -203,7 +185,7 @@ open class AndroidJUnit5UnitTest : JavaExec() {
203185
private fun buildArgs(
204186
junit5: AndroidJUnitPlatformExtension,
205187
reportsDir: File,
206-
testRootDirs: Set<File>): List<String> {
188+
testRootDirs: List<File>): List<String> {
207189
val args = mutableListOf<String>()
208190

209191
// Log Details

build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ allprojects {
3737
google()
3838
jcenter()
3939
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
40-
maven { url "https://dl.bintray.com/jetbrains/spek" }
4140
}
4241

4342
// Store deployment credentials

0 commit comments

Comments
 (0)