recover-rtti-0.2.0.0: tests/Test/RecoverRTTI/Arbitrary.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Test.RecoverRTTI.Arbitrary (
ClassifiedGen(..)
, arbitraryClassifiedGen
-- * Example values of reference cells
, exampleIORef
, exampleSTRef
, exampleMVar
, exampleTVar
) where
import Control.Concurrent.MVar (newEmptyMVar)
import Control.Concurrent.STM (newTVarIO)
import Control.Monad
import Control.Monad.ST.Unsafe (unsafeSTToIO)
import Data.Bifunctor
import Data.IORef (newIORef)
import Data.Maybe (catMaybes)
import Data.SOP
import Data.SOP.Dict
import Data.STRef (newSTRef)
import Data.Tree (Tree)
import Data.Void
import GHC.Real
import System.IO.Unsafe (unsafePerformIO)
import Unsafe.Coerce (unsafeCoerce)
import qualified Data.Aeson as Aeson
import qualified Data.ByteString as BS.Strict
import qualified Data.ByteString.Lazy as BS.Lazy
import qualified Data.ByteString.Short as BS.Short
import qualified Data.IntMap as IntMap
import qualified Data.Map as Map
import qualified Data.Sequence as Seq
import qualified Data.Set as Set
import qualified Data.Text as Text.Strict
import qualified Data.Text.Lazy as Text.Lazy
import qualified Data.Tree as Tree
import qualified Data.Vector as Vector
import Test.QuickCheck hiding (classify, NonEmpty)
import Debug.RecoverRTTI
import Debug.RecoverRTTI.TypeLevel
import Test.RecoverRTTI.ConcreteClassifier
import Test.RecoverRTTI.Orphans ()
import Test.RecoverRTTI.UserDefined
{-------------------------------------------------------------------------------
Generic auxiliary
-------------------------------------------------------------------------------}
newtype SizedGen a = SizedGen (Int -> Gen a)
deriving (Functor)
runSized :: Int -> SizedGen a -> Gen a
runSized n (SizedGen gen) = gen n
ignoreSize :: Gen a -> SizedGen a
ignoreSize gen = SizedGen $ \_sz -> gen
{-------------------------------------------------------------------------------
Arbitrary instance
-------------------------------------------------------------------------------}
-- | Quickcheck generator along with a classifier
data ClassifiedGen a where
ClassifiedGen ::
(Show a, Eq a)
=> { -- | The classifier for the generator
genClassifier :: ConcreteClassifier a
-- | The classified generator itself
--
-- The size argument determines the maximum size of the /value/
-- (as opposed to the maximum size of the /type/)
, classifiedGen :: SizedGen a
}
-> ClassifiedGen a
canShowClassifiedGen :: ClassifiedGen a -> Dict Show a
canShowClassifiedGen ClassifiedGen{} = Dict
canEqClassifiedGen :: ClassifiedGen a -> Dict Eq a
canEqClassifiedGen ClassifiedGen{} = Dict
defaultClassifiedGen ::
(Arbitrary a, Show a, Eq a)
=> ConcreteClassifier a
-> ClassifiedGen a
defaultClassifiedGen cc = ClassifiedGen cc $ ignoreSize arbitrary
-- | Generated arbitrary classifier along with a generator for that value
--
-- NOTE: The @sz@ parameter limits the size of the /type tree/ (i.e., the number
-- of recursive calls to arbitraryClassifiedGen), /not/ the size of the
-- generated /values/.
arbitraryClassifiedGen :: Int -> Gen (Some ClassifiedGen)
arbitraryClassifiedGen typSz
| typSz < 0 = error "arbitraryClassifiedGen: uhoh.. bug"
| typSz == 0 = elements leaves
| otherwise = oneof (elements leaves : catMaybes compound)
where
-- Leaves of the tree (values with no recursion)
--
-- Since there are the leaves, we don't need to check the size
leaves :: [Some ClassifiedGen]
leaves = concat [
-- Primitive types
[ Some $ defaultClassifiedGen CC_Bool
, Some $ defaultClassifiedGen CC_Char
, Some $ defaultClassifiedGen CC_Double
, Some $ defaultClassifiedGen CC_Float
, Some $ defaultClassifiedGen CC_Int
, Some $ defaultClassifiedGen CC_Int16
, Some $ defaultClassifiedGen CC_Int8
, Some $ defaultClassifiedGen CC_Int32
, Some $ defaultClassifiedGen CC_Int64
, Some $ defaultClassifiedGen CC_Integer
, Some $ defaultClassifiedGen CC_Ordering
, Some $ defaultClassifiedGen CC_Unit
, Some $ defaultClassifiedGen CC_Word
, Some $ defaultClassifiedGen CC_Word8
, Some $ defaultClassifiedGen CC_Word16
, Some $ defaultClassifiedGen CC_Word32
, Some $ defaultClassifiedGen CC_Word64
]
-- Strings
--
-- Avoid generating the empty string (recognized as @[Void]@)
, let mapList :: Arbitrary a => Int -> ([a] -> b) -> SizedGen b
mapList minSize f = SizedGen $ \valSz -> do
n <- choose (minSize, max minSize valSz) -- maybe valSz == 0
f <$> vector n
in [
Some $ ClassifiedGen CC_String (mapList 1 id)
, Some $ ClassifiedGen CC_BS_Strict (mapList 0 BS.Strict.pack)
, Some $ ClassifiedGen CC_BS_Lazy (mapList 0 BS.Lazy.pack)
, Some $ ClassifiedGen CC_BS_Short (mapList 0 BS.Short.pack)
, Some $ ClassifiedGen CC_Text_Strict (mapList 0 Text.Strict.pack)
, Some $ ClassifiedGen CC_Text_Lazy (mapList 0 Text.Lazy.pack)
]
-- Aeson
, [ Some $ ClassifiedGen CC_Value arbitraryAesonValue ]
-- Reference cells
, [ Some $ ClassifiedGen CC_STRef (ignoreSize $ pure exampleSTRef)
, Some $ ClassifiedGen CC_STRef (ignoreSize $ pure exampleIORef)
, Some $ ClassifiedGen CC_MVar (ignoreSize $ pure exampleMVar)
, Some $ ClassifiedGen CC_TVar (ignoreSize $ pure exampleTVar)
]
-- Functions
--
-- For functions we don't currently try to be clever and /generate/
-- functions. Instead, we just try a few different categories.
, map (\f -> Some $ ClassifiedGen CC_Fun (ignoreSize $ pure f)) [
-- Parametrically polymorphic function
unsafeCoerce (id :: Int -> Int)
, unsafeCoerce (const :: Int -> Bool -> Int)
-- Ad-hoc polymorphic function
, unsafeCoerce (negate :: Int -> Int)
, unsafeCoerce ((+) :: Int -> Int -> Int)
-- Partial application
, unsafeCoerce (const 1 :: Bool -> Int)
, unsafeCoerce ((+) 1 :: Int -> Int)
]
]
-- Compound
--
-- These are only used if @sz > 0@.
compound :: [Maybe (Gen (Some ClassifiedGen))]
compound = [
-- Lists
--
-- We have to be careful not to generate @[Char]@, because this is
-- inferred as @String@
guard (typSz >= 1) >> (return $ do
Some a <- arbitraryClassifiedGen (typSz - 1)
genMaybeF
(\case FJust CC_Char -> CC_String
c -> CC_List c)
(return [])
(\(SizedGen gen) -> SizedGen $ \valSz -> do
-- Pick number of list elements (don't generate empty list)
n <- choose (1, 5)
-- Then divide total size of each list element
vectorOf n (gen (valSz `div` n))
)
a
)
-- Maybe
, guard (typSz >= 1) >> (return $ do
Some a <- arbitraryClassifiedGen (typSz - 1)
genMaybeF CC_Maybe (return Nothing) (fmap Just) a
)
-- Either
, guard (typSz >= 2) >> (return $ do
Some a <- arbitraryClassifiedGen (typSz `div` 2)
Some b <- arbitraryClassifiedGen (typSz `div` 2)
genEitherF CC_Either (fmap Left) (fmap Right) a b
)
-- Ratio
, guard (typSz >= 1) >> (return $ do
Some a <- arbitraryClassifiedGen (typSz `div` 2)
genF
CC_Ratio
(\(SizedGen gen) -> SizedGen $ \sz ->
(:%) <$> gen (sz `div` 2) <*> gen (sz `div` 2)
)
a
)
-- Set
-- For set we must pick an ordered type, so we just pick Int
, return (do
genMaybeF
CC_Set
(return Set.empty)
-- Same strategy as for lists
(\(SizedGen gen) -> SizedGen $ \valSz -> do
n <- choose (1, 5)
Set.fromList <$> vectorOf n (gen (valSz `div` n))
)
(defaultClassifiedGen CC_Int)
)
-- Map
-- Pick Int for the keys, but randomly for the values
, guard (typSz >= 1) >> (return $ do
Some b <- arbitraryClassifiedGen (typSz - 1)
genMaybePairF
CC_Map
(return Map.empty)
(\(SizedGen genX) (SizedGen genY) -> SizedGen $ \valSz -> do
n <- choose (1, 5)
Map.fromList <$> vectorOf n (
(,) <$> genX (valSz `div` n `div` 2)
<*> genY (valSz `div` n `div` 2)
)
)
(defaultClassifiedGen CC_Int)
b
)
-- IntSet
, return $ return $ Some (defaultClassifiedGen CC_IntSet)
-- IntMap
, guard (typSz >= 1) >> (return $ do
Some b <- arbitraryClassifiedGen (typSz - 1)
genMaybeF
CC_IntMap
(return IntMap.empty)
(\(SizedGen genY) -> SizedGen $ \valSz -> do
n <- choose (1, 5)
IntMap.fromList <$> vectorOf n (
(,) <$> arbitrary
<*> genY (valSz `div` n)
)
)
b
)
-- Sequence
, guard (typSz >= 1) >> (return $ do
Some a <- arbitraryClassifiedGen (typSz - 1)
genMaybeF
CC_Sequence
(return Seq.empty)
(\(SizedGen genX) -> SizedGen $ \valSz -> do
n <- choose (1, 5)
Seq.fromList <$> vectorOf n (genX (valSz `div` n))
)
a
)
-- Tree
, guard (typSz >= 1) >> (return $ do
Some a <- arbitraryClassifiedGen (typSz - 1)
genF
CC_Tree
(\(SizedGen genX) -> SizedGen $ \valSz -> do
n <- choose (1, 5)
mkSomeTree <$> vectorOf n (genX (valSz `div` n))
)
a
)
--
-- User-defined
--
, guard (typSz >= 1) >> (return $ do
Some a <- arbitraryClassifiedGen (typSz - 1)
genMaybeF
CC_User_NonRec
(NR1 <$> arbitrary)
(\gen -> SizedGen $ \valSz ->
NR2 <$> runSized valSz gen <*> arbitrary
)
a
)
, guard (typSz >= 1) >> (return $ do
Some a <- arbitraryClassifiedGen (typSz - 1)
genMaybeF
CC_User_Rec
(return RNil)
(\gen -> SizedGen $ \valSz -> do
-- Similar strategy as for lists
n <- choose (1, 5)
recursiveFromList <$> vectorOf n (runSized (valSz `div` n) gen)
)
a
)
, return $ do
return $ Some $ ClassifiedGen (CC_User_Unlifted (FJust CC_Unit)) $ SizedGen $ \_ ->
return exampleContainsUnlifted
-- Tuples
, guard (typSz >= 2) >> (return $
arbitraryTuple typSz $ \np ->
case ( all_NP (hmap canShowClassifiedGen np)
, all_NP (hmap canEqClassifiedGen np)
) of
(Dict, Dict) ->
return . Some $ ClassifiedGen {
genClassifier =
CC_Tuple (ConcreteClassifiers (hmap genClassifier np))
, classifiedGen = SizedGen $ \valSz -> do
let valSz' = valSz `div` lengthSList np
tupleFromNP <$>
hsequence(hmap (runSized valSz' . classifiedGen) np)
}
)
]
genMaybeF ::
( forall x. Show x => Show (f x)
, forall x. Eq x => Eq (f x)
)
=> (forall x. MaybeF ConcreteClassifier x -> ConcreteClassifier (f x))
-> Gen (f Void)
-> (SizedGen a -> SizedGen (f a))
-> ClassifiedGen a -> Gen (Some ClassifiedGen)
genMaybeF cc genNothing genJust (ClassifiedGen cA genA) =
elements [
Some $ ClassifiedGen (cc FNothing) (ignoreSize $ genNothing)
, Some $ ClassifiedGen (cc (FJust cA)) (genJust genA)
]
genEitherF ::
( forall x y. (Show x, Show y) => Show (f x y)
, forall x y. (Eq x, Eq y) => Eq (f x y)
)
=> (forall x y. EitherF ConcreteClassifier x y -> ConcreteClassifier (f x y))
-> (SizedGen a -> SizedGen (f a Void))
-> (SizedGen b -> SizedGen (f Void b))
-> ClassifiedGen a
-> ClassifiedGen b
-> Gen (Some ClassifiedGen)
genEitherF cc genLeft genRight (ClassifiedGen cA genA) (ClassifiedGen cB genB) =
elements [
Some $ ClassifiedGen (cc (FLeft cA)) (genLeft genA)
, Some $ ClassifiedGen (cc (FRight cB)) (genRight genB)
]
genMaybePairF ::
( forall x y. (Show x, Show y) => Show (f x y)
, forall x y. (Eq x, Eq y) => Eq (f x y)
)
=> (forall x y. MaybePairF ConcreteClassifier x y -> ConcreteClassifier (f x y))
-> Gen (f Void Void)
-> (SizedGen a -> SizedGen b -> SizedGen (f a b))
-> ClassifiedGen a -> ClassifiedGen b -> Gen (Some ClassifiedGen)
genMaybePairF cc genNothing genJust (ClassifiedGen cA genA) (ClassifiedGen cB genB) =
elements [
Some $ ClassifiedGen (cc FNothingPair) (ignoreSize $ genNothing)
, Some $ ClassifiedGen (cc (FJustPair cA cB)) (genJust genA genB)
]
genF ::
( forall x. Show x => Show (f x)
, forall x. Eq x => Eq (f x)
)
=> (forall x. ConcreteClassifier x -> ConcreteClassifier (f x))
-> (SizedGen a -> SizedGen (f a))
-> ClassifiedGen a -> Gen (Some ClassifiedGen)
genF cc gen (ClassifiedGen cA genA) = return $
Some $ ClassifiedGen (cc cA) (gen genA)
-- We check that we cover all cases of 'Classifier' rather than
-- 'ConcreteClassifier': it is important that we generate test cases for
-- everything we classify in the main library.
_checkAllCases :: Classifier a -> ()
_checkAllCases = \case
-- Primitive types
C_Bool -> ()
C_Char -> ()
C_Double -> ()
C_Float -> ()
C_Int -> ()
C_Int16 -> ()
C_Int8 -> ()
C_Int32 -> ()
C_Int64 -> ()
C_Integer -> ()
C_Ordering -> ()
C_Unit -> ()
C_Word -> ()
C_Word8 -> ()
C_Word16 -> ()
C_Word32 -> ()
C_Word64 -> ()
-- String types
C_String -> ()
C_BS_Strict -> ()
C_BS_Lazy -> ()
C_BS_Short -> ()
C_Text_Strict -> ()
C_Text_Lazy -> ()
-- Aeson
C_Value -> ()
-- Compound
C_Maybe{} -> ()
C_Either{} -> ()
C_List{} -> ()
C_Ratio{} -> ()
C_Set{} -> ()
C_Map{} -> ()
C_IntSet{} -> ()
C_IntMap{} -> ()
C_Tuple{} -> ()
C_Sequence{} -> ()
C_Tree{} -> ()
-- Reference cells
C_STRef -> ()
C_TVar -> ()
C_MVar -> ()
-- Functions
C_Fun -> ()
-- User-defined
C_Custom{} -> ()
-- | Generate arbitrary tuple size
arbitraryTuple :: forall r.
Int -- ^ Maximum type size (should be at least 2)
-> (forall xs.
(SListI xs, IsValidSize (Length xs))
=> NP ClassifiedGen xs -> Gen r
)
-> Gen r
arbitraryTuple = \typSz k -> do
tupleSz <- choose (2, min typSz 62)
let typSz' = typSz `div` tupleSz
case toValidSize tupleSz of
Nothing ->
error "arbitraryTuple: impossible, this is a valid tuple size"
Just (Some valid@(ValidSize n _)) ->
go typSz' n $ \(np :: NP ClassifiedGen xs) ->
case liftValidSize (valid :: ValidSize (Length xs))
of Dict -> k np
where
go :: Int
-> Sing (n :: Nat)
-> (forall xs.
(SListI xs, Length xs ~ n)
=> NP ClassifiedGen xs -> Gen r
)
-> Gen r
go _ SZ k = k Nil
go typSz' (SS n) k = do
Some c <- arbitraryClassifiedGen typSz'
go typSz' n $ \cs -> k (c :* cs)
instance Arbitrary (Some Value) where
arbitrary = sized $ \sz -> do
-- @sz@ will range from 0..100, but we don't want to generate types that
-- large
Some (ClassifiedGen cc gen) <- arbitraryClassifiedGen (sz `div` 10)
-- For the values however we want to be able to generate larger trees
Some . Value cc <$> runSized sz gen
{-------------------------------------------------------------------------------
Auxiliary tree functions
-------------------------------------------------------------------------------}
mkSomeTree :: [a] -> Tree a
mkSomeTree [] = error "mkSomeTree: empty"
mkSomeTree [x] = Tree.Node x []
mkSomeTree [x, y] = Tree.Node x [Tree.Node y []]
mkSomeTree (x : xs) =
let (left, right) = split xs
in Tree.Node x [mkSomeTree left, mkSomeTree right]
-- | Split list into halves
--
-- If the input has at least two elements, neither list will be empty
--
-- > split "abcde" == ("ace","bd")
split :: [a] -> ([a], [a])
split [] = ([], [])
split (x:xs) = first (x:) $ splot xs
-- | Auxiliary to 'split'
splot :: [a] -> ([a], [a])
splot [] = ([], [])
splot (x:xs) = second (x:) $ split xs
{-------------------------------------------------------------------------------
Auxiliary Aeson
-------------------------------------------------------------------------------}
arbitraryAesonValue :: SizedGen Aeson.Value
arbitraryAesonValue = SizedGen $ go
where
go :: Int -> Gen Aeson.Value
go 0 = oneof nonRecursive
go sz = oneof (nonRecursive ++ recursive sz)
nonRecursive :: [Gen Aeson.Value]
nonRecursive = [
Aeson.String . Text.Strict.pack <$> arbitrary
, Aeson.Number . fromInteger <$> arbitrary
, Aeson.Bool <$> arbitrary
, return Aeson.Null
]
recursive :: Int -> [Gen Aeson.Value]
recursive sz = [
do n <- choose (0, 5)
Aeson.Array . Vector.fromList <$> replicateM n (go (sz `div` n))
, do n <- choose (0, 5)
Aeson.object <$> replicateM n (
(Aeson..=)
<$> fieldName
<*> go (sz `div` n)
)
]
-- We're not interested in testing crazy values
fieldName :: Gen Text.Strict.Text
fieldName = elements ["a", "b", "c"]
{-------------------------------------------------------------------------------
Some global variables, which we use only as input to the tests
-------------------------------------------------------------------------------}
exampleIORef :: SomeSTRef
{-# NOINLINE exampleIORef #-}
exampleIORef = unsafePerformIO $
-- IORef is indistinguishable from STRef on the heap
unsafeCoerce <$> newIORef (unsafeCoerce ())
exampleSTRef :: SomeSTRef
exampleSTRef = unsafePerformIO $ unsafeSTToIO $
unsafeCoerce <$> newSTRef (unsafeCoerce ())
exampleMVar :: SomeMVar
{-# NOINLINE exampleMVar #-}
exampleMVar = unsafePerformIO $
SomeMVar <$> newEmptyMVar
exampleTVar :: SomeTVar
{-# NOINLINE exampleTVar #-}
exampleTVar = unsafePerformIO $
SomeTVar <$> newTVarIO (unsafeCoerce ())