为什么我在Kotlin上运行这个特定的Cucumber步骤时发生ArrayIndexOutOfBoundsException?

我正在运行一个使用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阶梯中的“Then”运行完美,而且这个步骤没有错误:

 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 { } 

Stacktrace是:

 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签署的所有源代码在这里注释掉了。 (请原谅,因为我是新手,我正在使用的很多东西,从最初的重构重构正在进行。)

这似乎是优化Kotlin编译匿名代码块之间的一个不幸的交互,假设Cucumber是如何将JVM存储到lambdas的引用的,以及Cucumber是否使用了一些不应该靠近的JVM内部元素!

您的其他Kotlin步骤不会因各种(不同)原因而触发该错误。

简而言之,如果Kotlin可以实现一个块或lambda作为一个静态单例,那么它可能是出于性能的原因。 这干扰了一些非常规的反射魔术黄瓜执行(详情如下)。

解决方法是在Cucumber代码中添加一个额外的检查,虽然可以说更好的解决方法是重写Cucumber代码以正确使用泛型反射 。

解决方法是确保Kotlin不会通过包含对包含实例的引用来优化lambda表达式。 即使是这样简单的东西:

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

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

细节

当Cucumber在例如cucumber.api.java8.En中添加一个带有lambda的步骤定义时,它反思了lambda有关泛型的信息。

这样做的方式是使用访问hack来达到lambda的类定义中的sun.reflect.ConstantPool字段。 这是一个本地类型,是类的实现细节,存储对类使用的常量的引用。 然后Cucumber向后遍历这些代表lambda构造函数的常量。 然后使用另一个内部hack,一个名为getArgumentTypesjdk.internal.org.objectweb.asm.Type静态方法来找出lambda的签名。

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

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

正确的解决办法是重写Cucumber的类型自省,根本不使用常量池!