Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I run tests compiling a kotlin file in memory and check the result?

So far I have

import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler

  MyProjectCompiler.initialize("SampleKtFileOutput")
    .packageName("com.test.sample")
    .compile(File(someFile.path))
    .result { ktSource: String -> K2JVMCompiler()
       .exec(System.out, /** arguments here?*/) }

This manually starts the compiler, but I would like to compile the resulting String from the first compiler (MyProjectCompiler which generates kotlin source) in-memory and check the result without writing to a file.

I would like to include everything on the current classpath if possible.

like image 571
Preston Garno Avatar asked Aug 25 '17 19:08

Preston Garno


People also ask

How does Kotlin get compiled?

Yes, when targeting the JVM, Kotlin is compiled to JVM *. class files, which is a bytecode format that can later be either interpreted by a JVM, or compiled to the machine code by the JVM during the program run (JIT), or even compiled ahead-of-time (AOT) down to the machine code.

Can Javac compile Kotlin?

No, kotlinc compiles only Kotlin files ( . kt ). A mixed-language project requires combining both kotlinc and javac .


1 Answers

I found the easiest way to do it is to use something like the code in the original question and use java.io.tmpdir. Here's a re-usable solution:

Add the kotlin compiler as a test dependency:

testCompile group: 'org.jetbrains.kotlin', name: 'kotlin-compiler', version: "$kotlin_version"

Wrapper for the compiler:

object JvmCompile {

  fun exe(input: File, output: File): Boolean = K2JVMCompiler().run {
    val args = K2JVMCompilerArguments().apply {
      freeArgs = listOf(input.absolutePath)
      loadBuiltInsFromDependencies = true
      destination = output.absolutePath
      classpath = System.getProperty("java.class.path")
          .split(System.getProperty("path.separator"))
          .filter {
            it.asFile().exists() && it.asFile().canRead()
          }.joinToString(":")
      noStdlib = true
      noReflect = true
      skipRuntimeVersionCheck = true
      reportPerf = true
    }
    output.deleteOnExit()
    execImpl(
        PrintingMessageCollector(
            System.out,
            MessageRenderer.WITHOUT_PATHS, true),
        Services.EMPTY,
        args)
  }.code == 0

}

Classloader for creating objects from the compiled classes:

class Initializer(private val root: File) {

  val loader = URLClassLoader(
      listOf(root.toURI().toURL()).toTypedArray(),
      this::class.java.classLoader)

  @Suppress("UNCHECKED_CAST") 
  inline fun <reified T> loadCompiledObject(clazzName: String): T? 
      = loader.loadClass(clazzName).kotlin.objectInstance as T

  @Suppress("UNCHECKED_CAST") 
  inline fun <reified T> createInstance(clazzName: String): T? 
      = loader.loadClass(clazzName).kotlin.createInstance() as T

}

Example test case:

First make a kotlin source file

MockClasswriter("""
    |
    |package com.test
    |
    |class Example : Consumer<String> {
    |  override fun accept(value: String) {
    |    println("found: '$\value'")
    |  }
    |}
    """.trimMargin("|"))
    .writeToFile(codegenOutputFile)

Make sure it compiles:

assertTrue(JvmCompile.exe(codegenOutputFile, compileOutputDir))

Load the class as interface instance

Initializer(compileOutputDir)
      .createInstance<Consumer<String>>("com.test.Example")
      ?.accept("Hello, world!")

The output will be as expected: found: 'Hello, world!'

like image 153
Preston Garno Avatar answered Nov 15 '22 19:11

Preston Garno