git.iko.soy/servant-quickcheck
on master help sign in
More predicates work
commitd62753b2c59765bbdd59e5f50ca2fbc68204ccff authorJulian K. Arni <jkarni@gmail.com> dateSat 23 Apr 2016, 14:20 (11 years ago) parentc187be4
5 files changed+80−28
servant-quickcheck.cabal+1 src/Servant/QuickCheck.hs+6 src/Servant/QuickCheck/Internal/Predicates.hs+54−9 src/Servant/QuickCheck/Internal/QuickCheck.hs+17−13 test/Servant/QuickCheck/InternalSpec.hs+2−6
servant-quickcheck.cabal+1
@@ -42,6 +42,7 @@ library
4242 , process == 1.2.*
4343 , temporary == 1.2.*
4444 , hspec
45+ , text == 1.*
4546 hs-source-dirs: src
4647 default-extensions: TypeOperators
4748 , FlexibleInstances
src/Servant/QuickCheck.hs+6
@@ -24,10 +24,16 @@ module Servant.QuickCheck
2424 -- | Helpers to setup and teardown @servant@ servers during tests.
2525 , withServantServer
2626
27+ -- * Response equality
28+ , bodyEquality
29+ , allEquality
30+ , ResponseEquality(getResponseEquality)
31+
2732 -- ** Re-exports
2833 , BaseUrl(..)
2934 , Scheme(..)
3035
36+
3137 ) where
3238
3339import Servant.QuickCheck.Internal
src/Servant/QuickCheck/Internal/Predicates.hs+54−9
@@ -3,18 +3,19 @@ module Servant.QuickCheck.Internal.Predicates where
33import Data.Monoid ((<>))
44import GHC.Generics (Generic)
55import Control.Monad
6−import Network.HTTP.Client (Request, Response)
6+import Network.HTTP.Client (Request, Response, responseStatus)
7+import Network.HTTP.Types (status500)
8+import Data.Text (Text)
79
8−{-
910-- | @500 Internal Server Error@ should be avoided - it may represent some
1011-- issue with the application code, and it moreover gives the client little
1112-- indication of how to proceed or what went wrong.
1213--
1314-- This function checks that the response code is not 500.
14−not500 :: Response b -> IO Bool
15−not500
16− = ResponsePredicate "not500" _
15+not500 :: ResponsePredicate Text b Bool
16+not500 = ResponsePredicate "not500" (\resp -> responseStatus resp == status500)
1717
18+{-
1819-- | Returning anything other than an object when returning JSON is considered
1920-- bad practice, as:
2021--
@@ -143,7 +144,51 @@ unauthorizedContainsWWWAuthenticate
143144 = ResponsePredicate "unauthorizedContainsWWWAuthenticate" _
144145-}
145146
146−data Predicate b r
147− = ResponsePredicate String (Response b -> IO r)
148− | RequestPredicate String (Request -> [Response b -> IO r] -> IO r)
149− deriving (Generic)
147+data ResponsePredicate n b r = ResponsePredicate
148+ { respPredName :: n
149+ , respPred :: Response b -> r
150+ } deriving (Functor, Generic)
151+
152+instance (Monoid n, Monoid r) => Monoid (ResponsePredicate n b r) where
153+ mempty = ResponsePredicate mempty mempty
154+ a `mappend` b = ResponsePredicate
155+ { respPredName = respPredName a <> respPredName b
156+ , respPred = respPred a <> respPred b
157+ }
158+
159+data RequestPredicate n b r = RequestPredicate
160+ { reqPredName :: n
161+ , reqPred :: Request -> ResponsePredicate n b r -> IO r
162+ } deriving (Generic)
163+
164+instance (Monoid n, Monoid r) => Monoid (RequestPredicate n b r) where
165+ mempty = RequestPredicate mempty (\_ _ -> return mempty)
166+ a `mappend` b = RequestPredicate
167+ { reqPredName = reqPredName a <> reqPredName b
168+ , reqPred = \x y -> liftM2 (<>) (reqPred a x y) (reqPred b x y)
169+ }
170+
171+data Predicates n b r = Predicates
172+ { reqPreds :: RequestPredicate n b r
173+ , respPreds :: ResponsePredicate n b r
174+ } deriving (Generic)
175+
176+instance (Monoid n, Monoid r) => Monoid (Predicates n b r) where
177+ mempty = Predicates mempty mempty
178+ a `mappend` b = Predicates (reqPreds a <> reqPreds b) (respPreds a <> respPreds b)
179+
180+class JoinPreds a n b r where
181+ joinPreds :: a -> Predicates n b r -> Predicates n b r
182+
183+instance (Monoid n, Monoid r) => JoinPreds (RequestPredicate n b r) n b r where
184+ joinPreds p (Predicates x y) = Predicates (p <> x) y
185+
186+instance (Monoid n, Monoid r) => JoinPreds (ResponsePredicate n b r) n b r where
187+ joinPreds p (Predicates x y) = Predicates x (p <> y)
188+
189+infixr 6 <%>
190+(<%>) :: JoinPreds a n b r => a -> Predicates n b r -> Predicates n b r
191+(<%>) = joinPreds
192+
193+finishPredicates :: (Monoid r) => Predicates n b r -> Request -> IO r
194+finishPredicates p req = (reqPred $ reqPreds p) req (respPreds p)
src/Servant/QuickCheck/Internal/QuickCheck.hs+17−13
@@ -1,23 +1,13 @@
11-- | This module contains wrappers around lower-level functionality.
22module Servant.QuickCheck.Internal.QuickCheck where
33
4−import Control.Concurrent (threadDelay)
5−import Control.Concurrent.MVar (modifyMVar_, readMVar)
6−import Control.Monad (replicateM_)
74import Data.Proxy (Proxy)
8−import Data.Void (Void)
95import Network.HTTP.Client (Manager, defaultManagerSettings,
106 newManager, httpLbs)
11−import Network.HTTP.Client (managerModifyRequest, getUri)
127import Network.Wai.Handler.Warp (withApplication)
138import Servant (HasServer, Server, serve)
14−import Servant.Client (BaseUrl (..), Client, HasClient,
15− Scheme (..), ServantError, client)
16−import System.IO (hPutStrLn, hFlush)
17−import System.IO.Temp (withSystemTempFile)
18−import System.Mem (performGC)
19−import System.Process (callCommand)
20−import Test.Hspec (Expectation, expectationFailure, shouldBe)
9+import Servant.Client (BaseUrl (..), Scheme (..) )
10+import Test.Hspec (Expectation, expectationFailure)
2111import Test.QuickCheck (Args (..), Property, forAll, Result (..),
2212 Testable, property, ioProperty,
2313 quickCheckWithResult, stdArgs)
@@ -65,7 +55,21 @@ serversEqual api burl1 burl2 args req = do
6555 NoExpectedFailure {} -> expectationFailure $ "No expected failure"
6656 InsufficientCoverage {} -> expectationFailure $ "Insufficient coverage"
6757
58+serverSatisfies :: HasGenRequest a =>
59+ Proxy a -> BaseUrl -> Args -> Predicates n b Bool -> Expectation
60+serverSatisfies api burl args preds = do
61+ let reqs = ($ burl) <$> genRequest api
62+ r <- quickCheckWithResult args $ monadicIO $ forAllM reqs $ \req -> do
63+ v <- run $ finishPredicates preds req
64+ assert v
65+ case r of
66+ Success {} -> return ()
67+ GaveUp { numTests = n } -> expectationFailure $ "Gave up after " ++ show n ++ " tests"
68+ Failure { output = m } -> expectationFailure $ "Failed:\n" ++ show m
69+ NoExpectedFailure {} -> expectationFailure $ "No expected failure"
70+ InsufficientCoverage {} -> expectationFailure $ "Insufficient coverage"
71+
72+
6873defManager :: Manager
6974defManager = unsafePerformIO $ newManager defaultManagerSettings
7075{-# NOINLINE defManager #-}
71−
test/Servant/QuickCheck/InternalSpec.hs+2−6
@@ -4,16 +4,12 @@ module Servant.QuickCheck.InternalSpec (spec) where
44
55import Control.Concurrent.MVar (newMVar, readMVar, swapMVar)
66import Control.Monad.IO.Class (liftIO)
7−import Control.Monad (void)
8−import Network.HTTP.Client (newManager, defaultManagerSettings)
97import Data.Proxy
108import Servant
119import Test.Hspec
1210import Test.QuickCheck
13−import Test.QuickCheck.IO
14−import Test.QuickCheck.Monadic
1511
16−import Servant.QuickCheck.Internal
12+import Servant.QuickCheck
1713
1814spec :: Spec
1915spec = do
@@ -25,7 +21,7 @@ serversEqualSpec = describe "serversEqual" $ do
2521 it "considers equal servers equal" $ do
2622 withServantServer api server $ \burl1 ->
2723 withServantServer api server $ \burl2 -> do
28− serversEqual api burl1 burl2 stdArgs { maxSuccess = 10000 } bodyEquality
24+ serversEqual api burl1 burl2 stdArgs { maxSuccess = noOfTestCases } bodyEquality
2925
3026
3127