7

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 捕获所有异常?

4

2 回答 2

4

Ctrl由于当用户通过按+手动中断测试时 QuickCheck 会执行正确的操作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
于 2012-03-04T13:02:37.510 回答
1

也许这个chasingbottoms包会有用? http://hackage.haskell.org/packages/archive/ChasingBottoms/1.3.0.3/doc/html/Test-ChasingBottoms-TimeOut.html

于 2012-03-04T07:07:58.960 回答