File: Interval.hs

package info (click to toggle)
haskell-postgresql-simple 0.7.0.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 564 kB
  • sloc: haskell: 6,438; makefile: 2
file content (176 lines) | stat: -rw-r--r-- 4,921 bytes parent folder | download | duplicates (2)
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
{-# LANGUAGE QuasiQuotes #-}

{-

Testing strategies:

fromString . toString == id           ** Todo?

toString . fromString == almost id    ** Todo?

postgresql -> haskell -> postgresql   *  Done

haskell -> postgresql -> haskell      ** Todo?

But still,  what we really want to establish is that the two values
correspond;  for example,  a conversion that consistently added hour
when printed to a string and subtracted an hour when parsed from string
would still pass these tests.


Right now,  we are checking that 1400+ timestamps in the range of 1860 to
2060 round trip from postgresql to haskell and back in 5 different timezones.
In addition to UTC,  the four timezones were selected so that 2 have a positive
offset,  and 2 have a negative offset,   and that 2 have an offset of a
whole number of hours,  while the other two do not.

It may be worth adding a few more timezones to ensure better test coverage.

We are checking a handful of selected timestamps to ensure we hit
various corner-cases in the code,  in addition to 1400 timestamps randomly
generated with granularity of seconds down to microseconds in powers of ten.

-}

module Interval (testInterval) where

import Common
import Control.Monad(forM_, replicateM_)
import Data.Time.Compat
import Data.Time.LocalTime.Compat (CalendarDiffTime(..))
import Data.ByteString(ByteString)
import Database.PostgreSQL.Simple.SqlQQ

data IntervalTestCase = IntervalTestCase
  { label :: String
  , inputMonths :: Integer
  , inputSeconds :: NominalDiffTime
  , asText :: String
  }
  deriving (Eq, Show)

testInterval :: TestEnv -> Assertion
testInterval env@TestEnv{..} = do

  initializeTable env

  let milliseconds = 0.001
      seconds = 1
      minutes = 60 * seconds
      hours = 60 * minutes
      days = 24 * hours
      weeks = 7 * days
      months = 1
      years = 12 * months

  mapM (checkRoundTrip env)
    [ IntervalTestCase
      { label = "zero"
      , inputMonths = 0
      , inputSeconds = 0
      , asText = "PT0"
      }
    , IntervalTestCase
      { label = "1 year"
      , inputMonths = 1 * years
      , inputSeconds = 0
      , asText = "P1Y"
      }
    , IntervalTestCase
      { label = "2 months"
      , inputMonths = 2 * months
      , inputSeconds = 0
      , asText = "P2M"
      }
    , IntervalTestCase
      { label = "3 weeks"
      , inputMonths = 0
      , inputSeconds = 3 * weeks
      , asText = "P3W"
      }
    , IntervalTestCase
      { label = "4 days"
      , inputMonths = 0
      , inputSeconds = 4 * days
      , asText = "P4D"
      }
    , IntervalTestCase
      { label = "5 hours"
      , inputMonths = 0
      , inputSeconds = 5 * hours
      , asText = "PT5H"
      }
    , IntervalTestCase
      { label = "6 minutes"
      , inputMonths = 0
      , inputSeconds = 6 * minutes
      , asText = "PT6M"
      }
    , IntervalTestCase
      { label = "7 seconds"
      , inputMonths = 0
      , inputSeconds = 7 * seconds
      , asText = "PT7S"
      }
    , IntervalTestCase
      { label = "8 milliseconds"
      , inputMonths = 0
      , inputSeconds = 8 * milliseconds
      , asText = "PT0.008S"
      }
    , IntervalTestCase
      { label = "combination of intervals (day-size or bigger)"
      , inputMonths = 2 * years + 4 * months
      , inputSeconds = 3 * weeks + 5 * days
      , asText = "P2Y4M3W5D"
      }
    , IntervalTestCase
      { label = "combination of intervals (smaller than day-size)"
      , inputMonths = 0
      , inputSeconds = 18 * hours + 56 * minutes + 23 * seconds + 563 * milliseconds
      , asText = "PT18H56M23.563S"
      }
    , IntervalTestCase
      { label = "full combination of intervals"
      , inputMonths = 2 * years + 4 * months
      , inputSeconds = 3 * weeks + 5 * days + 18 * hours + 56 * minutes + 23 * seconds + 563 * milliseconds
      , asText = "P2Y4M3W5DT18H56M23.563S"
      }
    ]

  return ()

initializeTable :: TestEnv -> IO ()
initializeTable TestEnv{..} = withTransaction conn $ do
  execute_ conn
     [sql| CREATE TEMPORARY TABLE testinterval
             ( id serial, sample interval, PRIMARY KEY(id) ) |]

  return ()

checkRoundTrip :: TestEnv -> IntervalTestCase -> IO ()
checkRoundTrip TestEnv{..} IntervalTestCase{..} = do

  let input = CalendarDiffTime
        { ctMonths = inputMonths
        , ctTime = inputSeconds
        }

  [(returnedId :: Int, output :: CalendarDiffTime)] <- query conn [sql|
      INSERT INTO testinterval (sample)
      VALUES (?)
      RETURNING id, sample
    |] (Only input)

  assertBool ("CalendarDiffTime did not round-trip from Haskell to SQL and back (" ++ label ++ ")") $
      output == input

  [(Only isExpectedIso)] <- query conn [sql|
      SELECT sample = (?)::interval
      FROM testinterval
      WHERE id = ?
    |] (asText, returnedId)

  assertBool ("CalendarDiffTime inserted did not match ISO8601 equivalent \"" ++ asText ++ "\". (" ++ label ++ ")")
    isExpectedIso