api-tools-0.8.0.1: src/Data/API/Tools/QuickCheck.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Data.API.Tools.QuickCheck
( quickCheckTool
) where
import Data.API.TH
import Data.API.Tools.Combinators
import Data.API.Tools.Datatypes
import Data.API.Types
import Data.API.Utils
import Control.Applicative
import Data.Monoid
import Data.Time
import Language.Haskell.TH
import Safe
import Test.QuickCheck as QC
import Prelude
-- | Tool to generate 'Arbitrary' instances for generated types.
quickCheckTool :: APITool
quickCheckTool = apiNodeTool $ apiSpecTool gen_sn_ab gen_sr_ab gen_su_ab gen_se_ab mempty
-- | Generate an 'Arbitrary' instance for a newtype that respects its
-- filter. We don't try to generate arbitrary data matching a regular
-- expression, however: instances must be supplied manually. When
-- generating arbitrary integers, use 'arbitraryBoundedIntegral'
-- rather than 'arbitrary' (the latter tends to generate non-unique
-- values).
gen_sn_ab :: Tool (APINode, SpecNewtype)
gen_sn_ab = mkTool $ \ ts (an, sn) -> case snFilter sn of
Nothing | snType sn == BTint -> mk_instance ts an sn [e| QC.arbitraryBoundedIntegral |]
| otherwise -> mk_instance ts an sn [e| arbitrary |]
Just (FtrIntg ir) -> mk_instance ts an sn [e| arbitraryIntRange ir |]
Just (FtrUTC ur) -> mk_instance ts an sn [e| arbitraryUTCRange ur |]
Just (FtrStrg _) -> return []
where
mk_instance ts an sn arb = optionalInstanceD ts ''Arbitrary [nodeRepT an]
[simpleD 'arbitrary [e| fmap $(nodeNewtypeConE ts an sn) $arb |]]
-- | Generate an 'Arbitrary' instance for a record:
--
-- > instance Arbitrary Foo where
-- > arbitrary = sized $ \ x -> Foo <$> resize (x `div` 2) arbitrary <*> ... <*> resize (x `div` 2) arbitrary
gen_sr_ab :: Tool (APINode, SpecRecord)
gen_sr_ab = mkTool $ \ ts (an, sr) -> optionalInstanceD ts ''QC.Arbitrary [nodeRepT an]
[simpleD 'arbitrary (bdy an sr)]
where
-- Reduce size of fields to avoid generating massive test data
-- by giving an arbitrary implementation like this:
-- sized (\ x -> JobSpecId <$> resize (x `div` 2) arbitrary <*> ...)
bdy an sr = do x <- newName "x"
appE (varE 'QC.sized) $ lamE [varP x] $
applicativeE (nodeConE an) $
replicate (length $ srFields sr) $
[e| QC.resize ($(varE x) `div` 2) arbitrary |]
-- | Generate an 'Arbitrary' instance for a union:
--
-- > instance Arbitrary Foo where
-- > arbitrary = oneOf [ fmap Bar arbitrary, fmap Baz arbitrary ]
gen_su_ab :: Tool (APINode, SpecUnion)
gen_su_ab = mkTool $ \ ts (an, su) -> optionalInstanceD ts ''QC.Arbitrary [nodeRepT an]
[simpleD 'arbitrary (bdy an su)]
where
bdy an su | null (suFields su) = nodeConE an
| otherwise = [e| oneof $(listE alts) |]
where
alts = [ [e| fmap $(nodeAltConE an k) arbitrary |]
| (k, _) <- suFields su ]
-- | Generate an 'Arbitrary' instance for an enumeration:
--
-- > instance Arbitrary Foo where
-- > arbitrary = elements [Bar, Baz]
gen_se_ab :: Tool (APINode, SpecEnum)
gen_se_ab = mkTool $ \ ts (an, se) -> optionalInstanceD ts ''QC.Arbitrary [nodeRepT an]
[simpleD 'arbitrary (bdy an se)]
where
bdy an se | null ks = nodeConE an
| otherwise = varE 'elements `appE` listE ks
where
ks = map (nodeAltConE an . fst) $ seAlts se
-- | Generate an arbitrary 'Int' in a given range.
arbitraryIntRange :: IntRange -> Gen Int
arbitraryIntRange (IntRange (Just lo) Nothing ) = QC.choose (lo, maxBound)
arbitraryIntRange (IntRange Nothing (Just hi)) = QC.choose (minBound, hi)
arbitraryIntRange (IntRange (Just lo) (Just hi)) = QC.choose (lo, hi)
arbitraryIntRange (IntRange Nothing Nothing ) = QC.arbitrary
-- | Generate an arbitrary 'UTCTime' in a given range.
-- TODO: we might want to generate a broader range of sample times,
-- rather than just the extrema.
arbitraryUTCRange :: UTCRange -> Gen UTCTime
arbitraryUTCRange (UTCRange (Just lo) Nothing ) = pure lo
arbitraryUTCRange (UTCRange Nothing (Just hi)) = pure hi
arbitraryUTCRange (UTCRange (Just lo) (Just hi)) = QC.elements [lo, hi]
arbitraryUTCRange (UTCRange Nothing Nothing ) = QC.arbitrary
-- TODO: use a more arbitrary instance (quickcheck-instances?)
-- (in particular, there are no subsecond-resolution times here)
instance QC.Arbitrary UTCTime where
arbitrary = QC.elements
[ mk "2010-01-01T00:00:00Z"
, mk "2013-05-27T19:13:50Z"
, mk "2011-07-20T22:04:00Z"
, mk "2012-02-02T15:45:11Z"
, mk "2009-11-12T20:57:54Z"
, mk "2000-10-28T21:03:24Z"
, mk "1965-03-10T09:23:01Z"
-- , mk "1965-03-10T09:23:01.001Z"
-- , mk "1965-03-10T09:23:01.000001Z"
]
where
mk = fromJustNote lab . parseUTC'
lab = "Data.API.Tools.QuickCheck.Arbitrary-UTCTime"