{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module Tests.Util (
K(..), KeyType, overKX, dummyKey
, SortType(..)
, GenFunc(..), gf1, gf2, gf3, gf4
, GenType(..)
, TestType(..)
, ttProp
, groupTree
, readShow, readShow1, showShow1, showShow2
, Context(..)
, Bazaar(..)
, keyGen, valGen, mapSize, mapGen, neMapGen, setGen, neSetGen
, intKeyGen, intMapGen, neIntMapGen, intSetGen, neIntSetGen
, seqGen, neSeqGen
) where
import Control.Applicative
import Control.Monad
import Data.Bifunctor
import Data.Char
import Data.Foldable
import Data.Function
import Data.Functor.Apply
import Data.Functor.Classes
import Data.Functor.Identity
import Data.IntMap (IntMap)
import Data.IntMap.NonEmpty (NEIntMap)
import Data.IntSet (IntSet, Key)
import Data.IntSet.NonEmpty (NEIntSet)
import Data.Kind
import Data.List.NonEmpty (NonEmpty(..))
import Data.Map (Map)
import Data.Map.NonEmpty (NEMap)
import Data.Maybe
import Data.Semigroup.Foldable
import Data.Sequence (Seq(..))
import Data.Sequence.NonEmpty (NESeq(..))
import Data.Set (Set)
import Data.Set.NonEmpty (NESet)
import Data.Text (Text)
import Data.These
import Hedgehog
import Hedgehog.Function hiding ((:*:))
import Hedgehog.Internal.Property
import Test.Tasty
import Test.Tasty.Hedgehog
import Text.Read
import qualified Data.IntMap as IM
import qualified Data.IntMap.NonEmpty as NEIM
import qualified Data.IntSet as IS
import qualified Data.IntSet.NonEmpty as NEIS
import qualified Data.List.NonEmpty as NE
import qualified Data.Map as M
import qualified Data.Map.NonEmpty as NEM
import qualified Data.Sequence.NonEmpty as NESeq
import qualified Data.Set as S
import qualified Data.Set.NonEmpty as NES
import qualified Data.Text as T
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
#if !MIN_VERSION_base(4,11,0)
import Data.Semigroup (Semigroup(..))
#endif
groupTree :: Group -> TestTree
groupTree Group{..} = testGroup (unGroupName groupName)
(map (uncurry go) groupProperties)
where
go :: PropertyName -> Property -> TestTree
go n = testProperty (mkName (unPropertyName n))
mkName = map deUnderscore . drop (length @[] @Char "prop_")
deUnderscore '_' = ' '
deUnderscore c = c
-- | test for stability
data K a b = K { getKX :: !a, getKY :: !b }
deriving (Show, Read, Generic)
withK :: (a -> b -> c) -> K a b -> c
withK f (K x y) = f x y
overKX :: (a -> c) -> K a b -> K c b
overKX f (K x y) = K (f x) y
instance Eq a => Eq (K a b) where
(==) = (==) `on` getKX
instance Ord a => Ord (K a b) where
compare = compare `on` getKX
instance (Vary a, Vary b) => Vary (K a b)
instance (Arg a, Arg b) => Arg (K a b)
type KeyType = K Int Text
instance Semigroup KeyType where
K x1 y1 <> K x2 y2 = K (x1 + x2) (y1 <> y2)
instance Monoid KeyType where
mempty = K 0 ""
mappend = (<>)
dummyKey :: KeyType
dummyKey = K 0 "hello"
#if MIN_VERSION_base(4,11,0)
instance (Num a, Monoid b) => Num (K a b) where
#else
instance (Num a, Semigroup b, Monoid b) => Num (K a b) where
#endif
K x1 y1 + K x2 y2 = K (x1 + x2) (y1 <> y2)
K x1 y1 - K x2 y2 = K (x1 - x2) (y1 <> y2)
K x1 y1 * K x2 y2 = K (x1 * x2) (y1 <> y2)
negate (K x y) = K (negate x) y
abs (K x y) = K (abs x) y
signum (K x y) = K (signum x) y
fromInteger n = K (fromInteger n) mempty
data Context a b t = Context (b -> t) a
deriving Functor
data Bazaar a b t = Done t
| More a (Bazaar a b (b -> t))
deriving Functor
instance Apply (Bazaar a b) where
#if MIN_VERSION_semigroupoids(5,2,2)
liftF2 f = \case
Done x -> fmap (f x)
More x b -> More x . liftA2 (\g r y -> f (g y) r) b
#else
(<.>) = \case
Done x -> fmap x
More x b -> More x . liftA2 (\g r y -> g y r) b
#endif
instance Applicative (Bazaar a b) where
pure = Done
liftA2 = liftF2
data SortType :: Type -> Type where
STAsc :: Ord a => SortType a
STDesc :: Ord a => SortType a
STDistinctAsc :: Ord a => SortType (a, b)
STDistinctDesc :: Ord a => SortType (a, b)
data GenType :: Type -> Type -> Type where
GTNEMap :: GenType (Map KeyType Text) (NEMap KeyType Text)
GTMap :: GenType (Map KeyType Text) (Map KeyType Text )
GTNESet :: GenType (Set KeyType ) (NESet KeyType )
GTNEIntMap :: GenType (IntMap Text ) (NEIntMap Text )
GTNEIntSet :: GenType IntSet NEIntSet
GTIntMap :: GenType (IntMap Text ) (IntMap Text )
GTNESeq :: GenType (Seq Text ) (NESeq Text )
GTNESeqList :: GenType (NonEmpty Text ) (NESeq Text )
GTSeq :: GenType (Seq Text ) (Seq Text )
GTKey :: GenType KeyType KeyType
GTIntKey :: GenType Int Int
GTVal :: GenType Text Text
GTSize :: GenType Int Int
GTOther :: Gen a
-> GenType a a
GTMaybe :: GenType a b
-> GenType (Maybe a) (Maybe b)
(:&:) :: GenType a b
-> GenType c d
-> GenType (a, c) (b, d)
GTNEList :: Maybe (Range Int)
-> GenType a b
-> GenType [a] (NonEmpty b)
GTSet :: GenType (Set KeyType) (Set KeyType)
GTIntSet :: GenType IntSet IntSet
GTSorted :: SortType a
-> GenType [a] (NonEmpty a)
-> GenType [a] (NonEmpty a)
data GenFunc :: Type -> Type -> Type -> Type where
GF :: (Show a, Arg a, Vary a, Show b)
=> Gen b
-> ((a -> b) -> f)
-> GenFunc f c d
gf1 :: (Show a, Arg a, Vary a, Show b)
=> Gen b
-> GenFunc (a -> b) c d
gf1 = (`GF` id)
gf2 :: (Show a, Show b, Arg a, Vary a, Arg b, Vary b, Show c)
=> Gen c
-> GenFunc (a -> b -> c) d e
gf2 = (`GF` curry)
gf3 :: (Show a, Show b, Show c, Arg a, Vary a, Arg b, Vary b, Arg c, Vary c, Show d)
=> Gen d
-> GenFunc (a -> b -> c -> d) e f
gf3 = (`GF` (curry . curry))
gf4 :: (Show a, Show b, Show c, Arg a, Vary a, Arg b, Vary b, Arg c, Vary c, Show d, Show e, Arg d, Vary d)
=> Gen e
-> GenFunc (a -> b -> c -> d -> e) f g
gf4 = (`GF` (curry . curry . curry))
data TestType :: Type -> Type -> Type where
TTNEMap :: (Eq a, Show a)
=> TestType (Map KeyType a) (NEMap KeyType a )
TTNEIntMap :: (Eq a, Show a)
=> TestType (IntMap a ) (NEIntMap a )
TTNESet :: TestType (Set KeyType ) (NESet KeyType )
TTNEIntSet :: TestType IntSet NEIntSet
TTMap :: (Eq a, Show a)
=> TestType (Map KeyType a) (Map KeyType a )
TTSet :: TestType (Set KeyType ) (Set KeyType )
TTNESeq :: (Eq a, Show a)
=> TestType (Seq a ) (NESeq a )
TTNESeqList :: (Eq a, Show a)
=> TestType (NonEmpty a ) (NESeq a )
TTKey :: TestType KeyType KeyType
TTVal :: TestType Text Text
TTOther :: (Eq a, Show a)
=> TestType a a
TTThese :: (Eq a, Show a, Monoid a, Eq c, Show c, Monoid c)
=> TestType a b
-> TestType c d
-> TestType (a, c) (These b d)
TTMThese :: (Eq a, Show a, Monoid a, Eq c, Show c, Monoid c)
=> TestType a b
-> TestType c d
-> TestType (a, c) (Maybe (These b d))
TTMaybe :: TestType a b
-> TestType (Maybe a) (Maybe b)
TTEither :: TestType a b
-> TestType c d
-> TestType (Either a c) (Either b d)
TTNEList :: TestType a b
-> TestType [a] (NonEmpty b)
TTCtx :: TestType (c -> t) (d -> u)
-> TestType a b
-> TestType (Context a c t) (Context b d u)
TTBazaar :: (Show a, Show b, Show c, Show d)
=> GenType c d
-> TestType t u
-> TestType a b
-> TestType (Bazaar a c t) (Bazaar b d u)
(:*:) :: (Eq a, Eq b, Eq c, Eq d, Show a, Show b, Show c, Show d)
=> TestType a b
-> TestType c d
-> TestType (a, c) (b, d)
(:?>) :: GenFunc f c d
-> TestType c d
-> TestType (f -> c) (f -> d)
(:->) :: (Show a, Show b)
=> GenType a b
-> TestType c d
-> TestType (a -> c) (b -> d)
infixr 2 :&:
infixr 1 :->
infixr 1 :?>
infixr 2 :*:
runSorter
:: SortType a
-> [a]
-> [a]
runSorter = \case
STAsc -> S.toAscList . S.fromList
STDesc -> S.toDescList . S.fromList
STDistinctAsc -> M.toAscList . M.fromList
STDistinctDesc -> M.toDescList . M.fromList
runGT :: GenType a b -> Gen (a, b)
runGT = \case
GTNEMap -> (\n -> (NEM.IsNonEmpty n, n)) <$> neMapGen
GTMap -> join (,) <$> mapGen
GTNESet -> (\n -> (NES.IsNonEmpty n, n)) <$> neSetGen
GTNEIntMap -> (\n -> (NEIM.IsNonEmpty n, n)) <$> neIntMapGen
GTNEIntSet -> (\n -> (NEIS.IsNonEmpty n, n)) <$> neIntSetGen
GTIntMap -> join (,) <$> intMapGen
GTSet -> join (,) <$> setGen
GTIntSet -> join (,) <$> intSetGen
GTNESeq -> (\n -> (NESeq.IsNonEmpty n, n)) <$> neSeqGen
GTNESeqList -> (\n -> (toNonEmpty n, n)) <$> neSeqGen
GTSeq -> join (,) <$> seqGen
GTKey -> join (,) <$> keyGen
GTIntKey -> join (,) <$> intKeyGen
GTVal -> join (,) <$> valGen
GTSize -> join (,) <$> Gen.int mapSize
GTOther g -> join (,) <$> g
GTMaybe g -> maybe (Nothing, Nothing) (bimap Just Just) <$>
Gen.maybe (runGT g)
g1 :&: g2 -> do
(x1, y1) <- runGT g1
(x2, y2) <- runGT g2
pure ((x1,x2), (y1,y2))
GTNEList r g -> first toList . NE.unzip <$>
Gen.nonEmpty (fromMaybe mapSize r) (runGT g)
GTSorted s g -> bimap (runSorter s) (fromJust . NE.nonEmpty . runSorter s . toList) <$>
runGT g
runTT :: Monad m => TestType a b -> a -> b -> PropertyT m ()
runTT = \case
TTNEMap -> \x y -> do
assert $ NEM.valid y
unKMap x === unKMap (NEM.IsNonEmpty y)
TTNEIntMap -> \x y -> do
assert $ NEIM.valid y
x === NEIM.IsNonEmpty y
TTNESet -> \x y -> do
assert $ NES.valid y
unKSet x === unKSet (NES.IsNonEmpty y)
TTNEIntSet -> \x y -> do
assert $ NEIS.valid y
x === NEIS.IsNonEmpty y
TTMap -> \x y ->
unKMap x === unKMap y
TTSet -> \x y ->
unKSet x === unKSet y
TTNESeq -> \x y ->
x === NESeq.IsNonEmpty y
TTNESeqList -> \x y ->
x === toNonEmpty y
TTKey -> \(K x1 y1) (K x2 y2) -> do
x1 === x2
y1 === y2
TTVal -> (===)
TTOther -> (===)
TTThese t1 t2 -> \(x1, x2) -> \case
This y1 -> do
runTT t1 x1 y1
x2 === mempty
That y2 -> do
x1 === mempty
runTT t2 x2 y2
These y1 y2 -> do
runTT t1 x1 y1
runTT t2 x2 y2
TTMThese t1 t2 -> \(x1, x2) -> \case
Nothing -> do
x1 === mempty
x2 === mempty
Just (This y1) -> do
runTT t1 x1 y1
x2 === mempty
Just (That y2) -> do
x1 === mempty
runTT t2 x2 y2
Just (These y1 y2) -> do
runTT t1 x1 y1
runTT t2 x2 y2
TTMaybe tt -> \x y -> do
isJust y === isJust y
traverse_ (uncurry (runTT tt)) $ liftA2 (,) x y
TTEither tl tr -> \case
Left x -> \case
Left y -> runTT tl x y
Right _ -> annotate "Left -> Right" *> failure
Right x -> \case
Left _ -> annotate "Right -> Left" *> failure
Right y -> runTT tr x y
TTNEList tt -> \xs ys -> do
length xs === length ys
zipWithM_ (runTT tt) xs (toList ys)
TTCtx tSet tView -> \(Context xS xV) (Context yS yV) -> do
runTT tSet xS yS
runTT tView xV yV
TTBazaar gNew tRes tView -> testBazaar gNew tRes tView
t1 :*: t2 -> \(x1, x2) (y1, y2) -> do
runTT t1 x1 y1
runTT t2 x2 y2
GF gt c :?> tt -> \gx gy -> do
f <- c <$> forAllFn (fn gt)
runTT tt (gx f) (gy f)
gt :-> tt -> \f g -> do
(x, y) <- forAll $ runGT gt
runTT tt (f x) (g y)
where
unKMap :: (Ord k, Ord j) => Map (K k j) c -> Map (k, j) c
unKMap = M.mapKeys (withK (,))
unKSet :: (Ord k, Ord j) => Set (K k j) -> Set (k, j)
unKSet = S.map (withK (,))
testBazaar
:: forall a b c d t u m. (Show a, Show b, Show c, Show d, Monad m)
=> GenType c d
-> TestType t u
-> TestType a b
-> Bazaar a c t
-> Bazaar b d u
-> PropertyT m ()
testBazaar gNew tRes0 tView = go [] [] tRes0
where
go :: [a] -> [b] -> TestType t' u' -> Bazaar a c t' -> Bazaar b d u' -> PropertyT m ()
go xs ys tRes = \case
Done xRes -> \case
Done yRes -> do
annotate "The final result matches"
runTT tRes xRes yRes
More yView _ -> do
annotate "ys had more elements than xs"
annotate $ show xs
annotate $ show ys
annotate $ show yView
failure
More xView xNext -> \case
Done _ -> do
annotate "xs had more elements than ys"
annotate $ show xs
annotate $ show ys
annotate $ show xView
failure
More yView yNext -> do
annotate "Each individual piece matches pair-wise"
runTT tView xView yView
annotate "The remainders also match"
go (xView:xs) (yView:ys) (gNew :-> tRes) xNext yNext
-- ---------------------
-- Properties
-- ---------------------
ttProp :: TestType a b -> a -> b -> Property
ttProp tt x = property . runTT tt x
readShow
:: (Show a, Read a, Eq a)
=> Gen a
-> Property
readShow g = property $ do
m0 <- forAll g
tripping m0 show readMaybe
readShow1
:: (Eq (f a), Show1 f, Show a, Show (f a), Read1 f, Read a)
=> Gen (f a)
-> Property
readShow1 g = property $ do
m0 <- forAll g
tripping m0 (($ "") . showsPrec1 0) (fmap fst . listToMaybe . readsPrec1 0)
showShow1
:: (Show1 f, Show a, Show (f a))
=> Gen (f a)
-> Property
showShow1 g = property $ do
m0 <- forAll g
let s0 = show m0
s1 = showsPrec1 0 m0 ""
s0 === s1
showShow2
:: (Show2 f, Show a, Show b, Show (f a b))
=> Gen (f a b)
-> Property
showShow2 g = property $ do
m0 <- forAll g
let s0 = show m0
s2 = showsPrec2 0 m0 ""
s0 === s2
-- readShow2
-- :: (Eq (f a b), Show2 f, Show a, Show b, Show (f a b), Read2 f, Read a, Read b)
-- => Gen (f a b)
-- -> Property
-- readShow2 g = property $ do
-- m0 <- forAll g
-- tripping m0 (($ "") . showsPrec2 0) (fmap fst . listToMaybe . readsPrec2 0)
-- ---------------------
-- Generators
-- ---------------------
keyGen :: MonadGen m => m KeyType
keyGen = K <$> intKeyGen
<*> Gen.text (Range.linear 0 5) Gen.alphaNum
valGen :: MonadGen m => m Text
valGen = Gen.text (Range.linear 0 5) Gen.alphaNum
mapSize :: Range Int
mapSize = Range.exponential 4 8
mapGen :: MonadGen m => m (Map KeyType Text)
mapGen = Gen.map mapSize $ (,) <$> keyGen <*> valGen
neMapGen :: (MonadGen m, GenBase m ~ Identity) => m (NEMap KeyType Text)
neMapGen = Gen.just $ NEM.nonEmptyMap <$> mapGen
setGen :: MonadGen m => m (Set KeyType)
setGen = Gen.set mapSize keyGen
neSetGen :: (MonadGen m, GenBase m ~ Identity) => m (NESet KeyType)
neSetGen = Gen.just $ NES.nonEmptySet <$> setGen
intKeyGen :: MonadGen m => m Key
intKeyGen = Gen.int (Range.linear (-100) 100)
intMapGen :: MonadGen m => m (IntMap Text)
intMapGen = IM.fromDistinctAscList . M.toList <$> Gen.map mapSize ((,) <$> intKeyGen <*> valGen)
neIntMapGen :: (MonadGen m, GenBase m ~ Identity) => m (NEIntMap Text)
neIntMapGen = Gen.just $ NEIM.nonEmptyMap <$> intMapGen
intSetGen :: MonadGen m => m IntSet
intSetGen = IS.fromDistinctAscList . S.toList <$> Gen.set mapSize intKeyGen
neIntSetGen :: (MonadGen m, GenBase m ~ Identity) => m NEIntSet
neIntSetGen = Gen.just $ NEIS.nonEmptySet <$> intSetGen
seqGen :: MonadGen m => m (Seq Text)
seqGen = Gen.seq mapSize valGen
neSeqGen :: (MonadGen m, GenBase m ~ Identity) => m (NESeq Text)
neSeqGen = Gen.just $ NESeq.nonEmptySeq <$> seqGen
-- ---------------------
-- Orphans
-- ---------------------
instance Arg Char where
build = via ord chr
instance Arg Text where
build = via T.unpack T.pack
instance Vary Char where
vary = contramap ord vary
instance Vary Text where
vary = contramap T.unpack vary