{-# options_haddock hide #-} module Polysemy.Test.Hedgehog where import qualified Control.Monad.Trans.Writer.Lazy as MTL import qualified Hedgehog as Native import Hedgehog.Internal.Property (Failure, Journal, TestT (TestT), failWith) import Polysemy.Writer (Writer, tell) import qualified Polysemy.Test.Data.Hedgehog as Hedgehog import Polysemy.Test.Data.Hedgehog (Hedgehog, liftH) -- |Interpret 'Hedgehog' into @'TestT' IO@ by simple embedding of the native combinators. interpretHedgehog :: Member (Embed (TestT m)) r => InterpreterFor (Hedgehog m) r interpretHedgehog = interpret \case Hedgehog.LiftH t -> embed t -- |Interpret 'Hedgehog' in terms of @'Error' 'Failure'@ and @'Writer' 'Journal'@, which correspond to the monad stack -- wrapped by 'TestT'. rewriteHedgehog :: Members [Error Failure, Writer Journal, Embed m] r => InterpreterFor (Hedgehog m) r rewriteHedgehog = interpret \case Hedgehog.LiftH (TestT t) -> do (result, journal) <- embed (MTL.runWriterT (runExceptT t)) tell journal fromEither result -- |Embeds 'Hedgehog.assert'. assert :: ∀ m r . Monad m => HasCallStack => Member (Hedgehog m) r => Bool -> Sem r () assert a = withFrozenCallStack $ liftH @m (Native.assert a) infix 4 === -- |Embeds 'Hedgehog.==='. -- -- >>> 5 === 6 -- 5 === 6 -- ^^^^^^^ -- │ ━━━ Failed (- lhs) (+ rhs) ━━━ -- │ - 5 -- │ + 6 (===) :: ∀ a m r . Monad m => Eq a => Show a => HasCallStack => Member (Hedgehog m) r => a -> a -> Sem r () a === b = withFrozenCallStack $ liftH @m (a Native.=== b) infix 4 /== -- |Prefix variant of '(===)'. assertEq :: ∀ a m r . Monad m => Eq a => Show a => HasCallStack => Member (Hedgehog m) r => a -> a -> Sem r () assertEq a b = withFrozenCallStack $ liftH @m (a Native.=== b) -- |Embeds 'Hedgehog./=='. -- -- >>> 5 /== 5 -- 5 /== 5 -- ^^^^^^^ -- │ ━━━ Failed (no differences) ━━━ -- │ 5 (/==) :: ∀ a m r . Monad m => Eq a => Show a => HasCallStack => Member (Hedgehog m) r => a -> a -> Sem r () a /== b = withFrozenCallStack $ liftH @m (a Native./== b) -- |Prefix variant of '(/==)'. assertNeq :: ∀ a m r . Monad m => Eq a => Show a => HasCallStack => Member (Hedgehog m) r => a -> a -> Sem r () assertNeq a b = withFrozenCallStack $ liftH @m (a Native./== b) -- |Embeds 'Hedgehog.evalEither'. evalEither :: ∀ a m e r . Show e => Monad m => HasCallStack => Member (Hedgehog m) r => Either e a -> Sem r a evalEither e = withFrozenCallStack $ liftH @m (Native.evalEither e) -- |Given a reference value, unpacks an 'Either' with 'evalEither' and applies '===' to the result in the -- 'Right' case, and produces a test failure in the 'Left' case. assertRight :: ∀ a m e r . Eq a => Show e => Show a => Monad m => HasCallStack => Member (Hedgehog m) r => a -> Either e a -> Sem r () assertRight a = withFrozenCallStack $ (assertEq @_ @m a) <=< evalEither @_ @m -- |Like 'assertRight', but for two nested Eithers. assertRight2 :: ∀ a m e1 e2 r . Eq a => Show e1 => Show e2 => Show a => Monad m => HasCallStack => Member (Hedgehog m) r => a -> Either e1 (Either e2 a) -> Sem r () assertRight2 a = withFrozenCallStack $ assertRight @_ @m a <=< evalEither @_ @m -- |Like 'assertRight', but for three nested Eithers. assertRight3 :: ∀ a m e1 e2 e3 r . Eq a => Show e1 => Show e2 => Show e3 => Show a => Monad m => HasCallStack => Member (Hedgehog m) r => a -> Either e1 (Either e2 (Either e3 a)) -> Sem r () assertRight3 a = withFrozenCallStack $ assertRight2 @_ @m a <=< evalEither @_ @m -- |Like 'evalEither', but for 'Left'. evalLeft :: ∀ a m e r . Show a => Monad m => HasCallStack => Member (Hedgehog m) r => Either e a -> Sem r e evalLeft = \case Right a -> withFrozenCallStack $ liftH @m $ failWith Nothing $ show a Left e -> pure e -- |Like 'assertRight', but for 'Left'. assertLeft :: ∀ a m e r . Eq e => Show e => Show a => Monad m => HasCallStack => Member (Hedgehog m) r => e -> Either e a -> Sem r () assertLeft e = withFrozenCallStack $ (assertEq @_ @m e) <=< evalLeft @_ @m data ValueIsNothing = ValueIsNothing deriving Show -- |Like 'evalEither', but for 'Maybe'. evalMaybe :: ∀ a m r . Monad m => HasCallStack => Member (Hedgehog m) r => Maybe a -> Sem r a evalMaybe ma = withFrozenCallStack $ evalEither @_ @m (maybeToRight ValueIsNothing ma) -- |Given a reference value, asserts that the scrutinee is 'Just' and its contained value matches the target. assertJust :: ∀ a m r . Eq a => Show a => Monad m => HasCallStack => Member (Hedgehog m) r => a -> Maybe a -> Sem r () assertJust target ma = withFrozenCallStack $ assertRight @_ @m target (maybeToRight ValueIsNothing ma) -- |Run a Polysemy 'Error' effect and assert its result. evalError :: ∀ e a m r . Show e => Monad m => HasCallStack => Member (Hedgehog m) r => Sem (Error e : r) a -> Sem r a evalError sem = withFrozenCallStack $ evalEither @_ @m =<< runError sem -- |Assert that two numeric values are closer to each other than the specified @delta@. assertCloseBy :: ∀ a m r . Num a => Ord a => Monad m => HasCallStack => Member (Hedgehog m) r => a -> a -> a -> Sem r () assertCloseBy delta target scrutinee = withFrozenCallStack $ assert @m (abs (scrutinee - target) < delta) -- |Assert that two fractional values are closer to each other than @0.001@. assertClose :: ∀ a m r . Ord a => Fractional a => Monad m => HasCallStack => Member (Hedgehog m) r => a -> a -> Sem r () assertClose = withFrozenCallStack $ assertCloseBy @_ @m 0.001