packages feed

nonempty-containers-0.3.0.0: test/Tests/Util.hs

{-# 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