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