Kotest прерывает gradle: тестовое задание с java.lang.IllegalArgumentException: получено событие сбоя для теста с неизвестным идентификатором

#unit-testing #kotlin #gradle #junit #kotest

#модульное тестирование #kotlin #gradle #junit #kotest

Вопрос:

Я начинаю с модульных тестов в Kotlin с использованием Kotest. Я использую следующие технологии, которые каким-то образом интегрируются с Kotest:

  • Сам Kotest
  • Kotlin / JVM
  • Gradle
  • Очарование
  • Pitest
  • Плагин IntelliJ IDEA «Kotest»

В gradle я включил следующие зависимости:

  • io.kotest:kotest-runner-junit5:$kotest_version : Фреймворк Kotest
  • io.kotest:kotest-assertions-core:$kotest_version : Утверждения ядра JVM Kotest
  • io.kotest:kotest-property:$kotest_version : Проверка свойств Kotest
  • io.kotest:kotest-extensions-allure:$kotest_version : Сбор данных для Allure
  • io.kotest:kotest-plugins-pitest:$kotest_version : Плагин для Pitest

Проблема в том, что теперь, когда я запускаю свои тесты через задачу gradle :test , я получаю следующее исключение:

java.lang.IllegalArgumentException: Received a failure event for test with unknown id '2.27'. Registered test ids: '[2.1, :test, 2.25]'

Неизвестный идентификатор / зарегистрированные идентификаторы отличаются при каждом запуске теста. На самом деле появляется много ошибок, но эта появляется последней. Ниже приведен полный вывод gradle (сокращенные внутренние вызовы):

 Testing started at 18:39 ...

> Task :kaptGenerateStubsKotlin UP-TO-DATE
> Task :kaptKotlin UP-TO-DATE
> Task :compileKotlin UP-TO-DATE
> Task :compileJava NO-SOURCE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE
> Task :kaptGenerateStubsTestKotlin UP-TO-DATE
> Task :kaptTestKotlin UP-TO-DATE
> Task :compileTestKotlin UP-TO-DATE
> Task :preTest
> Task :compileTestJava NO-SOURCE
> Task :processTestResources UP-TO-DATE
> Task :testClasses UP-TO-DATE
> Task :test
~~~ Kotest Configuration ~~~
-> Parallelization factor: 1
-> Default test timeout: 600000ms
-> Default test order: Sequential
-> Default isolation mode: SingleInstance
-> Global soft assertations: False
-> Write spec failure file: False
-> Fail on ignored tests: False
-> Spec execution order: SpecExecutionOrder
-> Extensions
  - io.kotest.engine.extensions.SystemPropertyTagExtension
  - io.kotest.core.extensions.RuntimeTagExtension
  - io.kotest.engine.extensions.RuntimeTagExpressionExtension
-> Listeners
  - io.kotest.extensions.allure.AllureTestReporter
  - class io.kotest.engine.config.LoadConfigFromClasspathKt$toDetectedConfig$beforeAfterAllListener$1

Nov. 04, 2020 6:39:48 NACHM. org.junit.platform.launcher.core.TestExecutionListenerRegistry lambda$notifyEach$1
WARNING: TestExecutionListener [org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestExecutionListener] threw exception for method: executionFinished(TestIdentifier [uniqueId = '[engine:kotest]/[spec:com.example.core.common.JSONFileTests]/[test:Writing to and reading from file should result in the original value]', parentId = '[engine:kotest]/[spec:com.example.core.common.JSONFileTests]', displayName = 'Writing to and reading from file should result in the original value', legacyReportingName = 'Writing to and reading from file should result in the original value', source = FileSource [file = D:devMinecraft Server Watchermsw-serverJSONFileTests.kt, filePosition = FilePosition [line = 40, column = -1]], tags = [], type = TEST], TestExecutionResult [status = FAILED, throwable = java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: OffsetSeconds])
java.lang.AssertionError <3 internal calls>
    at jdk.internal.reflect.GeneratedMethodAccessor29.invoke(Unknown Source) <11 internal calls>
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) <17 internal calls>
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
    at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:274)
    at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:84)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
    at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
    at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source) <5 internal calls>

