packages feed

pgmq-inspect-0.7.0.0: test/ListenerSpec.hs

module ListenerSpec (tests) where

import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (cancel, withAsync)
import Control.Concurrent.MVar
import Control.Concurrent.STM
import Control.Monad (forM_, replicateM, void)
import EphemeralDb (withPgmqDb)
import EphemeralPg qualified as Pg
import Hasql.Decoders qualified as D
import Hasql.Pool qualified as Pool
import Hasql.Session qualified as Session
import Hasql.Statement (unpreparable)
import HttpSpec (session)
import Pgmq.Inspect.Listener
import System.Timeout (timeout)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit

waitSTM :: STM a -> IO a
waitSTM action = timeout 2000000 (atomically action) >>= maybe (assertFailure "listener acknowledgement timed out") pure

backendCount :: Pool.Pool -> IO Int
backendCount pool = fromIntegral <$> session pool (Session.statement () (unpreparable "select count(*)::int from pg_stat_activity where application_name = 'pgmq-inspect-listener'" mempty (D.singleRow (D.column (D.nonNullable D.int4)))))

waitNoBackend :: Pool.Pool -> IO ()
waitNoBackend pool = timeout 2000000 loop >>= maybe (assertFailure "listener backend leaked") pure
  where
    loop = backendCount pool >>= \n -> if n == 0 then pure () else threadDelay 10000 >> loop

tests :: TestTree
tests =
  testGroup
    "WebSocket listener ownership"
    [ testCase "refcounted channels use one backend and UNLISTEN after last subscriber" $ do
        result <- withPgmqDb $ \pool database -> withListener (Pg.connectionSettings database) $ \listener -> do
          delivered <- newTVarIO (0 :: Int)
          keys <- replicateM 10 (subscribeChannel listener "inspect_test" (modifyTVar' delivered (+ 1)))
          waitSTM (listenerAcknowledged listener "inspect_test" >>= check)
          backendCount pool >>= (@?= 1)
          atomically (listenerChannelCount listener) >>= (@?= 1)
          unsubscribeChannel listener "inspect_test" (keys !! 0)
          session pool (Session.script "NOTIFY inspect_test")
          waitSTM (readTVar delivered >>= check . (== 9))
          forM_ (drop 1 keys) (unsubscribeChannel listener "inspect_test")
          waitSTM (listenerAcknowledged listener "inspect_test" >>= check . not)
          atomically (listenerChannelCount listener) >>= (@?= 0)
          session pool (Session.script "NOTIFY inspect_test")
          threadDelay 20000
          readTVarIO delivered >>= (@?= 9)
        either (assertFailure . show) pure result,
      testCase "cancellation during fd wait clears health, registrations and backend" $ do
        result <- withPgmqDb $ \pool database -> forM_ [1 .. 10 :: Int] $ \_ -> do
          ready <- newEmptyMVar
          withAsync (withListener (Pg.connectionSettings database) (\listener -> putMVar ready listener >> (newEmptyMVar >>= takeMVar))) $ \owner -> do
            listener <- takeMVar ready
            void (subscribeChannel listener "inspect_cancel" (pure ()))
            waitSTM (listenerAcknowledged listener "inspect_cancel" >>= check)
            cancel owner
            atomically (listenerHealthy listener) >>= (@?= False)
            atomically (listenerChannelCount listener) >>= (@?= 0)
            waitNoBackend pool
        either (assertFailure . show) pure result,
      testCase "buffered notification batches do not starve new channel acknowledgement" $ do
        result <- withPgmqDb $ \pool database -> withListener (Pg.connectionSettings database) $ \listener -> do
          delivered <- newTVarIO (0 :: Int)
          void (subscribeChannel listener "inspect_flood" (modifyTVar' delivered (+ 1)))
          waitSTM (listenerAcknowledged listener "inspect_flood" >>= check)
          session pool (Session.script "DO $$ BEGIN FOR i IN 1..300 LOOP PERFORM pg_notify('inspect_flood', i::text); END LOOP; END $$")
          void (subscribeChannel listener "inspect_new" (pure ()))
          waitSTM (listenerAcknowledged listener "inspect_new" >>= check)
          waitSTM (readTVar delivered >>= check . (== 300))
        either (assertFailure . show) pure result
    ]