@@ -11,13 +11,12 @@ import Data.API.Tools.Combinators
1111import Data.API.Tools.Datatypes
1212import Data.API.Types
1313
14- import GHC.Generics
1514import Control.Applicative
1615import Data.Monoid
1716import Data.Time
1817import Language.Haskell.TH
19- import Test.QuickCheck as QC
2018import Prelude
19+ import Test.QuickCheck as QC
2120
2221
2322-- | Tool to generate 'Arbitrary' instances for generated types. This tool generates
@@ -26,22 +25,15 @@ import Prelude
2625quickCheckTool :: APITool
2726quickCheckTool = apiNodeTool $ apiSpecTool gen_sn_ab gen_sr_ab gen_su_ab gen_se_ab mempty
2827
29- -- | Helper to create an 'Arbitrary' implementation. It will check if we have a 'Generic'
30- -- instance for the underlying type and, if we have, we will implement 'shrink' in terms of
31- -- 'genericShrink', otherwise we will just alias it to '[]' (i.e. a no-op). This avoids
32- -- imposing to the caller a mandatory 'Generic' instance on the type when using this tool,
33- -- but it will get them a \"shrinker for free\" if they define a 'Generic' instance.
28+ -- | Helper to create an 'Arbitrary' implementation.
3429mkArbitraryInstance :: ToolSettings
3530 -> TypeQ
3631 -> ExpQ
3732 -- ^ The body of the 'arbitrary' method.
33+ -> ExpQ
34+ -- ^ The body of the 'shrink' method.
3835 -> Q [Dec ]
39- mkArbitraryInstance ts typeQ arbitraryBody = do
40- tq <- sequence [typeQ]
41- hasGeneric <- isInstance ''Generic tq
42- let shrinkBody = case hasGeneric of
43- True -> [e | genericShrink |]
44- False -> [e | pure [] |]
36+ mkArbitraryInstance ts typeQ arbitraryBody shrinkBody = do
4537 optionalInstanceD ts ''QC. Arbitrary [typeQ]
4638 [ simpleD 'arbitrary arbitraryBody
4739 , simpleD 'shrink shrinkBody
@@ -55,24 +47,38 @@ mkArbitraryInstance ts typeQ arbitraryBody = do
5547-- values).
5648gen_sn_ab :: Tool (APINode , SpecNewtype )
5749gen_sn_ab = mkTool $ \ ts (an, sn) -> case snFilter sn of
58- Nothing | snType sn == BTint -> mk_instance ts an sn [e | QC.arbitraryBoundedIntegral |]
59- | otherwise -> mk_instance ts an sn [e | arbitrary |]
60- Just (FtrIntg ir) -> mk_instance ts an sn [e | arbitraryIntRange ir |]
61- Just (FtrUTC ur) -> mk_instance ts an sn [e | arbitraryUTCRange ur |]
50+ Nothing | snType sn == BTint -> mk_instance ts an sn [e | QC.arbitraryBoundedIntegral |] (shrinkNewtype ts an sn)
51+ | otherwise -> mk_instance ts an sn [e | arbitrary |] (shrinkNewtype ts an sn)
52+ Just (FtrIntg ir) ->
53+ mk_instance ts an sn [e | arbitraryIntRange ir |] (shrinkNewtype ts an sn)
54+ Just (FtrUTC ur) ->
55+ mk_instance ts an sn [e | arbitraryUTCRange ur |] (shrinkNewtype ts an sn)
6256 Just (FtrStrg _) -> return []
6357 where
6458 mk_instance ts an sn arb =
6559 mkArbitraryInstance ts (nodeRepT an) [e | fmap $(nodeNewtypeConE ts an sn) $arb |]
6660
61+ -- shrinking a newtype means calling shrink and repack the newtype.
62+ -- Example:
63+ -- shrink = \x -> case x of { Foo y -> map Foo (shrink y) }
64+ shrinkNewtype ts an sn = do
65+ x <- newName " x"
66+ y <- newName " y"
67+ lamE [varP x] $
68+ caseE (varE x) [
69+ match (nodeNewtypeConP ts an sn [varP y])
70+ (normalB [| map $ (nodeNewtypeConE ts an sn) (QC. shrink $ (varE y)) | ])
71+ []
72+ ]
6773
6874-- | Generate an 'Arbitrary' instance for a record:
6975--
7076-- > instance Arbitrary Foo where
7177-- > arbitrary = sized $ \ x -> Foo <$> resize (x `div` 2) arbitrary <*> ... <*> resize (x `div` 2) arbitrary
72- -- > shrink = genericShrink
78+ -- > shrink = (TH-derived shrinker)
7379
7480gen_sr_ab :: Tool (APINode , SpecRecord )
75- gen_sr_ab = mkTool $ \ ts (an, sr) -> mkArbitraryInstance ts (nodeRepT an) (bdy an sr)
81+ gen_sr_ab = mkTool $ \ ts (an, sr) -> mkArbitraryInstance ts (nodeRepT an) (bdy an sr) [ e | \_ -> [] |]
7682 where
7783 -- Reduce size of fields to avoid generating massive test data
7884 -- by giving an arbitrary implementation like this:
@@ -83,35 +89,47 @@ gen_sr_ab = mkTool $ \ ts (an, sr) -> mkArbitraryInstance ts (nodeRepT an) (bdy
8389 replicate (length $ srFields sr) $
8490 [e | QC.resize ($(varE x) `div` 2) arbitrary |]
8591
86-
8792-- | Generate an 'Arbitrary' instance for a union:
8893--
8994-- > instance Arbitrary Foo where
9095-- > arbitrary = oneOf [ fmap Bar arbitrary, fmap Baz arbitrary ]
9196
9297gen_su_ab :: Tool (APINode , SpecUnion )
93- gen_su_ab = mkTool $ \ ts (an, su) -> mkArbitraryInstance ts (nodeRepT an) (bdy an su)
98+ gen_su_ab = mkTool $ \ ts (an, su) -> mkArbitraryInstance ts (nodeRepT an) (bdy an su) (shrinkUnion an su)
9499 where
95100 bdy an su | null (suFields su) = nodeConE an
96101 | otherwise = [e | oneof $(listE alts) |]
97102 where
98103 alts = [ [e | fmap $(nodeAltConE an k) arbitrary |]
99104 | (k, _) <- suFields su ]
100105
106+ -- For a union, we shrink the individual wrappers.
107+ shrinkUnion :: APINode -> SpecUnion -> ExpQ
108+ shrinkUnion an su = do
109+ x <- newName " x"
110+ y <- newName " y"
111+ lamE [varP x] $ caseE (varE x) (map (shrink_alt y) (suFields su))
112+ where
113+ shrink_alt y (fn,_) =
114+ match (nodeAltConP an fn [varP y])
115+ (normalB [| map $ (nodeAltConE an fn) (QC. shrink $ (varE y)) | ])
116+ []
101117
102118-- | Generate an 'Arbitrary' instance for an enumeration:
103119--
104120-- > instance Arbitrary Foo where
105121-- > arbitrary = elements [Bar, Baz]
106122
107123gen_se_ab :: Tool (APINode , SpecEnum )
108- gen_se_ab = mkTool $ \ ts (an, se) -> mkArbitraryInstance ts (nodeRepT an) (bdy an se)
124+ gen_se_ab = mkTool $ \ ts (an, se) -> mkArbitraryInstance ts (nodeRepT an) (bdy an se) noShrink
109125 where
110126 bdy an se | null ks = nodeConE an
111127 | otherwise = varE 'elements `appE` listE ks
112128 where
113129 ks = map (nodeAltConE an . fst ) $ seAlts se
114130
131+ noShrink :: ExpQ
132+ noShrink = [e | \_ -> [] |]
115133
116134-- | Generate an arbitrary 'Int' in a given range.
117135arbitraryIntRange :: IntRange -> Gen Int
0 commit comments