@@ -17,9 +17,10 @@
17{-# LANGUAGE UndecidableInstances #-}
17{-# LANGUAGE UndecidableInstances #-}
19module Roboservant.StateMachine
19module Roboservant.StateMachine
20−( prop_sequential
20+ ( prop_sequential,
21−, prop_concurrent
21+ prop_concurrent,
24import Control.Monad.IO.Class (MonadIO, liftIO)
25import Control.Monad.IO.Class (MonadIO, liftIO)
25import Data.Dynamic (Dynamic, dynApply, dynTypeRep, fromDynamic)
26import Data.Dynamic (Dynamic, dynApply, dynTypeRep, fromDynamic)
@@ -28,42 +29,45 @@ import qualified Data.List.NonEmpty as NEL
28import qualified Data.Map.Strict as Map
29import qualified Data.Map.Strict as Map
29import Data.Maybe (mapMaybe)
30import Data.Maybe (mapMaybe)
30import Data.Typeable (TypeRep)
31import Data.Typeable (TypeRep)
31import GHC.IORef (readIORef)
33import GHC.IORef (readIORef)
32import Hedgehog
34import Hedgehog
33import qualified Hedgehog.Gen as Gen
35import qualified Hedgehog.Gen as Gen
34import qualified Hedgehog.Range as Range
36import qualified Hedgehog.Range as Range
37+import Roboservant.Hedgehog
38+import Roboservant.Types
35import Servant
39import Servant
36import Type.Reflection (SomeTypeRep)
40import Type.Reflection (SomeTypeRep)
38−import Roboservant.Types
39−import Roboservant.Hedgehog
41−callEndpoint :: (MonadGen n, MonadIO m) => ReifiedApi -> [Dynamic] -> Command n m State
42+callEndpoint :: (MonadGen n, MonadIO m) => ReifiedApi -> Command n m State
42−callEndpoint staticRoutes _seed =
43+callEndpoint staticRoutes =
43 let gen :: MonadGen n => State Symbolic -> Maybe (n (Op Symbolic))
44 let gen :: MonadGen n => State Symbolic -> Maybe (n (Op Symbolic))
44− gen State {..}
45+ gen State {..} = case preloads of
46+ (v : _) -> Just $ pure $ Preload (dynTypeRep v) v
45− | any null options = Nothing
48+ if any null options
46− | otherwise = Just $ do
50+ else Just $ do
47− uncurry Op <$> chooseOne options
51+ uncurry Op <$> chooseOne options
49 chooseOne ::
53 chooseOne ::
50 MonadGen n =>
54 MonadGen n =>
51 [ ( ApiOffset,
55 [ ( ApiOffset,
52− [(TypeRep, [Var (Opaque (IORef Dynamic)) Symbolic])]
56+ [[Var (Opaque (IORef Dynamic)) Symbolic]]
56 ( ApiOffset,
60 ( ApiOffset,
57− [(TypeRep, Var (Opaque (IORef Dynamic)) Symbolic)]
61+ [Var (Opaque (IORef Dynamic)) Symbolic]
59 chooseOne opts = do
63 chooseOne opts = do
60 (offset, args) <- elementOrFail opts
64 (offset, args) <- elementOrFail opts
61− (offset,) <$> mapM (\(tr, argOpts) -> (tr,) <$> elementOrFail argOpts) args
65+ (offset,) <$> mapM elementOrFail args
62− options :: [(ApiOffset, [(TypeRep, [Var (Opaque (IORef Dynamic)) Symbolic])])]
66+ options :: [(ApiOffset, [[Var (Opaque (IORef Dynamic)) Symbolic]])]
65 ( \(offset, argreps, _retType, _dynCall) -> (offset,) <$> do
69 ( \(offset, argreps, _retType, _dynCall) -> (offset,) <$> do
66− mapM (\x -> (x,) <$> fillableWith x) argreps
70+ mapM fillableWith argreps
68 staticRoutes
72 staticRoutes
69 fillableWith :: TypeRep -> Maybe [Var (Opaque (IORef Dynamic)) Symbolic]
73 fillableWith :: TypeRep -> Maybe [Var (Opaque (IORef Dynamic)) Symbolic]
@@ -72,14 +76,13 @@ callEndpoint staticRoutes _seed =
72 (MonadIO m) =>
76 (MonadIO m) =>
73 Op Concrete ->
77 Op Concrete ->
74 m (Opaque (IORef Dynamic))
78 m (Opaque (IORef Dynamic))
75− execute (Preload _tr v) = fmap Opaque . liftIO $ do
79+ execute (Preload _tr v) = Opaque <$> liftIO (newIORef v)
76− newIORef . concrete $ v
77 execute (Op (ApiOffset offset) args) = do
80 execute (Op (ApiOffset offset) args) = do
78 fmap Opaque . liftIO $ do
81 fmap Opaque . liftIO $ do
79− realArgs <- mapM (\(_tr, v) -> readIORef (opaque v)) args
82+ realArgs <- mapM (readIORef . opaque) args
80 let (_offset, _staticArgs, _ret, endpoint) = staticRoutes !! offset
83 let (_offset, _staticArgs, _ret, endpoint) = staticRoutes !! offset
81 -- now, magic happens: we apply some dynamic arguments to a dynamic
84 -- now, magic happens: we apply some dynamic arguments to a dynamic
82− -- function and hopefully somtehing useful pops out the end.
85+ -- function and hopefully something useful pops out the end.
83 func = foldr (\arg curr -> flip dynApply arg =<< curr) (Just endpoint) realArgs
86 func = foldr (\arg curr -> flip dynApply arg =<< curr) (Just endpoint) realArgs
84 let showable = map dynTypeRep (endpoint : realArgs)
87 let showable = map dynTypeRep (endpoint : realArgs)
85 case func of
88 case func of
@@ -95,9 +98,13 @@ callEndpoint staticRoutes _seed =
96 [ Update $ \s@State {..} op o' ->
99 [ Update $ \s@State {..} op o' ->
97 case op of
100 case op of
98− Preload tr _v -> s { stateRefs =
101+ Preload tr _v ->
99− Map.insertWith (<>) tr (pure o') stateRefs }
104+ Map.insertWith (<>) tr (pure o') stateRefs,
105+ preloads = drop 1 preloads
100− (Op (ApiOffset offset) _args) ->
107+ (Op (ApiOffset offset) _args) ->
102 { stateRefs =
109 { stateRefs =
103 let (_, _, tr, _) = staticRoutes !! offset
110 let (_, _, tr, _) = staticRoutes !! offset
@@ -107,33 +114,29 @@ callEndpoint staticRoutes _seed =
107 (pure o')
114 (pure o')
108 stateRefs
115 stateRefs
110− -- , Ensure (no-500 here)
113prop_sequential :: forall api. (FlattenServer api, ToReifiedApi (Endpoints api)) => Server api -> [Dynamic] -> PropertyT IO ()
119prop_sequential :: forall api. (FlattenServer api, ToReifiedApi (Endpoints api)) => Server api -> [Dynamic] -> PropertyT IO ()
114prop_sequential server seed = do
120prop_sequential server seed = do
115 let reifiedApi = toReifiedApi (flattenServer @api server) (Proxy @(Endpoints api))
121 let reifiedApi = toReifiedApi (flattenServer @api server) (Proxy @(Endpoints api))
117 actions <-
122 actions <-
118 forAll $ do
123 forAll $ do
119 Gen.sequential
124 Gen.sequential
120 (Range.linear 1 100)
125 (Range.linear 1 100)
121− (State mempty)
126+ (State mempty seed)
122− [callEndpoint reifiedApi seed]
127+ [callEndpoint reifiedApi]
124− executeSequential (State mempty) actions
128+ executeSequential (State mempty seed) actions
126prop_concurrent :: forall api. (FlattenServer api, ToReifiedApi (Endpoints api)) => Server api -> [Dynamic] -> PropertyT IO ()
130prop_concurrent :: forall api. (FlattenServer api, ToReifiedApi (Endpoints api)) => Server api -> [Dynamic] -> PropertyT IO ()
127prop_concurrent server seed =
131prop_concurrent server seed =
128− let reifiedApi = toReifiedApi (flattenServer @api server) (Proxy @(Endpoints api)) in
132+ let reifiedApi = toReifiedApi (flattenServer @api server) (Proxy @(Endpoints api))
129− let initialState = State mempty
131 actions <-
134 actions <-
133 Gen.parallel
136 Gen.parallel
134 (Range.linear 1 50)
137 (Range.linear 1 50)
135 (Range.linear 1 10)
138 (Range.linear 1 10)
136− initialState
139+ (State mempty seed)
137− [callEndpoint reifiedApi seed]
140+ [callEndpoint reifiedApi]
139− executeParallel initialState actions
142+ executeParallel (State mempty seed) actions