packages feed

impure-containers-0.5.0: test/Spec.hs

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Main (main) where

import           Control.Applicative
import           Data.Coerce
import           Data.Foldable
import           Data.Functor.Compose
import           Data.Monoid                          (All (..))
import           Data.Traversable
import           Test.Framework
                 (Test, defaultMain, testGroup)
import           Test.Framework.Providers.HUnit       (testCase)
import           Test.Framework.Providers.QuickCheck2 (testProperty)
import           Test.HUnit                           (Assertion, (@?=))
import           Test.QuickCheck
                 (Arbitrary (..), Gen, choose, listOf, shrinkIntegral,
                 vectorOf)

import           Control.Monad
import           Control.Monad.ST
import           Data.Function                        (on)
import           Data.Functor.Identity
import           Data.List                            (groupBy)
import qualified Data.List                            as List
import qualified Data.Map.Strict                      as Map
import qualified Data.Set                             as Set
import           Data.Word
import           Debug.Trace

import           Data.Primitive.Array.Maybe

import qualified Data.ArrayList.Generic               as ArrayList
import qualified Data.Graph.Immutable                 as Graph
import qualified Data.Graph.Mutable                   as MGraph
import qualified Data.Heap.Mutable.ModelD             as HeapD
import           Data.Maybe.Unsafe                    hiding (maybe)
import qualified Data.Maybe.Unsafe                    as UMaybe
import qualified Data.Trie.Mutable.Bits               as BitTrie
import qualified Data.Vector                          as V

main :: IO ()
main = defaultMain tests

tests :: [Test]
tests =
  [ testGroup "Heaps"
    [ testProperty "Model D Push No Crash" multipush
    , testProperty "Model D Push Pop" heapPushPop
    , testProperty "Model D List" heapMatchesList
    ]
  , testGroup "ArrayList"
    [ testProperty "Insertion followed by freezing" arrayListWorks
    ]
  , testGroup "Graph"
    [ testProperty "Building only from vertices" graphBuildingVertices
    , testProperty "Trivial case for Dijkstras Algorithm" dijkstraEasyDistance
    ]
  , testGroup "MaybeArray"
    [ testCase "Values are as expected" maybeArrayWorks
    ]
  , testGroup "Bit Trie"
    [ testProperty "Basic Insert and Lookup" bitTrieBasic
    , testProperty "Prefixes" bitTriePrefix
    ]
  , testGroup "Unsafe Maybe"
    [ testCase "Unsafe Maybe functions" unsafeMaybeWorks
    -- , testProperty "Unsafe Maybe Quickcheck props" unsafeMaybeProp
    ]
  ]

testElements :: Int
testElements = 15

newtype Min = Min { getMin :: Word32 }
  deriving (Show,Read,Eq,Ord)

instance Arbitrary Min where
  arbitrary = fmap Min (choose (0,20))
  shrink (Min a) = fmap Min $ filter (>= 0) $ shrinkIntegral a

instance Monoid Min where
  mempty = Min maxBound
  mappend (Min a) (Min b) = Min (min a b)

newtype MyElement = MyElement { getMyElement :: Int }
  deriving (Show,Read,Eq,Ord)

instance Arbitrary MyElement where
  arbitrary = fmap MyElement (choose (0,fromIntegral testElements - 1))
  shrink (MyElement a) = fmap MyElement $ filter (>= 0) $ shrinkIntegral a -- fmap MyElement (enumFromTo 0 (a - 1))

newtype TenElemsOrLessList a = TenElemsOrLessList [a]
  deriving (Read,Show,Eq,Ord)

instance Arbitrary a => Arbitrary (TenElemsOrLessList a) where
  arbitrary = fmap TenElemsOrLessList $ flip vectorOf arbitrary =<< choose (0 :: Int,10)
  shrink (TenElemsOrLessList a) = fmap TenElemsOrLessList $ shrink a

multipush :: [(Min,MyElement)] -> Bool
multipush xs = runST $ do
  h <- trace "Running Test" (HeapD.new testElements)
  HeapD.pushList (coerce xs :: [(Min,Int)]) h
  return True

heapPushPop :: [(Min,MyElement)] -> Bool
heapPushPop xs =
  let res = runST $ do
        h <- HeapD.new testElements
        forM xs $ \(c,MyElement i) -> do
          HeapD.push c i h
          HeapD.pop h
   in sequence res == coerce (Just xs)

heapMatchesList :: [(Min,MyElement)] -> Bool
heapMatchesList xs' =
  let xs = coerce xs' :: [(Min,Int)]
      xsSet = fmap (\(p,e) -> (e,p)) xs
      ys = Map.fromListWith mappend xsSet
      listRes = Map.toList $ Map.fromListWith Set.union $ map (\(e,p) -> (p,Set.singleton e)) (Map.toList ys)
      heapRes = runST $ do
        h <- HeapD.new testElements
        HeapD.pushList xs h
        HeapD.popAll h
      heapResSet = map (\pairs@((p,_) : _) -> (p,Set.fromList $ map snd pairs))
        $ groupBy (on (==) fst) heapRes
  in heapResSet == listRes

arrayListWorks :: [Int] -> Bool
arrayListWorks xs =
  let ys = runST $ do
        a <- ArrayList.new 1
        forM_ xs $ \x -> ArrayList.push a x
        ArrayList.freeze a
   in xs == V.toList ys

