2
votes

I'd like to pass an integer as a CLI argument to a Haskell program that makes use of QuickCheck / monadicIO. That integer is going to be used inside the assert to make the tests customizable. The problem is that once I parse the integer value in main, I don't know how to pass it inside of the monadicIO call without using something as ugly as an IORef. I would think that an elegant solution might be the Reader monad, but I couldn't find a solution to make it work, seen as quickCheck is rigid in its arguments. Any ideas?

Later Edit 1: As requested, I'm attaching the actual code I'm trying this on, and failing. The commented-out lines represent my failed attempt. Background: the test suite is intended to exercise a very simple remote endpoint that computes the SHA512 of the randomized input generated by QuickCheck. The remote endpoint is Python/Flask based.

Later Edit 2 in response to @user2407038: I could make propHasExpectedLengthCeiling take an additional argument of type Int, but quickCheck would generate random values for it, and that's not what I want happening. My goal is to use the maxSegmentLengthCeiling that I'm taking in from the command-line arguments and use it in let testPassed = actualMaxSegmentLength <= maxSegmentLengthCeiling inside of the monadicIO block. Right now maxSegmentLengthCeiling is specified as a top-level value, which means I have to recompile the code every time I change the value. I don't yet have any code that involves IORef because that's a last resort and the essence of my question is how to avoid going the IORef route.

import qualified Data.ByteString.Lazy.Char8 as LC

import Control.Applicative     ( (<$>) )
import Data.Function           ( on )
import Data.List               ( groupBy )
import Data.Char               ( isDigit )
--import Safe                    ( headMay
--                               , readMay
--                               )
--import System.Environment      ( getArgs )
import Network.HTTP.Conduit    ( simpleHttp )
import Test.QuickCheck         ( Arbitrary
                               , Property
                               , arbitrary
                               , choose
                               , frequency
                               , quickCheckWith
                               , stdArgs
                               , vectorOf
                               )
import Test.QuickCheck.Test    ( Args
                               , maxSuccess
                               )
import Test.QuickCheck.Monadic ( assert
                               , monadicIO
                               , run
                               )

newtype CustomInput = MkCustomInput String deriving Show

instance Arbitrary CustomInput where
  arbitrary =
    let
      genCustomInput = vectorOf 20
                       $ frequency [ (26, choose ('0','9'))
                                   , (10, choose ('a','z'))
                                   ]
    in
      MkCustomInput <$> genCustomInput

maxSegmentLengthCeiling :: Int
maxSegmentLengthCeiling = 22

urlPrefix :: String
urlPrefix = "http://192.168.2.3:5000/sha512sum/"

propHasExpectedLengthCeiling :: CustomInput -> Property
propHasExpectedLengthCeiling (MkCustomInput input) = monadicIO $ do
  testPassed <- run $ do
    response <- simpleHttp $ urlPrefix ++ input
    let stringResponse = LC.unpack response
    let brokenDownStringResponse = groupBy ( (==) `on` isDigit ) stringResponse
    let actualMaxSegmentLength = maximum $ map length brokenDownStringResponse
    let testPassed = actualMaxSegmentLength <= maxSegmentLengthCeiling
    putStrLn ""
    putStrLn ""
    putStrLn $ "Input:       " ++ input
    putStrLn $ "Control sum: " ++ stringResponse
    putStrLn $ "Breakdown:   " ++ show brokenDownStringResponse
    putStrLn $ "Max. length: " ++ show actualMaxSegmentLength
    putStrLn $ "Ceiling:     " ++ show maxSegmentLengthCeiling
    putStrLn $ "Test result: " ++ if testPassed then "Pass" else "Fail"
    putStrLn ""
    putStrLn ""
    return testPassed
  assert $ testPassed

customArgs :: Args
customArgs = stdArgs { maxSuccess = 1000000 }

--readMayAsInt :: String -> Maybe Int
--readMayAsInt = readMay

main :: IO ()
main =
--main = do
--  cliArgs <- getArgs
--  let ceilingInputMay = headMay cliArgs >>= readMayAsInt
--  maxSegmentLengthCeiling <- case ceilingInputMay of
--                               (Just lengthCeiling) -> return lengthCeiling
--                               Nothing              -> error "No valid number given"
  quickCheckWith
    customArgs
    propHasExpectedLengthCeiling
1
Some examples of what exactly you tried (and how they didn't work) would be helpful and would better illustrate what exactly you want to do.Mike H-R
Where exactly would you like to use cliArgs or any other input? Could you make propHasExpectedLengthCeiling take an additional argument (` :: Int -> CustomInput -> Property)? I don't know how IORef` factors into this at all. Could you clarify that, or include the code that uses IORef, or code that doesn't compile, but looks something like what you would want to have?user2407038
@user2407038, please see later edit 2.Toshio
@Toshio Make the Int a parameter to propHasExpectedLengthCeiling and invoke it as main = do [n] <- getArgs; quickCheckWith customArgs (propHasExpectedLengthCeiling (read n)). What am I missing here?user2407038
@user2407038, yes, somehow that didn't occur to me. Please copy/paste your comment in the form of an answer so I can give you credit. Thanks.Toshio

1 Answers

1
votes

Make maxSegmentLengthCeiling a parameter to propHasExpectedLengthCeiling :

propHasExpectedLengthCeiling :: Int -> CustomInput -> Property

and invoke it as

main = do 
  [n] <- getArgs
  quickCheckWith customArgs (propHasExpectedLengthCeiling (read n))