@@ -1,104 +1,41 @@
1−{-# LANGUAGE UndecidableInstances #-}
2−{-# LANGUAGE TypeFamilies #-}
3−{-# LANGUAGE GADTs #-}
4−{-# LANGUAGE PolyKinds #-}
5−{-# LANGUAGE TypeApplications #-}
6−{-# LANGUAGE MultiParamTypeClasses #-}
71{-# LANGUAGE AllowAmbiguousTypes #-}
82{-# LANGUAGE DataKinds #-}
9−{-# LANGUAGE DeriveAnyClass #-}
10−{-# LANGUAGE DeriveGeneric #-}
113{-# LANGUAGE DerivingStrategies #-}
12−{-# LANGUAGE ExistentialQuantification #-}
134{-# LANGUAGE FlexibleContexts #-}
145{-# LANGUAGE FlexibleInstances #-}
6+{-# LANGUAGE GADTs #-}
157{-# LANGUAGE GeneralizedNewtypeDeriving #-}
16−{-# LANGUAGE KindSignatures #-}
178{-# LANGUAGE LambdaCase #-}
18−{-# LANGUAGE OverloadedStrings #-}
19−{-# LANGUAGE PartialTypeSignatures #-}
20−{-# LANGUAGE RankNTypes #-}
9+{-# LANGUAGE PolyKinds #-}
2110{-# LANGUAGE RecordWildCards #-}
2211{-# LANGUAGE ScopedTypeVariables #-}
2312{-# LANGUAGE StandaloneDeriving #-}
24−{-# LANGUAGE TemplateHaskell #-}
2513{-# LANGUAGE TupleSections #-}
14+{-# LANGUAGE TypeApplications #-}
15+{-# LANGUAGE TypeFamilies #-}
2616{-# LANGUAGE TypeOperators #-}
27−
28−-- for servant
17+{-# LANGUAGE UndecidableInstances #-}
2918
3019module Roboservant.StateMachine where
3120
32−import Control.Arrow (second)
33−-- import Data.Dynamic (Dynamic, fromDynamic)
34−
35−--import Test.QuickCheck (Property)
36−-- import Test.StateMachine
37−
38−import Type.Reflection (SomeTypeRep)
39−import Servant.API.Flatten (Flat)
40−import GHC.TypeLits (Nat, Symbol)
21+import Control.Monad.Except (runExceptT)
22+import Control.Monad.IO.Class (MonadIO, liftIO)
23+import Data.Dynamic (Dynamic, dynApply, dynTypeRep, fromDynamic, toDyn)
4124import Data.Function ((&))
42−import Control.Concurrent (forkIO)
43−import Control.Monad ((<=<))
44−import Control.Monad.IO.Class
45−import Data.Aeson
46−import Data.Dynamic
4725import Data.IORef (IORef, newIORef)
4826import qualified Data.List.NonEmpty as NEL
4927import Data.List.NonEmpty (NonEmpty)
5028import Data.Map.Strict (Map)
5129import qualified Data.Map.Strict as Map
52−import Data.Maybe
53−import Data.Type.HasClass
54−import Data.Type.HasClassPreludeInstances
55−import Data.Typeable (TypeRep, Typeable, typeOf, typeRep)
56−import Debug.Trace (traceM, traceShowM)
57−import GHC.Generics (Generic)
58−import GHC.Generics
30+import Data.Maybe (mapMaybe)
31+import Data.Typeable (TypeRep, Typeable, typeRep)
5932import GHC.IORef (readIORef)
60−import Control.Monad.Except (runExceptT)
61−import Hedgehog
33+import GHC.TypeLits (Symbol)
6234import Hedgehog
6335import qualified Hedgehog.Gen as Gen
6436import qualified Hedgehog.Range as Range
65−import Network.HTTP.Client (defaultManagerSettings, newManager)
66−import Network.Wai
67−import Network.Wai.Handler.Warp
68−import Roboservant.ContextualGenRequest
6937import Servant
70−import Servant.Client
71−import Servant.Server (Server)
72−
73−-- | can then use either `forAllParallelCommands` or `forAllCommands` to turn
74−-- this into a property
75−--
76−-- TODO: how do we tie the model, command and response types to the Server?
77−-- genStateMachine :: HasContextualGenRequest a => Server a -> StateMachine Model Command IO Response
78−-- genStateMachine = StateMachine initModel _transition _precondition _postcondition
79−-- Nothing _generator _shrinker _semantics _mock
80−
81−-- initModel :: Model r
82−-- initModel = Model mempty
83−
84−-- newtype Model r = Model (Map TypeRep Dynamic)
85−
86−-- data Command r
87−-- = CallEndpoint
88−-- | Read (Reference (Opaque (IORef Int)) r)
89−-- | Write (Reference (Opaque (IORef Int)) r) Int
90−-- | Increment (Reference (Opaque (IORef Int)) r)
91−
92−-- data Response r
93−-- = Created (Reference (Opaque (IORef Int)) r)
94−-- | ReadValue Int
95−-- | Written
96−-- | Incremented
97−
98−-- deriving Show
99−
100−-- instance Eq MyDyn where
101−-- MyDyn a == MyDyn b = show a == show b
38+import Type.Reflection (SomeTypeRep)
10239
10340data State v
10441 = State
@@ -109,24 +46,30 @@ class FlattenServer api where
10946 flattenServer :: Server api -> Bundled (Endpoints api)
11047
11148instance
112− ( Endpoints (endpoint :<|> api) ~ (endpoint ': Endpoints api)
113− , Server (endpoint :<|> api) ~ (Server endpoint :<|> Server api)
114− , FlattenServer api
115− ) => FlattenServer (endpoint :<|> api) where
49+ ( Endpoints (endpoint :<|> api) ~ (endpoint ': Endpoints api),
50+ Server (endpoint :<|> api) ~ (Server endpoint :<|> Server api),
51+ FlattenServer api
52+ ) =>
53+ FlattenServer (endpoint :<|> api)
54+ where
11655 flattenServer (endpoint :<|> server) = endpoint `AnEndpoint` flattenServer @api server
11756
11857instance
119− ( HasServer (x :> api) '[]
120− , Endpoints (x :> api) ~ '[x :> api]
121− ) => FlattenServer (x :> api) where
58+ ( HasServer (x :> api) '[],
59+ Endpoints (x :> api) ~ '[x :> api]
60+ ) =>
61+ FlattenServer (x :> api)
62+ where
12263 flattenServer server = server `AnEndpoint` NoEndpoints
12364
12465instance
12566 ( HasServer (Verb method statusCode contentTypes responseType) '[],
12667 Endpoints (Verb method statusCode contentTypes responseType) ~ '[Verb method statusCode contentTypes responseType]
127− ) => FlattenServer (Verb method statusCode contentTypes responseType) where
68+ ) =>
69+ FlattenServer (Verb method statusCode contentTypes responseType)
70+ where
12871 flattenServer server = server `AnEndpoint` NoEndpoints
129−
72+
13073type ReifiedEndpoint = ([TypeRep], TypeRep, Dynamic)
13174
13275type ReifiedApi = [(ApiOffset, [TypeRep], TypeRep, Dynamic)]
@@ -144,18 +87,20 @@ class ToReifiedEndpoint (endpoint :: *) where
14487instance ToReifiedApi '[] where
14588 toReifiedApi NoEndpoints _ = []
14689
147−instance (Typeable (Normal (ServerT endpoint Handler)), NormalizeFunction (ServerT endpoint Handler), ToReifiedEndpoint endpoint, ToReifiedApi endpoints, Typeable (ServerT endpoint Handler))
148− => ToReifiedApi (endpoint : endpoints)
90+instance
91+ (Typeable (Normal (ServerT endpoint Handler)), NormalizeFunction (ServerT endpoint Handler), ToReifiedEndpoint endpoint, ToReifiedApi endpoints, Typeable (ServerT endpoint Handler)) =>
92+ ToReifiedApi (endpoint : endpoints)
14993 where
150− toReifiedApi (endpoint `AnEndpoint` endpoints) _ =
151− withOffset (toReifiedEndpoint (toDyn (normalize endpoint)) (Proxy @endpoint))
152− : map (\(n, x, y, z) -> (n + 1, x, y, z))
153− (toReifiedApi endpoints (Proxy @endpoints))
154− where
155− withOffset (x, y, z) = (0, x, y, z)
94+ toReifiedApi (endpoint `AnEndpoint` endpoints) _ =
95+ withOffset (toReifiedEndpoint (toDyn (normalize endpoint)) (Proxy @endpoint))
96+ : map
97+ (\(n, x, y, z) -> (n + 1, x, y, z))
98+ (toReifiedApi endpoints (Proxy @endpoints))
99+ where
100+ withOffset (x, y, z) = (0, x, y, z)
156101
157102class NormalizeFunction m where
158− type Normal m
103+ type Normal m
159104 normalize :: m -> Normal m
160105
161106instance NormalizeFunction x => NormalizeFunction (r -> x) where
@@ -167,32 +112,36 @@ instance Typeable x => NormalizeFunction (Handler x) where
167112 normalize handler = (runExceptT . runHandler') handler >>= \case
168113 Left serverError -> pure (Left serverError)
169114 Right x -> pure (Right (typeRep (Proxy @x), toDyn x))
170−
171−instance Typeable responseType
172− => ToReifiedEndpoint (Verb method statusCode contentTypes responseType)
115+
116+instance
117+ Typeable responseType =>
118+ ToReifiedEndpoint (Verb method statusCode contentTypes responseType)
173119 where
174− toReifiedEndpoint endpoint _ =
175− ([], typeRep (Proxy @responseType), endpoint)
120+ toReifiedEndpoint endpoint _ =
121+ ([], typeRep (Proxy @responseType), endpoint)
176122
177−instance (ToReifiedEndpoint endpoint)
178− => ToReifiedEndpoint ((x :: Symbol) :> endpoint)
123+instance
124+ (ToReifiedEndpoint endpoint) =>
125+ ToReifiedEndpoint ((x :: Symbol) :> endpoint)
179126 where
180− toReifiedEndpoint endpoint _ =
181− toReifiedEndpoint endpoint (Proxy @endpoint)
127+ toReifiedEndpoint endpoint _ =
128+ toReifiedEndpoint endpoint (Proxy @endpoint)
182129
183−instance (Typeable requestType, ToReifiedEndpoint endpoint)
184− => ToReifiedEndpoint (Capture name requestType :> endpoint)
130+instance
131+ (Typeable requestType, ToReifiedEndpoint endpoint) =>
132+ ToReifiedEndpoint (Capture name requestType :> endpoint)
185133 where
186− toReifiedEndpoint endpoint _ =
187− toReifiedEndpoint endpoint (Proxy @endpoint)
188− & \(args, result, typeRepMap) -> (typeRep (Proxy @requestType) : args, result, typeRepMap)
134+ toReifiedEndpoint endpoint _ =
135+ toReifiedEndpoint endpoint (Proxy @endpoint)
136+ & \(args, result, typeRepMap) -> (typeRep (Proxy @requestType) : args, result, typeRepMap)
189137
190−instance (Typeable requestType, ToReifiedEndpoint endpoint)
191− => ToReifiedEndpoint (ReqBody contentTypes requestType :> endpoint)
138+instance
139+ (Typeable requestType, ToReifiedEndpoint endpoint) =>
140+ ToReifiedEndpoint (ReqBody contentTypes requestType :> endpoint)
192141 where
193− toReifiedEndpoint endpoint _ =
194− toReifiedEndpoint endpoint (Proxy @endpoint)
195− & \(args, result, typeRepMap) -> (typeRep (Proxy @requestType) : args, result, typeRepMap)
142+ toReifiedEndpoint endpoint _ =
143+ toReifiedEndpoint endpoint (Proxy @endpoint)
144+ & \(args, result, typeRepMap) -> (typeRep (Proxy @requestType) : args, result, typeRepMap)
196145
197146newtype ApiOffset = ApiOffset Int
198147 deriving (Eq, Show)
@@ -205,7 +154,7 @@ data Op (v :: * -> *) = Op ApiOffset [(TypeRep, Var (Opaque (IORef Dynamic)) v)]
205154deriving instance Show (Op Symbolic)
206155
207156instance HTraversable Op where
208− htraverse r op@(Op offset args) = Op offset <$> traverse (\(t, v) -> (t,) <$> htraverse r v) args
157+ htraverse r (Op offset args) = Op offset <$> traverse (\(t, v) -> (t,) <$> htraverse r v) args
209158
210159callEndpoint :: (MonadGen n, MonadIO m) => ReifiedApi -> Command n m State
211160callEndpoint staticRoutes =
@@ -231,7 +180,7 @@ callEndpoint staticRoutes =
231180 options :: [(ApiOffset, [(TypeRep, [Var (Opaque (IORef Dynamic)) Symbolic])])]
232181 options =
233182 mapMaybe
234− ( \(offset, argreps, retType, dynCall) -> (offset,) <$> do
183+ ( \(offset, argreps, _retType, _dynCall) -> (offset,) <$> do
235184 mapM (\x -> (x,) <$> fillableWith x) argreps
236185 )
237186 staticRoutes
@@ -244,8 +193,8 @@ callEndpoint staticRoutes =
244193 execute (Op (ApiOffset offset) args) = do
245194 -- traceM (show (offset, args))
246195 fmap Opaque . liftIO $ do
247− realArgs <- mapM (\(tr, v) -> readIORef (opaque v)) args
248− let (_offset, staticArgs, ret, endpoint) = staticRoutes !! offset
196+ realArgs <- mapM (\(_tr, v) -> readIORef (opaque v)) args
197+ let (_offset, _staticArgs, _ret, endpoint) = staticRoutes !! offset
249198 -- now, magic happens: we apply some dynamic arguments to a dynamic
250199 -- function and hopefully somtehing useful pops out the end.
251200 func = foldr (\arg curr -> flip dynApply arg =<< curr) (Just endpoint) realArgs
@@ -257,14 +206,11 @@ callEndpoint staticRoutes =
257206 Nothing -> error ("all screwed up: " <> maybe ("nothing: " <> show showable) (show . dynTypeRep) func)
258207 Just f -> liftIO f >>= \case
259208 Left (serverError :: ServerError) -> error (show serverError)
260− Right (typeRep :: SomeTypeRep, (dyn :: Dynamic)) -> newIORef dyn
209+ Right (_typeRep :: SomeTypeRep, (dyn :: Dynamic)) -> newIORef dyn
261210 in Command
262211 gen
263212 execute
264− [ Update $ \s@State {..} (Op (ApiOffset offset) args) o' ->
265− -- let foo :: Var (Opaque Dynamic) v -> Var (Opaque (IORef Dynamic)) v
266− -- foo = _ . opaque
267− -- in
213+ [ Update $ \s@State {..} (Op (ApiOffset offset) _args) o' ->
268214 s
269215 { stateRefs =
270216 let (_, _, tr, _) = staticRoutes !! offset
@@ -277,8 +223,8 @@ callEndpoint staticRoutes =
277223 -- , Ensure (no-500 here)
278224 ]
279225
280−prop_sm_sequential :: ReifiedApi -> Property
281−prop_sm_sequential reifiedApi = do
226+prop_sequential :: ReifiedApi -> Property
227+prop_sequential reifiedApi = do
282228 property $ do
283229 let initialState = State mempty
284230 actions <-
@@ -289,54 +235,16 @@ prop_sm_sequential reifiedApi = do
289235 [callEndpoint reifiedApi]
290236 executeSequential initialState actions
291237
292−newtype Foo = Foo Int
293− deriving (Generic, Eq, Show, Typeable)
294− deriving newtype (FromHttpApiData, ToHttpApiData)
295−
296−instance ToJSON Foo
297−
298−instance FromJSON Foo
299−
300−type FooApi =
301− "item" :> Get '[JSON] Foo
302− :<|> "itemAdd" :> Capture "one" Foo :> Capture "two" Foo :> Get '[JSON] Foo
303− :<|> "item" :> Capture "itemId" Foo :> Get '[JSON] ()
304−
305−intro = pure (Foo 1)
306−
307−combine = (\(Foo a) (Foo b) -> pure (Foo (a + b)))
308−
309−eliminate =
310− ( \(Foo a) ->
311− if a > 10
312− then error "eliminate blew up, oh no!"
313− else pure ()
314− )
315−
316−fooServer :: Server FooApi
317−fooServer =
318− intro
319− :<|> combine
320− :<|> eliminate
321−
322−bundledFooServer :: Bundled (Endpoints FooApi)
323−bundledFooServer = flattenServer @FooApi fooServer
324−
325−runServer :: IO ()
326−runServer = do
327− let port = 3000
328− settings =
329− setPort port
330− $ setBeforeMainLoop (putStrLn ("listening on port " ++ show port))
331− $ defaultSettings
332− runSettings settings (serve fooApi fooServer)
333−
334−fooApi :: Proxy FooApi
335−fooApi = Proxy
336−
337−introC :<|> combineC :<|> eliminateC = Servant.Client.client fooApi
338−
339−tests :: IO Bool
340−tests = do
341− let reifiedApi = toReifiedApi (flattenServer @FooApi fooServer) (Proxy @(Endpoints FooApi))
342− checkParallel $ Group "props" [("aprop", prop_sm_sequential reifiedApi)]
238+prop_concurrent :: ReifiedApi -> Property
239+prop_concurrent reifiedApi =
240+ let initialState = State mempty
241+ in withTests 1000 . withRetries 10 . property $ do
242+ actions <-
243+ forAll $
244+ Gen.parallel
245+ (Range.linear 1 50)
246+ (Range.linear 1 10)
247+ initialState
248+ [callEndpoint reifiedApi]
249+ test $
250+ executeParallel initialState actions