Как запустить тесты, компилирующие файл kotlin в памяти, и проверить результат?

Пока у меня есть

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?*/) }

Это вручную запускает компилятор, но я хотел бы скомпилировать результирующую строку из первого компилятора (MyProjectCompiler который генерирует источник kotlin) в памяти и проверяет результат без записи в файл.

Я хотел бы включить все в текущий classpath, если это возможно.

2 ответа

Решение

Я нашел самый простой способ сделать это, использовать что-то вроде кода в исходном вопросе и использовать java.io.tmpdir, Вот многоразовое решение:

Добавьте компилятор kotlin в качестве тестовой зависимости:

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

Оболочка для компилятора:

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 для создания объектов из скомпилированных классов:

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

}

Пример теста:

Сначала создайте исходный файл kotlin

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

Убедитесь, что он компилируется:

assertTrue(JvmCompile.exe(codegenOutputFile, compileOutputDir))

Загрузить класс как экземпляр интерфейса

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

Результат будет таким, как ожидалось: found: 'Hello, world!'

Чтение источника K2JVMCompiler Класс, кажется, что компилятор поддерживает только компиляцию для файлов. Копая глубже, кажется слишком сложным, чтобы подделать записи org.jetbrains.kotlin.codegen.KotlinCodegenFacade статический метод compileCorrectFiles,

Лучше всего догадаться, чтобы использовать файловую систему для этого. Временный RAM-диск может удовлетворить ваши потребности. (Это встроенный macOS например)

Другие вопросы по тегам