Почему я получаю ArrayIndexOutOfBoundsException, выполняющий этот конкретный шаг Cucumber в Котлине?

Я работаю с файлом функций JCM Cucumber, используя Java8 и PicoContainer. Я убрал эти шаги так, чтобы они были пустыми, и я все еще получаю сообщение об ошибке. Вот моя особенность:

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).

По какой-то причине выполнение этого шага в Котлине вызывает ошибку:

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

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 хранит ссылки на лямбда, а использование Cucumber некоторых внутренних компонентов JVM, что он не должен приближаться!

Ваши другие шаги Kotlin не вызывают ошибку по разным причинам.

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

Исправление будет заключаться в добавлении дополнительной проверки в код огурца, хотя, возможно, лучшим решением было бы переписать код Огурца для правильного использования дженериков .

Обходной путь заключается в том, чтобы гарантировать, что Kotlin не оптимизирует лямбда, включив ссылку на содержащий экземпляр. Даже что-то простое, как ссылка на this :

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

достаточно, чтобы убедить Котлина не выполнять оптимизацию.

Детали

Когда Cucumber добавляет определение шага с помощью лямбда, например, cucumber.api.java8.En он анализирует лямбду для получения информации о дженериках.

То, как это делается, – это использовать хакер доступа, чтобы достичь поля sun.reflect.ConstantPool в определении класса лямбда. Это родной тип и представляет собой деталь реализации класса, сохраняя ссылки на константы, используемые классом. Затем огурец повторяется назад, просматривая константу, представляющую конструктор лямбды. Затем он использует другой внутренний хак, статический метод, называемый getArgumentTypes на jdk.internal.org.objectweb.asm.Type , чтобы определить подпись лямбды.

Запустив javap -v против сгенерированных классов, выяснилось , что когда Kotlin создает лямбда-блок в статическом INSTANCE он добавляет постоянное поле под названием INSTANCE которое затем появляется в постоянном пуле класса. Это поле является экземпляром анонимного внутреннего класса с именем типа ClipboardSteps$1 а не лямбдой как таковым, поэтому его внутренняя typestring разбивает мини-парсер внутри getArgumentTypes , который является ошибкой, которую вы видите.

Таким образом, быстрое исправление в Cucumber состоит в том, чтобы проверить, что имя члена постоянного пула "<init>" , которое представляет конструктор лямбды, и игнорировать что-либо еще, например, нашего члена INSTANCE .

Правильное исправление будет состоять в том, чтобы переписать интроспекцию типа Cucumber, чтобы не использовать постоянный пул вообще!