Как я могу предотвратить QuickCheck от перехвата всех исключений? - PullRequest
7 голосов
/ 04 марта 2012

Библиотека 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 обрабатывает свойство как потерпевшее неудачу и пытается уменьшить ввод, вызвавший сбой.Этот процесс сжатия затем не запускается с привязкой ко времени, в результате чего общее время, используемое вычислениями, превышает установленный предел времени.

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

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

Ответы [ 2 ]

4 голосов
/ 04 марта 2012

Поскольку 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
1 голос
/ 04 марта 2012

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

...