Nov. 04, 2020 6:39:48 NACHM. org.junit.platform.launcher.core.TestExecutionListenerRegistry lambda$notifyEach$1
WARNING: TestExecutionListener [org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestExecutionListener] threw exception for method: executionFinished(TestIdentifier [uniqueId = '[engine:kotest]/[spec:com.example.core.common.JSONFileTests]/[test:Writing to and reading from file should result in the original value]', parentId = '[engine:kotest]/[spec:com.example.core.common.JSONFileTests]', displayName = 'Writing to and reading from file should result in the original value', legacyReportingName = 'Writing to and reading from file should result in the original value', source = FileSource [file = D:devMinecraft Server Watchermsw-serverJSONFileTests.kt, filePosition = FilePosition [line = 40, column = -1]], tags = [], type = TEST], TestExecutionResult [status = FAILED, throwable = java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: OffsetSeconds])
java.lang.AssertionError <3 internal calls>
    at jdk.internal.reflect.GeneratedMethodAccessor29.invoke(Unknown Source) <11 internal calls>
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) <17 internal calls>
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
    at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:274)
    at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:84)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
    at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
    at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source) <5 internal calls>
    at java.base/java.lang.Thread.run(Thread.java:834)

Nov. 04, 2020 6:39:48 NACHM. org.junit.platform.launcher.core.TestExecutionListenerRegistry lambda$notifyEach$1
WARNING: TestExecutionListener [org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestExecutionListener] threw exception for method: executionFinished(TestIdentifier [uniqueId = '[engine:kotest]/[spec:com.example.core.common.JSONFileTests]/[test:Writing to and reading from file should result in the original value]', parentId = '[engine:kotest]/[spec:com.example.core.common.JSONFileTests]', displayName = 'Writing to and reading from file should result in the original value', legacyReportingName = 'Writing to and reading from file should result in the original value', source = FileSource [file = D:devMinecraft Server Watchermsw-serverJSONFileTests.kt, filePosition = FilePosition [line = 40, column = -1]], tags = [], type = TEST], TestExecutionResult [status = SUCCESSFUL, throwable = null])
java.lang.AssertionError <3 internal calls>
    at jdk.internal.reflect.GeneratedMethodAccessor29.invoke(Unknown Source) <11 internal calls>
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) <17 internal calls>
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
    at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:274)
    at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:84)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
    at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
    at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source) <5 internal calls>
    at java.base/java.lang.Thread.run(Thread.java:834)

Nov. 04, 2020 6:39:48 NACHM. org.junit.platform.launcher.core.TestExecutionListenerRegistry lambda$notifyEach$1
WARNING: TestExecutionListener [org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestExecutionListener] threw exception for method: executionFinished(TestIdentifier [uniqueId = '[engine:kotest]/[spec:com.example.core.common.JSONFileTests]/[test:Writing to and reading from file should result in the original value]', parentId = '[engine:kotest]/[spec:com.example.core.common.JSONFileTests]', displayName = 'Writing to and reading from file should result in the original value', legacyReportingName = 'Writing to and reading from file should result in the original value', source = FileSource [file = D:devMinecraft Server Watchermsw-serverJSONFileTests.kt, filePosition = FilePosition [line = 40, column = -1]], tags = [], type = TEST], TestExecutionResult [status = SUCCESSFUL, throwable = null])
java.lang.AssertionError <3 internal calls>
    at jdk.internal.reflect.GeneratedMethodAccessor29.invoke(Unknown Source) <11 internal calls>
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) <17 internal calls>
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
    at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:274)
    at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:84)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
    at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
    at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source) <5 internal calls>
    at java.base/java.lang.Thread.run(Thread.java:834)

Nov. 04, 2020 6:39:48 NACHM. org.junit.platform.launcher.core.TestExecutionListenerRegistry lambda$notifyEach$1
WARNING: TestExecutionListener [org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestExecutionListener] threw exception for method: executionFinished(TestIdentifier [uniqueId = '[engine:kotest]/[spec:com.example.core.common.JSONFileTests]/[test:Writing to and reading from file should result in the original value]', parentId = '[engine:kotest]/[spec:com.example.core.common.JSONFileTests]', displayName = 'Writing to and reading from file should result in the original value', legacyReportingName = 'Writing to and reading from file should result in the original value', source = FileSource [file = D:devMinecraft Server Watchermsw-serverJSONFileTests.kt, filePosition = FilePosition [line = 40, column = -1]], tags = [], type = TEST], TestExecutionResult [status = SUCCESSFUL, throwable = null])
java.lang.AssertionError <3 internal calls>
    at jdk.internal.reflect.GeneratedMethodAccessor29.invoke(Unknown Source) <11 internal calls>
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) <17 internal calls>
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
    at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:274)
    at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:84)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
    at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
    at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source) <5 internal calls>
    at java.base/java.lang.Thread.run(Thread.java:834)

Received a failure event for test with unknown id '2.27'. Registered test ids: '[2.1, :test, 2.25]'
java.lang.IllegalArgumentException: Received a failure event for test with unknown id '2.27'. Registered test ids: '[2.1, :test, 2.25]' <19 internal calls>
    at java.base/java.lang.Thread.run(Thread.java:834)
> Task :test FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':test'.
> Received a failure event for test with unknown id '2.27'. Registered test ids: '[2.1, :test, 2.25]'
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
* Get more help at https://help.gradle.org
BUILD FAILED in 5s
10 actionable tasks: 2 executed, 8 up-to-date
  

