{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeOperators #-} module Test.FinMap (finMapTests) where import Control.Monad (foldM) import Data.Foldable.WithIndex (itoList) import Data.Functor.WithIndex (FunctorWithIndex(imap)) import Data.Foldable.WithIndex (FoldableWithIndex(ifoldMap)) import Data.Proxy (Proxy(Proxy)) import Data.Type.Equality ((:~:)(Refl)) import Data.Parameterized.Fin (Fin) import qualified Data.Parameterized.Fin as Fin import Data.Parameterized.NatRepr (LeqProof, NatRepr, type (<=), type (+)) import qualified Data.Parameterized.NatRepr as NatRepr import Hedgehog import qualified Hedgehog.Gen as HG import Hedgehog.Range (linear) import Test.Tasty import Test.Tasty.Hedgehog #if __GLASGOW_HASKELL__ >= 806 import Test.Tasty.HUnit (assertBool, testCase) import qualified Hedgehog.Classes as HC #endif import qualified Data.Parameterized.FinMap.Safe as S import qualified Data.Parameterized.FinMap.Unsafe as U import qualified Data.Parameterized.Vector as Vec import Test.Fin (genFin) import Test.Vector (SomeVector(..), genSomeVector, genVectorOfLength, genOrdering, orderingEndomorphisms, orderingToStringFuns) data SomeSafeFinMap a = forall n. SomeSafeFinMap (NatRepr n) (S.FinMap n a) data SomeUnsafeFinMap a = forall n. SomeUnsafeFinMap (NatRepr n) (U.FinMap n a) instance Show a => Show (SomeSafeFinMap a) where show (SomeSafeFinMap _ v) = show v instance Show a => Show (SomeUnsafeFinMap a) where show (SomeUnsafeFinMap _ v) = show v genSafeFinMap :: (Monad m) => NatRepr n -> GenT m a -> GenT m (S.FinMap (n + 1) a) genSafeFinMap n genElem = S.fromVector <$> genVectorOfLength n (HG.maybe genElem) genUnsafeFinMap :: (Monad m) => NatRepr n -> GenT m a -> GenT m (U.FinMap (n + 1) a) genUnsafeFinMap n genElem = U.fromVector <$> genVectorOfLength n (HG.maybe genElem) genSomeSafeFinMap :: (Monad m) => GenT m a -> GenT m (SomeSafeFinMap a) genSomeSafeFinMap genElem = do SomeVector v <- genSomeVector (HG.maybe genElem) return (SomeSafeFinMap (Vec.length v) (S.fromVector v)) genSomeUnsafeFinMap :: (Monad m) => GenT m a -> GenT m (SomeUnsafeFinMap a) genSomeUnsafeFinMap genElem = do SomeVector v <- genSomeVector (HG.maybe genElem) return (SomeUnsafeFinMap (Vec.length v) (U.fromVector v)) prop_incMax_size_safe :: Property prop_incMax_size_safe = property $ do SomeSafeFinMap _ fm <- forAll $ genSomeSafeFinMap genOrdering Fin.finToNat (S.size (S.incMax fm)) === Fin.finToNat (S.size fm) prop_incMax_size_unsafe :: Property prop_incMax_size_unsafe = property $ do SomeUnsafeFinMap _ fm <- forAll $ genSomeUnsafeFinMap genOrdering Fin.finToNat (U.size (U.incMax fm)) === Fin.finToNat (U.size fm) prop_imap_const_safe :: Property prop_imap_const_safe = property $ do f <- forAll (HG.element orderingEndomorphisms) SomeSafeFinMap _ fm <- forAll (genSomeSafeFinMap genOrdering) imap (const f) fm === fmap f fm prop_imap_const_unsafe :: Property prop_imap_const_unsafe = property $ do f <- forAll (HG.element orderingEndomorphisms) SomeUnsafeFinMap _ fm <- forAll (genSomeUnsafeFinMap genOrdering) imap (const f) fm === fmap f fm prop_ifoldMap_const_safe :: Property prop_ifoldMap_const_safe = property $ do f <- forAll (HG.element orderingToStringFuns) SomeSafeFinMap _ fm <- forAll (genSomeSafeFinMap genOrdering) ifoldMap (const f) fm === foldMap f fm prop_ifoldMap_const_unsafe :: Property prop_ifoldMap_const_unsafe = property $ do f <- forAll (HG.element orderingToStringFuns) SomeUnsafeFinMap _ fm <- forAll (genSomeUnsafeFinMap genOrdering) ifoldMap (const f) fm === foldMap f fm cancelPlusOne :: forall f g i n. f i -> g n -> LeqProof (i + 1) (n + 1) -> LeqProof i n cancelPlusOne i n NatRepr.LeqProof = case NatRepr.plusMinusCancel n (NatRepr.knownNat :: NatRepr 1) of Refl -> case NatRepr.plusMinusCancel i (NatRepr.knownNat :: NatRepr 1) of Refl -> case NatRepr.leqSub2 (NatRepr.LeqProof :: LeqProof (i + 1) (n + 1)) (NatRepr.LeqProof :: LeqProof 1 1) of NatRepr.LeqProof -> NatRepr.LeqProof withIndexSafe :: SomeSafeFinMap a -> (forall n. Fin n -> S.FinMap n a -> PropertyT IO ()) -> PropertyT IO () withIndexSafe (SomeSafeFinMap n fm) k = case NatRepr.isZeroOrGT1 n of Left Refl -> k Fin.minFin (S.incMax fm) Right NatRepr.LeqProof -> do idx <- forAll (genFin n) k idx fm withIndexUnsafe :: SomeUnsafeFinMap a -> (forall n. Fin n -> U.FinMap n a -> PropertyT IO ()) -> PropertyT IO () withIndexUnsafe (SomeUnsafeFinMap n fm) k = case NatRepr.isZeroOrGT1 n of Left Refl -> k Fin.minFin (U.incMax fm) Right NatRepr.LeqProof -> do idx <- forAll (genFin n) k idx fm withSizeUnsafe :: U.FinMap n a -> (forall i. (i + 1 <= n + 1, i <= n) => NatRepr i -> r) -> r withSizeUnsafe fm k = case U.size fm of (sz :: Fin (n + 1)) -> Fin.viewFin (\(i :: NatRepr i) -> case cancelPlusOne i (Proxy :: Proxy n) NatRepr.LeqProof of NatRepr.LeqProof -> k i) sz prop_insert_size_safe :: Property prop_insert_size_safe = property $ do sfm <- forAll $ genSomeSafeFinMap genOrdering withIndexSafe sfm $ \idx fm -> do o <- forAll genOrdering let size = Fin.finToNat (S.size fm) let newSize = Fin.finToNat (S.size (S.insert (Fin.embed idx) o fm)) assert (size == newSize || size + 1 == newSize) prop_insert_size_unsafe :: Property prop_insert_size_unsafe = property $ do sfm <- forAll $ genSomeUnsafeFinMap genOrdering withIndexUnsafe sfm $ \idx fm -> do o <- forAll genOrdering let size = Fin.finToNat (U.size fm) let newSize = Fin.finToNat (U.size (U.insert (Fin.embed idx) o fm)) assert (size == newSize || size + 1 == newSize) prop_insert_delete_safe :: Property prop_insert_delete_safe = property $ do sfm <- forAll $ genSomeSafeFinMap genOrdering withIndexSafe sfm $ \idx fm -> do o <- forAll genOrdering S.delete idx (S.insert idx o fm) === S.delete idx fm prop_insert_delete_unsafe :: Property prop_insert_delete_unsafe = property $ do sfm <- forAll $ genSomeUnsafeFinMap genOrdering withIndexUnsafe sfm $ \idx fm -> do o <- forAll genOrdering U.delete idx (U.insert idx o fm) === U.delete idx fm prop_delete_insert_safe :: Property prop_delete_insert_safe = property $ do sfm <- forAll $ genSomeSafeFinMap genOrdering withIndexSafe sfm $ \idx fm -> do o <- forAll genOrdering S.insert idx o (S.delete idx fm) === S.insert idx o fm prop_delete_insert_unsafe :: Property prop_delete_insert_unsafe = property $ do sfm <- forAll $ genSomeUnsafeFinMap genOrdering withIndexUnsafe sfm $ \idx fm -> do o <- forAll genOrdering U.insert idx o (U.delete idx fm) === U.insert idx o fm prop_empty_insert_safe :: Property prop_empty_insert_safe = property $ do withIndexSafe (SomeSafeFinMap (NatRepr.knownNat @0) S.empty) $ \idx fm -> do o <- forAll genOrdering fm /== S.insert idx o fm prop_empty_insert_unsafe :: Property prop_empty_insert_unsafe = property $ do withIndexUnsafe (SomeUnsafeFinMap (NatRepr.knownNat @0) U.empty) $ \idx fm -> do o <- forAll genOrdering fm /== U.insert idx o fm prop_insert_insert_safe :: Property prop_insert_insert_safe = property $ do sfm <- forAll $ genSomeSafeFinMap genOrdering withIndexSafe sfm $ \idx fm -> do o <- forAll genOrdering S.insert idx o (S.insert idx o fm) === S.insert idx o fm prop_insert_insert_unsafe :: Property prop_insert_insert_unsafe = property $ do sfm <- forAll $ genSomeUnsafeFinMap genOrdering withIndexUnsafe sfm $ \idx fm -> do o <- forAll genOrdering U.insert idx o (U.insert idx o fm) === U.insert idx o fm prop_delete_delete_safe :: Property prop_delete_delete_safe = property $ do sfm <- forAll $ genSomeSafeFinMap genOrdering withIndexSafe sfm $ \idx fm -> do S.delete idx (S.delete idx fm) === S.delete idx fm prop_delete_delete_unsafe :: Property prop_delete_delete_unsafe = property $ do sfm <- forAll $ genSomeUnsafeFinMap genOrdering withIndexUnsafe sfm $ \idx fm -> do U.delete idx (U.delete idx fm) === U.delete idx fm -- | Type used for comparative API tests data MatchedMaps a = forall n. MatchedMaps { _unsafe :: U.FinMap n a , _safe :: S.FinMap n a } operations :: Show a => Gen a -> -- | For testing 'fmap'. [a -> a] -> [MatchedMaps a -> PropertyT IO (MatchedMaps a)] operations genValue valEndomorphisms = [ \(MatchedMaps u s) -> withSizeUnsafe u $ \sz -> do case NatRepr.isZeroOrGT1 sz of Left Refl -> do v <- forAll genValue return $ MatchedMaps (U.insert Fin.minFin v (U.incMax u)) (S.insert Fin.minFin v (S.incMax s)) Right NatRepr.LeqProof -> do idx <- Fin.embed <$> forAll (genFin sz) v <- forAll genValue return (MatchedMaps (U.insert idx v u) (S.insert idx v s)) , \(MatchedMaps u s) -> withSizeUnsafe u $ \sz -> do case NatRepr.isZeroOrGT1 sz of Left Refl -> return (MatchedMaps u s) Right NatRepr.LeqProof -> do idx <- Fin.embed <$> forAll (genFin sz) return (MatchedMaps (U.delete idx u) (S.delete idx s)) , \(MatchedMaps u s) -> return (MatchedMaps (U.incMax u) (S.incMax s)) , \(MatchedMaps u s) -> do f <- forAll (HG.element (id:valEndomorphisms)) return (MatchedMaps (fmap f u) (fmap f s)) , \(MatchedMaps u s) -> do f <- forAll (HG.element (id:valEndomorphisms)) return (MatchedMaps (imap (const f) u) (imap (const f) s)) , \(MatchedMaps _ _) -> do v <- forAll genValue return (MatchedMaps (U.singleton v) (S.singleton v)) , \(MatchedMaps _ _) -> return (MatchedMaps (U.empty @0) S.empty) , \(MatchedMaps _ _) -> return (MatchedMaps (U.empty @8) S.empty) ] -- | Possibly the most important and far-reaching test: The unsafe API should -- yield the same results as the safe API, after some randomized sequence of -- operations. prop_safe_unsafe :: Property prop_safe_unsafe = property $ do numOps <- forAll (HG.integral (linear 0 (99 :: Int))) let empty = MatchedMaps (U.empty @0) S.empty MatchedMaps u s <- doTimes (chooseAndApply orderingOps) numOps empty itoList u === itoList s where orderingOps = operations genOrdering orderingEndomorphisms chooseAndApply :: [a -> PropertyT IO b] -> a -> PropertyT IO b chooseAndApply funs arg = do f <- forAll (HG.element funs) f arg doTimes f n m = foldM (\accum () -> f accum) m (replicate n ()) finMapTests :: IO TestTree finMapTests = testGroup "FinMap" <$> return [ testPropertyNamed "incSize-decSize-safe" "prop_incMax_size_safe" prop_incMax_size_safe , testPropertyNamed "incSize-decSize-unsafe" "prop_incMax_size_unsafe" prop_incMax_size_unsafe , testPropertyNamed "insert-size-safe" "prop_insert_size_safe" prop_insert_size_safe , testPropertyNamed "insert-size-unsafe" "prop_insert_size_unsafe" prop_insert_size_unsafe , testPropertyNamed "insert-delete-safe" "prop_insert_delete_safe" prop_insert_delete_safe , testPropertyNamed "insert-delete-unsafe" "prop_insert_delete_unsafe" prop_insert_delete_unsafe , testPropertyNamed "delete-insert-safe" "prop_delete_insert_safe" prop_delete_insert_safe , testPropertyNamed "delete-insert-unsafe" "prop_delete_insert_unsafe" prop_delete_insert_unsafe , testPropertyNamed "empty-insert-safe" "prop_empty_insert_safe" prop_empty_insert_safe , testPropertyNamed "empty-insert-unsafe" "prop_empty_insert_unsafe" prop_empty_insert_unsafe , testPropertyNamed "insert-insert-safe" "prop_insert_insert_safe" prop_insert_insert_safe , testPropertyNamed "insert-insert-unsafe" "prop_insert_insert_unsafe" prop_insert_insert_unsafe , testPropertyNamed "delete-delete-safe" "prop_delete_delete_safe" prop_delete_delete_safe , testPropertyNamed "delete-delete-unsafe" "prop_delete_delete_unsafe" prop_delete_delete_unsafe , testPropertyNamed "imap-const-safe" "prop_imap_const_safe" prop_imap_const_safe , testPropertyNamed "imap-const-unsafe" "prop_imap_const_unsafe" prop_imap_const_unsafe , testPropertyNamed "ifoldMap-const-safe" "prop_ifoldMap_const_safe" prop_ifoldMap_const_safe , testPropertyNamed "ifoldMap-const-unsafe" "prop_ifoldMap_const_unsafe" prop_ifoldMap_const_unsafe , testPropertyNamed "safe-unsafe" "prop_safe_unsafe" prop_safe_unsafe #if __GLASGOW_HASKELL__ >= 806 , testCase "Eq-Safe-FinMap-laws-1" $ assertBool "Eq-Safe-FinMap-laws-1" =<< HC.lawsCheck (HC.eqLaws (genSafeFinMap (NatRepr.knownNat @1) genOrdering)) , testCase "Eq-Unsafe-FinMap-laws-1" $ assertBool "Eq-Unsafe-FinMap-laws-1" =<< HC.lawsCheck (HC.eqLaws (genUnsafeFinMap (NatRepr.knownNat @1) genOrdering)) , testCase "Eq-Safe-FinMap-laws-10" $ assertBool "Eq-Safe-FinMap-laws-10" =<< HC.lawsCheck (HC.eqLaws (genSafeFinMap (NatRepr.knownNat @10) genOrdering)) , testCase "Eq-Unsafe-FinMap-laws-10" $ assertBool "Eq-Unsafe-FinMap-laws-10" =<< HC.lawsCheck (HC.eqLaws (genUnsafeFinMap (NatRepr.knownNat @10) genOrdering)) , testCase "Semigroup-Safe-FinMap-laws-1" $ assertBool "Semigroup-Safe-FinMap-laws-1" =<< HC.lawsCheck (HC.semigroupLaws (genSafeFinMap (NatRepr.knownNat @1) genOrdering)) , testCase "Semigroup-Unsafe-FinMap-laws-1" $ assertBool "Semigroup-Unsafe-FinMap-laws-1" =<< HC.lawsCheck (HC.semigroupLaws (genUnsafeFinMap (NatRepr.knownNat @1) genOrdering)) , testCase "Semigroup-Safe-FinMap-laws-10" $ assertBool "Semigroup-Safe-FinMap-laws-10" =<< HC.lawsCheck (HC.semigroupLaws (genSafeFinMap (NatRepr.knownNat @10) genOrdering)) , testCase "Semigroup-Unsafe-FinMap-laws-10" $ assertBool "Semigroup-Unsafe-FinMap-laws-10" =<< HC.lawsCheck (HC.semigroupLaws (genUnsafeFinMap (NatRepr.knownNat @10) genOrdering)) , testCase "Monoid-Safe-FinMap-laws-1" $ assertBool "Monoid-Safe-FinMap-laws-1" =<< HC.lawsCheck (HC.monoidLaws (genSafeFinMap (NatRepr.knownNat @1) genOrdering)) , testCase "Monoid-Unsafe-FinMap-laws-1" $ assertBool "Monoid-Unsafe-FinMap-laws-1" =<< HC.lawsCheck (HC.monoidLaws (genUnsafeFinMap (NatRepr.knownNat @1) genOrdering)) , testCase "Monoid-Safe-FinMap-laws-10" $ assertBool "Monoid-Safe-FinMap-laws-10" =<< HC.lawsCheck (HC.monoidLaws (genSafeFinMap (NatRepr.knownNat @10) genOrdering)) , testCase "Monoid-Unsafe-FinMap-laws-10" $ assertBool "Monoid-Unsafe-FinMap-laws-10" =<< HC.lawsCheck (HC.monoidLaws (genUnsafeFinMap (NatRepr.knownNat @10) genOrdering)) , testCase "Foldable-Safe-FinMap-laws-1" $ assertBool "Foldable-Safe-FinMap-laws-1" =<< HC.lawsCheck (HC.foldableLaws (genSafeFinMap (NatRepr.knownNat @1))) , testCase "Foldable-Unsafe-FinMap-laws-1" $ assertBool "Foldable-Unsafe-FinMap-laws-1" =<< HC.lawsCheck (HC.foldableLaws (genUnsafeFinMap (NatRepr.knownNat @1))) , testCase "Foldable-Safe-FinMap-laws-10" $ assertBool "Foldable-Safe-FinMap-laws-10" =<< HC.lawsCheck (HC.foldableLaws (genSafeFinMap (NatRepr.knownNat @10))) , testCase "Foldable-Unsafe-FinMap-laws-10" $ assertBool "Foldable-Unsafe-FinMap-laws-10" =<< HC.lawsCheck (HC.foldableLaws (genUnsafeFinMap (NatRepr.knownNat @10))) , testCase "Traversable-Safe-FinMap-laws-1" $ assertBool "Traversable-Safe-FinMap-laws-1" =<< HC.lawsCheck (HC.traversableLaws (genSafeFinMap (NatRepr.knownNat @1))) , testCase "Traversable-Unsafe-FinMap-laws-1" $ assertBool "Traversable-Unsafe-FinMap-laws-1" =<< HC.lawsCheck (HC.traversableLaws (genUnsafeFinMap (NatRepr.knownNat @1))) , testCase "Traversable-Safe-FinMap-laws-10" $ assertBool "Traversable-Safe-FinMap-laws-10" =<< HC.lawsCheck (HC.traversableLaws (genSafeFinMap (NatRepr.knownNat @10))) , testCase "Traversable-Unsafe-FinMap-laws-10" $ assertBool "Traversable-Unsafe-FinMap-laws-10" =<< HC.lawsCheck (HC.traversableLaws (genUnsafeFinMap (NatRepr.knownNat @10))) #endif ]