KT-50033 Add missing public API packages to module-info and setup a test
The test checks that new packages are not accidentally non-exported, so each new stdlib package must be either exported or specified in that test's expected non-exported package list.
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
package test.jdk9
|
||||
|
||||
import org.objectweb.asm.*
|
||||
import java.io.File
|
||||
import java.lang.module.ModuleDescriptor
|
||||
import java.util.jar.JarFile
|
||||
import kotlin.test.*
|
||||
|
||||
class NonExportedPackagesTest {
|
||||
|
||||
@Test
|
||||
fun stdlib() {
|
||||
checkNonExportedPackages("kotlin-stdlib", setOf(
|
||||
"kotlin.collections.builders",
|
||||
"kotlin.js",
|
||||
"kotlin.jvm.internal.unsafe",
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stdlibJdk7() {
|
||||
checkNonExportedPackages("kotlin-stdlib-jdk7", setOf(
|
||||
"kotlin.internal.jdk7",
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stdlibJdk8() {
|
||||
checkNonExportedPackages("kotlin-stdlib-jdk8", setOf(
|
||||
"kotlin.internal.jdk8",
|
||||
"kotlin.random.jdk8",
|
||||
))
|
||||
}
|
||||
|
||||
private fun checkNonExportedPackages(jarShortName: String, expectedPackages: Set<String>) {
|
||||
val file = findJar(jarShortName)
|
||||
JarFile(file).use { jar ->
|
||||
val moduleInfoEntry = jar.getJarEntry("META-INF/versions/9/module-info.class") ?: error("module-info is not found in $file")
|
||||
val descriptor = jar.getInputStream(moduleInfoEntry).use { infoStream -> ModuleDescriptor.read(infoStream) }
|
||||
val packages = mutableSetOf<String>()
|
||||
|
||||
for (entry in jar.entries()) {
|
||||
if (entry.isDirectory) continue
|
||||
val name = entry.name
|
||||
if (name == moduleInfoEntry.name) continue
|
||||
if (name.endsWith(".class", ignoreCase = true) &&
|
||||
(!name.startsWith("META-INF", ignoreCase = true)) ||
|
||||
name.startsWith("META-INF/versions", ignoreCase = true)
|
||||
) {
|
||||
jar.getInputStream(entry).use { classStream ->
|
||||
val visitor = ClassFqnVisitor()
|
||||
ClassReader(classStream).accept(visitor, ClassReader.SKIP_CODE)
|
||||
visitor.fqname?.run { substringBeforeLast('/').replace('/', '.') }?.let(packages::add)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val nonExported = packages - descriptor.exports().filter { it.targets().isEmpty() }.map { it.source() }
|
||||
assertEquals(expectedPackages, nonExported)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private fun findJar(shortName: String): File {
|
||||
val jars = System.getProperty("stdlibJars").split(File.pathSeparator)
|
||||
return jars.map(::File).single { it.name.matches(Regex("""${Regex.escape(shortName)}(?!-[a-z]).+\.jar""")) }
|
||||
}
|
||||
|
||||
private class ClassFqnVisitor : ClassVisitor(Opcodes.ASM9) {
|
||||
var fqname: String? = null
|
||||
|
||||
override fun visit(version: Int, access: Int, name: String?, signature: String?, superName: String?, interfaces: Array<out String>?) {
|
||||
super.visit(version, access, name, signature, superName, interfaces)
|
||||
fqname = name
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user