bytetrie-0.1.0.0: test/Main.hs
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
import Control.Monad (forM_)
import Data.Bifunctor (first)
import Data.Bytes.Types (Bytes (Bytes))
import Data.List (isInfixOf, sort)
import Data.Monoid (Sum)
import Data.Proxy (Proxy (..))
import Data.Trie.Word8 (Trie)
import Data.Word (Word8)
import Test.Tasty (TestTree, defaultMain, testGroup)
import Test.Tasty.HUnit (assertBool, testCase)
import Test.Tasty.QuickCheck (Arbitrary, Discard (Discard), property, testProperty, (===))
import qualified Data.Bytes as Bytes
import qualified Data.Bytes.Text.Latin1 as Latin1
import qualified Data.Trie.Word8 as Trie
import qualified GHC.Exts as Exts
import qualified Test.QuickCheck.Classes as QCC
import qualified Test.Tasty.QuickCheck as TQC
import qualified TestData
main :: IO ()
main = defaultMain tests
tests :: TestTree
tests =
testGroup
"bytetrie"
[ testGroup
"validity"
[ testProperty "fromList validity" $ \alist ->
let a = Trie.fromList alist :: Trie Int
in Trie.valid a
, testProperty "unionWith maintains invariants" $ \xsList ysList ->
let xs = Trie.fromList xsList :: Trie Int
ys = Trie.fromList ysList
in Trie.valid (Trie.unionWith (+) xs ys)
, testProperty "delete maintains invariants" $ \alist k v ->
let a = Trie.fromList alist :: Trie Int
a' = Trie.insert k v a
in Trie.valid (Trie.delete k a) && Trie.valid (Trie.delete k a')
]
, testGroup
"lookup"
[ testProperty "lookup existing key" $
\(xs :: [(Bytes, Int)]) (k, v) ys ->
let alist = xs ++ [(k, v)] ++ ys
in if
| Nothing <- lookup k ys ->
Trie.lookup k (Trie.fromList alist) === lookup k (reverse alist)
| otherwise -> property Discard
, testProperty "lookup missing key" $
\(alist :: [(Bytes, Int)]) (k :: Bytes) ->
if
| Just _ <- lookup k alist -> property Discard
| otherwise -> Trie.lookup k (Trie.fromList alist) === Nothing
]
, testGroup
"unionWith"
[ lawsToTest (QCC.semigroupLaws (Proxy :: Proxy (Trie [Integer])))
, lawsToTest (QCC.monoidLaws (Proxy :: Proxy (Trie [Integer])))
, lawsToTest (QCC.commutativeMonoidLaws (Proxy :: Proxy (Trie (Sum Integer))))
]
, lawsToTest (QCC.functorLaws (Proxy :: Proxy Trie))
, testGroup
"lookupTrie"
[ testProperty "alpha" $
\(x :: Bytes) (y :: Bytes) (val :: Int) (xs :: [(Bytes, Int)]) ->
let base = Trie.insert (x <> y) val (Trie.fromList xs)
in Trie.lookup (x <> y) base === Trie.lookup y (Trie.lookupTrie x base)
, testProperty "beta" $
\(x :: Bool) (y :: Bool) (val :: Int) (xs :: [([Bool], Int)]) ->
let xs' = map (first (foldMap boolToBytes)) xs
base = Trie.insert (boolToBytes x <> boolToBytes y) val (Trie.fromList xs')
in Trie.lookup (boolToBytes x <> boolToBytes y) base === Trie.lookup (boolToBytes y) (Trie.lookupTrie (boolToBytes x) base)
]
, testGroup
"stripPrefix"
[ testProperty "finds longest prefix" $
\(a :: Bytes) (b :: Bytes) (c :: Bytes) (d :: Bytes) ->
if
| Just (wc, _) <- Bytes.uncons c
, Just (wd, _) <- Bytes.uncons d
, wc /= wd ->
let t :: Trie Int = Trie.fromList [(a, 1), (a <> b, 2), (a <> b <> d, 3)]
expected = Just ((a <> b, 2), c)
found = Trie.stripPrefixWithKey t (a <> b <> c)
in found === expected
| otherwise -> property Discard
, testProperty "finds nothing" $
\(a :: Bytes) (b :: Bytes) ->
if
| Just (wa, _) <- Bytes.uncons a
, Just (wb, _) <- Bytes.uncons b
, wa /= wb ->
let t :: Trie Int = Trie.fromList [(a, 1)]
in Trie.stripPrefixWithKey t b === Nothing
| otherwise -> property Discard
]
, testGroup
"delete"
[ testProperty "maintains invariant" $
\(xs :: [(Bytes, Int)]) (k, v) ys ->
if
| Nothing <- lookup k xs
, Nothing <- lookup k ys ->
let t = Trie.fromList $ xs ++ [(k, v)] ++ ys
t' = Trie.fromList $ xs ++ ys
in Trie.delete k t === t'
| otherwise -> property Discard
]
, testProperty "toList is sorted" $ \alist ->
let a = Trie.fromList alist :: Trie ()
out = Trie.toList a
in out == sort out
, testCase "sed works" $ do
let sedList = Trie.multiFindReplace Latin1.toString Latin1.toString
outp = sedList TestData.replacements TestData.bigstring
replaced = ["Francisco", "Bernardo", "Marcellus", "Horatio", "Ghost", "What", "Why"]
forM_ replaced $ \word -> do
let assertName = "replaced " ++ word
assertBool assertName $ not (word `isInfixOf` outp)
]
instance (Arbitrary a) => Arbitrary (Trie a) where
arbitrary = Trie.fromList <$> TQC.arbitrary
instance Arbitrary Bytes where
arbitrary = do
xs :: [Word8] <- TQC.arbitrary
front <- TQC.choose (0, 2)
back <- TQC.choose (0, 2)
let frontPad = replicate front (254 :: Word8)
let backPad = replicate back (254 :: Word8)
let raw = Exts.fromList (frontPad ++ xs ++ backPad)
pure (Bytes raw front (length xs))
lawsToTest :: QCC.Laws -> TestTree
lawsToTest (QCC.Laws name pairs) = testGroup name (map (uncurry TQC.testProperty) pairs)
boolToBytes :: Bool -> Bytes
boolToBytes True = Bytes.singleton 1
boolToBytes False = Bytes.singleton 0