I define the class Response that generate valid responses for a type a :
response :: IO a
I use the Random instance of Int as the generator
instance Response a => Response (Maybe a) where
response= do
b <- randomRIO(0,1 :: Int)
case b of 0 -> response >>= return . Just ; _ -> return Nothing
instance Response String where
response= replicateM 5 $ randomRIO ('a','z')
instance Response Int where
response= randomRIO(1,1000)
instance Response Integer where
response= randomRIO(1,1000)
instance (Response a, Response b) => Response (a,b) where
response= fmap (,) response `ap` response
For enumerable types I use Bounded and Enum.
instance (Bounded a, Enum a) => Response a where
response= mx
where
mx= do
let x= typeOfIO mx
n <- randomRIO ( fromEnum $ minBound `asTypeOf` x
, fromEnum $ maxBound `asTypeOf` x)
return $ toEnum n
where
typeOfIO :: IO a -> a
typeOfIO = error $ "typeOfIO not defined"
With these instances I can redefine ask, which takes a widget in the View applicative and generates a response in the FlowM monad:
ask :: (Response a) => View v m a -> FlowM v m a
ask w = do
w `MFlow.Forms.wmodify` (\v x -> consume v >> return (v,x))
`seq` rest
where
consume= liftIO . B.writeFile "dev/null" . B.concat . map toByteString
rest= do
bool <- liftIO $ response
case bool of
False -> fail ""
True -> do
b <- liftIO response
r <- liftIO response
case (b,r) of
(True,x) -> breturn x
_ -> ask w
It executes the widget rendering code and simulate the conplete pass trough a ByteString channel by sending the result to /dev/null. Then he uses the Response instance to return a result to the flow. Simulation of the back button is possible (by fail) and also failed validations are simulated, which invokes ask again (at the end)
There are however callbacks and modifiers that do something with re result before returning to the main flow. To take care of it, waction and wmodify have also tweaks that can simulate valid entry values for them.
The resulting code is here: MFlow.Forms.Test.
Let's use it . This example below creates 15 thread and invoke the shopCart procedure, which is persistent (it remenber the state when it restart, it is a nice feature of MFlow).
runTest is the primitive that spawn the threads and invoke the flows:
addMessageFlows [("shop" ,runFlow shopCart)]
runTest [(15, "shop")]
data ShopOptions= IPhone | IPod | IPad deriving (Bounded, Enum,Read, Show, Typeable)
-- A persistent flow (uses step). The process is killed after 10 seconds of inactivity
-- but it is restarted automatically. if you restart the program, it remember the shopping cart
-- defines a table with links enclosed that return an user defined type.
shopCart = do
setTimeouts 10 0
shopCart1 (V.fromList [0,0,0:: Int])
where
shopCart1 cart= do
o <- step . ask $
table ! [border 1,thestyle "width:20%;margin-left:auto;margin-right:auto"]
<<< caption << "choose an item"
++> thead << tr << concatHtml[ th << bold << "item", th << bold << "times chosen"]
++> (tbody
<<< tr ! [rowspan 2] << td << linkHome
++> (tr <<< td <<< wlink IPhone (bold <<"iphone") <++ td << ( bold << show ( cart V.! 0))
<|> tr <<< td <<< wlink IPad (bold <<"ipad") <++ td << ( bold << show ( cart V.! 1))
<|> tr <<< td <<< wlink IPod (bold <<"ipod") <++ td << ( bold << show ( cart V.! 2)))
)
let i =fromEnum o
let newCart= cart V.// [(i, cart V.! i + 1 )]
shopCart1 newCart
where
linkHome= (toHtml $ hotlink noScript << bold << "home")
When executed properly this example iterates to fill a simple shopping cart from the user input. But with the test we can check the application in different load conditions, so we can discover bottlenecks, bugs, performance issues and other interesting things.
The complete code of the example is at the demos.hs in the Git repository.
This is a (reduced) flow log of one of these 15 threads:
3729 178
[ "() "
, "B IPad "
, "G "
, "B IPad "
, "G "
, "G "
, "B IPod "
, "G "
, "G "
, "B IPad "
, "G "
, "B IPhone "
, "G "
, "B IPad "
, "B IPhone "
]
Stat "pkpvo/void" 178 ( Nothing ) 0
"G " means that the generator has selected the back button.
To summarize, the stateful , typed nature and the simplicity of the MFlow user interface makes the test infrastructure very simple and powerful. And this is the beginning.