packages feed

dep-t-advice-0.5.1.0: test/simple-advice-tests.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE BlockArguments #-}

module Main (main) where

import Prelude hiding (log)
import Barbies
import Dep.Has
import Dep.SimpleAdvice
import Dep.SimpleAdvice.Basic
import Control.Monad.Dep
import Control.Monad.Reader
import Control.Monad.Writer
import Data.List.NonEmpty 
import Data.Typeable
import Data.Coerce
import Data.Functor.Identity
import Data.Kind
import Data.List (intercalate)
import Data.SOP
import GHC.Generics
import Test.Tasty
import Test.Tasty.HUnit
import Data.IORef
import System.IO

printArgs' :: forall m r. MonadIO m => Handle -> String -> Advice (Show `And` Eq) m r
printArgs' h s = restrictArgs (\Dict -> Dict) (printArgs h s) 

-- the "component" we want to decorate
data Foo m = Foo { 
        runFoo :: Int -> Bool -> m () 
    ,   runBar :: m Int
    ,   runBaz :: Char -> m Char
    } 
    deriving stock Generic

fooFunc :: MonadWriter [String] m => Int -> Bool -> m ()  
fooFunc = \_ _ -> tell ["foo"] 

foo :: MonadWriter [String] m => Foo m
foo = Foo fooFunc (pure 5) (\_ -> pure 'c')

-- works with functions of any number of arguments
someAdvice :: MonadWriter [String] m => Advice Top m r 
someAdvice = makeExecutionAdvice \action -> do
    tell ["before"]
    r <- action
    tell ["after"]
    pure r

someAdvice' :: MonadWriter [String] m => Advice Top m r 
someAdvice' = 
    (makeExecutionAdvice \action -> do
        r <- action
        tell ["after"]
        pure r)
    <>
    (makeExecutionAdvice \action -> do
        tell ["before"]
        r <- action
        pure r)

advisedFoo :: MonadWriter [String] m => Foo m
advisedFoo = advising (adviseRecord @Top @Top \_ -> someAdvice) foo

-- Unlike regular advices, which require decorated
-- functions to be sufficiently polymorphic,
-- "simple" advices can decorate
-- non-DepT *concrete* monads.
concreteFoo :: IORef [String] -> Foo IO
concreteFoo ref = Foo {
        runFoo = \_ _ -> modifyIORef ref (\xs -> xs ++ ["foo"])
    ,   runBar = pure 5
    ,   runBaz = \_ -> pure 'c'
    }

refAdvice :: MonadIO m => IORef [String] -> Advice Top m r 
refAdvice ref = makeExecutionAdvice \action -> do
    liftIO $ modifyIORef ref (\xs -> xs ++ ["before"])
    r <- action
    liftIO $ modifyIORef ref (\xs -> xs ++ ["after"])
    pure r

concreteAdvisedFoo :: IORef [String] -> Foo IO
concreteAdvisedFoo ref =
    advising (adviseRecord @Top @Top \_ -> refAdvice ref) (concreteFoo ref)

printAdvisedFoo :: IORef [String] -> Foo IO
printAdvisedFoo ref =
    advising (adviseRecord @_ @Top (\_ -> printArgs' stdout "args: ")) (concreteFoo ref)

--
-- Stuff for testing the TypeReps in adviseRecord
data AAA m = AAA { aaa :: BBB m } deriving Generic
-- just to check that newtypes are handled correctly
newtype BBB m = BBB { bbb :: CCC m } deriving Generic 
data CCC m = CCC { ccc :: Int -> Bool ->  m () } deriving Generic

type Trace = Writer [(TypeRep, String)]

tracedEnv :: AAA Trace
tracedEnv = AAA {
        aaa = BBB {
            bbb = CCC { 
               ccc = \_ _ -> pure () 
            }
        }
    }

doTrace :: NonEmpty (TypeRep, String) -> Advice ca Trace r
doTrace trace = makeExecutionAdvice \action -> do
    tell (toList trace) 
    action

--
--
tests :: TestTree
tests =
  testGroup
    "All"
    [
      testCase "adviseBare" $
        assertEqual "" ["before","foo","after"] $
            let advisedFunc = advise @Top someAdvice fooFunc
             in execWriter $ runAspectT $ advisedFunc 0 False
    , testCase "adviseBare_monoid" $
        assertEqual "" ["before","foo","after"] $
            let advisedFunc = advise @Top someAdvice' fooFunc
             in execWriter $ runAspectT $ advisedFunc 0 False
    , testCase "adviseRecord" $
        assertEqual "" ["before","foo","after"] $
          let advised = advising (adviseRecord @Top @Top \_ -> someAdvice) foo
           in execWriter $ runFoo advised 0 False
    , testCase "concrete adviseRecord" $ do
        ref <- newIORef []
        () <- runFoo (concreteAdvisedFoo ref) 0 False
        result <- readIORef ref
        assertEqual "" ["before","foo","after"] result
    , testCase "print adviseRecord" $ do
        ref <- newIORef []
        () <- runFoo (printAdvisedFoo ref) 0 False
        result <- readIORef ref
        assertEqual "" ["foo"] result
    , testCase "trace" $ do
        let tracedEnv' = advising (adviseRecord @Top @Top doTrace) tracedEnv
            result = execWriter $ (ccc . bbb . aaa) tracedEnv' 0 False
            expected = [
                  (typeRep (Proxy @CCC), "ccc")
                , (typeRep (Proxy @BBB), "bbb")
                , (typeRep (Proxy @AAA), "aaa")
                ]
        assertEqual "" expected result
    ]

main :: IO ()
main = defaultMain tests