packages feed

iproute-1.8.0: test/RouteTableSpec.hs

{-# LANGUAGE FlexibleInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module RouteTableSpec where

import Control.Monad
import qualified Data.Foldable as Foldable
import Data.Function (on)
import Data.IP
import Data.IP.RouteTable.Internal
import Data.List (nub, sort)
import qualified Data.List as List
import Test.Hspec
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck

----------------------------------------------------------------
--
-- Arbitrary
--

instance Arbitrary (AddrRange IPv4) where
    arbitrary = arbitraryIP arbitrary 32

instance Arbitrary (AddrRange IPv6) where
    arbitrary = arbitraryIP arbitrary 128

instance Arbitrary IPv4 where
    arbitrary = arbitraryAdr toIPv4 255 4

instance Arbitrary IPv6 where
    arbitrary = arbitraryAdr toIPv6 65535 8

arbitraryAdr :: Routable a => ([Int] -> a) -> Int -> Int -> Gen a
arbitraryAdr func width adrlen = func <$> replicateM adrlen (choose (0, width))

arbitraryIP :: Routable a => Gen a -> Int -> Gen (AddrRange a)
arbitraryIP adrGen msklen = makeAddrRange <$> adrGen <*> choose (0, msklen)

----------------------------------------------------------------
--
-- Spec
--

spec :: Spec
spec = do
    describe "fromList" $ do
        prop
            "creates the same tree for random input and ordered input"
            (sort_ip :: [AddrRange IPv4] -> Bool)
        prop
            "creates the same tree for random input and ordered input"
            (sort_ip :: [AddrRange IPv6] -> Bool)
        prop
            "stores input in the incremental order"
            (ord_ip :: [AddrRange IPv4] -> Bool)
        prop
            "stores input in the incremental order"
            (ord_ip :: [AddrRange IPv6] -> Bool)
    describe "toList" $ do
        prop
            "expands as sorted"
            (fromto_ip :: [AddrRange IPv4] -> Bool)
        prop
            "expands as sorted"
            (fromto_ip :: [AddrRange IPv6] -> Bool)
    describe "folds" $ do
        prop "foldl" prop_foldl
        prop "foldr" prop_foldr
    describe "monoid" $ do
        prop "monoid instance" prop_monoid

sort_ip :: (Routable a, Ord a) => [AddrRange a] -> Bool
sort_ip xs = fromList (zip xs xs) == fromList (zip xs' xs')
  where
    xs' = sort xs

fromto_ip :: (Routable a, Ord a) => [AddrRange a] -> Bool
fromto_ip xs = nub (sort xs) == ys
  where
    ys = map fst . toList . fromList $ zip xs xs

ord_ip :: Routable a => [AddrRange a] -> Bool
ord_ip xs = isOrdered . fromList $ zip xs xs

isOrdered :: Routable k => IPRTable k a -> Bool
isOrdered = foldt (\x v -> v && ordered x) True

ordered :: Routable k => IPRTable k a -> Bool
ordered Nil = True
ordered (Node k _ _ l r) = ordered' k l && ordered' k r
  where
    ordered' _ Nil = True
    ordered' k1 (Node k2 _ _ _ _) = k1 >:> k2

-- Foldl and foldr properties are adapted from Data.Map tests
prop_foldl :: Int -> [(AddrRange IPv4, Int)] -> Property
prop_foldl n ys =
    not (null ys) ==>
        let xs = List.nubBy ((==) `on` fst) ys
            m = fromList xs
         in Foldable.foldl (+) n m == List.foldr ((+) . snd) n xs
                && Foldable.foldl (flip (:)) [] m == reverse (List.map snd (List.sort xs))
                && foldlWithKey (\b _ a -> a + b) n m == List.foldr ((+) . snd) n xs
                && foldlWithKey (\b k a -> (k, a) : b) [] m == reverse (List.sort xs)

prop_foldr :: Int -> [(AddrRange IPv4, Int)] -> Property
prop_foldr n ys =
    not (null ys) ==>
        let xs = List.nubBy ((==) `on` fst) ys
            m = fromList xs
         in Foldable.foldr (+) n m == List.foldr ((+) . snd) n xs
                && Foldable.foldr (:) [] m == List.map snd (List.sortBy (compare `on` fst) xs)
                && foldrWithKey (\_ a b -> a + b) n m == List.foldr ((+) . snd) n xs
                && foldrWithKey (\k a b -> (k, a) : b) [] m == List.sort xs

prop_monoid :: [(AddrRange IPv4, ())] -> [(AddrRange IPv4, ())] -> Property
prop_monoid xs ys =
    not (null xs) && not (null ys) ==>
        let xm = fromList xs
            ym = fromList ys
         in empty <> xm == xm
                && ym <> empty == ym
                && xm <> ym == fromList (xs ++ ys)