Spock проблема синхронизации с запуском Platform.runLater () в блоке «когда» - PullRequest
0 голосов
/ 28 апреля 2020

Если метод должен быть вызван в потоке JavaFX, кажется, что я должен сделать следующее в моем when блоке:

... setup ...
// def throwable

when:
Platform.runLater( new Runnable(){
    @Override
    void run() {
        // try {
        log.debug "in runnable, calling run method ..."
        someObject.methodWhichMustRunInJavaFXThread()
        log.debug "... run method finished normally"
        // }catch( Throwable t ){
        //     log.error( t.message, t )
        //     throwable = t
        // }
    }
})
WaitForAsyncUtils.waitForFxEvents()
log.debug "waitForFXEvents ended..."
// if throwable != null 
//     throw throwable
/* NB it appears that re-throwing the Throwable like this after waitForFxEvents
is probably the only way to bring it to the developer's attention! 
PS I added this re-throwing idea only a few hours after submitting the question. I am 
currently monitoring things to find whether this in fact solves the problem */

then:
// throwable == null 
/* in fact this seems to be a rather naive check: from my experimentation, 
regardless of whether caught in the above catch clause, it appears that if a throwable
is thrown in `run`, although "invocation counting tests" are performed in the "then" 
block, this sort of "static" equality check will never be performed in the "then" block
*/
... other verifications...

Но я периодически обнаруживаю, что такой метод может привести к ужасной утечке из-за сбоя в последующем тесте, если в методе run возникнет исключение. Я думал, что попытка перехватить любые такие throwables, как показано в приведенном выше коде, если вы раскомментируете все строки комментариев, может решить проблему, но на самом деле нет: throwable бросается в run и перехватывается предложением catch , по-прежнему можно отнести к более позднему методу!

Это пример неправильного вывода сбоя Спока: хотя этот NPE добавляется в Platform.runLater( ... ) в предыдущем методе тестирования (фактически в полностью отличающийся Specification в файле initial_load_testing. groovy), этот сбой на самом деле объясняется тестом, который происходит через некоторое произвольное время после него.

Обратите внимание на упоминание "отложенного исключения" .. .

java.lang.RuntimeException: java.lang.NullPointerException: Cannot invoke method setRoot() on null object
    at org.testfx.util.WaitForAsyncUtils.---- Delayed Exception: (See Trace Below) ----(WaitForAsyncUtils.java:0)
Caused by: java.lang.NullPointerException: Cannot invoke method setRoot() on null object
    at core.FileHandlingFramework.tryToLoadFile(filehandlingframework.groovy:83)
    at core.StdFileHandlingFrameworkTemplate.tryToOpenFile(stdfilehandlingframeworktemplate.groovy:97)
    at core.App.start(main.groovy:120)
    at core.AppStdSpec2$1.run(initial_load_testing.groovy:92)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
    at com.sun.glass.ui.gtk.GtkApplication.lambda$runLoop$11(GtkApplication.java:277)
    at java.base/java.lang.Thread.run(Thread.java:834)

Что я нахожу странным, так это то, что я позволил событиям потока JavaFX "всплывать" с WaitForAsyncUtils.waitForFxEvents() ... и все же кажется, что бросание бросаемого объекта иногда может быть обнаруженным фреймворком Спока ПОСЛЕ того, как * waitForFXEvents() метод заканчивается.

Есть ли решение для этого? Прерывистый характер этого явления является реальной проблемой.

1 Ответ

0 голосов
/ 29 апреля 2020

Предварительно следующее решение выглядит как для работы, перебрасывая любое исключение, пойманное в потоке FX до окончания блока when:

void executeInFXThreadAndWait( def closure, def ... params ){
    Throwable fXThrowable
    Platform.runLater({ 
        try {
            closure(params)
        }catch( Throwable t ){
            if( t.message != 'exit' ) {
                log.error(t.message, t)
                fXThrowable = t
            }
        }
    })
    WaitForAsyncUtils.waitForFxEvents()
    if( fXThrowable != null ){
        // add calling trace so fail trace identifies Specification line concerned
        fXThrowable.stackTrace += Thread.currentThread().stackTrace
        throw fXThrowable
    }
}

Использование (расширение App Application):

...
def myClosure = { args ->
    App.instance.start( args[ 0 ] )
}
...

when:
TestUtilities.instance.executeInFXThreadAndWait( myClosure, mockStage )

then:
...

Обратите внимание, что при тестировании на «выход» иногда я специально выбрасываю исключение в тесте, чтобы «извлечь» из метода, в котором то, что необходимо проверить, уже было проверено. Если сообщение Throwable является «выходом», оно игнорируется.

Гипотеза: я предполагаю, не зная, что структура Спока способна определить, было ли «обработано» исключение, и что если он будет переброшен таким образом, то фреймворк не получит «отложенное исключение». Комментарии экспертов Спока приветствуются.

Позже
Несколько дней спустя: похоже, вердикт состоит в том, что это улучшит ситуацию. Тем не менее, все еще может произойти, без какой-либо очевидной предсказуемости или согласованности, что отказ от одной спецификации и функции сообщается в другой спецификации и функции. Каждый раз, когда это происходит, повторный запуск тех же тестов происходит без таких ложных ошибок.

Я полагаю, что Спок не претендует на возможность иметь дело с параллельным программированием. Было бы хорошо узнать, сталкивался ли кто-нибудь еще с этим явлением, когда делал Platform.runLater в блоке when.

...