haskellioquickcheckhspec

Using IO within a QuickCheck property test?


I'm currently writing a Haskell library to replace a closed-source 3rd party command line application. This 3rd party CLI has a spec that I've replicated, but the actually binary allows much more permissive inputs than the spec.

I'd like to be able to generate inputs using QuickCheck, then compare the result of a function in my library to the stdout of the 3rd party CLI app. The part I'm getting stuck on is how to introduce IO within a property test.

Here's the code I have so far:

{-# LANGUAGE OverloadedStrings #-}

import qualified Data.Text as T
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck

-- This function lives in my library, this is just a sample
-- the actual function does things but is still pure, and has the type Text -> Int
exampleCountFuncInModule :: T.Text -> Int
exampleCountFuncInModule t = T.length t

-- contrived example generator
genSmallString :: Gen T.Text
genSmallString = do 
    smallList <- T.pack <$> resize 2 (listOf1 arbitraryASCIIChar)
    pure ("^" `T.append` smallList)

main :: IO ()
main = do
    hspec $ do
        prop "some property" $ do
            verbose $ forAll genSmallString $ \xs -> (not . T.null) xs ==> do
                let myCount = exampleCountFuncInModule xs
                -- Want to run external program here, and read the result as an Int
                let otherProgramCount = 2
                myCount == otherProgramCount

I found that QuickCheck has an ioProperty, and that seems like it might be what I want, I'm just not sure how to fit that into what I already have.


Solution

  • I think I figured this out, I used this:

    test :: Text -> IO Bool 
    test t = do
        (exitCode, stdOut, stdErr) <- callCmd $ "bin/cliTool" :| [Text.unpack t]
        let cliCount = read stdOut :: Int
        let myCount = countOfThingsInString t
        return $ cliCount == myCount
    

    Then in my hspec tests:

    main :: IO ()
    main = do
        hspec $ do
           describe "tests" $ do
                prop "test IO" $ do
                    verbose $ forAll arbitrarySmallSpecifier (ioProperty . test)