/* * 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) { 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() 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?) { super.visit(version, access, name, signature, superName, interfaces) fqname = name } } }