[Test] Extract MockLibraryUtil to :compiler:tests-compiler-utils

Also provide MockLibraryUtilExt with bridges to MockLibraryUtil
  with JUnit4Assertions
This commit is contained in:
Dmitriy Novozhilov
2020-12-22 13:56:06 +03:00
committed by TeamCityServer
parent eadec08937
commit cb7b1652e7
26 changed files with 271 additions and 172 deletions
@@ -23,6 +23,7 @@ dependencies {
testCompile(project(":compiler:cli-js"))
testCompile(project(":compiler:serialization"))
testCompile(projectTests(":compiler:test-infrastructure-utils"))
testCompile(project(":kotlin-preloader"))
testCompileOnly(intellijCoreDep()) { includeJars("intellij-core") }
testCompile(intellijDep()) {
@@ -0,0 +1,81 @@
/*
* Copyright 2010-2020 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.
*/
@file:JvmName("JvmCompilationUtils")
package org.jetbrains.kotlin.test
import org.jetbrains.kotlin.test.util.KtTestUtil
import org.jetbrains.kotlin.utils.rethrow
import java.io.File
import java.io.IOException
import java.io.StringWriter
import java.nio.charset.Charset
import java.util.*
import javax.tools.Diagnostic
import javax.tools.DiagnosticCollector
import javax.tools.JavaFileObject
import javax.tools.ToolProvider
@JvmOverloads
@Throws(IOException::class)
fun compileJavaFiles(files: Collection<File>, options: List<String?>?, javaErrorFile: File? = null, assertions: Assertions): Boolean {
val javaCompiler = ToolProvider.getSystemJavaCompiler()
val diagnosticCollector = DiagnosticCollector<JavaFileObject>()
javaCompiler.getStandardFileManager(diagnosticCollector, Locale.ENGLISH, Charset.forName("utf-8")).use { fileManager ->
val javaFileObjectsFromFiles = fileManager.getJavaFileObjectsFromFiles(files)
val task = javaCompiler.getTask(
StringWriter(), // do not write to System.err
fileManager,
diagnosticCollector,
options,
null,
javaFileObjectsFromFiles
)
val success = task.call() // do NOT inline this variable, call() should complete before errorsToString()
if (javaErrorFile == null || !javaErrorFile.exists()) {
assertions.assertTrue(success) { errorsToString(diagnosticCollector, true) }
} else {
assertions.assertEqualsToFile(javaErrorFile, errorsToString(diagnosticCollector, false))
}
return success
}
}
fun compileJavaFilesExternallyWithJava9(files: Collection<File>, options: List<String?>): Boolean {
return compileJavaFilesExternally(files, options, KtTestUtil.getJdk9Home())
}
fun compileJavaFilesExternally(files: Collection<File>, options: List<String?>, jdkHome: File): Boolean {
val command: MutableList<String?> = ArrayList()
command.add(File(jdkHome, "bin/javac").path)
command.addAll(options)
for (file in files) {
command.add(file.path)
}
return try {
val process = ProcessBuilder().command(command).inheritIO().start()
process.waitFor()
process.exitValue() == 0
} catch (e: Exception) {
throw rethrow(e)
}
}
private fun errorsToString(diagnosticCollector: DiagnosticCollector<JavaFileObject>, humanReadable: Boolean): String {
val builder = StringBuilder()
for (diagnostic in diagnosticCollector.diagnostics) {
if (diagnostic.kind != Diagnostic.Kind.ERROR) continue
if (humanReadable) {
builder.append(diagnostic).append("\n")
} else {
builder.append(File(diagnostic.source.toUri()).name).append(":")
.append(diagnostic.lineNumber).append(":")
.append(diagnostic.columnNumber).append(":")
.append(diagnostic.code).append("\n")
}
}
return builder.toString()
}
@@ -0,0 +1,219 @@
/*
* Copyright 2010-2020 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 org.jetbrains.kotlin.test
import com.intellij.openapi.util.io.FileUtil
import com.intellij.util.io.ZipUtil
import org.jetbrains.kotlin.cli.common.CLICompiler
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.js.K2JSCompiler
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
import org.jetbrains.kotlin.preloading.ClassPreloadingUtils
import org.jetbrains.kotlin.preloading.Preloader
import org.jetbrains.kotlin.test.util.KtTestUtil
import org.jetbrains.kotlin.utils.PathUtil
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.PrintStream
import java.lang.ref.SoftReference
import java.util.regex.Pattern
import java.util.zip.ZipOutputStream
import kotlin.reflect.KClass
object MockLibraryUtil {
private var compilerClassLoader = SoftReference<ClassLoader>(null)
@JvmStatic
fun compileJvmLibraryToJar(
sourcesPath: String,
jarName: String,
addSources: Boolean = false,
allowKotlinSources: Boolean = true,
extraOptions: List<String> = emptyList(),
extraClasspath: List<String> = emptyList(),
useJava9: Boolean = false,
assertions: Assertions
): File {
return compileLibraryToJar(
sourcesPath,
KtTestUtil.tmpDirForReusableFolder("testLibrary-$jarName"),
jarName,
addSources,
allowKotlinSources,
extraOptions,
extraClasspath,
useJava9,
assertions
)
}
@JvmStatic
fun compileJavaFilesLibraryToJar(
sourcesPath: String,
jarName: String,
addSources: Boolean = false,
extraOptions: List<String> = emptyList(),
extraClasspath: List<String> = emptyList(),
assertions: Assertions
): File {
return compileJvmLibraryToJar(
sourcesPath, jarName, addSources,
allowKotlinSources = false,
extraClasspath = extraClasspath, extraOptions = extraOptions,
assertions = assertions
)
}
@JvmStatic
fun compileLibraryToJar(
sourcesPath: String,
contentDir: File,
jarName: String,
addSources: Boolean = false,
allowKotlinSources: Boolean = true,
extraOptions: List<String> = emptyList(),
extraClasspath: List<String> = emptyList(),
useJava9: Boolean = false,
assertions: Assertions
): File {
val classesDir = File(contentDir, "classes")
val srcFile = File(sourcesPath)
val kotlinFiles = FileUtil.findFilesByMask(Pattern.compile(".*\\.kt"), srcFile)
if (srcFile.isFile || kotlinFiles.isNotEmpty()) {
KtAssert.assertTrue("Only java files are expected", allowKotlinSources)
compileKotlin(sourcesPath, classesDir, extraOptions, *extraClasspath.toTypedArray())
}
val javaFiles = FileUtil.findFilesByMask(Pattern.compile(".*\\.java"), srcFile)
if (javaFiles.isNotEmpty()) {
val classpath = mutableListOf<String>()
classpath += ForTestCompileRuntime.runtimeJarForTests().path
classpath += KtTestUtil.getAnnotationsJar().path
classpath += extraClasspath
// Probably no kotlin files were present, so dir might not have been created after kotlin compiler
if (classesDir.exists()) {
classpath += classesDir.path
} else {
FileUtil.createDirectory(classesDir)
}
val options = listOf(
"-classpath", classpath.joinToString(File.pathSeparator),
"-d", classesDir.path
)
val compile =
if (useJava9) ::compileJavaFilesExternallyWithJava9
else { files, opts -> compileJavaFiles(files, opts, assertions = assertions) }
val success = compile(javaFiles, options)
if (!success) {
throw AssertionError("Java files are not compiled successfully")
}
}
return createJarFile(contentDir, classesDir, jarName, sourcesPath.takeIf { addSources })
}
@JvmStatic
fun compileJsLibraryToJar(sourcesPath: String, jarName: String, addSources: Boolean, extraOptions: List<String> = emptyList()): File {
val contentDir = KtTestUtil.tmpDirForReusableFolder("testLibrary-$jarName")
val outDir = File(contentDir, "out")
val outputFile = File(outDir, "$jarName.js")
compileKotlin2JS(sourcesPath, outputFile, extraOptions)
return createJarFile(contentDir, outDir, jarName, sourcesPath.takeIf { addSources })
}
@JvmStatic
fun createJarFile(contentDir: File, dirToAdd: File, jarName: String, sourcesPath: String? = null): File {
val jarFile = File(contentDir, "$jarName.jar")
ZipOutputStream(FileOutputStream(jarFile)).use { zip ->
ZipUtil.addDirToZipRecursively(zip, jarFile, dirToAdd, "", null, null)
if (sourcesPath != null) {
ZipUtil.addDirToZipRecursively(zip, jarFile, File(sourcesPath), "src", null, null)
}
}
return jarFile
}
private fun runJvmCompiler(args: List<String>) {
runCompiler(compiler2JVMClass, args)
}
private fun runJsCompiler(args: List<String>) {
runCompiler(compiler2JSClass, args)
}
// Runs compiler in custom class loader to avoid effects caused by replacing Application with another one created in compiler.
private fun runCompiler(compilerClass: Class<*>, args: List<String>) {
val outStream = ByteArrayOutputStream()
val compiler = compilerClass.newInstance()
val execMethod = compilerClass.getMethod("exec", PrintStream::class.java, Array<String>::class.java)
val invocationResult = execMethod.invoke(compiler, PrintStream(outStream), args.toTypedArray()) as Enum<*>
KtAssert.assertEquals(String(outStream.toByteArray()), ExitCode.OK.name, invocationResult.name)
}
@JvmStatic
@JvmOverloads
fun compileKotlin(
sourcesPath: String,
outDir: File,
extraOptions: List<String> = emptyList(),
vararg extraClasspath: String
) {
val classpath = mutableListOf<String>()
if (File(sourcesPath).isDirectory) {
classpath += sourcesPath
}
classpath += extraClasspath
val args = mutableListOf(
sourcesPath,
"-d", outDir.absolutePath,
"-classpath", classpath.joinToString(File.pathSeparator)
) + extraOptions
runJvmCompiler(args)
}
private fun compileKotlin2JS(sourcesPath: String, outputFile: File, extraOptions: List<String>) {
runJsCompiler(listOf("-meta-info", "-output", outputFile.absolutePath, sourcesPath, *extraOptions.toTypedArray()))
}
fun compileKotlinModule(buildFilePath: String) {
runJvmCompiler(listOf("-no-stdlib", "-Xbuild-file", buildFilePath))
}
private val compiler2JVMClass: Class<*>
@Synchronized get() = loadCompilerClass(K2JVMCompiler::class)
private val compiler2JSClass: Class<*>
@Synchronized get() = loadCompilerClass(K2JSCompiler::class)
@Synchronized
private fun loadCompilerClass(compilerClass: KClass<out CLICompiler<*>>): Class<*> {
val classLoader = compilerClassLoader.get() ?: createCompilerClassLoader().also { classLoader ->
compilerClassLoader = SoftReference<ClassLoader>(classLoader)
}
return classLoader.loadClass(compilerClass.java.name)
}
@Synchronized
private fun createCompilerClassLoader(): ClassLoader {
return ClassPreloadingUtils.preloadClasses(
listOf(PathUtil.kotlinPathsForDistDirectory.compilerPath),
Preloader.DEFAULT_CLASS_NUMBER_ESTIMATE, null, null
)
}
}