seqn-0.1.0.0: test/MSeq.hs
{-# OPTIONS_GHC -Wno-orphans #-} -- Arbitrary instances
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module MSeq (mseqTests) where
import Prelude hiding (break, concatMap, drop, dropWhile, filter, liftA2, lookup, map, replicate, reverse, splitAt, scanl, scanr, span, take, takeWhile, traverse, zipWith, zipWith3)
import Data.Coerce (coerce)
import qualified Control.Applicative as Ap
import qualified Data.Foldable as F
import qualified Data.Foldable.WithIndex as IFo
import Data.Functor.Identity (Identity(..))
import qualified Data.List as L
import Data.Monoid (Sum(..))
import Data.Proxy (Proxy(..))
import Data.Semigroup (stimes)
import Test.Tasty
import Test.Tasty.QuickCheck hiding (generate)
import qualified Test.QuickCheck.Classes.Base as QLaws
import Data.Seqn.MSeq
import qualified Data.Seqn.Internal.MSeq as MSeqInternal
import qualified Data.Seqn.Internal.MTree as MTreeInternal
import ListExtra (unsnocL)
import qualified ListLikeTests as LL
import TestUtil ((.:), ListLike(..), Sqrt1(..), tastyLaws)
mseqTests :: TestTree
mseqTests = testGroup "Data.Seqn.MSeq"
[ testGroup "properties"
[
LL.listLike @(MSeq A)
-- Measured queries
, testProperty "summaryMay" $ \(xs :: MSeq A) ->
summaryMay xs === foldMap (Just . measure) (F.toList xs)
, testProperty "summary" $ \(xs :: MSeq D) ->
summary xs === foldMap measure (F.toList xs)
, testProperty "binarySearchPrefix" $ \(xs :: MSeq S) y ->
let p = (>=y) . getSum
xs' = F.toList xs
iws =
[ (i, foldMap measure (L.take (i+1) xs'))
| i <- [0 .. length xs - 1]
]
lastFalse = snd <$> unsnocL [i | (i,w) <- iws, not (p w)]
firstTrue = fst <$> L.uncons [i | (i,w) <- iws, p w]
in binarySearchPrefix p xs === (lastFalse, firstTrue)
, testProperty "binarySearchSuffix" $ \(xs :: MSeq S) y ->
let p = (>=y) . getSum
xs' = F.toList xs
iws =
[ (i, foldMap measure (L.drop i xs'))
| i <- [0 .. length xs - 1]
]
lastTrue = snd <$> unsnocL [i | (i,w) <- iws, p w]
firstFalse = fst <$> L.uncons [i | (i,w) <- iws, not (p w)]
in binarySearchSuffix p xs === (lastTrue, firstFalse)
-- Construct
, LL.fromRevList @(MSeq A) fromRevList
, LL.empty @(MSeq A) empty
, LL.singleton @(MSeq A) singleton
, LL.replicate @(MSeq A) replicate
, LL.replicateA @(MSeq A) replicateA
, LL.generate @(MSeq A) generate
, LL.generateA @(MSeq A) generateA
, LL.unfoldr @(MSeq A) unfoldr
, LL.unfoldl @(MSeq A) unfoldl
, LL.unfoldrM @(MSeq A) unfoldrM
, LL.unfoldlM @(MSeq A) unfoldlM
, (LL.<>) @(MSeq A) (<>)
, LL.stimes @(MSeq A) stimes
, LL.mconcat @(MSeq A) mconcat
, LL.concatMap @(MSeq A) concatMap
, testProperty "mfix" $ \n ->
F.toList (mkMfix n) ===
fmap (LI . L.replicate 10 . fromIntegral) [0 .. n-1]
, LL.read @(MSeq A)
-- Convert
, LL.toRevList @(MSeq A) toRevList
, LL.show @(MSeq A)
-- Fold
, LL.foldMap @(MSeq A) foldMap
, LL.foldMap' @(MSeq A) F.foldMap'
, LL.foldr @(MSeq A) foldr
, LL.foldl @(MSeq A) F.foldl
, LL.foldl' @(MSeq A) F.foldl'
, LL.foldr' @(MSeq A) F.foldr'
, LL.ifoldMap @(MSeq A) IFo.ifoldMap
, LL.ifoldr @(MSeq A) IFo.ifoldr
, LL.ifoldl @(MSeq A) IFo.ifoldl
, LL.ifoldr' @(MSeq A) IFo.ifoldr'
, LL.ifoldl' @(MSeq A) IFo.ifoldl'
-- Index
, LL.lookup @(MSeq A) lookup
, LL.index @(MSeq A) index
, LL.update @(MSeq A) update
, LL.adjust @(MSeq A) adjust
, LL.insertAt @(MSeq A) insertAt
, LL.deleteAt @(MSeq A) deleteAt
-- Slice
, LL.cons @(MSeq A) cons
, LL.snoc @(MSeq A) snoc
, LL.uncons @(MSeq A) uncons
, LL.unsnoc @(MSeq A) unsnoc
, LL.take @(MSeq A) take
, LL.drop @(MSeq A) drop
, LL.slice @(MSeq A) slice
, LL.splitAt @(MSeq A) splitAt
, LL.takeEnd @(MSeq A) takeEnd
, LL.dropEnd @(MSeq A) dropEnd
, LL.splitAtEnd @(MSeq A) splitAtEnd
-- Filter
, LL.filter @(MSeq A) filter
, LL.mapMaybe @(MSeq A) @(MSeq B) mapMaybe
, LL.mapEither @(MSeq A) @(MSeq B) @(MSeq C) mapEither
, LL.filterM @(MSeq A) filterA
, LL.mapMaybeM @(MSeq A) @(MSeq B) mapMaybeA
, LL.mapEitherM @(MSeq A) @(MSeq B) @(MSeq C) mapEitherA
, LL.takeWhile @(MSeq A) takeWhile
, LL.dropWhile @(MSeq A) dropWhile
, LL.span @(MSeq A) span
, LL.break @(MSeq A) break
, LL.takeWhileEnd @(MSeq A) takeWhileEnd
, LL.dropWhileEnd @(MSeq A) dropWhileEnd
, LL.spanEnd @(MSeq A) spanEnd
, LL.breakEnd @(MSeq A) breakEnd
-- Transform
, LL.map @(MSeq A) @(MSeq B) map
, LL.liftA2 @(Sqrt1 MSeq A) @(Sqrt1 MSeq B) @(Sqrt1 MSeq C) (coerce liftA2)
, LL.traverse @(MSeq A) @(MSeq B) traverse
, LL.imap @(MSeq A) @(MSeq B) imap
, LL.itraverse @(MSeq A) @(MSeq B) itraverse
, LL.reverse @(MSeq A) reverse
, LL.intersperse @(MSeq A) intersperse
, LL.scanl @(MSeq A) @(MSeq B) scanl
, LL.scanr @(MSeq A) @(MSeq B) scanr
, LL.sort @(MSeq A) sort
, LL.sortBy @(MSeq A) sortBy
-- Search and test
, LL.eq @(MSeq A) (==)
, LL.cmp @(MSeq A) compare
, LL.findEnd @(MSeq A) findEnd
, LL.findIndex @(MSeq A) findIndex
, LL.findIndexEnd @(MSeq A) findIndexEnd
, LL.infixIndices @(MSeq A) infixIndices
, LL.binarySearchFind @(MSeq A) binarySearchFind
, LL.isPrefixOf @(MSeq A) isPrefixOf
, LL.isSuffixOf @(MSeq A) isSuffixOf
, LL.isInfixOf @(MSeq A) isInfixOf
, LL.isSubsequenceOf @(MSeq A) isSubsequenceOf
-- Zip and unzip
, LL.zipWith @(MSeq A) @(MSeq B) @(MSeq C) zipWith
, LL.zipWith3 @(MSeq A) @(MSeq B) @(MSeq C) @(MSeq A) zipWith3
, LL.zipWithM @(MSeq A) @(MSeq B) @(MSeq C) zipWithM
, LL.zipWith3M @(MSeq A) @(MSeq B) @(MSeq C) @(MSeq A) zipWith3M
, LL.unzipWith @(MSeq A) @(MSeq B) @(MSeq C) unzipWith
, LL.unzipWith3 @(MSeq A) @(MSeq B) @(MSeq C) @(MSeq A) unzipWith3
]
, testGroup "valid"
[
-- Arbitrary
testProperty "arbitrary" $ id @(MSeq A)
-- Construct
, testProperty "fromList" $ fromList @A
, testProperty "fromRevList" $ fromRevList @A
, testProperty "replicate" $ replicate @A
, testProperty "generate" $ \n -> generate n . applyFun @Int @A
, testProperty "unfoldr" $ unfoldr (L.uncons @A)
, testProperty "unfoldl" $ unfoldl (unsnocL @A)
, testProperty "<>" $ (<>) @(MSeq A)
, testProperty "stimes" $ stimes @(MSeq A) @Int
, testProperty "mconcat" $ mconcat @(MSeq A)
, testProperty "concatMap []" $ concatMap @B @[] . applyFun @A
, testProperty "mfix" mkMfix
-- Index
, testProperty "adjust" $ adjust . applyFun @A
, testProperty "update" $ update @A
, testProperty "insertAt" $ insertAt @A
, testProperty "deleteAt" $ deleteAt @A
-- Slice
, testProperty "cons" $ cons @A
, testProperty "snoc" $ snoc @A
, testProperty "uncons" $ fmap snd . uncons @A
, testProperty "unsnoc" $ fmap fst . unsnoc @A
, testProperty "take" $ take @A
, testProperty "drop" $ drop @A
, testProperty "slice" $ slice @A
, testProperty "splitAt" $ splitAt @A
, testProperty "takeEnd" $ takeEnd @A
, testProperty "dropEnd" $ dropEnd @A
, testProperty "splitAtEnd" $ splitAtEnd @A
-- Transform
, testProperty "fmap" $ map . applyFun @A @B
, testProperty "liftA2" $ \(Sqrt1 xs) (Sqrt1 ys) (f :: Fun (A,B) C) ->
liftA2 (applyFun2 f) xs ys
, testProperty "traverse" $ runIdentity .: traverse . applyFun @A @(Identity B)
, testProperty "imap" $ imap . applyFun2 @_ @A @B
, testProperty "itraverse" $ runIdentity .: itraverse . applyFun2 @_ @A @(Identity B)
, testProperty "reverse" $ reverse @A
, testProperty "intersperse" $ intersperse @A
, testProperty "scanl" $ scanl . applyFun2 @A @B
, testProperty "scanr" $ scanr . applyFun2 @A @B
, testProperty "sort" $ sort @A
, testProperty "sortBy" $ sortBy @A compare
-- Filter
, testProperty "filter" $ filter . applyFun @A
, testProperty "mapMaybe" $ mapMaybe . applyFun @A @(Maybe B)
, testProperty "mapEither" $ mapEither . applyFun @A @(Either B C)
, testProperty "takeWhile" $ takeWhile . applyFun @A
, testProperty "dropWhile" $ dropWhile . applyFun @A
, testProperty "span" $ span . applyFun @A
, testProperty "break" $ break . applyFun @A
, testProperty "takeWhileEnd" $ takeWhileEnd . applyFun @A
, testProperty "dropWhileEnd" $ dropWhileEnd . applyFun @A
, testProperty "spanEnd" $ spanEnd . applyFun @A
, testProperty "breakEnd" $ breakEnd . applyFun @A
-- Zip and unzip
, testProperty "zipWith" $ zipWith . applyFun2 @A @B @C
, testProperty "zipWith3" $ zipWith3 . applyFun3 @A @B @A @C
, testProperty "unzipWith" $ unzipWith . applyFun @A @(B,C)
, testProperty "unzipWith3" $ unzipWith3 . applyFun @A @(B,C,B)
-- Random
, testProperty "random transforms" $ \tf (xs :: MSeq A) ->
let xs' = unTransform tf xs
in classify (not (null xs')) "non-empty" xs'
]
, testGroup "laws" $ L.map tastyLaws $
let pa = Proxy @(MSeq A)
porda = Proxy @(MSeq A)
in
[ QLaws.eqLaws pa
, QLaws.ordLaws porda
, QLaws.isListLaws pa
, QLaws.semigroupLaws pa
, QLaws.monoidLaws pa
, QLaws.showLaws pa
, QLaws.showReadLaws pa
]
]
instance (Arbitrary a, Measured a) => Arbitrary (MSeq a) where
arbitrary = oneof
[ fromList <$> arbitrary
, fromRevList <$> arbitrary
, randomStructure
]
where
randomStructure = sized $ \n -> do
n' <- choose (0,n)
if n' == 0
then pure empty
else Ap.liftA2 MSeqInternal.MTree arbitrary (go (n'-1))
where
go 0 = pure MTreeInternal.MTip
go n = do
ln <- choose (0,n-1)
Ap.liftA3 MTreeInternal.link arbitrary (go ln) (go (n-1-ln))
shrink = fmap fromList . shrink . F.toList
newtype Transform a = Transform { unTransform :: MSeq a -> MSeq a }
instance Show (Transform a) where
show _ = "Transform"
instance (Measured a, Arbitrary a, CoArbitrary a) => Arbitrary (Transform a) where
arbitrary = Transform . foldr (.) id <$> listOf tf
where
tf = oneof
[ (fst .) . splitAt <$> arbitrary
, (snd .) . splitAt <$> arbitrary
, (<>) <$> arbitrary
, flip (<>) <$> arbitrary
, insertAt <$> arbitrary <*> arbitrary
, deleteAt <$> arbitrary
, map <$> arbitrary
, fmap cons arbitrary
, fmap (flip snoc) arbitrary
, pure (maybe empty snd . uncons)
, pure (maybe empty fst . unsnoc)
, filter <$> arbitrary
, mapMaybe <$> arbitrary
]
instance (Show a, Measured a, Eq (Measure a), Show (Measure a)) =>
Testable (MSeq a) where
property t =
counterexample ("Invalid: " ++ MSeqInternal.debugShowsPrec 0 t "") $
MSeqInternal.valid t
instance (Testable a, Testable b, a ~ MSeq x, b ~ MSeq y) => Testable (a, b) where
property (a, b) = property a .&&. property b
instance
(Testable a, Testable b, Testable c, a ~ MSeq x, b ~ MSeq y, c ~ MSeq z) =>
Testable (a, b, c) where
property (a, b, c) = property a .&&. property b .&&. property c
instance (Testable a, a ~ MSeq x) => Testable [a] where
property = conjoin
instance Measured a => ListLike (MSeq a) where
type E (MSeq a) = a
fromL = fromList
toL = F.toList
-- A non-commutative semigroup
-- From https://math.stackexchange.com/a/2893712
newtype Bato = Bato Integer deriving (Eq, Show)
instance Semigroup Bato where
Bato x <> Bato y = Bato (abs x * y)
newtype A = A Integer
deriving newtype (Eq, Ord, Read, Show, Arbitrary, CoArbitrary)
instance Function A where
function = functionMap (\(A x) -> x) A
instance Measured A where
type Measure A = Bato
measure (A x) = Bato x
newtype B = B Integer
deriving newtype (Eq, Ord, Show, Arbitrary, CoArbitrary)
instance Function B where
function = functionMap (\(B x) -> x) B
instance Measured B where
type Measure B = Bato
measure (B x) = Bato x
newtype C = C Integer
deriving newtype (Eq, Ord, Show, Arbitrary, CoArbitrary)
instance Function C where
function = functionMap (\(C x) -> x) C
instance Measured C where
type Measure C = Bato
measure (C x) = Bato x
newtype D = D Integer
deriving newtype (Eq, Ord, Show, Arbitrary, CoArbitrary)
instance Measured D where
type Measure D = Maybe Bato
measure (D x) = Just (Bato x)
data LI = LI [Integer] deriving (Eq, Show)
takeL :: Int -> LI -> LI
takeL n (LI xs) = LI (L.take n xs)
instance Measured LI where
type Measure LI = Bato
measure (LI xs) = Bato (head xs)
-- map (replicate 10) [0..n-1]
mkMfix :: Int -> MSeq LI
mkMfix n =
map (takeL 10)
(mfix (\ ~(LI is) -> generate n (\i -> LI (fromIntegral i : is))))
newtype S = S Word
deriving newtype (Eq, Ord, Show, Arbitrary)
instance Measured S where
type Measure S = Sum Word
measure (S x) = Sum x