packages feed

stm-ringbuffer-0.1.0.0: test/TestTRingBuffer.hs

-- Unit tests for TRingBuffer.
{-# OPTIONS_GHC -Wno-type-defaults -Wno-unused-do-bind #-}

module TestTRingBuffer (testTRingBuffer) where

import Control.Concurrent.Async ( async )
import Control.Concurrent.STM ( atomically, orElse )
import Control.Concurrent.STM.TRingBuffer as TRB
    ( TRingBuffer(..),
      new,
      isEmpty,
      length,
      isFull,
      newIO,
      pushBack,
      popFront,
      pushFront,
      peekFront,
      flushFront,
      popBack,
      peekBack,
      flushBack )
import Control.Monad
    ( Monad((>>), (>>=)), replicateM, replicateM_, forM_ )
import Data.Functor ( (<&>) )
import Test.Hspec
    ( SpecWith, describe, it, shouldReturn, shouldBe )
import Prelude hiding (length)

testTRingBuffer :: SpecWith ()
testTRingBuffer = describe "Unit tests for TRingBuffer" $ do
  describe "new" $ do
    let newBuf = atomically $ new 10
    it "should be empty upon init" $
      (newBuf >>= atomically . isEmpty) `shouldReturn` True
    it "should have length zero upon init" $
      (newBuf >>= atomically . length) `shouldReturn` 0
    it "should not be full upon init" $
      (newBuf >>= atomically . isFull) `shouldReturn` False
    it "should have size it is initialized with" $
      (newBuf <&> size) `shouldReturn` 10

  describe "newIO" $ do
    let newBuf = newIO 10
    it "should be empty upon init" $
      (newBuf >>= atomically . isEmpty) `shouldReturn` True
    it "should have length zero upon init" $
      (newBuf >>= atomically . length) `shouldReturn` 0
    it "should not be full upon init" $
      (newBuf >>= atomically . isFull) `shouldReturn` False
    it "should have size it is initialized with" $
      (newBuf <&> size) `shouldReturn` 10

  describe "Eq instance" $ do
    it "returns True for same ring buffer" $ do
      buf <- newIO 10 :: IO (TRingBuffer Int)
      (buf == buf) `shouldBe` True
    it "returns False for different ring buffers" $ do
      buf1 <- newIO 10 :: IO (TRingBuffer Int)
      buf2 <- newIO 10
      (buf1 == buf2) `shouldBe` False

  describe "length" $ it "returns length of array" $ do
    buf <- newIO 3
    atomically (length buf) `shouldReturn` 0
    atomically (replicateM 3 (pushBack buf 0 >> length buf))
      `shouldReturn` [1, 2, 3]

  describe "isEmpty" $ it "returns whether array is empty" $ do
    buf <- newIO 3
    atomically (isEmpty buf) `shouldReturn` True
    atomically (replicateM 3 (pushBack buf 0 >> isEmpty buf))
      `shouldReturn` [False, False, False]

  describe "isFull" $ it "returns whether array is full" $ do
    buf <- newIO 3
    atomically (isFull buf) `shouldReturn` False
    atomically (replicateM 3 (pushBack buf 0 >> isFull buf))
      `shouldReturn` [False, False, True]

  describe "pushBack" $ do
    let newBuf = newIO 10
    let pushN n b = replicateM_ n (pushBack b 0)
    it "increases the length" $ do
      buf <- newBuf
      atomically $ pushN 3 buf
      atomically (length buf) `shouldReturn` 3
      atomically $ pushN 7 buf
      atomically (length buf) `shouldReturn` 10
    it "retries when buffer is full" $ do
      buf <- newBuf
      atomically ((pushN 11 buf >> pure True) `orElse` pure False)
        `shouldReturn` False

  describe "popFront" $ do
    let testSeq = [1 .. 10]
    it "takes elements from pushBack in same order" $ do
      buf <- newIO 1
      async $ forM_ testSeq (atomically . pushBack buf)
      replicateM 10 (atomically (popFront buf)) `shouldReturn` testSeq
    it "takes elements from pushFront in reverse order" $ do
      buf <- newIO 10
      forM_ testSeq (atomically . pushFront buf)
      replicateM 10 (atomically (popFront buf)) `shouldReturn` reverse testSeq
    it "retries when buffer is empty" $ do
      buf <- newIO 10
      atomically ((popFront buf >> pure True) `orElse` pure False)
        `shouldReturn` False

  describe "peekFront" $ do
    let testHead = 1
        testTail = [2 .. 10]
    it "looks at the first element pushed back" $ do
      buf <- newIO 10
      forM_ (testHead : testTail) (atomically . pushBack buf)
      atomically (peekFront buf) `shouldReturn` testHead
    it "retries when buffer is empty" $ do
      buf <- newIO 10
      atomically ((popFront buf >> pure True) `orElse` pure False)
        `shouldReturn` False

  describe "flushFront" $ do
    let testSeq = [1 .. 10]
    it "takes elements from pushBack in same order" $ do
      buf <- newIO 10
      forM_ testSeq (atomically . pushBack buf)
      atomically (flushFront buf) `shouldReturn` testSeq
    it "takes elements from pushFront in reverse order" $ do
      buf <- newIO 10
      forM_ testSeq (atomically . pushFront buf)
      atomically (flushFront buf) `shouldReturn` reverse testSeq
    it "leaves the queue empty" $ do
      buf <- newIO 10
      forM_ testSeq (atomically . pushFront buf)
      atomically (flushFront buf)
      atomically (isEmpty buf) `shouldReturn` True

  describe "pushFront" $ do
    let newBuf = newIO 10
    let pushN n b = replicateM_ n (pushFront b 0)
    it "increases the length" $ do
      buf <- newBuf
      atomically $ pushN 3 buf
      atomically (length buf) `shouldReturn` 3
      atomically $ pushN 7 buf
      atomically (length buf) `shouldReturn` 10
    it "retries when buffer is full" $ do
      buf <- newBuf
      atomically ((pushN 11 buf >> pure True) `orElse` pure False)
        `shouldReturn` False

  describe "popBack" $ do
    let testSeq = [1 .. 10]
    it "takes elements from pushFront in same order" $ do
      buf <- newIO 1
      async $ forM_ testSeq (atomically . pushFront buf)
      replicateM 10 (atomically (popBack buf)) `shouldReturn` testSeq
    it "takes elements from pushBack in reverse order" $ do
      buf <- newIO 10
      forM_ testSeq (atomically . pushBack buf)
      replicateM 10 (atomically (popBack buf)) `shouldReturn` reverse testSeq
    it "retries when buffer is empty" $ do
      buf <- newIO 10
      atomically ((popBack buf >> pure True) `orElse` pure False)
        `shouldReturn` False

  describe "peekBack" $ do
    let testHead = 1
        testTail = [2..10]
    it "looks at the first element pushed front" $ do
      buf <- newIO 10
      forM_ (testHead : testTail) (atomically . pushFront buf)
      atomically (peekBack buf) `shouldReturn` testHead
    it "retries when buffer is empty" $ do
      buf <- newIO 10
      atomically ((popBack buf >> pure True) `orElse` pure False)
        `shouldReturn` False

  describe "flushBack" $ do
    let testSeq = [1 .. 10]
    it "takes elements from pushFront in same order" $ do
      buf <- newIO 10
      forM_ testSeq (atomically . pushFront buf)
      atomically (flushBack buf) `shouldReturn` testSeq
    it "takes elements from pushBack in reverse order" $ do
      buf <- newIO 10
      forM_ testSeq (atomically . pushBack buf)
      atomically (flushBack buf) `shouldReturn` reverse testSeq
    it "leaves the queue empty" $ do
      buf <- newIO 10
      forM_ testSeq (atomically . pushBack buf)
      atomically (flushBack buf)
      atomically (isEmpty buf) `shouldReturn` True