packages feed

parsley-core-2.1.0.1: test/CommonTest/Queue.hs

{-# LANGUAGE FlexibleInstances, AllowAmbiguousTypes, TypeApplications, RankNTypes, ScopedTypeVariables, ConstraintKinds #-}
module CommonTest.Queue where
import Test.Tasty (testGroup, TestTree)
import Test.Tasty.HUnit ( testCase, (@?=) )
import Test.Tasty.QuickCheck
  ( listOf,
    (===),
    (==>),
    (.&&.),
    testProperty,
    Arbitrary(arbitrary),
    Property )

import Prelude hiding (null)
import Parsley.Internal.Common.QueueLike  (QueueLike(empty, null, size, enqueue, dequeue, enqueueAll))
import Parsley.Internal.Common.Queue ()
import Parsley.Internal.Common.Queue.Impl (Queue(..))
import qualified Parsley.Internal.Common.Queue.Impl as Queue

instance Arbitrary a => Arbitrary (Queue a) where
  arbitrary = do ins <- listOf arbitrary
                 outs <- listOf arbitrary
                 return $ Queue { ins = ins, insz = length ins
                                , outs = outs, outsz = length outs
                                }

class QueueLike q => QueueLikeImpl q where
  toList :: q a -> [a]

instance QueueLikeImpl Queue where
  toList = Queue.toList

type TestContext q = (Arbitrary (q Integer), QueueLikeImpl q, Show (q Integer))

tests :: TestTree
tests = testGroup "Queue" (genTests @Queue)

genTests :: forall q. TestContext q => [TestTree]
genTests = [ emptyTests @q
           , enqueueTests @q
           , testProperty "toList should roundtrip with enqueueAll" $ toListRound @q
           , dequeueTests @q
           ]

emptyTests :: forall q. TestContext q => TestTree
emptyTests = testGroup "empty should" [
    testCase "be null" $ null @q empty @?= True,
    testCase "have size 0" $ size @q empty @?= 0
  ]

enqueueTests :: forall q. TestContext q => TestTree
enqueueTests = testGroup "enqueue should" [
    testProperty "increase size by one" $ uncurry (enqueueSizeBy1 @q),
    testProperty "render empty non-null" $ flip (enqueueNonNull @q) empty,
    testProperty "render any other queue non-null" $ uncurry (enqueueNonNull @q),
    testProperty "behave like snoc" $ uncurry (enqueueIsSnoc @q),
    testProperty "serve as a model for enqueueAll" $ uncurry (enqueueAllModelsEnqueue @q)
  ]

enqueueSizeBy1 :: forall q. TestContext q => Integer -> q Integer -> Property
enqueueSizeBy1 x q = size q + 1 === size (enqueue x q)

enqueueNonNull :: forall q. TestContext q => Integer -> Queue Integer -> Bool
enqueueNonNull x = not . Queue.null . Queue.enqueue x

enqueueIsSnoc :: forall q. TestContext q => Integer -> Queue Integer -> Property
enqueueIsSnoc x q = Queue.toList q ++ [x] === Queue.toList (Queue.enqueue x q)

enqueueAllModelsEnqueue :: forall q. TestContext q => [Integer] -> Queue Integer -> Property
enqueueAllModelsEnqueue xs q = Queue.enqueueAll xs q === foldl (flip Queue.enqueue) q xs

dequeueTests :: forall q. TestContext q => TestTree
dequeueTests = testGroup "dequeue should" [
    testProperty "decrease size by one when non-empty" $ dequeueSizeBy1 @q,
    testProperty "behave like tail" $ dequeueIsTail @q
  ]

dequeueSizeBy1 :: forall q. TestContext q => Queue Integer -> Property
dequeueSizeBy1 q = not (Queue.null q) ==> Queue.size q === Queue.size (snd (Queue.dequeue q)) + 1

dequeueIsTail :: forall q. TestContext q => Queue Integer -> Property
dequeueIsTail q = not (Queue.null q) ==>
  let (x, q') = Queue.dequeue q
  in Queue.toList q === x : Queue.toList q'

toListRound :: forall q. TestContext q => [Integer] -> Property
toListRound xs = toList @q (enqueueAll xs empty) === xs