2012-03-03 36 views
7

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!" 

因为快速检查捕获所有的异常,timeout游:它实际上并没有中止计算!相反,QuickCheck将该属性视为失败,并尝试缩小导致失败的输入。这个缩小的过程不会随时间限制运行,导致计算所用的总时间超过规定的时间限制。

有人可能会认为我可以使用QuickCheck的within组合器来限制计算时间。 (within如果一个属性在给定的时间限制内没有完成,则将其视为失败)。但是,within并不完全符合我的要求,因为QuickCheck仍会尝试缩小导致失败的输入,这个过程可以需要太长时间。 (对我来说可以替代的一个版本是within,它可以防止QuickCheck将输入收缩到一个因为它没有在给定的时间限制内完成而失败的属性。)

如何防止QuickCheck捕捉所有例外?

回答

4

由于快速检查做正确的事,当用户按下手动中断测试按Ctrl + Ç,你也许可以通过写类似timeout的东西来解决这个问题,但抛出的asynchroneous 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 
+0

Upvoted,因为这个解决方案解决了特定的使用情况(即对整个QuickCheck计算的时间限制)。仔细研究源代码,它看起来像QuickCheck是硬连线来专门处理UserInterrupt异常。 不幸的是,这个解决方案并不能完全回答我的问题:QuickCheck仍然会吞噬除UserInterrupt之外的所有内容! – 2012-03-10 03:08:47