Commit c48f3d46 authored by Alfredo Di Napoli's avatar Alfredo Di Napoli

Add tests for rollback and RW consistency

parent a75d6e90
Pipeline #7550 passed with stages
in 117 minutes and 2 seconds
...@@ -764,9 +764,9 @@ common commonTestDependencies ...@@ -764,9 +764,9 @@ common commonTestDependencies
, postgres-options >= 0.2 && < 0.3 , postgres-options >= 0.2 && < 0.3
, postgresql-simple >= 0.6.4 && <= 0.7.0.0 , postgresql-simple >= 0.6.4 && <= 0.7.0.0
, quickcheck-instances ^>= 0.3.25.2 , quickcheck-instances ^>= 0.3.25.2
, random
, raw-strings-qq , raw-strings-qq
, resource-pool >= 0.4.0.0 && < 0.5 , resource-pool >= 0.4.0.0 && < 0.5
, recover-rtti
, safe-exceptions >= 0.1.7.4 && < 0.2 , safe-exceptions >= 0.1.7.4 && < 0.2
, servant-auth-client , servant-auth-client
, servant-client >= 0.20 && < 0.21 , servant-client >= 0.20 && < 0.21
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-} {-# LANGUAGE TupleSections #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-| Tests for the transactional DB API -} {-| Tests for the transactional DB API -}
...@@ -9,6 +10,7 @@ module Test.Database.Transactions ( ...@@ -9,6 +10,7 @@ module Test.Database.Transactions (
tests tests
) where ) where
import System.Random.Stateful
import Control.Exception.Safe import Control.Exception.Safe
import Control.Exception.Safe qualified as Safe import Control.Exception.Safe qualified as Safe
import Control.Monad.Reader import Control.Monad.Reader
...@@ -32,6 +34,7 @@ import Test.Database.Types hiding (Counter) ...@@ -32,6 +34,7 @@ import Test.Database.Types hiding (Counter)
import Test.Hspec import Test.Hspec
import Test.Tasty.HUnit hiding (assert) import Test.Tasty.HUnit hiding (assert)
import Text.RawString.QQ import Text.RawString.QQ
import Control.Concurrent.Async (forConcurrently)
-- --
-- For these tests we do not want to test the normal GGTX database queries, but rather -- For these tests we do not want to test the normal GGTX database queries, but rather
...@@ -170,6 +173,10 @@ tests = parallel $ around withTestCounterDB $ ...@@ -170,6 +173,10 @@ tests = parallel $ around withTestCounterDB $
it "Simple updates works" simplePGUpdateWorks it "Simple updates works" simplePGUpdateWorks
describe "PG Queries and Updates" $ do describe "PG Queries and Updates" $ do
it "Supports mixing queries and updates" mixQueriesAndUpdates it "Supports mixing queries and updates" mixQueriesAndUpdates
describe "Rollback support" $ do
it "can rollback in case of errors" testRollback
describe "Read/Write Consistency" $ do
it "should return a consistent state to different actors" testConsistency
simplePGQueryWorks :: DBHandle -> Assertion simplePGQueryWorks :: DBHandle -> Assertion
simplePGQueryWorks env = flip runReaderT env $ runTestMonad $ do simplePGQueryWorks env = flip runReaderT env $ runTestMonad $ do
...@@ -197,3 +204,37 @@ mixQueriesAndUpdates env = flip runReaderT env $ runTestMonad $ do ...@@ -197,3 +204,37 @@ mixQueriesAndUpdates env = flip runReaderT env $ runTestMonad $ do
liftIO $ do liftIO $ do
final_1 `shouldBe` (Counter (CounterId 2) 0) final_1 `shouldBe` (Counter (CounterId 2) 0)
final_2 `shouldBe` (Counter (CounterId 3) 1) final_2 `shouldBe` (Counter (CounterId 3) 1)
testRollback :: DBHandle -> Assertion
testRollback env = flip runReaderT env $ runTestMonad $ do
initialCounter <- runDBTx $ insertCounter >>= stepCounter . counterId
liftIO $ counterValue initialCounter `shouldBe` 1
-- Let's do another transaction where at the very last instruction we
-- fail.
Safe.handle (\(_ :: SomeException) -> pure ()) $ runDBTx $ do
_x' <- stepCounter (counterId initialCounter)
dbFail (Prelude.userError "urgh")
-- Let's check that the second 'stepCounter' didn't actually modified the counter's value.
finalCounter <- runDBTx $ getCounterById (counterId initialCounter)
liftIO $ counterValue finalCounter `shouldBe` 1
-- | In this test we create concurrent actors all writing to the /same/ counter.
-- Each one should observe only the state it's updating.
testConsistency :: DBHandle -> Assertion
testConsistency env = do
let competing_actors = 10
initialCounter <- flip runReaderT env $ runTestMonad $ runDBTx insertCounter
results <- forConcurrently [ 1 .. competing_actors ] $ \x -> flip runReaderT env $ runTestMonad $ do
-- random delay
liftIO $ do
delay_us <- uniformRM (100, 2_000_000) globalStdGen
threadDelay delay_us
runDBTx $ do
_ <- updateCounter (counterId initialCounter) x
getCounterById (counterId initialCounter)
-- Each actor should observe a consistent state.
liftIO $ results `shouldBe` map (Counter (CounterId 2)) [ 1 .. competing_actors ]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment