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 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
|
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module PgInit
( runConn
, runConn_
, runConnAssert
, runConnAssertUseConf
, MonadIO
, persistSettings
, MkPersistSettings (..)
, BackendKey(..)
, GenerateKey(..)
-- re-exports
, module Control.Monad.Trans.Reader
, module Control.Monad
, module Database.Persist.Sql
, module Database.Persist.SqlBackend
, module Database.Persist
, module Database.Persist.Sql.Raw.QQ
, module Init
, module Test.Hspec
, module Test.Hspec.Expectations.Lifted
, module Test.HUnit
, AValue (..)
, BS.ByteString
, Int32, Int64
, liftIO
, mkPersist, migrateModels, mkMigrate, share, sqlSettings, persistLowerCase, persistUpperCase
, mkEntityDefList
, setImplicitIdDef
, SomeException
, Text
, TestFn(..)
, LoggingT
, ResourceT
, UUID(..)
, sqlSettingsUuid
) where
import Init
( GenerateKey(..)
, MonadFail
, RunDb
, TestFn(..)
, UUID(..)
, arbText
, asIO
, assertEmpty
, assertNotEmpty
, assertNotEqual
, isTravis
, liftA2
, sqlSettingsUuid
, truncateTimeOfDay
, truncateToMicro
, truncateUTCTime
, (==@)
, (@/=)
, (@==)
)
-- re-exports
import Control.Exception (SomeException)
import Control.Monad (forM_, liftM, replicateM, void, when)
import Control.Monad.Trans.Reader
import Data.Aeson (FromJSON, ToJSON, Value(..), object)
import qualified Data.Text.Encoding as TE
import Database.Persist.Postgresql.JSON ()
import Database.Persist.Sql.Raw.QQ
import Database.Persist.SqlBackend
import Database.Persist.TH
( MkPersistSettings(..)
, migrateModels
, mkEntityDefList
, mkMigrate
, mkPersist
, persistLowerCase
, persistUpperCase
, setImplicitIdDef
, share
, sqlSettings
)
import Test.Hspec
( Arg
, Spec
, SpecWith
, afterAll_
, before
, beforeAll
, before_
, describe
, fdescribe
, fit
, hspec
, it
)
import Test.Hspec.Expectations.Lifted
import Test.QuickCheck.Instances ()
import UnliftIO
-- testing
import Test.HUnit (Assertion, assertBool, assertFailure, (@=?), (@?=))
import Test.QuickCheck
import Control.Monad (unless, (>=>))
import Control.Monad.IO.Unlift (MonadUnliftIO)
import Control.Monad.Logger
import Control.Monad.Trans.Resource (ResourceT, runResourceT)
import qualified Data.ByteString as BS
import qualified Data.HashMap.Strict as HM
import Data.Int (Int32, Int64)
import Data.Maybe (fromMaybe)
import Data.Monoid ((<>))
import Data.Text (Text)
import Data.Vector (Vector)
import System.Environment (getEnvironment)
import System.Log.FastLogger (fromLogStr)
import Database.Persist
import Database.Persist.Postgresql
import Database.Persist.Sql
import Database.Persist.TH ()
_debugOn :: Bool
_debugOn = False
dockerPg :: IO (Maybe BS.ByteString)
dockerPg = do
env <- liftIO getEnvironment
return $ case lookup "POSTGRES_NAME" env of
Just _name -> Just "postgres" -- /persistent/postgres
_ -> Nothing
persistSettings :: MkPersistSettings
persistSettings = sqlSettings { mpsGeneric = True }
runConn :: MonadUnliftIO m => SqlPersistT (LoggingT m) t -> m ()
runConn f = runConn_ f >>= const (return ())
runConn_ :: MonadUnliftIO m => SqlPersistT (LoggingT m) t -> m t
runConn_ f = runConnInternal RunConnBasic f
-- | Data type to switch between pool creation functions, to ease testing both.
data RunConnType =
RunConnBasic -- ^ Use 'withPostgresqlPool'
| RunConnConf -- ^ Use 'withPostgresqlPoolWithConf'
deriving (Show, Eq)
runConnInternal :: MonadUnliftIO m => RunConnType -> SqlPersistT (LoggingT m) t -> m t
runConnInternal connType f = do
travis <- liftIO isTravis
let debugPrint = not travis && _debugOn
printDebug = if debugPrint then print . fromLogStr else void . return
poolSize = 1
connString <- if travis
then do
pure "host=localhost port=5432 user=perstest password=perstest dbname=persistent"
else do
host <- fromMaybe "localhost" <$> liftIO dockerPg
pure ("host=" <> host <> " port=5432 user=postgres dbname=test")
flip runLoggingT (\_ _ _ s -> printDebug s) $ do
logInfoN (if travis then "Running in CI" else "CI not detected")
let go =
case connType of
RunConnBasic ->
withPostgresqlPool connString poolSize $ runSqlPool f
RunConnConf -> do
let conf = PostgresConf
{ pgConnStr = connString
, pgPoolStripes = 1
, pgPoolIdleTimeout = 60
, pgPoolSize = poolSize
}
hooks = defaultPostgresConfHooks
withPostgresqlPoolWithConf conf hooks (runSqlPool f)
-- horrifying hack :( postgresql is having weird connection failures in
-- CI, for no reason that i can determine. see this PR for notes:
-- https://github.com/yesodweb/persistent/pull/1197
eres <- try go
case eres of
Left (err :: SomeException) -> do
eres' <- try go
case eres' of
Left (err' :: SomeException) ->
if show err == show err'
then throwIO err
else throwIO err'
Right a ->
pure a
Right a ->
pure a
runConnAssert :: SqlPersistT (LoggingT (ResourceT IO)) () -> Assertion
runConnAssert actions = do
runResourceT $ runConn $ actions >> transactionUndo
-- | Like runConnAssert, but uses the "conf" flavor of functions to test that code path.
runConnAssertUseConf :: SqlPersistT (LoggingT (ResourceT IO)) () -> Assertion
runConnAssertUseConf actions = do
runResourceT $ runConnInternal RunConnConf (actions >> transactionUndo)
newtype AValue = AValue { getValue :: Value }
-- Need a specialized Arbitrary instance
instance Arbitrary AValue where
arbitrary = AValue <$>
frequency [ (1, pure Null)
, (1, Bool <$> arbitrary)
, (2, Number <$> arbitrary)
, (2, String <$> arbText)
, (3, Array <$> limitIt 4 (fmap (fmap getValue) arbitrary))
, (3, object <$> arbObject)
]
where
limitIt :: Int -> Gen a -> Gen a
limitIt i x = sized $ \n -> do
let m = if n > i then i else n
resize m x
arbObject = limitIt 4 -- Recursion can make execution divergent
$ listOf -- [(,)] -> (,)
. liftA2 (,) arbText -- (,) -> Text and Value
$ limitIt 4 (fmap getValue arbitrary) -- Again, precaution against divergent recursion.
|