• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
checkForAtomicFunull78 private 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