11{-# LANGUAGE OverloadedStrings #-}
2+ {-# LANGUAGE RecordWildCards #-}
23{-# LANGUAGE TemplateHaskell #-}
34
45module Data.API.Tools.QuickCheck
@@ -15,13 +16,13 @@ import Control.Applicative
1516import Control.Monad
1617import Data.Monoid
1718import Data.Time
19+ import Data.Coerce
1820import Language.Haskell.TH
1921import Prelude
2022import Test.QuickCheck as QC
23+ import Language.Haskell.TH.Syntax (lift )
2124
22- -- | Tool to generate 'Arbitrary' instances for generated types. This tool generates
23- -- also a stock shrinker via the 'generic-arbitrary' package, which means we require
24- -- the wrapped type to be an instance of 'Generic'.
25+ -- | Tool to generate 'Arbitrary' instances for generated types.
2526quickCheckTool :: APITool
2627quickCheckTool = apiNodeTool $ apiSpecTool gen_sn_ab gen_sr_ab gen_su_ab gen_se_ab mempty
2728
@@ -50,9 +51,9 @@ gen_sn_ab = mkTool $ \ ts (an, sn) -> case snFilter sn of
5051 Nothing | snType sn == BTint -> mk_instance ts an sn [e | QC.arbitraryBoundedIntegral |] (shrinkNewtype ts an sn)
5152 | otherwise -> mk_instance ts an sn [e | arbitrary |] (shrinkNewtype ts an sn)
5253 Just (FtrIntg ir) ->
53- mk_instance ts an sn [e | arbitraryIntRange ir |] (shrinkIntRange ir ts an sn)
54+ mk_instance ts an sn [e | arbitraryIntRange ir |] (shrinkIntRange ir sn)
5455 Just (FtrUTC ur) ->
55- mk_instance ts an sn [e | arbitraryUTCRange ur |] (shrinkUTCRange ur ts an sn)
56+ mk_instance ts an sn [e | arbitraryUTCRange ur |] (shrinkUTCRange ur sn)
5657 Just (FtrStrg _) -> return []
5758 where
5859 mk_instance ts an sn arb =
@@ -72,32 +73,29 @@ shrinkNewtype ts an sn = do
7273 []
7374 ]
7475
75- -- | Attempts to shrink an input 'APINode' within the given 'IntRange', i.e. if the 'IntRange'
76- -- specifies an 'ir_lo', then we shrink such that the resulting shrunk values still satisfies
77- -- the min constrain of the range (i.e. we never generate values /smaller/ than 'ir_lo').
78- --
79- -- A few observations/remarks:
80- --
81- -- * If the 'ir_lo' is 'Nothing', then this because just 'shrinkNewtype', because we don't
82- -- really care about 'ir_hi' as shrinking by default won't generate value higher than the
83- -- value being shrunk (it would be a nonsense);
84- --
85- -- * We can generate code that typechecks only if we have a 'BTint', otherwise we don't shrink.
86- shrinkIntRange :: IntRange -> ToolSettings -> APINode -> SpecNewtype -> ExpQ
87- shrinkIntRange ir ts an sn = case ir_lo ir of
88- Nothing -> shrinkNewtype ts an sn
89- Just lowerBound -> do
90- x <- newName " x"
91- y <- newName " y"
92- lamE [varP x] $
93- caseE (varE x) [
94- match (nodeNewtypeConP ts an sn [varP y])
95- (normalB $ do
96- if snType sn == BTint
97- then [| map $ (nodeNewtypeConE ts an sn) $ filter (>= lowerBound) $ (QC. shrink $ (varE y)) | ]
98- else noShrink
99- ) []
100- ]
76+ shrinkWithinIntRange :: IntRange -> Int -> [Int ]
77+ shrinkWithinIntRange ir@ IntRange {.. } x = refine $ QC. shrink x
78+ where
79+ refine = case (ir_lo, ir_hi) of
80+ (Nothing , Nothing ) -> id -- avoid filter altogether
81+ _ -> filter (`inIntRange` ir)
82+
83+ shrinkWithinUTCRange :: UTCRange -> UTCTime -> [UTCTime ]
84+ shrinkWithinUTCRange ur@ UTCRange {.. } x = refine $ QC. shrink x
85+ where
86+ refine = case (ur_lo, ur_hi) of
87+ (Nothing , Nothing ) -> id -- avoid filter altogether
88+ _ -> filter (`inUTCRange` ur)
89+
90+ -- | Attempts to shrink an input 'APINode' within the given 'IntRange'.
91+ -- We can generate code that typechecks only if we have a 'BTint', otherwise we don't shrink.
92+ shrinkIntRange :: IntRange -> SpecNewtype -> ExpQ
93+ shrinkIntRange ir sn = do
94+ x <- newName " x"
95+ lamE [varP x] $
96+ if snType sn == BTint
97+ then [e | coerce (shrinkWithinIntRange $(lift ir) $ coerce $(varE x)) |]
98+ else noShrink
10199
102100noShrink :: ExpQ
103101noShrink = [e | \_ -> [] |]
@@ -106,21 +104,13 @@ noShrink = [e| \_ -> [] |]
106104-- specifies an 'ur_lo', then we shrink such that the resulting shrunk values still satisfies
107105-- the min constrain of the range (i.e. we never generate values /smaller/ than 'ur_lo').
108106-- Same proviso as for 'shrinkIntRange', it makes sense to apply the filter only for 'BTutc'.
109- shrinkUTCRange :: UTCRange -> ToolSettings -> APINode -> SpecNewtype -> ExpQ
110- shrinkUTCRange ur ts an sn = case ur_lo ur of
111- Nothing -> shrinkNewtype ts an sn
112- Just lowerBound -> do
113- x <- newName " x"
114- y <- newName " y"
115- lamE [varP x] $
116- caseE (varE x) [
117- match (nodeNewtypeConP ts an sn [varP y])
118- (normalB $ do
119- if snType sn == BTutc
120- then [| map $ (nodeNewtypeConE ts an sn) $ filter (>= $ (liftUTC lowerBound)) $ (QC. shrink $ (varE y)) | ]
121- else noShrink
122- ) []
123- ]
107+ shrinkUTCRange :: UTCRange -> SpecNewtype -> ExpQ
108+ shrinkUTCRange ur sn = do
109+ x <- newName " x"
110+ lamE [varP x] $
111+ if snType sn == BTutc
112+ then [e | coerce (shrinkWithinUTCRange $(lift ur) $ coerce $(varE x)) |]
113+ else noShrink
124114
125115-- | Generate an 'Arbitrary' instance for a record:
126116--
0 commit comments