Как я могу запретить QuickCheck перехватывать все исключения?

Библиотека QuickCheck, по-видимому, перехватывает все исключения, возникающие при тестировании свойства. В частности, такое поведение не позволяет мне установить ограничение по времени для всего вычисления QuickCheck. Например:

module QuickCheckTimeout where

import System.Timeout (timeout)
import Control.Concurrent (threadDelay)
import Test.QuickCheck (quickCheck, within, Property)
import Test.QuickCheck.Monadic (monadicIO, run, assert)

-- use threadDelay to simulate a slow computation
prop_slow_plus_zero_right_identity :: Int -> Property
prop_slow_plus_zero_right_identity i = monadicIO $ do
  run (threadDelay (100000 * i))
  assert (i + 0 == i)

runTests :: IO ()
runTests = do
  result <- timeout 3000000 (quickCheck prop_slow_plus_zero_right_identity)
  case result of
    Nothing -> putStrLn "timed out!"
    Just _  -> putStrLn "completed!"

Поскольку QuickCheck перехватывает все исключения, timeout прерывается: фактически вычисления не прерываются! Вместо этого QuickCheck рассматривает свойство как неисправное и пытается уменьшить ввод, вызвавший сбой. Затем этот процесс сжатия не запускается с ограничением по времени, в результате чего общее время, используемое для вычислений, превышает установленный предел времени.

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

Как я могу запретить QuickCheck перехватывать все исключения?


person Brad Larsen    schedule 03.03.2012    source источник


Ответы (2)


Поскольку QuickCheck делает все правильно, когда пользователь вручную прерывает тест, нажимая Ctrl+C, вы можете обойти эту проблему, написав что-то похожее на timeout, но который вызывает асинхронный UserInterrupt исключение вместо пользовательского типа исключения.

Это в значительной степени прямое задание копирования и вставки из источника System.Timeout:

import Control.Concurrent
import Control.Exception

timeout' n f = do
    pid <- myThreadId
    bracket (forkIO (threadDelay n >> throwTo pid UserInterrupt))
            (killThread)
            (const f)

При таком подходе вам придется использовать quickCheckResult и проверять причину сбоя, чтобы определить, истекло ли время ожидания теста или нет. Кажется, он работает достаточно прилично:

> runTests 
*** Failed! Exception: 'user interrupt' (after 13 tests):  
16
person hammar    schedule 04.03.2012
comment
Проголосовали за это решение, потому что это решение относится к конкретному варианту использования (т. Е. Устанавливает ограничение по времени на все вычисления QuickCheck). Изучая исходный код, можно сделать вывод, что QuickCheck специально запрограммирован на обработку исключения UserInterrupt. К сожалению, это решение не совсем отвечает на мой вопрос: QuickCheck по-прежнему поглощает все, кроме UserInterrupt! - person Brad Larsen; 10.03.2012

Может быть, пакет chasingbottoms был бы полезен? http://hackage.haskell.org/packages/archive/ChasingBottoms/1.3.0.3/doc/html/Test-ChasingBottoms-TimeOut.html

person Rusty    schedule 04.03.2012