Skip to content

Commit 02b4c49

Browse files
committed
Integration tests: support AGP 9 testing and version filtering
Preparation for testing AGP9. - Add min/max version to `@TestsAndroid` and `@TestsAndroidCompose` filter AGP versions. - Add SemVerRange to help with filtering. - Add AGP9 properties.
1 parent ee6ac18 commit 02b4c49

File tree

6 files changed

+130
-40
lines changed

6 files changed

+130
-40
lines changed

dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/junit/DokkaGradlePluginTestExtension.kt

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import org.jetbrains.dokka.it.gradle.junit.DokkaGradlePluginTestExtension.Compan
99
import org.jetbrains.dokka.it.gradle.junit.TestedVersions.Companion.dashSeparatedId
1010
import org.jetbrains.dokka.it.gradle.junit.TestedVersions.Companion.displayName
1111
import org.jetbrains.dokka.it.gradle.utils.SemVer
12+
import org.jetbrains.dokka.it.gradle.utils.SemVerRange
1213
import org.jetbrains.dokka.it.gradle.withJetBrainsCachedGradleVersion
1314
import org.jetbrains.dokka.it.gradle.withReadOnlyDependencyCache
1415
import org.jetbrains.dokka.it.systemProperty
@@ -21,7 +22,6 @@ import org.junit.platform.commons.logging.Logger
2122
import org.junit.platform.commons.logging.LoggerFactory
2223
import org.junit.platform.commons.support.AnnotationSupport
2324
import org.junit.platform.commons.support.AnnotationSupport.findAnnotation
24-
import org.junit.platform.commons.support.AnnotationSupport.isAnnotated
2525
import org.junit.platform.commons.support.ReflectionSupport
2626
import org.opentest4j.TestAbortedException
2727
import java.nio.file.Files
@@ -85,17 +85,29 @@ class DokkaGradlePluginTestExtension :
8585
val projectInitializer = ReflectionSupport.newInstance(dgpTest.projectInitializer.java)
8686
val sourceProjectDir = dgpTest.sourceProjectName
8787

88-
val isAndroidTest = context.hasOrParentHasAnnotation<TestsAndroid>()
89-
val isAndroidComposeTest = context.hasOrParentHasAnnotation<TestsAndroidCompose>()
88+
val testAndroidAnnotation = context.findClosestAnnotation<TestsAndroid>()
89+
val testsAndroidComposeAnnotation = context.findClosestAnnotation<TestsAndroidCompose>()
9090

9191
val gradleProperties = computeGradleProperties(
9292
context,
9393
dgpTest.gradlePropertiesProvider,
9494
)
9595

9696
val testedVersionsSource = when {
97-
isAndroidComposeTest -> TestedVersionsSource.AndroidCompose
98-
isAndroidTest -> TestedVersionsSource.Android
97+
testsAndroidComposeAnnotation != null -> TestedVersionsSource.AndroidCompose(
98+
agpVersionRange = SemVerRange.from(
99+
min = testsAndroidComposeAnnotation.minAgpVersion,
100+
max = testsAndroidComposeAnnotation.maxAgpVersion,
101+
),
102+
)
103+
104+
testAndroidAnnotation != null -> TestedVersionsSource.Android(
105+
agpVersionRange = SemVerRange.from(
106+
min = testAndroidAnnotation.minAgpVersion,
107+
max = testAndroidAnnotation.maxAgpVersion,
108+
),
109+
)
110+
99111
else -> TestedVersionsSource.Default
100112
}
101113

@@ -328,10 +340,12 @@ class DokkaGradlePluginTestExtension :
328340
internal val templateProjectsDir by systemProperty(::Path)
329341

330342
/**
331-
* Check if this [ExtensionContext] or any of its parents is annotated with [T].
343+
* Find the annotation of type [T] closest to the current [ExtensionContext].
332344
*/
333-
private inline fun <reified T : Annotation> ExtensionContext.hasOrParentHasAnnotation(): Boolean =
345+
private inline fun <reified T : Annotation> ExtensionContext.findClosestAnnotation(): T? =
334346
generateSequence(this) { it.parent.getOrNull() }
335-
.any { isAnnotated(it.element, T::class.java) }
347+
.firstNotNullOfOrNull {
348+
findAnnotation(it.element, T::class.java).getOrNull()
349+
}
336350
}
337351
}

dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/junit/TestedVersionsSource.kt

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
*/
44
package org.jetbrains.dokka.it.gradle.junit
55

6-
import org.jetbrains.dokka.it.gradle.junit.TestedVersions.Companion.displayName
76
import org.jetbrains.dokka.it.gradle.junit.TestedVersionsSource.Default.dokkaVersionOverride
87
import org.jetbrains.dokka.it.gradle.utils.SemVer
98
import org.jetbrains.dokka.it.gradle.utils.SemVer.Companion.contains
9+
import org.jetbrains.dokka.it.gradle.utils.SemVerRange
1010
import org.jetbrains.dokka.it.optionalSystemProperty
1111
import org.jetbrains.dokka.it.systemProperty
1212

