packages feed

hic-0.0.0.1: test/Language/Cimple/Analysis/Refined/ContextSpec.hs

{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Language.Cimple.Analysis.Refined.ContextSpec (spec) where

import           Control.Exception                          (evaluate)
import           Data.Bits                                  (shiftR, (.&.))
import           Data.Word                                  (Word32)
import           Language.Cimple.Analysis.Refined.Arbitrary ()
import           Language.Cimple.Analysis.Refined.Context
import           Test.Hspec
import           Test.Hspec.QuickCheck                      (prop)
import           Test.QuickCheck                            ((===))

spec :: Spec
spec = do
    describe "MappingContext" $ do
        it "starts empty" $ do
            getMapping 0 emptyContext `shouldBe` Nothing

        it "can push and retrieve a mapping" $ do
            let ctx = pushMapping 5 emptyContext
            getMapping 0 ctx `shouldBe` Just 5
            getMapping 1 ctx `shouldBe` Nothing

        it "can store multiple mappings" $ do
            let ctx = pushMapping 3 $ pushMapping 7 emptyContext
            getMapping 0 ctx `shouldBe` Just 3
            getMapping 1 ctx `shouldBe` Just 7
            getMapping 2 ctx `shouldBe` Nothing

        it "supports up to 15 mappings (due to 60/4)" $ do
            let ctx = foldl (flip pushMapping) emptyContext [0..14]
            getMapping 0 ctx `shouldBe` Just 14
            getMapping 14 ctx `shouldBe` Just 0
            getMapping 15 ctx `shouldBe` Nothing

        it "saturates at 30 mappings (due to 128-bit structure)" $ do
            -- The count field (bits 0-7) is capped at 30.
            let ctx = foldl (flip pushMapping) emptyContext [0..30]
            getMapping 29 ctx `shouldBe` Just 1
            getMapping 30 ctx `shouldBe` Nothing

        prop "last pushed mapping is at index 0" $ \m (ctx :: MappingContext) ->
            getMapping 0 (pushMapping m ctx) == Just (m `mod` 16)

        prop "pushing preserves existing mappings at shifted indices" $ \m (ctx :: MappingContext) ->
            let newCtx = pushMapping m ctx
                check i = getMapping (i + 1) newCtx == getMapping i ctx
            in all check [0..13]

        prop "getMapping returns Nothing for index >= count" $ \ctx ->
            let MappingContext w1 _ = ctx
                count = fromIntegral (w1 .&. 0xFF)
            in all (\i -> getMapping i ctx == Nothing) [count..29]

    describe "MappingRefinements" $ do
        it "starts empty" $ do
            getRefinement 0 emptyRefinements `shouldBe` Nothing

        it "can set and get refinements" $ do
            let refs = setRefinement 123 456 emptyRefinements
            getRefinement 123 refs `shouldBe` Just 456
            getRefinement 124 refs `shouldBe` Nothing

        it "can store many refinements" $ do
            let refs = foldl (\r i -> setRefinement i (fromIntegral i + 10) r) emptyRefinements [0..1000]
            getRefinement 0 refs `shouldBe` Just 10
            getRefinement 1000 refs `shouldBe` Just 1010

        prop "set and get refinement" $ \key (nodeID :: Word32) ->
            let n = nodeID .&. 0x3FFFFFFF
                refs' = setRefinement key n emptyRefinements
            in getRefinement key refs' === Just n