packages feed

prospect-0.1.0.0: test/Spec.hs

{-# LANGUAGE BangPatterns     #-}
{-# LANGUAGE DeriveFunctor    #-}
{-# LANGUAGE DeriveGeneric    #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RankNTypes       #-}
{-# LANGUAGE TemplateHaskell  #-}
{-# LANGUAGE TypeApplications #-}

{-# OPTIONS_GHC -O -fplugin Test.Inspection.Plugin #-}

module Main where

import Control.DeepSeq (NFData (..), force)
import Control.Exception (evaluate)
import Control.Monad.Codensity (improve)
import Control.Monad.Free (MonadFree, Free (..), liftF)
import Control.Monad.Prospect
import GHC.Generics (Generic1)
import Test.Hspec
import Test.Inspection (Result (..), inspectTest, hasNoGenerics)



main :: IO ()
main = hspec $ do
  let contT = Cont  $ const ()
      contI = ContI $ const ()

  describe "prospect" $ do
    testFree "should lazily produce values"
                 (Just "hello")
                 [ contT
                 , contT
                 ] $ do
      _ <- cont
      _ <- cont
      pure "hello"

    testFree "should not be cursed for producer patterns"
                 (Just ())
                 [ Action 1 ()
                 , Action 2 ()
                 ] $ do
      action 1
      action 2

    testFree "should not crash for a single continuation"
                 Nothing
                 [ contT
                 ] $ do
      cont

    testFree "should stop on an explicit bang pattern"
                 Nothing
                 [ contT
                 ] $ do
      !x <- cont
      pure "error"

    it "should not crash for guess" $ do
      prospect (pure @(Free Pattern) $ guess @Int)
        `shouldBe` (Nothing, [])

    it "should not crash for guess >>= pure" $ do
      prospect (pure @(Free Pattern) (guess @Int) >>= pure)
        `shouldBe` (Nothing, [])

    it "nothing you hold dear is safe" $ do
      let (u, z) = prospect $ do
            action $ guess @Int
      evaluate (force (u, z)) `shouldThrow` (== Guess)

    testFree "should stop before branching"
                 Nothing
                 [ contT
                 , Action 5 ()
                 , contT
                 ] $ do
      x <- cont
      action 5
      y <- cont
      if x
         then pure True
         else cont

  describe "verify" $ do
    it "should catch guesses" $ do
      verify guess `shouldBe` Nothing @()
      verify (guess + guess) `shouldBe` Nothing @Int
      verify (guess @[()]) `shouldBe` []

    it "should not mess with values" $ do
      verify 0 `shouldBe` Just @Int 0
      verify (1 + 2) `shouldBe` Just @Int 3

    it "should respect strictness otherwise" $ do
      verify (const 3 guess) `shouldBe` Just @Int 3


  describe "ensure" $ do
    let (a, ms) = prospect $ action 1 >> conti >>= action

    it "should crash if you're foolish" $ do
      a `shouldBe` Just ()
      evaluate (force $ show ms) `shouldThrow` (== Guess)

    it "shouldn't crash if you're wise" $ do
      a `shouldBe` Just ()
      fmap ensure ms
        `shouldBe` [Just (Action 1 ()), Just contI, Nothing]


  describe "explore" $ do
    it "should optimize away its generics" $ do
      $(inspectTest $ hasNoGenerics 'explorePattern)
        `shouldSatisfy` isSuccess


data Pattern a
  = Cont (Bool -> a)
  | ContI (Int -> a)
  | Action Int a
  deriving (Functor, Generic1)

instance Eq a => Eq (Pattern a) where
  Cont f == Cont g
      = f True  == g True
     && f False == g False
  ContI _ == ContI _ = True
  Action i a == Action j b
      = i == j
     && a == b
  _ == _ = False

instance Show (Pattern a) where
  show (Cont _)      = "Cont"
  show (ContI _)     = "ContI"
  show (Action i _ ) = "Action " ++ show i


instance NFData a => NFData (Pattern a) where
  rnf (Cont f)     = seq f ()
  rnf (ContI f)    = seq f ()
  rnf (Action i a) = seq i ()


cont :: MonadFree Pattern m => m Bool
cont = liftF $ Cont id


conti :: MonadFree Pattern m => m Int
conti = liftF $ ContI id


action :: MonadFree Pattern m => Int -> m ()
action i = liftF $ Action i ()


explorePattern :: Pattern (Free Pattern a) -> Free Pattern a
explorePattern = explore


isSuccess :: Result -> Bool
isSuccess (Success _) = True
isSuccess (Failure _) = False


testFree
    :: (Eq a, Show a)
    => String
    -> Maybe a
    -> [Pattern ()]
    -> (forall m. (MonadFree Pattern m) => m a)
    -> SpecWith (Arg Expectation)
testFree z v cs m =
  let (a, ms) = prospect $ improve m
   in it z $ do
        a  `shouldBe` v
        ms `shouldBe` cs