2012-03-03 30 views
7

La biblioteca QuickCheck parece captar todas las excepciones que se lanzan al probar una propiedad. En particular, este comportamiento me impide poner un límite de tiempo en todo el cálculo de QuickCheck. Por ejemplo:¿Cómo puedo evitar que QuickCheck capture todas las excepciones?

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

Debido QuickCheck captura todas las excepciones, timeout rompe: en realidad no abortar el cómputo! En cambio, QuickCheck considera que la propiedad ha fallado e intenta reducir la entrada que causó la falla. Este proceso de contracción no se ejecuta con un límite de tiempo, lo que hace que el tiempo total utilizado por el cálculo exceda el límite de tiempo prescrito.

Uno podría pensar que podría usar el combinador within de QuickCheck para limitar el tiempo de cálculo. (within considera que una propiedad ha fallado si no termina dentro del límite de tiempo dado.) Sin embargo, within no hace exactamente lo que quiero, ya que QuickCheck aún intenta reducir la entrada que causó la falla, un proceso que puede tomar demasiado tiempo. (Lo que alternativamente podría funcionar para mí es una versión de within que impide que QuickCheck reduzca las entradas a una propiedad que falló porque no finalizó dentro del límite de tiempo dado.)

¿Cómo puedo evitar que QuickCheck advierta? todas las excepciones?

Respuesta

4

Desde QuickCheck hace lo correcto cuando el usuario interrumpe manualmente la prueba pulsando Ctrl + C, usted podría ser capaz de solucionar este problema escribiendo algo similar a timeout, sino que emite una asynchroneous UserInterrupt excepción en lugar de un tipo de excepción personalizado.

Esto es prácticamente un trabajo de copia y pegar directamente de la fuente de System.Timeout:

import Control.Concurrent 
import Control.Exception 

timeout' n f = do 
    pid <- myThreadId 
    bracket (forkIO (threadDelay n >> throwTo pid UserInterrupt)) 
      (killThread) 
      (const f) 

Con este enfoque, usted tiene que utilizar quickCheckResult y comprobar la causa de fallo para detectar si la prueba Tiempo de espera agotado o no. Parece que funciona bastante decente:

> runTests 
*** Failed! Exception: 'user interrupt' (after 13 tests): 
16 
+0

upvoted porque esta solución se dirige al caso en particular (es decir, poner un límite de tiempo para el cálculo completo QuickCheck). Mirando por encima del código fuente, parece que QuickCheck está cableado para tratar la excepción UserInterrupt especialmente. Lamentablemente, esta solución no responde completamente a mi pregunta: ¡QuickCheck aún se traga todo menos UserInterrupt! –

Cuestiones relacionadas