-
Notifications
You must be signed in to change notification settings - Fork 303
Expand file tree
/
Copy pathDataTypeTest.hs
More file actions
130 lines (117 loc) · 4.29 KB
/
Copy pathDataTypeTest.hs
File metadata and controls
130 lines (117 loc) · 4.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wno-unused-top-binds #-}
module DataTypeTest
( specsWith
, dataTypeMigrate
, roundTime
, roundUTCTime
) where
import Control.Applicative (liftA2)
import qualified Data.ByteString as BS
import Data.Fixed (Pico)
import Data.Foldable (for_)
import Data.IntMap (IntMap)
import qualified Data.Text as T
import Data.Time (Day, UTCTime (..), TimeOfDay, timeToTimeOfDay, timeOfDayToTime)
import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds, posixSecondsToUTCTime)
import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)
import Test.QuickCheck.Gen (Gen(..))
import Test.QuickCheck.Instances ()
import Test.QuickCheck.Random (newQCGen)
import Database.Persist.TH
import Init
type Tuple a b = (a, b)
-- Test lower case names
share [mkPersist persistSettings, mkMigrate "dataTypeMigrate"] [persistLowerCase|
DataTypeTable no-json
text Text
textMaxLen Text maxlen=100
bytes ByteString
bytesTextTuple (Tuple ByteString Text)
bytesMaxLen ByteString maxlen=100
int Int
intList [Int]
intMap (IntMap Int)
double Double
bool Bool
day Day
utc UTCTime
|]
cleanDB' :: (MonadIO m) => SqlPersistT m ()
cleanDB' = deleteWhere ([] :: [Filter DataTypeTable])
roundFn :: RealFrac a => a -> Integer
roundFn = round
roundTime :: TimeOfDay -> TimeOfDay
roundTime t = timeToTimeOfDay $ fromIntegral $ roundFn $ timeOfDayToTime t
roundUTCTime :: UTCTime -> UTCTime
roundUTCTime t =
posixSecondsToUTCTime $ fromIntegral $ roundFn $ utcTimeToPOSIXSeconds t
randomValues :: Arbitrary a => Int -> IO [a]
randomValues i = do
gs <- replicateM i newQCGen
return $ zipWith (unGen arbitrary) gs [0..]
instance Arbitrary DataTypeTable where
arbitrary = DataTypeTable
<$> arbText -- text
<*> (T.take 100 <$> arbText) -- textManLen
<*> arbitrary -- bytes
<*> liftA2 (,) arbitrary arbText -- bytesTextTuple
<*> (BS.take 100 <$> arbitrary) -- bytesMaxLen
<*> arbitrary -- int
<*> arbitrary -- intList
<*> arbitrary -- intMap
<*> arbitrary -- double
<*> arbitrary -- bool
<*> arbitrary -- day
<*> (truncateUTCTime =<< arbitrary) -- utc
specsWith
:: forall db backend m entity.
( db ~ ReaderT backend m
, backend ~ SqlBackend
, PersistEntity entity
, PersistEntityBackend entity ~ SqlBackend
, Arbitrary entity
, MonadFail m
, MonadIO m
)
=> (db () -> IO ())
-- ^ DB Runner
-> Maybe (db [Text])
-- ^ Optional migrations to run
-> [TestFn entity]
-- ^ List of entity fields to test
-> [(String, entity -> Pico)]
-- ^ List of pico fields to test
-> (entity -> Double)
-> Spec
specsWith runDb mmigration checks apprxChecks doubleFn = describe "data type specs" $
it "handles all types" $ asIO $ runDb $ do
_ <- sequence_ mmigration
-- Ensure reading the data from the database works...
_ <- sequence_ mmigration
cleanDB'
rvals <- liftIO $ randomValues 1000
for_ rvals $ \x -> do
key <- insert x
Just y <- get key
liftIO $ do
let check :: (Eq a, Show a) => String -> (entity -> a) -> IO ()
check s f = (s, f x) @=? (s, f y)
-- Check floating-point near equality
let check' :: (Fractional p, Show p, Real p) => String -> (entity -> p) -> IO ()
check' s f
| abs (f x - f y) < 0.000001 = return ()
| otherwise = (s, f x) @=? (s, f y)
-- Check individual fields for better error messages
for_ checks $ \(TestFn msg f) -> check msg f
for_ apprxChecks $ \(msg, f) -> check' msg f
-- Do a special check for Double since it may
-- lose precision when serialized.
when (getDoubleDiff (doubleFn x) (doubleFn y) > 1e-14) $
check "double" doubleFn
where
normDouble :: Double -> Double
normDouble x | abs x > 1 = x / 10 ^ (truncate (logBase 10 (abs x)) :: Integer)
| otherwise = x
getDoubleDiff x y = abs (normDouble x - normDouble y)