packages feed

bugsnag-hs-0.1.0.0: test/Spec/Data/Buffer.hs

{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}

module Spec.Data.Buffer
  ( tests,
  )
where

import qualified Control.Concurrent
import qualified Control.Concurrent.STM as STM
import qualified Data.Buffer as Buffer
import qualified Data.Foldable
import qualified Data.List
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Maybe
import GHC.Natural (Natural)
import Hedgehog
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import qualified System.Exit

tests :: IO ()
tests = do
  res <- tests'
  if res
    then pure ()
    else System.Exit.die "Spec.Data.Buffer tests failed"

tests' :: IO Bool
tests' =
  checkParallel $
    Group
      "Spec.Data.Buffer"
      [ ("all items get sent", allItemsGetSent)
      ]

allItemsGetSent :: Property
allItemsGetSent = property $ do
  steps <- forAll $ numberItems <$> Gen.list (Range.linear 0 100) step
  size <- forAll $ fromIntegral <$> Gen.int (Range.linear 1 10)
  frequencyInMicroSeconds <- forAll $ waitMicroS
  actualOutput <- evalIO (run size frequencyInMicroSeconds steps)
  let expectedOutput = Data.Maybe.mapMaybe toItem steps
  Data.List.sort actualOutput === Data.List.sort expectedOutput

data Step
  = Push Item
  | WaitMicroS Int
  | Flush
  deriving (Show)

newtype Item = Item Int
  deriving (Eq, Ord, Show)

step :: Gen Step
step =
  Gen.frequency
    [ (5, Gen.constant (Push (Item 0))),
      (5, WaitMicroS <$> waitMicroS),
      (1, Gen.constant Flush)
    ]

numberItems :: [Step] -> [Step]
numberItems = snd . Data.List.mapAccumL numberItem 0

numberItem :: Int -> Step -> (Int, Step)
numberItem counter step =
  case step of
    Push (Item _) -> (counter + 1, Push (Item counter))
    _ -> (counter, step)

waitMicroS :: Gen Int
waitMicroS = Gen.int (Range.linear 0 maxWaitMicroS)

maxWaitMicroS :: Int
maxWaitMicroS = 10000

toItem :: Step -> Maybe Item
toItem step =
  case step of
    Push item -> Just item
    WaitMicroS _ -> Nothing
    Flush -> Nothing

run :: Natural -> Int -> [Step] -> IO [Item]
run size frequencyInMicroSeconds steps = do
  -- Setup the buffer to play the steps through.
  resultQueue <- STM.newTQueueIO
  buffer <-
    Buffer.new
      Buffer.defaultSettings
        { Buffer.write = writeToQueue resultQueue,
          Buffer.size,
          Buffer.frequencyInMicroSeconds
        }
  -- Play the steps one by one.
  Data.Foldable.traverse_ (play buffer) steps
  -- Give the queue the opportunity to flush by itself.
  Control.Concurrent.threadDelay (2 * maxWaitMicroS)
  mconcat <$> STM.atomically (STM.flushTQueue resultQueue)

play :: Buffer.Buffer Item -> Step -> IO ()
play buffer step =
  case step of
    Push item -> Buffer.push buffer item
    WaitMicroS delay -> Control.Concurrent.threadDelay delay
    Flush -> Buffer.flush buffer

writeToQueue :: STM.TQueue [a] -> NonEmpty.NonEmpty a -> IO ()
writeToQueue queue list = STM.atomically (STM.writeTQueue queue (NonEmpty.toList list))