@@ -11,6 +11,7 @@ import Data.API.Tools.Combinators
1111import Data.API.Tools.Datatypes
1212import Data.API.Types
1313
14+ import GHC.Generics
1415import Control.Applicative
1516import Data.Monoid
1617import Data.Time
@@ -25,6 +26,26 @@ import Prelude
2526quickCheckTool :: APITool
2627quickCheckTool = apiNodeTool $ apiSpecTool gen_sn_ab gen_sr_ab gen_su_ab gen_se_ab mempty
2728
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.
34+ mkArbitraryInstance :: ToolSettings
35+ -> TypeQ
36+ -> ExpQ
37+ -- ^ The body of the 'arbitrary' method.
38+ -> 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 | [] |]
45+ optionalInstanceD ts ''QC. Arbitrary [typeQ]
46+ [ simpleD 'arbitrary arbitraryBody
47+ , simpleD 'shrink shrinkBody
48+ ]
2849
2950-- | Generate an 'Arbitrary' instance for a newtype that respects its
3051-- filter. We don't try to generate arbitrary data matching a regular
@@ -40,10 +61,8 @@ gen_sn_ab = mkTool $ \ ts (an, sn) -> case snFilter sn of
4061 Just (FtrUTC ur) -> mk_instance ts an sn [e | arbitraryUTCRange ur |]
4162 Just (FtrStrg _) -> return []
4263 where
43- mk_instance ts an sn arb = optionalInstanceD ts ''Arbitrary [nodeRepT an]
44- [ simpleD 'arbitrary [e | fmap $(nodeNewtypeConE ts an sn) $arb |]
45- , simpleD 'shrink [e | genericShrink |]
46- ]
64+ mk_instance ts an sn arb =
65+ mkArbitraryInstance ts (nodeRepT an) [e | fmap $(nodeNewtypeConE ts an sn) $arb |]
4766
4867
4968-- | Generate an 'Arbitrary' instance for a record:
@@ -53,10 +72,7 @@ gen_sn_ab = mkTool $ \ ts (an, sn) -> case snFilter sn of
5372-- > shrink = genericShrink
5473
5574gen_sr_ab :: Tool (APINode , SpecRecord )
56- gen_sr_ab = mkTool $ \ ts (an, sr) -> optionalInstanceD ts ''QC. Arbitrary [nodeRepT an]
57- [ simpleD 'arbitrary (bdy an sr)
58- , simpleD 'shrink [e | genericShrink |]
59- ]
75+ gen_sr_ab = mkTool $ \ ts (an, sr) -> mkArbitraryInstance ts (nodeRepT an) (bdy an sr)
6076 where
6177 -- Reduce size of fields to avoid generating massive test data
6278 -- by giving an arbitrary implementation like this:
@@ -74,10 +90,7 @@ gen_sr_ab = mkTool $ \ ts (an, sr) -> optionalInstanceD ts ''QC.Arbitrary [nodeR
7490-- > arbitrary = oneOf [ fmap Bar arbitrary, fmap Baz arbitrary ]
7591
7692gen_su_ab :: Tool (APINode , SpecUnion )
77- gen_su_ab = mkTool $ \ ts (an, su) -> optionalInstanceD ts ''QC. Arbitrary [nodeRepT an]
78- [ simpleD 'arbitrary (bdy an su)
79- , simpleD 'shrink [e | genericShrink |]
80- ]
93+ gen_su_ab = mkTool $ \ ts (an, su) -> mkArbitraryInstance ts (nodeRepT an) (bdy an su)
8194 where
8295 bdy an su | null (suFields su) = nodeConE an
8396 | otherwise = [e | oneof $(listE alts) |]
@@ -92,10 +105,7 @@ gen_su_ab = mkTool $ \ ts (an, su) -> optionalInstanceD ts ''QC.Arbitrary [nodeR
92105-- > arbitrary = elements [Bar, Baz]
93106
94107gen_se_ab :: Tool (APINode , SpecEnum )
95- gen_se_ab = mkTool $ \ ts (an, se) -> optionalInstanceD ts ''QC. Arbitrary [nodeRepT an]
96- [ simpleD 'arbitrary (bdy an se)
97- , simpleD 'shrink [e | genericShrink |]
98- ]
108+ gen_se_ab = mkTool $ \ ts (an, se) -> mkArbitraryInstance ts (nodeRepT an) (bdy an se)
99109 where
100110 bdy an se | null ks = nodeConE an
101111 | otherwise = varE 'elements `appE` listE ks
0 commit comments