@@ -94,31 +94,48 @@ fun interface TestedVersionsSource<T : TestedVersions> {
9494
*
9595
* The test must be tagged with [TestsAndroid].
9696
*/
97-
object Android : TestedVersionsSource<TestedVersions.Android> {
97+
class Android(
98+
private val agpVersionRange: SemVerRange? = null,
99+
) : TestedVersionsSource<TestedVersions.Android> {
100+
98101
/**
99102
* All possible Android Gradle Plugin versions that could be tested.
100103
*
101-
* We test the latest v7 and v8 AGP versions.
104+
* We test the latest v7, v8, and v9 AGP versions.
102105
*
103106
* Note the AGP major version indicates the required major version of Gradle.
104107
* So, AGP 7.* supports Gradle 7, but will throw an error if used with Gradle 8.
105108
*/
106-
private val allAgpVersions: List<String> = listOf(
109+
private val allAgpVersions: List<SemVer> = listOf(
107110
"7.4.2",
108111
"8.11.1",
109112
"8.12.0",
110-
)
113+
"9.0.0-alpha13",
114+
).map { SemVer(it) }
115+
116+
private val matchedAgpVersions: List<SemVer> =
117+
if (agpVersionRange == null) {
118+
allAgpVersions
119+
} else {
120+
allAgpVersions.filter { it in agpVersionRange }
121+
}
122+
123+
init {
124+
require(matchedAgpVersions.isNotEmpty()) {
125+
"No AGP versions matched the given range: $agpVersionRange"
126+
}
127+
}
111128

112129
private val allVersions: Sequence<TestedVersions.Android> =
113130
sequence {
114131
Default.get().forEach { v ->
115-
allAgpVersions.forEach { agp ->
132+
matchedAgpVersions.forEach { agp ->
116133
yield(
117134
TestedVersions.Android(
118135
dgp = v.dgp,
119136
gradle = v.gradle,
120137
kgp = v.kgp,
121-
agp = SemVer(agp),
138+
agp = agp,
122139
)
123140
)
124141
}
@@ -127,30 +144,13 @@ fun interface TestedVersionsSource<T : TestedVersions> {
127144
isAgpCompatibleWithGradle(agp = v.agp, gradle = v.gradle)
128145
}
129146

130-
/**
131-
* All major versions that _must_ be included in the sequence of all versions.
132-
*
133-
* This check is required because some versions are filtered out.
134-
*/
135-
private val requiredAgpMajorVersions = listOf(7, 8)
136-
137-
init {
138-
val agpMajorVersions = allVersions.map { it.agp.major }
139-
140-
requiredAgpMajorVersions.forEach { requiredAgpMajor ->
141-
require(requiredAgpMajor in agpMajorVersions) {
142-
val versionsList = allVersions.joinToString("\n") { " - ${it.displayName()}" }
143-
"Tested versions missing AGP $requiredAgpMajor. All versions:\n$versionsList"
144-
}
145-
}
146-
}
147-
148147
override fun get(): Sequence<TestedVersions.Android> = allVersions
149148

150149
private fun isAgpCompatibleWithGradle(agp: SemVer, gradle: SemVer): Boolean {
151150
// AGP/Gradle compatibility definitions:
152151
// https://developer.android.com/build/releases/gradle-plugin?buildsystem=ndk-build#updating-gradle
153152
return when (agp.majorAndMinorVersions) {
153+
"9.0" -> gradle.major >= 9
154154
"8.12" -> gradle in "8.13.0".."9.0.0"
155155
"8.11" -> gradle in "8.13.0".."9.0.0"
156156
"8.10" -> gradle in "8.11.1"..<"9.0.0"
@@ -176,7 +176,11 @@ fun interface TestedVersionsSource<T : TestedVersions> {
176176
*
177177
* The test must be tagged with [TestsAndroidCompose].
178178
*/
179-
object AndroidCompose : TestedVersionsSource<TestedVersions.AndroidCompose> {
179+
class AndroidCompose(
180+
agpVersionRange: SemVerRange? = null,
181+
) : TestedVersionsSource<TestedVersions.AndroidCompose> {
182+
183+
private val androidVersions = Android(agpVersionRange)
180184

181185
/**
182186
* Versions of the `org.jetbrains.compose` plugins.
@@ -187,7 +191,7 @@ fun interface TestedVersionsSource<T : TestedVersions> {
187191
)
188192

189193
override fun get(): Sequence<TestedVersions.AndroidCompose> = sequence {
190-
Android.get().forEach { v ->
194+
androidVersions.get().forEach { v ->
191195
allComposeGradlePluginVersions.forEach { composeGP ->
192196
yield(
193197
TestedVersions.AndroidCompose(

dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/junit/WithGradleProperties.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,16 @@ fun interface GradlePropertiesProvider {
3636

3737
object Android : GradlePropertiesProvider {
3838
override fun get(): Map<String, String> = buildMap {
39+
putAll(Default.get())
3940
put("android.useAndroidX", "true")
41+
put("android.builtInKotlin", "false")
42+
}
43+
}
44+
45+
object AndroidKotlinBuiltIn : GradlePropertiesProvider {
46+
override fun get(): Map<String, String> = buildMap {
47+
putAll(Android.get())
48+
put("android.builtInKotlin", "true")
4049
}
4150
}
4251
}

dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/junit/testTags.kt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,18 @@ annotation class TestsKotlinMultiplatform
4444
* If a test is annotated with [TestsAndroid] then
4545
* [DokkaGradlePluginTestExtension] will run the test multiple times,
4646
* and provide a [DokkaGradleProjectRunner] using [TestedVersions.Android].
47+
*
48+
* @param[minAgpVersion] Inclusive lower-bound for AGP versions.
49+
* @param[maxAgpVersion] Exclusive upper-bound for AGP versions.
4750
*/
4851
@Tag("Android")
4952
@Target(FUNCTION, CLASS)
5053
@MustBeDocumented
5154
@Inherited
52-
annotation class TestsAndroid
55+
annotation class TestsAndroid(
56+
val minAgpVersion: String = "",
57+
val maxAgpVersion: String = "",
58+
)
5359

5460

5561
/**
@@ -58,10 +64,16 @@ annotation class TestsAndroid
5864
* If a test is annotated with [TestsAndroid] then
5965
* [DokkaGradlePluginTestExtension] will run the test multiple times,
6066
* and provide a [DokkaGradleProjectRunner] using [TestedVersions.AndroidCompose].
67+
*
68+
* @param[minAgpVersion] Inclusive lower-bound for AGP versions.
69+
* @param[maxAgpVersion] Exclusive upper-bound for AGP versions.
6170
*/
6271
@Tag("Compose")
6372
@TestsAndroid
6473
@Target(FUNCTION, CLASS)
6574
@MustBeDocumented
6675
@Inherited
67-
annotation class TestsAndroidCompose
76+
annotation class TestsAndroidCompose(
77+
val minAgpVersion: String = "",
78+
val maxAgpVersion: String = "",
79+
)

dokka-integration-tests/gradle/src/main/kotlin/org/jetbrains/dokka/it/gradle/utils/SemVer.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,10 @@ data class SemVer(
1111
) : Comparable<SemVer> {
1212
val major: Int
1313

14-
//region These fields are private to avoid IJ warnings, feel free to make them public if it's helpful.
15-
private val minor: Int
14+
val minor: Int
1615
val patch: Int
1716
private val prerelease: String?
1817
private val metadata: String?
19-
//endregion
2018

2119
init {
2220
val match = semverRegex.matchEntire(version) ?: error("Invalid version '$version'")
@@ -33,6 +31,7 @@ data class SemVer(
3331
this.major != other.major -> this.major.compareTo(other.major)
3432
this.minor != other.minor -> this.minor.compareTo(other.minor)
3533
this.patch != other.patch -> this.patch.compareTo(other.patch)
34+
3635
this.prerelease != other.prerelease -> {
3736
when {
3837
this.prerelease == null -> 1
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2014-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package org.jetbrains.dokka.it.gradle.utils
6+
7+
/**
8+
* A range of [SemVer] versions.
9+
*
10+
* Only compares using the major, minor and patch components of the versions.
11+
*/
12+
data class SemVerRange(
13+
private val minInclusive: SemVer?,
14+
private val maxExclusive: SemVer?,
15+
) {
16+
init {
17+
require(minInclusive != null || maxExclusive != null) {
18+
"Invalid range: At least one bound must be specified. minInclusive=$minInclusive, maxExclusive=$maxExclusive"
19+
}
20+
if (minInclusive != null && maxExclusive != null) {
21+
require(minInclusive < maxExclusive) {
22+
"Invalid range: minInclusive=$minInclusive must be less than maxExclusive=$maxExclusive"
23+
}
24+
}
25+
}
26+
27+
operator fun contains(version: SemVer): Boolean {
28+
val version = SemVer("${version.major}.${version.minor}.${version.patch}")
29+
30+
if (minInclusive != null) {
31+
if (version < minInclusive) return false
32+
}
33+
if (maxExclusive != null) {
34+
if (version >= maxExclusive) return false
35+
}
36+
return true
37+
}
38+
39+
companion object {
40+
internal fun from(
41+
min: String,
42+
max: String,
43+
): SemVerRange {
44+
val min = if (min.isNotBlank()) SemVer(min) else null
45+
val max = if (max.isNotBlank()) SemVer(max) else null
46+
return SemVerRange(
47+
minInclusive = min,
48+
maxExclusive = max,
49+
)
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)