【问题标题】:How do I run tests compiling a kotlin file in memory and check the result?如何在内存中运行编译 kotlin 文件的测试并检查结果?
【发布时间】:2017-08-25 19:12:07
【问题描述】:

到现在为止

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 源代码)编译生成的字符串,并在不写入文件的情况下检查结果。

如果可能,我想在当前类路径中包含所有内容。

【问题讨论】:

    标签: java android jvm kotlin kotlinc


    【解决方案1】:

    我发现最简单的方法是使用类似于原始问题中的代码并使用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
    
    }
    

    用于从已编译的类创建对象的类加载器:

    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!'

    【讨论】:

    • 我尝试了代码,但缺少 3 件事:MockClasswritercodegenOutputFilecompileOutputDir
    【解决方案2】:

    看了K2JVMCompiler类的源码,好像编译器只支持对文件的编译。深入挖掘,伪造org.jetbrains.kotlin.codegen.KotlinCodegenFacade 静态方法compileCorrectFiles 的条目似乎过于复杂。

    您最好的猜测是使用文件系统来执行此操作。临时 RAM 磁盘可能适合您的需要。 (这是macOS内置for example

    【讨论】:

    • 有没有办法使用 JSR 223 API 来做到这一点?我不太熟悉
    • JSR 223 只会让您向应用程序添加脚本功能,调整应用程序会更容易,但它不会帮助您使用 RAM 驱动器或在 RAM 中编译。
    • 这就是 REPL 的运行方式,不是吗?可以使用它来定义我一个一个编译的每种类型,然后以这种方式进行测试吗?我已经通过 kotlin 源代码,但我无法真正弄清楚如何以编程方式运行 REPL
    • 恕我直言:REPL 是命令行实用程序。另一方面,JSR 233 允许您在没有 REPL 的情况下在应用程序中运行预先编写的脚本。我已经使用它在应用程序中运行 Beanshell 和 Jython 脚本,例如,这有助于创建用户定义的行为。
    • 不管 223 我认为找出 REPL 可能比手动编译和类加载要容易得多。我至少会用字符串进行测试,但这并不难,因为我仍然有 IR 中的类。虽然在任何地方都找不到任何关于它的信息,所以可能需要一些工作
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-23
    • 2020-04-13
    • 2015-09-29
    相关资源
    最近更新 更多