Что может иметь отношение к делу:

1. Я использую пользовательские конфигурации задач в своем build.gradle.kts

Следующий раздел моего build.gradle.kts файла охватывает все материалы, связанные с тестированием, включая задачу по настройке среды для тестирования и вторую для последующей очистки:

 tasks.withType<Test> {
    useJUnitPlatform()
}

allure {
    autoconfigure = false
    version = "2.13.6"
}

configure<PitestPluginExtension> {
    testPlugin.set("Kotest")
    targetClasses.set(listOf("com.example.*"))
}

val testDir1Path = "/src/test/resources/DirectoryTest"
val testDir2Path = "/src/test/resources/Create"
tasks.register("preTest") {
    doLast {
        mkdir(testDir1Path)
        file("$testDir1Path/json00.json").apply { createNewFile() }.writeText("{ "s": "s", "i": 0, "b": true}")
        file("$testDir1Path/json01.json").apply { createNewFile() }.writeText("{}")
        file("$testDir1Path/json02.json").apply { createNewFile() }.writeText("{}")
        file("$testDir1Path/json03.json").apply { createNewFile() }.writeText("{}")
        file("$testDir1Path/json04.json").apply { createNewFile() }.writeText("{}")
        file("$testDir1Path/json05.json").apply { createNewFile() }.writeText("{}")
        file("$testDir1Path/json06.json").apply { createNewFile() }.writeText("{}")
    }
}

tasks.register<Delete>("postTest") {
    for (task in tasks.withType<Test>()) {
        mustRunAfter(task)
    }
    delete(testDir1Path)
    delete(testDir2Path)
    delete("/src/test/resources/NonExistent") // might be created by :pitest
}

tasks.withType<Test>().configureEach {
    dependsOn("preTest")
}
  

2. The test suite that first caused the error

The error occurred while I was working in the following Kotest test class, more precisely it occurred after I tried to implement a Test Factory and include it:

 class JSONFileTests : FunSpec({
    test("Loading a file into the model JSONModel01 should work") {
        shouldNotThrowAny {
            JSONFile("$exists/json00.json", JSONModel01.serializer())
        }.get() shouldBe JSONModel01("s", 0, true)
    }

    include("JSONModel01: ", echoTests(m1))
    include("JSONModel02: ", echoTests(m2))
    include("JSONModel03: ", echoTests(m3))
    include("JSONModel04: ", echoTests(m4))
    include("JSONModel05: ", echoTests(m5))
    include("JSONModel06: ", echoTests(m6))
}) {
    companion object {
        private val m1 = JSONFile("$exists/json01.json", JSONModel01.serializer()) to JSONModel01()
        private val m2 = JSONFile("$exists/json02.json", JSONModel02.serializer()) to JSONModel02()
        private val m3 = JSONFile("$exists/json03.json", JSONModel03.serializer()) to JSONModel03()
        private val m4 = JSONFile("$exists/json04.json", JSONModel04.serializer()) to JSONModel04()
        private val m5 = JSONFile("$exists/json05.json", JSONModel05.serializer()) to JSONModel05()
        private val m6 = JSONFile("$exists/json06.json", JSONModel06.serializer()) to JSONModel06()

        private fun cleanupAll() {
            m1.first.reload()
            m2.first.reload()
            m3.first.reload()
            m4.first.reload()
            m5.first.reload()
            m6.first.reload()
        }

        private fun <T : JSONModelMarker> echoTests(model: Pair<JSONFile<T>, T>) = funSpec {
            test("Writing to and reading from file should result in the original value") {
                model.first.apply { setAndCommit(model.second) }.get() shouldBe model.second
                model.first.reload()
            }
        }
    }
}
  

All the JSONModelXX classes are model data classes only intended for testing. JSONModelMarker is a marker interface implemented by all JSONModelXX classes. JSONFile<T> is the class I am trying to test.

3. IntelliJ notifications

I always get an «all tests successful» notification from IntelliJ, but when I look at the testing tool window, the tests are marked as «failed» because the gradle task :test failed. I don’t know how this plays into the issue.

4. I sometimes also receive a different exception

Если я запускаю только первый тест в наборе тестов через значок желоба IntelliJ, я получаю другое исключение : java.lang.ExceptionInInitializerError . Я немного покопался и обнаружил, что Scanner то, что используется для чтения содержимого файла JSON в JSONFile<T> классе, выдает a NoSuchElementException , потому что (по какой-то причине) файл пуст только во время выполнения этого теста. Это довольно странно, потому что самостоятельно определенная :preTest задача в my build.gradle.kts фактически записывает некоторые данные в каждый файл, и это содержимое действительно присутствует, когда я просматриваю файлы после :preTest :test запуска или. Вот полный вывод на консоль:

 java.lang.ExceptionInInitializerError
