Search code examples
haskellquickcheck

Using QuickCheck to test intentional error conditions


I've seen how QuickCheck can be used to test monadic and non-monadic code, but how can I use it to test code that handles errors, i.e., prints some message and then calls exitWith?


Solution

  • A disclaimer first: I'm not an expert on QuickCheck and I had no experience with monadic checking before your question, but I see stackoverflow as an opportunity to learn new things. If there's an expert answer saying this can be done better, I'll remove mine.

    Say you have a function test that can throw exceptions using exitWith. Here's how I think you can test it. The key function is protect, which catches the exception and converts it to something you can test against.

    import System.Exit
    import Test.QuickCheck
    import Test.QuickCheck.Property
    import Test.QuickCheck.Monadic
    
    test :: Int -> IO Int
    test n | n > 100   = do exitWith $ ExitFailure 1
           | otherwise = do print n
                            return n
    
    purifyException :: (a -> IO b) -> a -> IO (Maybe b)
    purifyException f x = protect (const Nothing) $ return . Just =<< f x
    
    testProp :: Property
    testProp = monadicIO $ do
      input <- pick arbitrary
      result <- run $ purifyException test $ input
      assert $ if input <= 100 then result == Just input
                               else result == Nothing
    

    There are two disadvantages to this, as far as I can see, but I found no way over them.

    1. I found no way to extract the ExitCode exception from the AnException that protect can handle. Therefore, all exit codes are treated the same here (they are mapped to Nothing). I would have liked to have:

      purifyException :: (a -> IO b) -> a -> IO (Either a ExitCode)
      
    2. I found no way to test the I/O behavior of test. Suppose test was:

      test :: IO ()
      test = do
        n <- readLn
        if n > 100 then exitWith $ ExitFailure 1
                   else print n
      

      Then how would you test it?

    I'd appreciate more expert answers too.