@@ -3,10 +3,19 @@
3{-# LANGUAGE DeriveDataTypeable #-}
3{-# LANGUAGE DeriveDataTypeable #-}
4{-# LANGUAGE DoAndIfThenElse #-}
4{-# LANGUAGE DoAndIfThenElse #-}
5{-# LANGUAGE ScopedTypeVariables #-}
5{-# LANGUAGE ScopedTypeVariables #-}
6+#if __GLASGOW_HASKELL__ >= 806
7+{-# LANGUAGE DerivingStrategies #-}
8+{-# LANGUAGE DerivingVia #-}
9+{-# LANGUAGE DeriveAnyClass #-}
11+module Main (main) where
6import Common
13import Common
7import Database.PostgreSQL.Simple.Copy
14import Database.PostgreSQL.Simple.Copy
15+import Database.PostgreSQL.Simple.ToField (ToField)
8import Database.PostgreSQL.Simple.FromField (FromField)
16import Database.PostgreSQL.Simple.FromField (FromField)
9import Database.PostgreSQL.Simple.HStore
17import Database.PostgreSQL.Simple.HStore
18+import Database.PostgreSQL.Simple.Newtypes
10import Database.PostgreSQL.Simple.Internal (breakOnSingleQuestionMark)
19import Database.PostgreSQL.Simple.Internal (breakOnSingleQuestionMark)
11import Database.PostgreSQL.Simple.Types(Query(..),Values(..), PGArray(..))
20import Database.PostgreSQL.Simple.Types(Query(..),Values(..), PGArray(..))
12import qualified Database.PostgreSQL.Simple.Transaction as ST
21import qualified Database.PostgreSQL.Simple.Transaction as ST
@@ -15,6 +24,7 @@ import Control.Applicative
15import Control.Exception as E
24import Control.Exception as E
16import Control.Monad
25import Control.Monad
17import Data.Char
26import Data.Char
27+import Data.Foldable (toList)
18import Data.List (concat, sort)
28import Data.List (concat, sort)
19import Data.IORef
29import Data.IORef
20import Data.Monoid ((<>))
30import Data.Monoid ((<>))
@@ -36,7 +46,7 @@ import qualified Data.Text.Encoding as T
36import qualified Data.Vector as V
46import qualified Data.Vector as V
37import System.FilePath
47import System.FilePath
38import System.Timeout(timeout)
48import System.Timeout(timeout)
39−import Data.Time(getCurrentTime, diffUTCTime)
49+import Data.Time.Compat (getCurrentTime, diffUTCTime)
40import System.Environment (getEnvironment)
50import System.Environment (getEnvironment)
42import Test.Tasty
52import Test.Tasty
@@ -44,6 +54,8 @@ import Test.Tasty.Golden
44import Notify
54import Notify
45import Serializable
55import Serializable
46import Time
56import Time
58+import Exception (testExceptions)
48tests :: TestEnv -> TestTree
60tests :: TestEnv -> TestTree
49tests env = testGroup "tests"
61tests env = testGroup "tests"
@@ -54,11 +66,14 @@ tests env = testGroup "tests"
54 , testCase "Notify" . testNotify
66 , testCase "Notify" . testNotify
55 , testCase "Serializable" . testSerializable
67 , testCase "Serializable" . testSerializable
56 , testCase "Time" . testTime
68 , testCase "Time" . testTime
69+ , testCase "Interval" . testInterval
57 , testCase "Array" . testArray
70 , testCase "Array" . testArray
58 , testCase "Array of nullables" . testNullableArray
71 , testCase "Array of nullables" . testNullableArray
59 , testCase "HStore" . testHStore
72 , testCase "HStore" . testHStore
60 , testCase "citext" . testCIText
73 , testCase "citext" . testCIText
61 , testCase "JSON" . testJSON
74 , testCase "JSON" . testJSON
75+ , testCase "Aeson newtype" . testAeson
76+ , testCase "DerivingVia" . testDerivingVia
62 , testCase "Question mark escape" . testQM
77 , testCase "Question mark escape" . testQM
63 , testCase "Savepoint" . testSavepoint
78 , testCase "Savepoint" . testSavepoint
64 , testCase "Unicode" . testUnicode
79 , testCase "Unicode" . testUnicode
@@ -70,6 +85,7 @@ tests env = testGroup "tests"
70 , testCase "2-ary generic" . testGeneric2
85 , testCase "2-ary generic" . testGeneric2
71 , testCase "3-ary generic" . testGeneric3
86 , testCase "3-ary generic" . testGeneric3
72 , testCase "Timeout" . testTimeout
87 , testCase "Timeout" . testTimeout
88+ , testCase "Exceptions" . testExceptions
75testBytea :: TestEnv -> TestTree
91testBytea :: TestEnv -> TestTree
@@ -123,7 +139,7 @@ testFold TestEnv{..} = do
123 xs <- readIORef ref
139 xs <- readIORef ref
124 writeIORef ref $! (a,b):xs
140 writeIORef ref $! (a,b):xs
125 xs <- readIORef ref
141 xs <- readIORef ref
126− reverse xs @?= [(a,b) | a <- [1..100], b <- [1..50]]
142+ reverse xs @?= [(a,b) | b <- [1..50], a <- [1..100]]
128 -- Make sure fold propagates our exception.
144 -- Make sure fold propagates our exception.
129 ref <- newIORef []
145 ref <- newIORef []
@@ -240,6 +256,40 @@ testJSON TestEnv{..} = do
240 js' <- query conn "SELECT ?::json" js
256 js' <- query conn "SELECT ?::json" js
241 [js] @?= js'
257 [js] @?= js'
259+testAeson :: TestEnv -> Assertion
260+testAeson TestEnv{..} = do
261+ roundTrip (Map.fromList [] :: Map Text Text)
262+ roundTrip (Map.fromList [("foo","bar"),("bar","baz"),("baz","hello")] :: Map Text Text)
263+ roundTrip (Map.fromList [("fo\"o","bar"),("b\\ar","baz"),("baz","\"value\\with\"escapes")] :: Map Text Text)
264+ roundTrip (V.fromList [1,2,3,4,5::Int])
265+ roundTrip ("foo" :: Text)
266+ roundTrip (42 :: Int)
268+ roundTrip :: (Eq a, Show a, Typeable a, ToJSON a, FromJSON a)=> a -> Assertion
270+ y <- query conn "SELECT ?::json" (Only (Aeson x))
271+ [Only (Aeson x)] @?= y
273+testDerivingVia :: TestEnv -> Assertion
274+testDerivingVia TestEnv{..} = do
275+#if __GLASGOW_HASKELL__ <806
278+ roundTrip $ DerivingVia1 42 "Meaning of Life"
280+ roundTrip :: (Eq a, Show a, Typeable a, ToField a, FromField a)=> a -> Assertion
282+ y <- query conn "SELECT ?::json" (Only x)
285+data DerivingVia1 = DerivingVia1 Int String
286+ deriving stock (Eq, Show, Generic)
287+ deriving anyclass (FromJSON, ToJSON)
288+ deriving (ToField, FromField) via Aeson DerivingVia1
243testQM :: TestEnv -> Assertion
293testQM :: TestEnv -> Assertion
244testQM TestEnv{..} = do
294testQM TestEnv{..} = do
245 -- Just test on a single string
295 -- Just test on a single string
@@ -397,6 +447,14 @@ testCopy TestEnv{..} = do
397 -- so that we can issue more queries:
447 -- so that we can issue more queries:
398 [Only (x::Int)] <- query_ conn "SELECT 2 + 2"
448 [Only (x::Int)] <- query_ conn "SELECT 2 + 2"
451+ copy_ conn "COPY copy_test TO STDOUT (FORMAT CSV)"
452+ (acc, count) <- foldCopyData conn
453+ (\acc row -> return (row:acc))
454+ (\acc count -> return (acc, count))
456+ sort acc @?= sort copyRows
401 copyRows = ["1,foo\n"
459 copyRows = ["1,foo\n"
402 ,"2,bar\n"]
460 ,"2,bar\n"]
@@ -416,12 +474,13 @@ testCopyFailures env = testGroup "Copy failures"
417goldenTest :: TestName -> IO BL.ByteString -> TestTree
475goldenTest :: TestName -> IO BL.ByteString -> TestTree
418goldenTest testName =
476goldenTest testName =
419− goldenVsString testName (resultsDir </> fileName<.>"expected")
477+ goldenVsStringDiff testName diff (resultsDir </> fileName<.>"expected")
421 resultsDir = "test" </> "results"
479 resultsDir = "test" </> "results"
422 fileName = map normalize testName
480 fileName = map normalize testName
423 normalize c | not (isAlpha c) = '-'
481 normalize c | not (isAlpha c) = '-'
424 | otherwise = c
482 | otherwise = c
483+ diff ref new = ["diff", "-u", ref, new]
426-- | Test that we provide a sensible error message on failure
485-- | Test that we provide a sensible error message on failure
427testCopyUniqueConstraintError :: TestEnv -> TestTree
486testCopyUniqueConstraintError :: TestEnv -> TestTree
@@ -550,7 +609,11 @@ isFormatError i FormatError{..}
550-- that 'testConnect' connects to the same database every time it is called.
609-- that 'testConnect' connects to the same database every time it is called.
551withTestEnv :: ByteString -> (TestEnv -> IO a) -> IO a
610withTestEnv :: ByteString -> (TestEnv -> IO a) -> IO a
552withTestEnv connstr cb =
611withTestEnv connstr cb =
553− withConn $ \conn ->
612+ withConn $ \conn -> do
613+ -- currently required for interval to work.
614+ -- we also test that this doesn't interfere with anything else
615+ execute_ conn "SET intervalstyle TO 'iso_8601'"
554 cb TestEnv
617 cb TestEnv
555 { conn = conn
618 { conn = conn
556 , withConn = withConn
619 , withConn = withConn
@@ -559,13 +622,27 @@ withTestEnv connstr cb =
559 withConn = bracket (connectPostgreSQL connstr) close
622 withConn = bracket (connectPostgreSQL connstr) close
561main :: IO ()
624main :: IO ()
562−main = do
625+main = withConnstring $ \connstring -> do
626+ withTestEnv connstring (defaultMain . tests)
628+withConnstring :: (BS8.ByteString -> IO ()) -> IO ()
629+withConnstring kont = do
563 env <- getEnvironment
630 env <- getEnvironment
564 case lookup "DATABASE_CONNSTRING" env of
631 case lookup "DATABASE_CONNSTRING" env of
632+ Just s -> kont (BS8.pack (special s))
633+ Nothing -> case lookup "GITHUB_ACTIONS" env of
634+ Just "true" -> kont (BS8.pack gha)
565− Nothing -> putStrLn "Set DATABASE_CONNSTRING environment variable"
635+ _ -> putStrLn "Set DATABASE_CONNSTRING environment variable"
566− Just s -> withTestEnv (BS8.pack (special s)) (defaultMain . tests)
568 -- https://www.appveyor.com/docs/services-databases/
637 -- https://www.appveyor.com/docs/services-databases/
569 special "appveyor" = "dbname='TestDb' user='postgres' password='Password12!'"
638 special "appveyor" = "dbname='TestDb' user='postgres' password='Password12!'"
570 special "travis" = ""
639 special "travis" = ""
571 special s = s
640 special s = s
643+ [ "dbname='postgres'"
645+ , "password='postgres'"