kotlin - 为什么在 Kotlin 中运行这个特定的 Cucumber 步骤时会出现 ArrayIndexOutOfBoundsException?

标签 kotlin bdd cucumber-jvm cucumber-java picocontainer

我正在使用 Java8 和 PicoContainer 运行 Cucumber JVM 功能文件。我已经删除了这些步骤,所以它们是空的,但我仍然遇到错误。这是我的特点:

Feature: Full Journey

Scenario: Can load a typical JIRA csv and calculate the distribution from it

Given a typical JIRA export "/closed_only_JIRA.csv"
When I import it into Montecarluni
Then I should see the distribution
"""
6, 15, 3, 14, 2, 5, 6, 8, 5, 10, 15, 4, 2, 1
"""
When I copy it to the clipboard
Then I should be able to paste it somewhere else

(是的,这是一个完整的旅程,而不是 BDD 场景。)

无论出于何种原因,在 Kotlin 中运行此步骤都会导致错误:
import cucumber.api.java8.En

class ClipboardSteps(val world : World) : En {
    init {
        When("^I copy it to the clipboard$", {
            // Errors even without any code here 
        })
    }
}

虽然这个 Java 类运行得很好:
import cucumber.api.java8.En;

public class JavaClipboardSteps implements En {

    public JavaClipboardSteps(World world) {
        When("^I copy it to the clipboard$", () -> {
            // Works just fine with code or without
        });
    }
}

我完全感到困惑,尤其是因为 Kotlin 步骤类中的“然后”运行良好,而其他步骤运行没有错误:
import cucumber.api.java8.En

class FileImportSteps(val world: World) : En {
    init {
        // There's a Given here

        When("^I import it into Montecarluni$", {
            // There's some code here
        })
    }
}

亚军,完成:
import cucumber.api.CucumberOptions
import cucumber.api.junit.Cucumber
import org.junit.runner.RunWith

@RunWith(Cucumber::class)
@CucumberOptions(
    format = arrayOf("pretty"),
    glue = arrayOf("com.lunivore.montecarluni.glue"),
    features = arrayOf("."))
class Runner {
}

堆栈跟踪是:
cucumber.runtime.CucumberException: java.lang.ArrayIndexOutOfBoundsException: 52

at cucumber.runtime.java.JavaBackend.addStepDefinition(JavaBackend.java:166)
at cucumber.api.java8.En.Then(En.java:280)
at com.lunivore.montecarluni.glue.DistributionSteps.<init>(DistributionSteps.kt:8)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.picocontainer.injectors.AbstractInjector.newInstance(AbstractInjector.java:145)
at org.picocontainer.injectors.ConstructorInjector$1.run(ConstructorInjector.java:342)
at org.picocontainer.injectors.AbstractInjector$ThreadLocalCyclicDependencyGuard.observe(AbstractInjector.java:270)
at org.picocontainer.injectors.ConstructorInjector.getComponentInstance(ConstructorInjector.java:364)
at org.picocontainer.injectors.AbstractInjectionFactory$LifecycleAdapter.getComponentInstance(AbstractInjectionFactory.java:56)
at org.picocontainer.behaviors.AbstractBehavior.getComponentInstance(AbstractBehavior.java:64)
at org.picocontainer.behaviors.Stored.getComponentInstance(Stored.java:91)
at org.picocontainer.DefaultPicoContainer.getInstance(DefaultPicoContainer.java:699)
at org.picocontainer.DefaultPicoContainer.getComponent(DefaultPicoContainer.java:647)
at org.picocontainer.DefaultPicoContainer.getComponent(DefaultPicoContainer.java:678)
at cucumber.runtime.java.picocontainer.PicoFactory.getInstance(PicoFactory.java:40)
at cucumber.runtime.java.JavaBackend.buildWorld(JavaBackend.java:131)
at cucumber.runtime.Runtime.buildBackendWorlds(Runtime.java:141)
at cucumber.runtime.model.CucumberScenario.run(CucumberScenario.java:38)
at cucumber.runtime.junit.ExecutionUnitRunner.run(ExecutionUnitRunner.java:102)
at cucumber.runtime.junit.FeatureRunner.runChild(FeatureRunner.java:63)
at cucumber.runtime.junit.FeatureRunner.runChild(FeatureRunner.java:18)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at cucumber.runtime.junit.FeatureRunner.run(FeatureRunner.java:70)
at cucumber.api.junit.Cucumber.runChild(Cucumber.java:95)
at cucumber.api.junit.Cucumber.runChild(Cucumber.java:38)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at cucumber.api.junit.Cucumber.run(Cucumber.java:100)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

