| /* |
| * Copyright 2010-2022 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", |
| "kotlin.internal.jdk7", |
| "kotlin.internal.jdk8", |
| "kotlin.random.jdk8", |
| )) |
| } |
| |
| @Test |
| fun stdlibJdk7() { |
| checkNonExportedPackages("kotlin-stdlib-jdk7", setOf()) |
| } |
| |
| @Test |
| fun stdlibJdk8() { |
| checkNonExportedPackages("kotlin-stdlib-jdk8", setOf()) |
| } |
| |
| 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 |
| } |
| } |
| } |