1 /* <lambda>null2 * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 */ 4 5 package kotlinx.coroutines.tools 6 7 import org.junit.* 8 import org.junit.runner.* 9 import org.junit.runners.* 10 import java.io.* 11 import java.util.* 12 import java.util.jar.* 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 } 41 } 42 43 @Test 44 fun testApi() { 45 val libsDir = File("../$rootDir/$moduleName/build/libs").absoluteFile.normalize() 46 val jarPath = getJarPath(libsDir) 47 val kotlinJvmMappingsFiles = listOf(libsDir.resolve("../visibilities.json")) 48 val visibilities = 49 kotlinJvmMappingsFiles 50 .map { readKotlinVisibilities(it) } 51 .reduce { m1, m2 -> m1 + m2 } 52 JarFile(jarPath).use { jarFile -> 53 val api = getBinaryAPI(jarFile, visibilities).filterOutNonPublic(nonPublicPackages) 54 api.dumpAndCompareWith(File("reference-public-api").resolve("$moduleName.txt")) 55 // check for atomicfu leaks 56 jarFile.checkForAtomicFu() 57 } 58 } 59 60 private fun getJarPath(libsDir: File): File { 61 val regex = Regex("$moduleName-.+\\.jar") 62 var files = (libsDir.listFiles() ?: throw Exception("Cannot list files in $libsDir")) 63 .filter { it.name.let { 64 it matches regex 65 && !it.endsWith("-sources.jar") 66 && !it.endsWith("-javadoc.jar") 67 && !it.endsWith("-tests.jar")} 68 && !it.name.contains("-metadata-")} 69 if (files.size > 1) // maybe multiplatform? 70 files = files.filter { it.name.startsWith("$moduleName-jvm-") } 71 return files.singleOrNull() ?: 72 error("No single file matching $regex in $libsDir:\n${files.joinToString("\n")}") 73 } 74 } 75 76 private val ATOMIC_FU_REF = "Lkotlinx/atomicfu/".toByteArray() 77 checkForAtomicFunull78private fun JarFile.checkForAtomicFu() { 79 val foundClasses = mutableListOf<String>() 80 for (e in entries()) { 81 if (!e.name.endsWith(".class")) continue 82 val bytes = getInputStream(e).use { it.readBytes() } 83 loop@for (i in 0 until bytes.size - ATOMIC_FU_REF.size) { 84 for (j in 0 until ATOMIC_FU_REF.size) { 85 if (bytes[i + j] != ATOMIC_FU_REF[j]) continue@loop 86 } 87 foundClasses += e.name // report error at the end with all class names 88 break@loop 89 } 90 } 91 if (foundClasses.isNotEmpty()) { 92 error("Found references to atomicfu in jar file $name in the following class files: ${ 93 foundClasses.joinToString("") { "\n\t\t" + it } 94 }") 95 } 96 } 97