/
GradleClassMethodRegexTestFilter.kt
98 lines (88 loc) · 4.62 KB
/
GradleClassMethodRegexTestFilter.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package io.kotest.runner.junit.platform.gradle
import io.kotest.core.descriptors.Descriptor
import io.kotest.core.descriptors.TestPath
import io.kotest.core.filter.TestFilter
import io.kotest.core.filter.TestFilterResult
import io.kotest.mpp.Logger
class GradleClassMethodRegexTestFilter(private val patterns: List<String>) : TestFilter {
private val logger = Logger(GradleClassMethodRegexTestFilter::class)
override fun filter(descriptor: Descriptor): TestFilterResult {
logger.log { Pair(descriptor.toString(), "Testing against $patterns") }
return when {
patterns.isEmpty() -> TestFilterResult.Include
patterns.all { match(it, descriptor) } -> TestFilterResult.Include
else -> TestFilterResult.Exclude(null)
}
}
/**
* Matches the pattern supplied from gradle build script or command line interface.
*
* supports:
* - gradle test --tests "SomeTest"
* - gradle test --tests "*Test"
* - gradle test --tests "io.package.*"
* - gradle test --tests "io.package"
* - gradle test --tests "io.package.SomeTest"
* - gradle test --tests "io.package.SomeTest.first level context*"
* - gradle test --tests "io.package.SomeTest.*"
* - gradle test --tests "io.*.SomeTest"
* - gradle test --tests "SomeTest.first level context*"
* - gradle test --tests "*.first level context*"
*
* Exact nested context / test matching is NOT CURRENTLY SUPPORTED.
* Kotest support lazy test registration within nested context. Gradle test filter does not
* natively work nicely with kotest. In order to make it work we need to think of a way to
* recursively apply partial context-search as we dive deeper into the contexts.
*
* Notes to Maintainers:
*
* Gradle supplies a pattern string which corresponds to a well-formed regex object.
* This can be directly usable for kotest.
* - A* becomes \QA\E.*
* - A*Test becomes \QA\E.*\QTest\E
* - io.*.A*Test becomes \Qio.\E.*\Q.A\E.*\QTest\E
* - io.*.A*Test.AccountDetails* becomes \Qio.\E.*\Q.A\E.*\QTest.AccountDetails\E.*
* - io.*.A*Test.some test context* becomes \Qio.\E.*\Q.A\E.*\QTest.some test context\E.*
*/
private fun match(pattern: String, descriptor: Descriptor): Boolean {
val path = descriptor.dotSeparatedFullPath().value
val regexPattern = "^(.*)$pattern".toRegex() // matches pattern exactly
val laxRegexPattern = "^(.*)$pattern(.*)\$".toRegex() // matches pattern that can be followed by others
val packagePath = descriptor.spec().id.value.split(".").dropLast(1).joinToString(".") // io.kotest
val isSimpleClassMatch by lazy {
// SomeTest or *Test
descriptor.spec().id.value.split(".").lastOrNull()?.matches(pattern.toRegex()) ?: false
}
val isSpecMatched by lazy { descriptor.spec().id.value.matches(regexPattern) } // *.SomeTest
val isFullPathMatched by lazy { path.matches(regexPattern) } // io.*.SomeTest
val isFullPathDotMatched by lazy { "$path.".matches(regexPattern) } // io.*. or io.*.SomeTest.*
// if there's no uppercase in the supplied pattern, activate trigger relaxed matching
val doesNotContainUppercase by lazy { pattern.replace("\\Q", "").replace("\\E", "").all { !it.isUpperCase() } }
val isPackageMatched by lazy { doesNotContainUppercase && packagePath.matches(laxRegexPattern) } // io.kotest
val isPackageWithDotMatched by lazy { doesNotContainUppercase && "$packagePath.".matches(laxRegexPattern) } // io.kotest.*
return isSimpleClassMatch ||
isFullPathMatched ||
isFullPathDotMatched ||
isSpecMatched ||
isPackageMatched ||
isPackageWithDotMatched
}
/**
* Returns a gradle-compatible dot-separated full path of the given descriptor.
* i.e. io.package.MyTest.given something -- should do something
*
* Note: I'm forced to do this... :(
*
* We cannot use the / separator for contexts as gradle rejects that.
* Filters also seemingly only works on first "." after the class. This was severely limiting.
* The other problem is that also means we can't have "." in the test / context path because gradle doesn't
* like it and will not even give us any candidate classes.
*/
private fun Descriptor.dotSeparatedFullPath(): TestPath = when (this) {
is Descriptor.SpecDescriptor -> TestPath(this.id.value)
is Descriptor.TestDescriptor -> when (this.parent) {
is Descriptor.SpecDescriptor -> TestPath("${this.parent.id.value}.${this.id.value}")
is Descriptor.TestDescriptor -> TestPath("${this.parent.dotSeparatedFullPath().value} -- ${this.id.value}")
}
}
}