@@ -1,5 +1,7 @@
1{-# LANGUAGE
1{-# LANGUAGE
3 , OverloadedLabels
5 , OverloadedLabels
4 , OverloadedStrings
6 , OverloadedStrings
5 , TypeApplications
7 , TypeApplications
@@ -11,6 +13,8 @@ module Property (propertyTests) where
11import Control.Monad.Trans
13import Control.Monad.Trans
12import Data.ByteString (ByteString)
14import Data.ByteString (ByteString)
13import Data.Scientific (fromFloatDigits)
15import Data.Scientific (fromFloatDigits)
16+import Data.Fixed (Fixed(MkFixed), Micro, Pico)
14import Squeal.PostgreSQL hiding (check, defaultMain)
18import Squeal.PostgreSQL hiding (check, defaultMain)
15import Hedgehog hiding (Range)
19import Hedgehog hiding (Range)
16import qualified Hedgehog.Gen as Gen
20import qualified Hedgehog.Gen as Gen
@@ -19,28 +23,111 @@ import qualified Hedgehog.Range as Range
20propertyTests :: IO ()
24propertyTests :: IO ()
21propertyTests = Main.defaultMain
25propertyTests = Main.defaultMain
22− [ roundtrip int2 (Gen.int16 Range.exponentialBounded)
26+ [ roundtrip int2 genInt16 id
23− , roundtrip int4 (Gen.int32 Range.exponentialBounded)
27+ , roundtrip int4 genInt32 id
24− , roundtrip int8 (Gen.int64 Range.exponentialBounded)
28+ , roundtrip int8 genInt64 id
25− , roundtrip bool Gen.bool
29+ , roundtrip bool Gen.bool id
26− , roundtrip numeric (scientific (Range.exponentialFloatFrom 0 (-1E9) 1E9))
30+ , roundtrip numeric genScientific id
27− , roundtrip float4 (Gen.float (Range.exponentialFloatFrom 0 (-1E9) 1E9))
31+ , roundtrip float4 genFloat id
28− , roundtrip float8 (Gen.double (Range.exponentialFloatFrom 0 (-1E308) 1E308))
32+ , roundtrip float8 genDouble id
33+ , roundtrip time genTimeOfDay normalizeTimeOfDay
34+ -- , roundtrip timetz genTimeWithZone normalizeTimeWithZone
35+ , roundtrip timestamp genLocalTime normalizeLocalTime
36+ , roundtrip timestamptz genUTCTime id
37+ , roundtrip date genDay id
38+ , roundtrip interval genDiffTime id
39+ , roundtrip int4range (genRange genInt32) normalizeIntRange
40+ , roundtrip int8range (genRange genInt64) normalizeIntRange
41+ , roundtrip numrange (genRange genScientific) id
42+ , roundtrip tsrange (genRange genLocalTime) (fmap normalizeLocalTime)
43+ , roundtrip tstzrange (genRange genUTCTime) id
44+ , roundtrip daterange (genRange genDay) normalizeIntRange
47+ genInt16 = Gen.int16 Range.exponentialBounded
48+ genInt32 = Gen.int32 Range.exponentialBounded
49+ genInt64 = Gen.int64 Range.exponentialBounded
31− scientific = fmap fromFloatDigits . Gen.float
50+ genScientific = fromFloatDigits <$>
51+ Gen.float (Range.exponentialFloatFrom 0 (-1E9) 1E9)
52+ genFloat = Gen.float (Range.exponentialFloatFrom 0 (-1E9) 1E9)
53+ genDouble = Gen.double (Range.exponentialFloatFrom 0 (-1E308) 1E308)
56+ ub <- Gen.filter (lb <=) gen
59+ , NonEmpty (Closed lb) (Closed ub)
60+ , NonEmpty (Closed lb) (Open ub)
61+ , NonEmpty (Closed lb) Infinite
62+ , NonEmpty (Open lb) (Closed ub)
63+ , NonEmpty (Open lb) (Open ub)
64+ , NonEmpty (Open lb) Infinite
65+ , NonEmpty Infinite (Closed ub)
66+ , NonEmpty Infinite (Open ub)
67+ , NonEmpty Infinite Infinite ]
69+ y <- toInteger <$> Gen.int (Range.constant 2000 2019)
70+ m <- Gen.int (Range.constant 1 12)
71+ d <- Gen.int (Range.constant 1 28)
72+ return $ fromGregorian y m d
73+ genDiffTime = secondsToDiffTime . toInteger <$>
74+ Gen.int (Range.constant 0 86401)
75+ genUTCTime = UTCTime <$> genDay <*> genDiffTime
77+ h <- Gen.int (Range.constant 0 23)
78+ m <- Gen.int (Range.constant 0 59)
79+ s <- MkFixed . toInteger <$> Gen.int (Range.constant 0 59)
80+ return $ TimeOfDay h m s
81+ genLocalTime = LocalTime <$> genDay <*> genTimeOfDay
82+ genTimeZone = Gen.element $ map (read @TimeZone)
83+ [ "UTC", "UT", "GMT", "EST", "EDT", "CST"
84+ , "CDT", "MST", "MDT", "PST", "PDT" ]
85+ genTimeWithZone = (,) <$> genTimeOfDay <*> genTimeZone
33connectionString :: ByteString
87connectionString :: ByteString
34connectionString = "host=localhost port=5432 dbname=exampledb"
88connectionString = "host=localhost port=5432 dbname=exampledb"
90+normalizeIntRange :: (Enum int, Ord int) => Range int -> Range int
91+normalizeIntRange = \case
97+ in if emptyNormalized l' u' then Empty else NonEmpty l' u'
100+ Open l -> Closed (succ l)
101+ normalized -> normalized
103+ Closed u -> Open (succ u)
104+ normalized -> normalized
105+ emptyNormalized (Closed l) (Open u) = l >= u
106+ emptyNormalized _ _ = False
108+normalizeTimeOfDay :: TimeOfDay -> TimeOfDay
109+normalizeTimeOfDay (TimeOfDay h m s) = TimeOfDay h m
110+ . fromRational @Pico
112+ . fromRational @Micro
116+normalizeLocalTime :: LocalTime -> LocalTime
117+normalizeLocalTime (LocalTime d t) = LocalTime d (normalizeTimeOfDay t)
119+normalizeTimeWithZone :: (TimeOfDay, TimeZone) -> (TimeOfDay, TimeZone)
120+normalizeTimeWithZone (t, z) = (normalizeTimeOfDay t, z)
37 :: (ToParam x ty, FromValue ty x, Show x, Eq x)
123 :: (ToParam x ty, FromValue ty x, Show x, Eq x)
38 => TypeExpression schemas ('NotNull ty)
124 => TypeExpression schemas ('NotNull ty)
40 -> IO Bool
127 -> IO Bool
41−roundtrip ty gen = check . property $ do
128+roundtrip ty gen norm = check . property $ do
42 x <- forAll gen
129 x <- forAll gen
43 Just (Only y) <- lift . withConnection connectionString $
130 Just (Only y) <- lift . withConnection connectionString $
44 firstRow =<< runQueryParams
131 firstRow =<< runQueryParams
45 (values_ (parameter @1 ty `as` #fromOnly)) (Only x)
132 (values_ (parameter @1 ty `as` #fromOnly)) (Only x)
46− x === y
133+ norm x === y