Caused by: java.lang.ArrayIndexOutOfBoundsException: 52
at jdk.internal.org.objectweb.asm.Type.getArgumentTypes(Type.java:358)
at cucumber.runtime.java8.ConstantPoolTypeIntrospector.getGenericTypes(ConstantPoolTypeIntrospector.java:32)
at cucumber.runtime.java.Java8StepDefinition.getParameterInfos(Java8StepDefinition.java:54)
at cucumber.runtime.java.Java8StepDefinition.<init>(Java8StepDefinition.java:44)
at cucumber.runtime.java.JavaBackend.addStepDefinition(JavaBackend.java:162)
... 44 more

这是怎么回事?

当前使用 Kotlin 步骤 checkin 的所有源代码均已注释掉,here . (请原谅我的困惑,因为我对我正在使用的很多东西都是新手;从最初的峰值重构正在进行中。)

最佳答案

这似乎是 Kotlin 编译匿名代码块的优化、Cucumber 关于 JVM 如何存储对 lambda 的引用的假设以及 Cucumber 使用一些不应该接近的 JVM 内部结构之间的不幸交互!

您的 other Kotlin steps不要因为各种(不同的)原因触发错误。

简而言之,如果 Kotlin 可以将 block 或 lambda 实现为静态单例,那么它可以实现,大概是出于性能原因。这会干扰 Cucumber 执行的一些非常规反射魔法(详情如下)。

修复 将是在 Cucumber 代码中添加额外的检查,尽管可以说更好的解决方法是重写 Cucumber 代码以使用 generics reflection properly .

解决方法是为了确保 Kotlin 不会通过包含对包含实例的引用来优化 lambda。甚至像引用 this 这样简单的事情:

When("^I import it into Montecarluni$") {
    this
    // your code
}

足以说服 Kotlin 不要执行优化。

细节

当 Cucumber 添加带有 lambda 的步骤定义时,例如cucumber.api.java8.En它自省(introspection) lambda 以获取有关泛型的信息。

这样做的方法是使用访问黑客访问 sun.reflect.ConstantPool lambda 类定义中的字段。这是 native 类型,是类的实现细节,存储对类使用的常量的引用。 Cucumber 然后通过这些向后迭代,寻找一个代表 lambda 构造函数的常量。然后它使用另一个内部 hack,一个名为 getArgumentTypes 的静态方法。在 jdk.internal.org.objectweb.asm.Type , 找出 lambda 的签名。

正在运行javap -v针对生成的类,似乎当 Kotlin 将 lambda block 变成静态单例时,它添加了一个名为 INSTANCE 的常量字段。然后出现在类的常量池中。该字段是一个匿名内部类的实例,其名称类似于 ClipboardSteps$1。而不是像这样的 lambda,所以它的内部类型字符串会破坏 getArgumentTypes 中的迷你解析器,这是您看到的错误。

所以快速修复在 Cucumber 中是检查常量池成员的名称是 "<init>" ,它代表 lambda 的构造函数,并忽略其他任何内容,例如我们的 INSTANCE成员。

正确修复 将重写 Cucumber 的类型自省(introspection)以根本不使用常量池!

关于kotlin - 为什么在 Kotlin 中运行这个特定的 Cucumber 步骤时会出现 ArrayIndexOutOfBoundsException?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43938447/

相关文章:

android - 重新创建 Activity 时未初始化 lateinit 属性

java - cucumber 如何查找功能文件是否已执行?任何Java方法?

java - 在其他项目中重用 Cucumber-JVM 步骤定义

java - 连接到特定的 WiFi (Android Q)

java - Gradle Kotlin DSL : Extract contents from dependency

python - 将参数从 WHEN 传递给 THEN

javascript - 通过 Jasmine 的 toHaveBeenCalledWith 方法使用对象类型

ajax - 如何让 Behat 等待 Angular ajax 调用?

java - 在 cucumber 测试期间无法执行目标 org.apache.maven.plugins :maven-surefire-plugin:2. 19.1 :test,

android - Android 上的外部键盘导致换行而不是搜索 - Kotlin