如何防止QuickCheck捕获所有异常?

时间:2012-03-03 21:48:02

标签: testing haskell 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捕获所有异常?

2 个答案:

答案 0 :(得分:4)

由于当用户通过按 Ctrl + C 手动中断测试时,QuickCheck会做正确的事情,您可以通过编写类似的东西解决此问题。 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 :(得分:1)