packages feed

radix-tree-0.1: test/TestMain.hs

----------------------------------------------------------------------------
-- |
-- Module      :  TestMain
-- Copyright   :  (c) Sergey Vinokurov 2018
-- License     :  BSD3-style (see LICENSE)
-- Maintainer  :  serg.foo@gmail.com
----------------------------------------------------------------------------

{-# LANGUAGE ScopedTypeVariables #-}

{-# OPTIONS_GHC -Wno-orphans #-}

module Main (main) where

import Data.ByteString.Short (ShortByteString)
import qualified Data.ByteString.Short as BSS

import Data.Char
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
import Data.RadixTree (RadixTree)
import qualified Data.RadixTree as RT
import Data.Word

import Test.QuickCheck
import Test.QuickCheck.Poly
import Test.Tasty
import Test.Tasty.QuickCheck as QC

newtype AsciiChar = AsciiChar { unAsciiChar :: Char }

instance Arbitrary AsciiChar where
  arbitrary = AsciiChar <$> choose ('a', 'z')
  shrink (AsciiChar 'a') = []
  shrink (AsciiChar c)   = [AsciiChar c' | c' <- ['a'..pred c]]

mkAsciiChar :: Word8 -> AsciiChar
mkAsciiChar = AsciiChar . chr. fromIntegral

asciiByte :: AsciiChar -> Word8
asciiByte = fromIntegral . ord . unAsciiChar

instance Arbitrary ShortByteString where
  arbitrary =
    BSS.pack . map asciiByte <$> listOf arbitrary
  shrink =
    map (BSS.pack . map asciiByte) . shrink . map mkAsciiChar . BSS.unpack

instance Arbitrary a => Arbitrary (RadixTree a) where
  arbitrary = RT.fromList <$> arbitrary
  shrink = map RT.fromList . shrink . RT.toAscList

main :: IO ()
main = defaultMain tests

tests :: TestTree
tests = testGroup "Tests" [properties]

properties :: TestTree
properties = testGroup "Properties" [qcProps]

qcProps :: TestTree
qcProps = adjustOption (\(QuickCheckTests n) -> QuickCheckTests (max 10000 n)) $ testGroup "radix tree"
  [ QC.testProperty "∀ t: RT.lookup k (RT.insert k v t) == v" $
    \(t :: RadixTree A) (k :: ShortByteString) (v :: A) ->
      RT.lookup k (RT.insert k v t) == Just v
  , QC.testProperty "∀ t: RT.lookup k (RT.insert k v2 (RT.insert k v1 t)) == v2" $
    \(t :: RadixTree A) (k :: ShortByteString) (v1 :: A) (v2 :: A) ->
      RT.lookup k (RT.insert k v2 (RT.insert k v1 t)) == Just v2

  , QC.testProperty "∀ xs: RT.fromList xs == M.fromList xs" $
    \(xs :: [(ShortByteString, A)]) ->
      RT.toAscList (RT.fromList xs) == M.toAscList (M.fromList xs)

  , QC.testProperty "∀ xs: RT.size (RT.fromList xs) == M.size (M.fromList xs)" $
    \(xs :: [(ShortByteString, A)]) ->
      RT.size (RT.fromList xs) == M.size (M.fromList xs)

  , QC.testProperty "∀ f: RT.mapMaybe f == M.mapMaybe f" $
    \(f :: Fun A (Maybe B)) ->
      RT.mapMaybe (applyFun f) ==== M.mapMaybe (applyFun f)

  , QC.testProperty "∀ k v t: RT.insert k v t == M.insert k v t" $
    \(k :: ShortByteString) (v :: A) ->
      RT.insert k v ==== M.insert k v

  , QC.testProperty "∀ f xs ys: RT.mergeWith f xs ys == M.mergeWith f xs ys" $
    \(f :: Fun (A, A) A) ->
      RT.unionWith (curry (applyFun f)) ===== M.unionWith (curry (applyFun f))
  ]

(====)
  :: Eq b
  => (RadixTree a -> RadixTree b)
  -> (Map ShortByteString a -> Map ShortByteString b)
  -> [(ShortByteString, a)]
  -> Bool
(====) f g xs =
  RT.toAscList (f (RT.fromList xs)) == M.toAscList (g (M.fromList xs))

(=====)
  :: Eq a
  => (RadixTree a -> RadixTree a -> RadixTree a)
  -> (Map ShortByteString a -> Map ShortByteString a -> Map ShortByteString a)
  -> [(ShortByteString, a)]
  -> [(ShortByteString, a)]
  -> Bool
(=====) f g xs ys =
  RT.toAscList (f (RT.fromList xs) (RT.fromList ys)) == M.toAscList (g (M.fromList xs) (M.fromList ys))

-- unitTests :: TestTree
-- unitTests = testGroup "Unit tests"
--   [ testCase "List comparison (different length)" $
--       [1, 2, 3] `compare` [1,2] @?= GT
--
--   -- the following test does not hold
--   , testCase "List comparison (same length)" $
--       [1, 2, 3] `compare` [1,2,2] @?= LT
--   ]