Skip to content

Commit 53c0028

Browse files
committed
Improve usability of PublicApiTest:
* No need to manually create tests for new modules (scans list of modules) * Pass test when used with "-Poverwrite.output=true"
1 parent 305c66a commit 53c0028

File tree

3 files changed

+61
-113
lines changed

3 files changed

+61
-113
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#
2+
# Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
#
4+
5+
module.roots=core integration native reactive ui
6+
module.marker=build.gradle
7+
module.ignore=kotlinx-coroutines-rx-example
8+
9+
packages.internal=kotlinx.coroutines.experimental.internal kotlinx.coroutines.experimental.scheduling

binary-compatibility-validator/test/PublicApiTest.kt

+41-103
Original file line numberDiff line numberDiff line change
@@ -5,124 +5,62 @@
55
package kotlinx.coroutines.experimental.tools
66

77
import org.junit.*
8-
import org.junit.rules.*
8+
import org.junit.runner.*
9+
import org.junit.runners.*
910
import java.io.*
11+
import java.util.*
1012
import java.util.jar.*
11-
12-
class PublicApiTest {
13-
14-
/*
15-
* How to add a test for your module kotlinx-coroutines-foo?
16-
*
17-
* Dump public declarations via PublicApiDump.kt and create file
18-
* reference-public-api/kotlinx-coroutines-foo.txt with dumped declarations.
19-
*
20-
* Then add test:
21-
*
22-
* @Test
23-
* fun kotlinxCorountesFoo() { // <- name pattern should match txt file from reference-public-api
24-
* snapshotAPIAndCompare($relative_path_to_module)
25-
* }
26-
*/
27-
28-
@Rule
29-
@JvmField
30-
val testName = TestName()
31-
32-
@Test
33-
fun kotlinxCoroutinesCore() {
34-
snapshotAPIAndCompare("core/kotlinx-coroutines-core", nonPublicPackages = listOf(
35-
"kotlinx.coroutines.experimental.internal",
36-
"kotlinx.coroutines.experimental.scheduling"))
37-
}
38-
39-
@Test
40-
fun kotlinxCoroutinesReactive() {
41-
snapshotAPIAndCompare("reactive/kotlinx-coroutines-reactive")
42-
}
43-
44-
@Test
45-
fun kotlinxCoroutinesReactor() {
46-
snapshotAPIAndCompare("reactive/kotlinx-coroutines-reactor")
47-
}
48-
49-
@Test
50-
fun kotlinxCoroutinesRx1() {
51-
snapshotAPIAndCompare("reactive/kotlinx-coroutines-rx1")
52-
}
53-
54-
@Test
55-
fun kotlinxCoroutinesRx2() {
56-
snapshotAPIAndCompare("reactive/kotlinx-coroutines-rx2")
57-
}
58-
59-
@Test
60-
fun kotlinxCoroutinesGuava() {
61-
snapshotAPIAndCompare("integration/kotlinx-coroutines-guava")
62-
}
63-
64-
@Test
65-
fun kotlinxCoroutinesJdk8() {
66-
snapshotAPIAndCompare("integration/kotlinx-coroutines-jdk8")
67-
}
68-
69-
70-
@Test
71-
fun kotlinxCoroutinesNio() {
72-
snapshotAPIAndCompare("integration/kotlinx-coroutines-nio")
73-
}
74-
75-
@Test
76-
fun kotlinxCoroutinesQuasar() {
77-
snapshotAPIAndCompare("integration/kotlinx-coroutines-quasar")
78-
}
79-
80-
@Test
81-
fun kotlinxCoroutinesAndroid() {
82-
snapshotAPIAndCompare("ui/kotlinx-coroutines-android")
83-
}
84-
85-
86-
@Test
87-
fun kotlinxCoroutinesJavafx() {
88-
snapshotAPIAndCompare("ui/kotlinx-coroutines-javafx")
13+
import kotlin.collections.ArrayList
14+
15+
@RunWith(Parameterized::class)
16+
class PublicApiTest(
17+
private val rootDir: String,
18+
private val moduleName: String
19+
) {
20+
companion object {
21+
private val apiProps = ClassLoader.getSystemClassLoader()
22+
.getResource("api.properties").openStream().use { Properties().apply { load(it) } }
23+
private val nonPublicPackages = apiProps.getProperty("packages.internal")!!.split(" ")
24+
25+
@Parameterized.Parameters(name = "{1}")
26+
@JvmStatic
27+
fun modules(): List<Array<Any>> {
28+
val moduleRoots = apiProps.getProperty("module.roots").split(" ")
29+
val moduleMarker = apiProps.getProperty("module.marker")!!
30+
val moduleIgnore = apiProps.getProperty("module.ignore")!!.split(" ").toSet()
31+
val modules = ArrayList<Array<Any>>()
32+
for (rootDir in moduleRoots) {
33+
File("../$rootDir").listFiles( FileFilter { it.isDirectory })?.forEach { dir ->
34+
if (dir.name !in moduleIgnore && File(dir, moduleMarker).exists()) {
35+
modules += arrayOf<Any>(rootDir, dir.name)
36+
}
37+
}
38+
}
39+
return modules
40+
}
8941
}
9042

9143
@Test
92-
fun kotlinxCoroutinesSwing() {
93-
snapshotAPIAndCompare("ui/kotlinx-coroutines-swing")
94-
}
95-
96-
private fun snapshotAPIAndCompare(basePath: String, jarPattern: String = basePath.substring(basePath.indexOf("/") + 1),
97-
publicPackages: List<String> = emptyList(), nonPublicPackages: List<String> = emptyList()) {
98-
val base = File("../$basePath/build/libs").absoluteFile.normalize()
99-
val jarFile = getJarPath(base, jarPattern)
100-
val kotlinJvmMappingsFiles = listOf(base.resolve("../visibilities.json"))
101-
102-
val publicPackagePrefixes = publicPackages.map { it.replace('.', '/') + '/' }
44+
fun testApi() {
45+
val libsDir = File("../$rootDir/$moduleName/build/libs").absoluteFile.normalize()
46+
val jarFile = getJarPath(libsDir)
47+
val kotlinJvmMappingsFiles = listOf(libsDir.resolve("../visibilities.json"))
10348
val visibilities =
10449
kotlinJvmMappingsFiles
105-
.map { readKotlinVisibilities(it).filterKeys { name -> publicPackagePrefixes.none { name.startsWith(it) } } }
50+
.map { readKotlinVisibilities(it) }
10651
.reduce { m1, m2 -> m1 + m2 }
107-
10852
val api = getBinaryAPI(JarFile(jarFile), visibilities).filterOutNonPublic(nonPublicPackages)
109-
110-
val target = File("reference-public-api")
111-
.resolve(testName.methodName.replaceCamelCaseWithDashedLowerCase() + ".txt")
112-
113-
api.dumpAndCompareWith(target)
53+
api.dumpAndCompareWith(File("reference-public-api").resolve("$moduleName.txt"))
11454
}
11555

116-
private fun getJarPath(base: File, jarPattern: String, kotlinVersion: String? = null): File {
117-
val versionPattern = kotlinVersion?.let { "-" + Regex.escape(it) } ?: ".+"
118-
val regex = Regex("$jarPattern$versionPattern\\.jar")
119-
val files = (base.listFiles() ?: throw Exception("Cannot list files in $base"))
56+
private fun getJarPath(libsDir: File): File {
57+
val regex = Regex("$moduleName-.+\\.jar")
58+
val files = (libsDir.listFiles() ?: throw Exception("Cannot list files in $libsDir"))
12059
.filter { it.name.let {
12160
it matches regex
12261
&& !it.endsWith("-sources.jar")
12362
&& !it.endsWith("-javadoc.jar")
12463
&& !it.endsWith("-tests.jar")} }
125-
126-
return files.singleOrNull() ?: throw Exception("No single file matching $regex in $base:\n${files.joinToString("\n")}")
64+
return files.singleOrNull() ?: throw Exception("No single file matching $regex in $libsDir:\n${files.joinToString("\n")}")
12765
}
12866
}

binary-compatibility-validator/test/utils.kt

+11-10
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,28 @@ fun List<ClassBinarySignature>.dumpAndCompareWith(to: File) {
1414
if (!to.exists()) {
1515
to.parentFile?.mkdirs()
1616
to.bufferedWriter().use { dump(to = it) }
17-
fail("Expected data file did not exist. Generating: $to")
17+
fail("Expected data file did not exist. Generated: $to")
1818
} else {
1919
val actual = dump(to = StringBuilder())
2020
assertEqualsToFile(to, actual)
2121
}
2222
}
2323

24-
private fun assertEqualsToFile(expectedFile: File, actual: CharSequence) {
24+
private fun assertEqualsToFile(to: File, actual: CharSequence) {
2525
val actualText = actual.trimTrailingWhitespacesAndAddNewlineAtEOF()
26-
val expectedText = expectedFile.readText().trimTrailingWhitespacesAndAddNewlineAtEOF()
27-
28-
if (OVERWRITE_EXPECTED_OUTPUT && expectedText != actualText) {
29-
expectedFile.writeText(actualText)
30-
assertEquals(expectedText, actualText, "Actual data differs from file content: ${expectedFile.name}, rewriting")
26+
val expectedText = to.readText().trimTrailingWhitespacesAndAddNewlineAtEOF()
27+
if (expectedText == actualText) return // Ok
28+
// Difference
29+
if (OVERWRITE_EXPECTED_OUTPUT) {
30+
to.writeText(actualText)
31+
println("Generated: $to")
32+
return // make test pass when overwriting output
3133
}
32-
34+
// Fail on difference
3335
assertEquals(
3436
expectedText,
3537
actualText,
36-
"Actual data differs from file content: ${expectedFile.name}\nTo overwrite the expected API rerun with -Doverwrite.output=true parameter\n"
38+
"Actual data differs from file content: ${to.name}\nTo overwrite the expected API rerun with -Doverwrite.output=true parameter\n"
3739
)
3840
}
3941

@@ -44,4 +46,3 @@ private fun CharSequence.trimTrailingWhitespacesAndAddNewlineAtEOF(): String =
4446

4547

4648
private val UPPER_CASE_CHARS = Regex("[A-Z]+")
47-
fun String.replaceCamelCaseWithDashedLowerCase() = replace(UPPER_CASE_CHARS) { "-" + it.value.toLowerCase() }

0 commit comments

Comments
 (0)