on master help sign in
wip
commitf686505db5856ca15904cd79449f65bcb1a58aaa authorMark Wotton <mwotton@gmail.com> dateThu 26 Nov 2020, 13:21 (6 years ago) parent67d88d2
5 files changed+51−36
package.yaml+1−1 roboservant.cabal+3−1 src/Roboservant/Direct.hs+19−21 src/Roboservant/Types/Breakdown.hs+14−13 test/Spec.hs+14
package.yaml+1−1
@@ -49,4 +49,4 @@ tests:
4949 - aeson
5050 - hspec
5151 - hspec-core
52−
52+ - http-api-data
roboservant.cabal+3−1
@@ -4,7 +4,7 @@ cabal-version: 1.12
44--
55-- see: https://github.com/sol/hpack
66--
7−-- hash: d00686eca2b790a054d75ee02699e0505188e10e975cd6ebc7b06eeeb82923b4
7+-- hash: 4774250c208a2100460b0df7518257286ca38d8612b517ddcbf3e37678b598cf
88
99name: roboservant
1010version: 0.1.0.2
@@ -60,6 +60,7 @@ test-suite roboservant-test
6060 type: exitcode-stdio-1.0
6161 main-is: Spec.hs
6262 other-modules:
63+ BuildFrom
6364 Foo
6465 Headers
6566 Post
@@ -77,6 +78,7 @@ test-suite roboservant-test
7778 , containers
7879 , hspec
7980 , hspec-core
81+ , http-api-data
8082 , lifted-base
8183 , monad-control
8284 , mtl
src/Roboservant/Direct.hs+19−21
@@ -4,16 +4,14 @@
44{-# LANGUAGE FlexibleContexts #-}
55{-# LANGUAGE FlexibleInstances #-}
66{-# LANGUAGE GADTs #-}
7−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
87{-# LANGUAGE LambdaCase #-}
98{-# LANGUAGE PolyKinds #-}
109{-# LANGUAGE RecordWildCards #-}
1110{-# LANGUAGE ScopedTypeVariables #-}
12−{-# LANGUAGE StandaloneDeriving #-}
1311{-# LANGUAGE TupleSections #-}
1412{-# LANGUAGE TypeApplications #-}
1513{-# LANGUAGE TypeFamilies #-}
16−{-# LANGUAGE TypeOperators #-}
14+
1715{-# LANGUAGE UndecidableInstances #-}
1816
1917module Roboservant.Direct
@@ -26,9 +24,7 @@ module Roboservant.Direct
2624where
2725
2826import Control.Exception.Lifted(throw,handle,Handler(..), Exception,SomeException,SomeAsyncException, catch, catches)
29−import Control.Monad(void,replicateM)
3027import Control.Monad.State.Strict(MonadState,MonadIO,get,modify',liftIO,runStateT)
31−import Control.Monad.Trans.Control(MonadBaseControl)
3228import Data.Dynamic (Dynamic, dynApply, dynTypeRep, fromDynamic)
3329import Data.Map.Strict(Map)
3430import qualified Data.Set as Set
@@ -40,6 +36,7 @@ import qualified Data.List.NonEmpty as NEL
4036import qualified Data.Map.Strict as Map
4137import Data.Time.Clock
4238import Data.List(sortOn)
39+import Control.Arrow(second)
4340
4441import Roboservant.Types.Breakdown
4542import Roboservant.Types
@@ -47,8 +44,7 @@ import Roboservant.Types
4744 FlattenServer (..),
4845-- ReifiedApi,
4946 ToReifiedApi (..),
50− )
51−
47+ ))
5248data RoboservantException
5349 = RoboservantException
5450 { failureReason :: FailureType
@@ -66,7 +62,7 @@ data FailureType
6662 deriving (Show,Eq)
6763
6864
69−data FuzzOp = FuzzOp
65+data FuzzOp = FuzzOp
7066 { apiOffset :: ApiOffset
7167 , provenance :: [Provenance]
7268 } deriving (Show,Eq)
@@ -95,7 +91,7 @@ data StopReason
9591data Report = Report
9692 { textual :: String
9793 , rsException :: RoboservantException}
98− deriving (Show)
94+ deriving (Show)
9995
10096fuzz :: forall api. (FlattenServer api, ToReifiedApi (Endpoints api))
10197 => Server api
@@ -107,7 +103,7 @@ fuzz server Config{..} checker = handle (pure . Just . formatException) $ do
107103 stash = addToStash seed mempty
108104 currentRng = mkStdGen rngSeed
109105
110−
106+
111107 deadline :: UTCTime <- addUTCTime (fromInteger $ maxRuntime * 1000000) <$> getCurrentTime
112108 (stopreason, fs ) <- runStateT
113109 (untilDone (maxReps, deadline) go <* (evaluateCoverage =<< get)) FuzzState{..}
@@ -126,7 +122,9 @@ fuzz server Config{..} checker = handle (pure . Just . formatException) $ do
126122 mapM_ print (Set.toList $ Set.fromList $ map apiOffset path)
127123 putStrLn ""
128124 putStrLn "types in stash"
129− mapM_ print (map (\x -> (fst x, NEL.length (snd x))) . sortOn (show . fst) . Map.toList $ stash)
125+ mapM_
126+ (print . second NEL.length)
127+ (sortOn (show . fst) . Map.toList stash)
130128
131129 -- evaluateCoverage :: FuzzState -> m ()
132130 evaluateCoverage f@FuzzState{..}
@@ -134,10 +132,10 @@ fuzz server Config{..} checker = handle (pure . Just . formatException) $ do
134132 | otherwise = do
135133 displayDiagnostics f
136134 throw $ RoboservantException (InsufficientCoverage coverage) Nothing f
137− where hitRoutes = (fromIntegral . Set.size . Set.fromList $ map apiOffset path)
138− totalRoutes = (fromIntegral routeCount)
135+ where hitRoutes = fromIntegral . Set.size . Set.fromList $ map apiOffset path
136+ totalRoutes = fromIntegral routeCount
139137 coverage = hitRoutes / totalRoutes
140−
138+
141139
142140 untilDone :: MonadIO m => (Integer,UTCTime) -> m a -> m StopReason
143141 untilDone (0,_) _ = pure HitMaxIterations
@@ -148,10 +146,10 @@ fuzz server Config{..} checker = handle (pure . Just . formatException) $ do
148146 else do
149147 action
150148 untilDone (n-1, deadline) action
151−
149+
152150 reifiedApi = toReifiedApi (flattenServer @api server) (Proxy @(Endpoints api))
153151 routeCount = length reifiedApi
154−
152+
155153 elementOrFail :: (MonadState FuzzState m, MonadIO m)
156154 => [a] -> m a
157155 elementOrFail [] = liftIO . throw . RoboservantException NoPossibleMoves Nothing =<< get
@@ -175,7 +173,7 @@ fuzz server Config{..} checker = handle (pure . Just . formatException) $ do
175173 options :: FuzzState -> [(ApiOffset, Dynamic, [NEL.NonEmpty ([Provenance], Dynamic)])]
176174 options FuzzState{..} =
177175 mapMaybe
178− ( \(offset, (argreps, dynCall)) -> (offset,dynCall,) <$> do
176+ ( \(offset, (argreps, dynCall)) -> (offset,dynCall,) <$>
179177 mapM (\(_tr,bf) -> bf stash ) argreps
180178 )
181179 reifiedApi
@@ -196,19 +194,19 @@ fuzz server Config{..} checker = handle (pure . Just . formatException) $ do
196194 ,"state"
197195 ,show st]
198196 -- liftIO $ putStrLn showable
199−
197+
200198 case func of
201199 Nothing -> error ("all screwed up 1: " <> maybe ("nothing: " <> show showable) (show . dynTypeRep) func)
202− Just (f') -> do
200+ Just f' -> do
203201 -- liftIO $ print
204202 case fromDynamic f' of
205203 Nothing -> error ("all screwed up 2: " <> maybe ("nothing: " <> show showable) (show . dynTypeRep) func)
206− Just (f) -> liftIO f >>= \case
204+ Just f -> liftIO f >>= \case
207205 -- parameterise this
208206 Left (serverError :: ServerError) ->
209207 case errHTTPCode serverError of
210208 500 -> throw serverError
211− _ -> do
209+ _ ->
212210 liftIO $ print ("ignoring non-500 error" , serverError)
213211
214212 Right (dyn :: NEL.NonEmpty Dynamic) -> do
src/Roboservant/Types/Breakdown.hs+14−13
@@ -23,14 +23,22 @@ import Data.Maybe (fromMaybe)
2323import Data.Proxy (Proxy (..))
2424import Data.Typeable (TypeRep, Typeable, typeRep)
2525import GHC.Generics(Generic)
26+import qualified GHC.Generics as Generics
2627
2728data Provenance
2829 = Provenance TypeRep Int
2930 deriving (Show,Eq)
3031type Stash = Map TypeRep (NonEmpty ([Provenance], Dynamic))
3132
33+
34+class Breakdown x where
35+ breakdown :: x -> NonEmpty Dynamic
36+-- default breakdown :: Typeable x => x -> NonEmpty Dynamic
37+-- breakdown = pure . toDyn
38+
3239class Typeable x => BuildFrom x where
3340 buildFrom :: Stash -> Maybe (NonEmpty ([Provenance],Dynamic))
41+
3442-- default buildFrom :: Stash -> Maybe (NonEmpty ([Provenance], Dynamic))
3543-- buildFrom = Map.lookup (typeRep (Proxy @x))
3644
@@ -41,11 +49,15 @@ newtype Atom x = Atom { unAtom :: x }
4149-- | can be broken down and built up from generic pieces
4250newtype Compound x = Compound { unCompound :: x }
4351
52+-- | so, this is quite tricky. we need to witness the structure of `x`, but
53+-- we don't have an example of it yet. not entirely sure where to go.
4454instance (Typeable x, Generic x) => BuildFrom (Compound x) where
45− buildFrom = error "buildfrom"
55+ buildFrom stash = error "oops"
56+-- breakdown = Map.fromListWith (<>) . fmap ((dynTypeRep &&& (\x -> NEL.fromList [x])) . toDyn . Generics.to) . _ . Generics.from
4657
58+-- | this should be a bit more tractable.
4759instance (Typeable x, Generic x) => Breakdown (Compound x) where
48− breakdown = error "breakdown"
60+ breakdown x = _ . Generics.from . unCompound $ x
4961
5062instance Typeable x => BuildFrom (Atom x) where
5163 buildFrom = Map.lookup (typeRep (Proxy @x))
@@ -72,11 +84,6 @@ instance (Typeable x, BuildFrom x) => BuildFrom (Maybe x) where
7284 (maybe [] (NEL.toList . (fmap (fmap (Just . promisedDyn @x))))
7385 $ buildFrom @x dict)
7486
75−class Breakdown x where
76− breakdown :: x -> NonEmpty Dynamic
77−-- default breakdown :: Typeable x => x -> NonEmpty Dynamic
78−-- breakdown = pure . toDyn
79−
8087instance Typeable a => Breakdown (Atom a) where
8188 breakdown = pure . toDyn . unAtom
8289
@@ -84,12 +91,6 @@ deriving via (Atom ()) instance Breakdown ()
8491deriving via (Atom Int) instance Breakdown Int
8592
8693
87−--let d = toDyn x in Map.fromList [(dynTypeRep d, pure d)]
88−
89−-- instance (Typeable x, Generic x) => Breakdown x where
90−-- breakdown = Map.fromListWith (<>) . fmap ((dynTypeRep &&& (\x -> NEL.fromList [x])) . toDyn . Generics.to) . _ . Generics.from
91−
92−
9394instance (Typeable a, Breakdown a) => Breakdown [a] where
9495 breakdown x = toDyn x :| mconcat (map (NEL.toList . breakdown) x)
9596
test/Spec.hs+14
@@ -9,6 +9,7 @@ import qualified Seeded
99import qualified Valid
1010import qualified Headers
1111import qualified Post
12+import qualified BuildFrom
1213-- import qualified UnsafeIO
1314
1415import Data.Dynamic(toDyn)
@@ -62,6 +63,13 @@ spec = do
6263 RS.fuzz @Headers.Api Headers.server defaultConfig noCheck
6364 >>= (`shouldSatisfy` isJust)
6465
66+ describe "can build from pieces" $ do
67+ it "should find a failure that requires some assembly" $ do
68+ RS.fuzz @BuildFrom.Api BuildFrom.server defaultConfig noCheck
69+ >>= (`shouldSatisfy` isJust)
70+
71+
72+
6573 -- -- -- The UnsafeIO checker does not actually really use the contextually aware stuff, though it
6674 -- -- -- could: it's mostly here to show how to test for concurrency problems.
6775 -- describe "concurrency bugs" $ do
@@ -94,6 +102,12 @@ deriving via (Atom Void) instance RS.BuildFrom Void
94102deriving via (Atom Post.FooPost) instance RS.BuildFrom Post.FooPost
95103deriving via (Atom Post.FooPost) instance RS.Breakdown Post.FooPost
96104
105+
106+deriving via (Compound BuildFrom.Wrapped) instance RS.BuildFrom BuildFrom.Wrapped
107+deriving via (Compound BuildFrom.Wrapped) instance RS.Breakdown BuildFrom.Wrapped
108+
109+
110+
97111-- deriving via (Atom Void) instance RS.BuildFrom Void
98112
99113-- instance RS.Breakdown Post.FooPost