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.
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)