{-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE PatternGuards #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} module Test.Context ( contextTests , genSomePayloadList , mkUAsgn , mkSAsgn ) where import Control.Lens import Data.Functor.Product (Product(Pair)) import Data.Kind import Data.Parameterized.Classes import qualified Data.Parameterized.Context as C import qualified Data.Parameterized.Context.Safe as S import qualified Data.Parameterized.Context.Unsafe as U import Data.Parameterized.Ctx import qualified Data.Parameterized.Ctx.Proofs as P import Data.Parameterized.Some import Data.Parameterized.TraversableFC import Data.Parameterized.TraversableFC.WithIndex import Hedgehog import qualified Hedgehog.Gen as HG import Hedgehog.Range import Test.Tasty import Test.Tasty.HUnit ( (@=?), (@?=), testCaseSteps ) import Test.Tasty.Hedgehog ---------------------------------------------------------------------- -- Create a Payload GADT which is the parameterized type used for many -- of the Context/Assignment tests in this module. data Payload (ty :: Type) where IntPayload :: Int -> Payload Int StringPayload :: String -> Payload String BoolPayload :: Bool -> Payload Bool deriving instance Eq (Payload ty) instance TestEquality Payload where testEquality (IntPayload x) (IntPayload y) = if x == y then Just Refl else Nothing testEquality (StringPayload x) (StringPayload y) = if x == y then Just Refl else Nothing testEquality (BoolPayload x) (BoolPayload y) = if x == y then Just Refl else Nothing testEquality _ _ = Nothing instance Show (Payload tp) where show (IntPayload x) = show x <> " :: Int" show (StringPayload x) = show x <> " :: String" show (BoolPayload x) = show x <> " :: Bool" instance ShowF Payload twiddle :: Payload a -> Payload a twiddle (IntPayload n) = IntPayload (n+1) twiddle (StringPayload str) = StringPayload (str++"asdf") twiddle (BoolPayload b) = BoolPayload (not b) twaddle :: Payload a -> Payload a twaddle (IntPayload n) = IntPayload (n-1) twaddle (StringPayload str) = StringPayload (reverse str) twaddle (BoolPayload b) = BoolPayload (not b) newtype Fun = Fun (forall a. Payload a -> Payload a) instance Show Fun where show _ = "unshowable" -- | Functions for e.g. testing functor laws funs :: [Fun] funs = [Fun twiddle, Fun twaddle, Fun id] ---------------------------------------------------------------------- -- Create another parameterized type for testing. This one is not a -- GADT, which will require some interesting implementation tricks. -- -- The common 'Maybe' type is potentially useable for this type, but -- there are some restrictions on 'Maybe'. For example, it is not -- possible to create a @ShowF Maybe@ because although 'Maybe' is of type -- @(k -> type)@, @k@ is unconstrained and doesn't contain a 'Show' -- constraint. data MyMaybe t = (Show t) => MyJust t | MyNothing instance ShowF MyMaybe instance Show (MyMaybe t) where show (MyJust x) = "MyJust " <> show x show MyNothing = "MyNothing" ---------------------------------------------------------------------- -- Some Hedgehog generators genSomePayload :: Monad m => GenT m (Some Payload) genSomePayload = HG.choice [ Some . IntPayload <$> HG.integral (linearBounded :: Range Int) , Some . StringPayload <$> HG.string (linear 1 32) HG.ascii , Some . BoolPayload <$> HG.element [ True, False ] ] -- generate a non-empty list of payload entries genSomePayloadList :: Monad m => GenT m [Some Payload] genSomePayloadList = HG.list (linear 1 10) genSomePayload type UAsgn = U.Assignment Payload type SAsgn = S.Assignment Payload mkUAsgn :: [Some Payload] -> Some UAsgn mkUAsgn = go U.empty where go :: UAsgn ctx -> [Some Payload] -> Some UAsgn go a [] = Some a go a (Some x : xs) = go (U.extend a x) xs mkSAsgn :: [Some Payload] -> Some SAsgn mkSAsgn = go S.empty where go :: SAsgn ctx -> [Some Payload] -> Some SAsgn go a [] = Some a go a (Some x : xs) = go (S.extend a x) xs ---------------------------------------------------------------------- -- A Ctx type that will be used for some of the Assignments tested here type TestCtx = U.EmptyCtx '::> Int '::> String '::> Int '::> Bool ---------------------------------------------------------------------- contextTests :: IO TestTree contextTests = testGroup "Context" <$> return [ testProperty "size (unsafe)" $ property $ do vals <- forAll genSomePayloadList Some a <- return $ mkUAsgn vals length vals === U.sizeInt (U.size a) , testProperty "size (safe)" $ property $ do vals <- forAll genSomePayloadList Some a <- return $ mkSAsgn vals length vals === S.sizeInt (S.size a) , testProperty "safe_index_eq" $ property $ do vals <- forAll genSomePayloadList i' <- forAll $ HG.int (linear 0 $ length vals - 1) Some a <- return $ mkSAsgn vals Just (Some idx) <- return $ S.intIndex i' (S.size a) Some (a S.! idx) === vals !! i' , testProperty "unsafe_index_eq" $ property $ do vals <- forAll genSomePayloadList i' <- forAll $ HG.int (linear 0 $ length vals - 1) Some a <- return $ mkUAsgn vals Just (Some idx) <- return $ U.intIndex i' (U.size a) Some (a U.! idx) === vals !! i' , testProperty "safe_tolist" $ property $ do vals <- forAll genSomePayloadList Some a <- return $ mkSAsgn vals let vals' = toListFC Some a vals === vals' , testProperty "unsafe_tolist" $ property $ do vals <- forAll genSomePayloadList Some a <- return $ mkUAsgn vals let vals' = toListFC Some a vals === vals' , testProperty "adjust test monadic" $ property $ do vals <- forAll genSomePayloadList i' <- forAll $ HG.int (linear 0 $ length vals - 1) Some x <- return $ mkUAsgn vals Some y <- return $ mkSAsgn vals Just (Some idx_x) <- return $ U.intIndex i' (U.size x) Just (Some idx_y) <- return $ S.intIndex i' (S.size y) x' <- U.adjustM (return . twiddle) idx_x x y' <- S.adjustM (return . twiddle) idx_y y toListFC Some x' === toListFC Some y' , testProperty "adjust test" $ property $ do vals <- forAll genSomePayloadList i' <- forAll $ HG.int (linear 0 $ length vals - 1) Some x <- return $ mkUAsgn vals Some y <- return $ mkSAsgn vals Just (Some idx_x) <- return $ U.intIndex i' (U.size x) Just (Some idx_y) <- return $ S.intIndex i' (S.size y) let x' = x & ixF idx_x %~ twiddle y' = y & ixF idx_y %~ twiddle toListFC Some x' === toListFC Some y' -- adjust actually modified the entry toListFC Some x /== toListFC Some x' toListFC Some y /== toListFC Some y' , testProperty "update test" $ property $ do vals <- forAll genSomePayloadList i' <- forAll $ HG.int (linear 0 $ length vals - 1) Some x <- return $ mkUAsgn vals Some y <- return $ mkSAsgn vals Just (Some idx_x) <- return $ U.intIndex i' (U.size x) Just (Some idx_y) <- return $ S.intIndex i' (S.size y) let x' = over (ixF idx_x) twiddle x y' = (ixF idx_y) %~ twiddle $ y updX = x & ixF idx_x .~ x' U.! idx_x updY = y & ixF idx_y .~ y' S.! idx_y toListFC Some updX === toListFC Some updY -- update actually modified the entry toListFC Some x /== toListFC Some updX toListFC Some y /== toListFC Some updY -- update modified the expected entry toListFC Some x' === toListFC Some updX toListFC Some y' === toListFC Some updY , testProperty "safe_eq" $ property $ do vals1 <- forAll genSomePayloadList vals2 <- forAll genSomePayloadList Some x <- return $ mkSAsgn vals1 Some y <- return $ mkSAsgn vals2 case testEquality x y of Just Refl -> vals1 === vals2 Nothing -> vals1 /== vals2 , testProperty "unsafe_eq" $ property $ do vals1 <- forAll genSomePayloadList vals2 <- forAll genSomePayloadList Some x <- return $ mkUAsgn vals1 Some y <- return $ mkUAsgn vals2 case testEquality x y of Just Refl -> vals1 === vals2 Nothing -> vals1 /== vals2 , testProperty "take none" $ property $ do vals1 <- forAll genSomePayloadList vals2 <- forAll genSomePayloadList vals3 <- forAll genSomePayloadList Some w <- return $ mkUAsgn vals1 Some x <- return $ mkUAsgn vals2 Some y <- return $ mkUAsgn vals3 let z = w U.<++> x U.<++> y case P.leftId z of Refl -> let r = C.take U.zeroSize (U.size z) z in assert $ isJust $ testEquality U.empty r , testProperty "drop none" $ property $ do vals1 <- forAll genSomePayloadList vals2 <- forAll genSomePayloadList vals3 <- forAll genSomePayloadList Some w <- return $ mkUAsgn vals1 Some x <- return $ mkUAsgn vals2 Some y <- return $ mkUAsgn vals3 let z = w U.<++> x U.<++> y case P.leftId z of Refl -> let r = C.drop U.zeroSize (U.size z) z in assert $ isJust $ testEquality z r , testProperty "take all" $ property $ do vals1 <- forAll genSomePayloadList vals2 <- forAll genSomePayloadList vals3 <- forAll genSomePayloadList Some w <- return $ mkUAsgn vals1 Some x <- return $ mkUAsgn vals2 Some y <- return $ mkUAsgn vals3 let z = w U.<++> x U.<++> y let r = C.take (U.size z) U.zeroSize z assert $ isJust $ testEquality z r , testProperty "drop all" $ property $ do vals1 <- forAll genSomePayloadList vals2 <- forAll genSomePayloadList vals3 <- forAll genSomePayloadList Some w <- return $ mkUAsgn vals1 Some x <- return $ mkUAsgn vals2 Some y <- return $ mkUAsgn vals3 let z = w U.<++> x U.<++> y let r = C.drop (U.size z) U.zeroSize z assert $ isJust $ testEquality U.empty r , testProperty "append_take" $ property $ do vals1 <- forAll genSomePayloadList vals2 <- forAll genSomePayloadList Some x <- return $ mkUAsgn vals1 Some y <- return $ mkUAsgn vals2 let z = x U.<++> y let x' = C.take (U.size x) (U.size y) z assert $ isJust $ testEquality x x' , testProperty "append_take_drop" $ property $ do vals1 <- forAll genSomePayloadList vals2 <- forAll genSomePayloadList Some x <- return $ mkUAsgn vals1 Some y <- return $ mkUAsgn vals2 let z = x U.<++> y let x' = C.take (U.size x) (U.size y) z let y' = C.drop (U.size x) (U.size y) z assert $ isJust $ testEquality x x' assert $ isJust $ testEquality y y' , testProperty "append_take_drop_multiple" $ property $ do vals1 <- forAll genSomePayloadList vals2 <- forAll genSomePayloadList vals3 <- forAll genSomePayloadList vals4 <- forAll genSomePayloadList vals5 <- forAll genSomePayloadList Some u <- return $ mkUAsgn vals1 Some v <- return $ mkUAsgn vals2 Some w <- return $ mkUAsgn vals3 Some x <- return $ mkUAsgn vals4 Some y <- return $ mkUAsgn vals5 let uv = u U.<++> v let wxy = w U.<++> x U.<++> y -- let z = u C.<++> v C.<++> w C.<++> x C.<++> y let z = uv U.<++> wxy let uv' = C.take (U.size uv) (U.size wxy) z let wxy' = C.drop (U.size uv) (U.size wxy) z let withWXY = C.dropPrefix z uv (error "failed dropPrefix") assert $ isJust $ testEquality (u U.<++> v) uv' assert $ isJust $ testEquality (w U.<++> x U.<++> y) wxy' assert $ isJust $ testEquality uv uv' assert $ isJust $ testEquality wxy wxy' withWXY $ \t -> assert $ isJust $ testEquality wxy' t , testProperty "zip/unzip" $ property $ do Some x <- mkUAsgn <$> forAll genSomePayloadList let zipped = C.zipWith Pair x x let (x', x'') = C.unzip zipped assert $ isJust $ testEquality x x' assert $ isJust $ testEquality x x'' , testProperty "fmapFC_identity" $ property $ do Some x <- mkUAsgn <$> forAll genSomePayloadList assert $ isJust $ testEquality x (fmapFC id x) , testProperty "fmapFC_assoc" $ property $ do Some x <- mkUAsgn <$> forAll genSomePayloadList Fun f <- forAll $ HG.element funs Fun g <- forAll $ HG.element funs assert $ isJust $ testEquality (fmapFC g (fmapFC f x)) (fmapFC (g . f) x) , testProperty "imapFC_index_noop" $ property $ do Some x <- mkUAsgn <$> forAll genSomePayloadList assert $ isJust $ testEquality x (imapFC (\idx _ -> x U.! idx) x) , testProperty "imapFC/fmapFC" $ property $ do Some x <- mkUAsgn <$> forAll genSomePayloadList Fun f <- forAll $ HG.element funs assert $ isJust $ testEquality (fmapFC f x) (imapFC (const f) x) , testProperty "ifoldMapFC/foldMapFC" $ property $ do Some x <- mkUAsgn <$> forAll genSomePayloadList assert $ foldMapFC show x == ifoldMapFC (const show) x , testProperty "itraverseFC/traverseFC" $ property $ do Some x <- mkUAsgn <$> forAll genSomePayloadList Fun f <- forAll $ HG.element funs let f' :: forall a. Payload a -> Identity (Payload a) f' = Identity . f assert $ isJust $ testEquality (runIdentity (traverseFC f' x)) (runIdentity (itraverseFC (const f') x)) , testCaseSteps "explicit indexing (unsafe)" $ \step -> do let mkUPayload :: U.Assignment Payload TestCtx mkUPayload = U.empty `U.extend` IntPayload 1 `U.extend` StringPayload "two" `U.extend` IntPayload 3 `U.extend` BoolPayload True -- Alternative construction using the 'generate' and a -- function consuming @Index ctx tp@ selectors to return -- the corresponding value mkUMyMaybe :: U.Assignment MyMaybe TestCtx mkUMyMaybe = U.generate U.knownSize setMyValue setMyValue :: U.Index TestCtx tp -> MyMaybe tp setMyValue idx | Just Refl <- testEquality (U.lastIndex U.knownSize) idx = MyJust False | Just Refl <- testEquality (U.skipIndex $ U.skipIndex $ U.skipIndex U.baseIndex) idx = MyJust 10 | Just Refl <- testEquality (U.skipIndex $ U.skipIndex $ U.nextIndex U.knownSize) idx = MyJust "twenty" | Just Refl <- testEquality (U.skipIndex $ U.nextIndex U.knownSize) idx = MyNothing | otherwise = error $ "setMyValue with unrecognized Index " <> show idx step "Verify size of Assignment" U.sizeInt (U.size mkUPayload) @?= 4 step "Verify show of Assignment" "[1 :: Int, \"two\" :: String, 3 :: Int, True :: Bool]" @=? show mkUPayload "[MyJust 10, MyJust \"twenty\", MyNothing, MyJust False]" @=? show mkUMyMaybe step "Verify show explicit indexing" Just "\"two\" :: String" @=? do Some i <- U.intIndex 1 (U.size mkUPayload) return $ show $ mkUPayload U.! i Just "1 :: Int" @=? do Some i <- U.intIndex 0 (U.size mkUPayload) return $ show $ mkUPayload U.! i "#<; @0=1 :: Int; @1=\"two\" :: String; @2=3 :: Int; @3=True :: Bool" @=? U.forIndex U.knownSize (\s idx -> s <> "; @" <> show idx <> "=" <> show (mkUPayload U.! idx)) "#<" (Nothing @String) @=? do Some i <- U.intIndex 8 (U.size mkUPayload) return $ show $ mkUPayload U.! i step "Verify invalid type at index" (Nothing :: Maybe Bool) @=? do Some i <- U.intIndex 1 (U.size mkUPayload) Refl <- testEquality (mkUPayload U.! i) (IntPayload 1) return True , testCaseSteps "explicit indexing (safe)" $ \step -> do let mkSPayload :: S.Assignment Payload TestCtx mkSPayload = S.empty `S.extend` IntPayload 1 `S.extend` StringPayload "two" `S.extend` IntPayload 3 `S.extend` BoolPayload True -- Alternative construction using the 'generate' and a -- function consuming @Index ctx tp@ selectors to return -- the corresponding value mkSMyMaybe :: S.Assignment MyMaybe TestCtx mkSMyMaybe = S.generate S.knownSize setMyValue setMyValue :: S.Index TestCtx tp -> MyMaybe tp setMyValue idx | Just Refl <- testEquality (S.lastIndex S.knownSize) idx = MyJust False | Just Refl <- testEquality (S.skipIndex $ S.skipIndex $ S.skipIndex S.baseIndex) idx = MyJust 10 | Just Refl <- testEquality (S.skipIndex $ S.skipIndex $ S.nextIndex S.knownSize) idx = MyJust "twenty" | Just Refl <- testEquality (S.skipIndex $ S.nextIndex S.knownSize) idx = MyNothing | otherwise = error $ "setMyValue with unrecognized Index " <> show idx step "Verify size of Assignment" S.sizeInt (S.size mkSPayload) @?= 4 step "Verify show of Assignment" "[1 :: Int, \"two\" :: String, 3 :: Int, True :: Bool]" @=? show mkSPayload "[MyJust 10, MyJust \"twenty\", MyNothing, MyJust False]" @=? show mkSMyMaybe step "Verify show explicit indexing" Just "\"two\" :: String" @=? do Some i <- S.intIndex 1 (S.size mkSPayload) return $ show $ mkSPayload S.! i Just "1 :: Int" @=? do Some i <- S.intIndex 0 (S.size mkSPayload) return $ show $ mkSPayload S.! i "#<; @3=True :: Bool; @2=3 :: Int; @1=\"two\" :: String; @0=1 :: Int" @=? S.forIndex S.knownSize (\s idx -> s <> "; @" <> show idx <> "=" <> show (mkSPayload S.! idx)) "#<" (Nothing @String) @=? do Some i <- S.intIndex 8 (S.size mkSPayload) return $ show $ mkSPayload S.! i step "Verify invalid type at index" (Nothing :: Maybe Bool) @=? do Some i <- S.intIndex 1 (S.size mkSPayload) Refl <- testEquality (mkSPayload S.! i) (IntPayload 1) return True , testCaseSteps "joined Assigment operations (unsafe)" $ \step -> do let mkU1 = U.empty `U.extend` IntPayload 1 mkU2 = U.empty `U.extend` StringPayload "two" `U.extend` IntPayload 3 `U.extend` BoolPayload True step "Length" U.sizeInt (U.size mkU1) + U.sizeInt (U.size mkU2) @?= U.sizeInt (U.size (mkU1 U.<++> mkU2)) step "Index adjustments" Just (Some i1) <- return $ U.intIndex 0 (U.size mkU1) v1s <- return $ show $ mkU1 U.! i1 "1 :: Int" @=? v1s Just (Some i2) <- return $ U.intIndex 2 (U.size mkU2) v2s <- return $ show $ mkU2 U.! i2 "True :: Bool" @=? v2s let mkUB = mkU1 U.<++> mkU2 v1s' <- return $ show $ mkUB U.! (U.leftIndex (U.size mkU2) i1) v1s' @?= v1s v2s' <- return $ show $ mkUB U.! (U.rightIndex (U.size mkU1) (U.size mkU2) i2) v2s' @?= v2s , testCaseSteps "joined Assigment operations (safe)" $ \step -> do let mkS1 = S.empty `S.extend` IntPayload 1 mkS2 = S.empty `S.extend` StringPayload "two" `S.extend` IntPayload 3 `S.extend` BoolPayload True step "Length" S.sizeInt (S.size mkS1) + S.sizeInt (S.size mkS2) @?= S.sizeInt (S.size (mkS1 S.<++> mkS2)) step "Index adjustments" Just (Some i1) <- return $ S.intIndex 0 (S.size mkS1) v1s <- return $ show $ mkS1 S.! i1 "1 :: Int" @=? v1s Just (Some i2) <- return $ S.intIndex 2 (S.size mkS2) v2s <- return $ show $ mkS2 S.! i2 "True :: Bool" @=? v2s let mkSB = mkS1 S.<++> mkS2 v1s' <- return $ show $ mkSB S.! (S.leftIndex (S.size mkS2) i1) v1s' @?= v1s v2s' <- return $ show $ mkSB S.! (S.rightIndex (S.size mkS1) (S.size mkS2) i2) v2s' @?= v2s ]