@@ -2,17 +2,17 @@
2{-# LANGUAGE FlexibleContexts #-}
2{-# LANGUAGE FlexibleContexts #-}
3{-# LANGUAGE GADTs #-}
3{-# LANGUAGE GADTs #-}
4{-# LANGUAGE LambdaCase #-}
4{-# LANGUAGE LambdaCase #-}
5−{-# LANGUAGE PolyKinds #-}
6{-# LANGUAGE RankNTypes #-}
5{-# LANGUAGE RankNTypes #-}
7{-# LANGUAGE RecordWildCards #-}
6{-# LANGUAGE RecordWildCards #-}
8{-# LANGUAGE ScopedTypeVariables #-}
7{-# LANGUAGE ScopedTypeVariables #-}
9{-# LANGUAGE TupleSections #-}
8{-# LANGUAGE TupleSections #-}
10−
9+{-# LANGUAGE TypeApplications #-}
11{-# LANGUAGE TypeFamilies #-}
10{-# LANGUAGE TypeFamilies #-}
12{-# LANGUAGE UndecidableInstances #-}
11{-# LANGUAGE UndecidableInstances #-}
12+{-# LANGUAGE PolyKinds #-}
14module Roboservant.Direct
14module Roboservant.Direct
16 Config (..),
16 Config (..),
17 -- TODO come up with something smarter than exporting all this, we should
17 -- TODO come up with something smarter than exporting all this, we should
18 -- have some nice error-display functions
18 -- have some nice error-display functions
@@ -25,30 +25,25 @@ module Roboservant.Direct
27import Control.Exception.Lifted
27import Control.Exception.Lifted
28− ( Exception,
30+ Exception,
30− SomeAsyncException,
31+ SomeAsyncException,
35− throw,
35+ Handler(Handler) )
37import Control.Monad.State.Strict
36import Control.Monad.State.Strict
40− StateT (runStateT),
37+ ( MonadIO(..), StateT(runStateT), modify', MonadState(get) )
43import Control.Monad.Trans.Control (MonadBaseControl)
38import Control.Monad.Trans.Control (MonadBaseControl)
44import qualified Data.Dependent.Map as DM
39import qualified Data.Dependent.Map as DM
45−import Data.Dynamic (Dynamic (..))
40+import Data.Dynamic ( Dynamic(..) )
46import qualified Data.IntSet as IntSet
41import qualified Data.IntSet as IntSet
47import Data.List.NonEmpty (NonEmpty (..))
42import Data.List.NonEmpty (NonEmpty (..))
48import qualified Data.List.NonEmpty as NEL
43import qualified Data.List.NonEmpty as NEL
49import Data.Maybe (mapMaybe)
44import Data.Maybe (mapMaybe)
50import qualified Data.Set as Set
45import qualified Data.Set as Set
51−import Data.Time.Clock (UTCTime, addUTCTime, getCurrentTime)
46+import Data.Time.Clock ( addUTCTime, getCurrentTime, UTCTime )
52import qualified Data.Vinyl as V
47import qualified Data.Vinyl as V
53import qualified Data.Vinyl.Curry as V
48import qualified Data.Vinyl.Curry as V
54import qualified Data.Vinyl.Functor as V
49import qualified Data.Vinyl.Functor as V
@@ -56,17 +51,16 @@ import GHC.Generics ((:*:) (..))
56import Roboservant.Types
51import Roboservant.Types
57 ( ApiOffset (..),
52 ( ApiOffset (..),
58 Argument (..),
53 Argument (..),
59− InteractionError(..),
54+ FlattenServer (..),
60 Provenance (..),
55 Provenance (..),
62 ReifiedEndpoint (..),
56 ReifiedEndpoint (..),
63 Stash (..),
57 Stash (..),
64 StashValue (..),
58 StashValue (..),
67−import Roboservant.Types.Config
62+import Servant (Endpoints, Proxy (Proxy), Server, ServerError (..))
69−import System.Random (Random (randomR), StdGen, mkStdGen)
63+import System.Random ( mkStdGen, Random(randomR), StdGen )
70import qualified Type.Reflection as R
64import qualified Type.Reflection as R
72data RoboservantException
66data RoboservantException
@@ -93,6 +87,15 @@ data FuzzOp
94 deriving (Show, Eq)
88 deriving (Show, Eq)
92+ { seed :: [(Dynamic, Int)],
93+ maxRuntime :: Double, -- seconds to test for
96+ coverageThreshold :: Double
96data FuzzState
99data FuzzState
97 = FuzzState
100 = FuzzState
98 { path :: [FuzzOp],
101 { path :: [FuzzOp],
@@ -105,7 +108,7 @@ data EndpointOption
105 = forall as.
108 = forall as.
106 (V.RecordToList as, V.RMap as) =>
109 (V.RecordToList as, V.RMap as) =>
107 EndpointOption
110 EndpointOption
108− { eoCall :: V.Curried as (IO (Either InteractionError (NonEmpty (Dynamic, Int)))),
111+ { eoCall :: V.Curried as (IO (Either ServerError (NonEmpty (Dynamic, Int)))),
109 eoArgs :: V.Rec (TypedF StashValue) as
112 eoArgs :: V.Rec (TypedF StashValue) as
@@ -121,18 +124,14 @@ data Report
122 deriving (Show)
125 deriving (Show)
126−-- fuzzClient :: Client api -> Config -> IO (Maybe Report)
127−-- fuzzClient = undefined
132− ReifiedApi ->
128+ forall api.
129+ (FlattenServer api, ToReifiedApi (Endpoints api)) =>
133 Config ->
131 Config ->
134 IO (Maybe Report)
133 IO (Maybe Report)
135−fuzz' reifiedApi Config {..} = handle (pure . Just . formatException) $ do
134+fuzz server Config {..} checker = handle (pure . Just . formatException) $ do
136 let path = []
135 let path = []
137 stash = addToStash seed mempty
136 stash = addToStash seed mempty
138 currentRng = mkStdGen rngSeed
137 currentRng = mkStdGen rngSeed
@@ -141,7 +140,7 @@ fuzz' reifiedApi Config {..} = handle (pure . Just . formatException) $ do
141 runStateT
140 runStateT
142 (untilDone (maxReps, deadline) go <* (evaluateCoverage =<< get))
141 (untilDone (maxReps, deadline) go <* (evaluateCoverage =<< get))
143 FuzzState {..}
142 FuzzState {..}
144− logInfo $ show stopreason
143+ print stopreason
145 pure Nothing
144 pure Nothing
147 -- something less terrible later
146 -- something less terrible later
@@ -150,15 +149,13 @@ fuzz' reifiedApi Config {..} = handle (pure . Just . formatException) $ do
151 (unlines [show failureType, show exception])
150 (unlines [show failureType, show exception])
153− displayDiagnostics FuzzState {..} = liftIO $
152+ displayDiagnostics FuzzState {..} = liftIO $ do
155− ["api endpoints covered"]
153+ putStrLn "api endpoints covered"
156− <> (map show . Set.toList . Set.fromList $ map apiOffset path)
154+ mapM_ print (Set.toList $ Set.fromList $ map apiOffset path)
157− <> ["", "types in stash"]
156+ putStrLn "types in stash"
158− <> DM.foldrWithKey (\_ v r -> (show . NEL.length . getStashValue $ v) : r) [] (getStash stash)
157+ DM.forWithKey_ (getStash stash) $ \_k v ->
159− -- <> (map (show . NEL.length . getStashValue ) $ DM.assocs (getStash stash))
161− -- (show . NEL.length $ getStashValue v))
158+ print (NEL.length $ getStashValue v)
163 evaluateCoverage f@FuzzState {..}
160 evaluateCoverage f@FuzzState {..}
164 | coverage > coverageThreshold = pure ()
161 | coverage > coverageThreshold = pure ()
@@ -178,7 +175,7 @@ fuzz' reifiedApi Config {..} = handle (pure . Just . formatException) $ do
179 _ <- action
176 _ <- action
180 untilDone (n -1, deadline) action
177 untilDone (n -1, deadline) action
181−
178+ reifiedApi = toReifiedApi (flattenServer @api server) (Proxy @(Endpoints api))
182 routeCount = length reifiedApi
179 routeCount = length reifiedApi
183 elementOrFail ::
180 elementOrFail ::
184 (MonadState FuzzState m, MonadIO m) =>
181 (MonadState FuzzState m, MonadIO m) =>
@@ -195,7 +192,7 @@ fuzz' reifiedApi Config {..} = handle (pure . Just . formatException) $ do
195 ( forall as.
192 ( forall as.
196 (V.RecordToList as, V.RMap as) =>
193 (V.RecordToList as, V.RMap as) =>
197 FuzzOp ->
194 FuzzOp ->
198− V.Curried as (IO (Either InteractionError (NonEmpty (Dynamic, Int)))) ->
195+ V.Curried as (IO (Either ServerError (NonEmpty (Dynamic, Int)))) ->
199 V.Rec (TypedF V.Identity) as ->
196 V.Rec (TypedF V.Identity) as ->
@@ -236,24 +233,27 @@ fuzz' reifiedApi Config {..} = handle (pure . Just . formatException) $ do
236 execute ::
233 execute ::
237 (MonadState FuzzState m, MonadIO m, V.RecordToList as, V.RMap as) =>
234 (MonadState FuzzState m, MonadIO m, V.RecordToList as, V.RMap as) =>
238 FuzzOp ->
235 FuzzOp ->
239− V.Curried as (IO (Either InteractionError (NonEmpty (Dynamic, Int)))) ->
236+ V.Curried as (IO (Either ServerError (NonEmpty (Dynamic, Int)))) ->
240 V.Rec (TypedF V.Identity) as ->
237 V.Rec (TypedF V.Identity) as ->
242 execute fuzzop func args = do
239 execute fuzzop func args = do
243− (liftIO . logInfo . show . (fuzzop,) . stash) =<< get
240+ (liftIO . print . (fuzzop,) . stash) =<< get
244 liftIO (V.runcurry' func argVals) >>= \case
241 liftIO (V.runcurry' func argVals) >>= \case
245− Left (e::InteractionError) ->
242+ -- parameterise this
246− if fatalError e
243+ Left (serverError :: ServerError) ->
247− then throw e
244+ case errHTTPCode serverError of
248− else pure ()
245+ 500 -> throw serverError
247+ liftIO $ print ("ignoring non-500 error", serverError)
249− Right (dyn :: NEL.NonEmpty (Dynamic, Int)) ->
248+ Right (dyn :: NEL.NonEmpty (Dynamic, Int)) -> do
249+ -- liftIO $ print ("storing", fmap dynTypeRep dyn)
251− ( \fs@FuzzState {..} ->
251+ ( \fs@FuzzState {..} ->
252− fs {stash = addToStash (NEL.toList dyn) stash}
252+ fs {stash = addToStash (NEL.toList dyn) stash}
255 argVals = V.rmap (\(_ :*: V.Identity x) -> V.Identity x) args
255 argVals = V.rmap (\(_ :*: V.Identity x) -> V.Identity x) args
256− -- argTypes = recordToList' (\(tr :*: _) -> R.SomeTypeRep tr) args
256+ -- argTypes = recordToList' (\(tr :*: _) -> R.SomeTypeRep tr) args
258 (MonadState FuzzState m, MonadIO m, MonadBaseControl IO m) =>
258 (MonadState FuzzState m, MonadIO m, MonadBaseControl IO m) =>
@@ -262,12 +262,13 @@ fuzz' reifiedApi Config {..} = handle (pure . Just . formatException) $ do
262 (execute op func args)
262 (execute op func args)
263 [ Handler (\(e :: SomeAsyncException) -> throw e),
263 [ Handler (\(e :: SomeAsyncException) -> throw e),
265− ( \(e :: SomeException) ->
265+ ( \(e :: SomeException) -> do
266+ displayDiagnostics =<< get
266 throw . RoboservantException ServerCrashed (Just e) =<< get
267 throw . RoboservantException ServerCrashed (Just e) =<< get
270− (liftIO healthCheck)
271+ (liftIO checker)
271 (\(e :: SomeException) -> throw . RoboservantException CheckerFailed (Just e) =<< get)
272 (\(e :: SomeException) -> throw . RoboservantException CheckerFailed (Just e) =<< get)
273addToStash ::
274addToStash ::