-- This makes sure that when you insert a bunch of vertices into
-- a graph, you get the same vertices back out. This does not
-- do anything to check for edges.
graphBuildingVertices :: [Int] -> Bool
graphBuildingVertices xs =
  let sg = runST $ Graph.create $ \mg -> do
        forM_ xs (MGraph.insertVertex mg)
      ys = Graph.with sg (Graph.verticesToVector . Graph.vertices)
   in List.nub (List.sort xs) == List.sort (V.toList ys)

graphBuildingEdgesEverywhere :: TenElemsOrLessList Int -> Bool
graphBuildingEdgesEverywhere (TenElemsOrLessList xs) =
  let onlyEdge = 77 :: Int
      sg = runST $ Graph.create $ \mg -> do
        vertices <- forM xs $ \x -> do
          MGraph.insertVertex mg x
        forM_ vertices $ \source -> do
          forM_ vertices $ \dest -> do
            MGraph.insertEdge mg source dest onlyEdge
   in getAll $ getConst $ Graph.with sg $ \g ->
             let v = Graph.vertices g -- Graph.vertices g Graph.verticesToVector . Graph.vertices)
                 vlist = Graph.verticesToVertexList v
              in for vlist $ \va ->
                   for vlist $ \vb ->
                     Const (All $ Graph.lookupEdge va vb g == Just onlyEdge)

-- Every node is connected to at most two other nodes. The end
-- nodes only have one neighbor. Go from one end node to the other.
dijkstraEasyDistance :: [Word32] -> Bool
dijkstraEasyDistance xs =
  let sg = runST $ Graph.create $ \mg -> do
        start <- MGraph.insertVertex mg (0 :: Int)
        let insertNext prevVertex zs i = case zs of
              [] -> return ()
              y : ys -> do
                vertex <- MGraph.insertVertex mg i
                MGraph.insertEdge mg prevVertex vertex y
                insertNext vertex ys (i + 1)
        insertNext start xs 1
   in Graph.with sg $ \g -> case (Graph.lookupVertex 0 g, Graph.lookupVertex (List.length xs) g) of
        (Nothing,Nothing) -> False
        (Nothing,Just _)  -> False
        (Just _,Nothing)  -> False
        (Just start, Just end) ->
          let expected = Min (sum xs)
           in expected == Graph.atVertex end
                (Graph.dijkstra
                  (\_ _ (Min x) distance -> Min (x + distance))
                  (Min 0) (Identity start) g
                )

data Thing = Foo | Bar Int | Baz Bool
  deriving (Eq,Show)

maybeArrayWorks :: IO ()
maybeArrayWorks = do
  arr <- newMaybeArray 17 Nothing
  writeMaybeArray arr 0 (Just Foo)
  writeMaybeArray arr 9 (Just (Bar 62))
  writeMaybeArray arr 16 (Just (Baz True))
  a <- readMaybeArray arr 0
  b <- readMaybeArray arr 9
  c <- readMaybeArray arr 16
  d <- readMaybeArray arr 12
  arr2 <- newMaybeArray 17 (Just (Baz True))
  writeMaybeArray arr 2 Nothing
  writeMaybeArray arr 7 (Just (Bar 15))
  e <- readMaybeArray arr 2
  f <- readMaybeArray arr 7
  (a,b,c,d,e,f) @?=
    ( Just Foo, Just $ Bar 62
    , Just $ Baz True, Nothing
    , Nothing, Just (Bar 15)
    )

unsafeMaybeWorks :: IO ()
unsafeMaybeWorks = (a,b,c,d,e,f,g,h) @?= (Nothing,Just 0,1,Nothing,Just 3,Nothing,Just 4,Nothing)
  where a = toMaybe $ nothing :: Maybe Int
        b = toMaybe $ just (0 :: Int)
        c = UMaybe.maybe (0 :: Int) (+1) (just 0)
        d = toMaybe $ nothing :: Maybe Int
        e = toMaybe $ fmap (+1) (just 2)
        f = toMaybe $ fmap (+1) nothing
        g = toMaybe $ just (just 4) >>= id
        h = toMaybe $ UMaybe.maybe nothing (const nothing) nothing :: Maybe Int

bitTrieBasic :: [Word8] -> Bool
bitTrieBasic xs =
  let res = runST $ do
        trie <- BitTrie.new
        for_ xs $ \x -> BitTrie.insert trie x x
        Const (All res) <- getCompose $ for_ xs $ \x -> Compose $ do
          m <- BitTrie.lookup trie x
          return $ Const $ case m of
            Nothing -> All False
            Just y -> All (x == y)
        return res
   in res == True

bitTriePrefix :: Word8 -> Bool
bitTriePrefix x = do
  let res = runST $ do
        trie <- BitTrie.new
        BitTrie.insertPrefix trie 4 0xF0 'B'
        BitTrie.insertPrefix trie 1 0x80 'A'
        BitTrie.insertPrefix trie 4 0x00 'C'
        m <- BitTrie.lookup trie x
        return $ case () of
          () | x <  0x10 -> m == Just 'C'
             | x >= 0xF0 -> m == Just 'B'
             | x >= 0x80 -> m == Just 'A'
             | otherwise -> m == Nothing
   in res == True