java.lang.ExceptionInInitializerError
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:315)
    at io.kotest.engine.launcher.ExecuteKt$execute$1.invokeSuspend(execute.kt:37)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:115)
    at io.kotest.engine.launcher.ExecuteKt.future(execute.kt:89)
    at io.kotest.engine.launcher.ExecuteKt.execute(execute.kt:34)
    at io.kotest.engine.launcher.MainKt.main(main.kt:12)
Caused by: java.util.NoSuchElementException
    at java.base/java.util.Scanner.throwFor(Scanner.java:937)

    at java.base/java.util.Scanner.next(Scanner.java:1478)
    at com.example.core.common.JSONFile$Companion.readFile(JSONFile.kt:14)
    at com.example.core.common.JSONFile$Companion.access$readFile(JSONFile.kt:11)
    at com.example.core.common.JSONFile.<init>(JSONFile.kt:27)
    at com.example.core.common.JSONFileTests.<clinit>(JSONFileTests.kt:24)
    ... 8 more


Process finished with exit code 0

[java.lang.ExceptionInInitializerError]
  

Теперь я не знаю, является ли это частью проблемы или отдельной проблемой, но на всякий случай я хотел включить его здесь. Та же проблема возникает, когда я переключаю тестовый запуск на IntelliJ IDEA в File-> Settings-> Build, Execution, Deployment-> Инструменты сборки-> Gradle

Вот и все! Теперь мой вопрос: что вызывает эту ошибку и как я могу ее исправить?

Запуск с

  • kotlin / JVM 1.4.10
  • kotest-runner-junit5 4.3.1
  • kotest-утверждения-ядро 4.3.1
  • kotest-свойство 4.3.1
  • kotest-extensions-allure 4.3.1
  • kotest-plugins-pitest 4.3.1
  • gradle 6.7
  • allure 2.13.6
  • плагин allure gradle 2.8.1
  • плагин pitest gradle 1.5.1
  • intellij IDEA Ultimate 2020.2
  • плагин kotest IntelliJ 1.1.20-IC-2020.2
  • OpenJDK 11.0.2

Комментарии:

1. Вы пробовали менять тестовый бегун? jetbrains.com/help/img/idea/2020.2/gradle_test_runner.png

2. У меня нет. Это необходимо? Если так или иначе возможно, я хотел бы продолжить тестирование через gradle. Но я попробую

3. Пожалуйста, проверьте. Просто чтобы исключить возможность ошибки в плагине Gradle…

4. Хорошо, теперь я протестировал его с помощью тестового запуска IntelliJ IDEA, и теперь у меня та же проблема, что и в # 4. К настоящему времени я почти убежден, что это отдельная проблема, тем не менее, у меня нет решения для нее.

5. Согласно stacktrace, исключения происходят внутри сопутствующего объекта JSONFile класса. Не могли бы вы показать его содержимое?

Ответ №1:

Похоже, я нашел решение.

Проблема, похоже, в том, что Kotest (а позже и JUnit) хочет получить уникальные тесты, что означает уникальные имена тестов. И обычно эти проблемы будут отмечены плагином Kotest IntelliJ. Но если вы используете тестовую фабрику, все становится немного более непрозрачным. Я предполагал, что include(prefix, testFactory) это добавляет prefix к имени теста, но это не так. это означает, что фабричный метод

 private fun <T : JSONModelMarker> echoTests(model: Pair<JSONFile<T>, T>) = funSpec {
    test("Writing to and reading from file should result in the original value") {
        model.first.apply { setAndCommit(model.second) }.get() shouldBe model.second
        model.first.reload()
    }
}
  

фактически создается тест с фиксированным идентификатором при каждом его вызове. Теперь, когда я включаю эту тестовую фабрику несколько раз через include() , я фактически создаю повторяющиеся тесты, которые затем завершают тестовую задачу. Чтобы решить эту проблему, я просто реализовал функциональность для чередования имен тестов, например:

 private fun <T : JSONModelMarker> echoTests(name: String, model: Pair<JSONFile<T>, T>) = funSpec {
    test("$name: Writing to and reading from file should result in the original value") {
        model.first.apply { setAndCommit(model.second) }.get() shouldBe model.second
        model.first.reload()
    }
}
  

Теперь, когда я вызываю include() метод, я опускаю префикс (потому что он не имеет значения) и добавляю уникальное имя (которое ранее было префиксом) к вызову заводского метода:

 include(echoTests("JSONModel01", m1))
  

После того, как я это сделал, ошибка больше не возникала.

Комментарии:

1. Можете ли вы подать это как ошибку в kotest, io?