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