packages feed

streamly-0.9.0: test/Streamly/Test/Prelude.hs

{-# OPTIONS_GHC -Wno-deprecations #-}

-- |
-- Module      : Streamly.Test.Prelude
-- Copyright   : (c) 2020 Composewell Technologies
--
-- License     : BSD-3-Clause
-- Maintainer  : streamly@composewell.com
-- Stability   : experimental
-- Portability : GHC

module Streamly.Test.Prelude (main) where

import Control.Concurrent (myThreadId, threadDelay)
import Control.Exception (Exception, try)
import Control.Monad.Catch (throwM)
import Control.Monad.Error.Class (throwError, MonadError)
import Control.Monad.Trans.Except (runExceptT, ExceptT)
import Data.List (sort)
import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
import System.Random (randomIO)
import Test.Hspec as H

import Streamly.Prelude (SerialT, IsStream)

import qualified Streamly.Prelude as S

toListSerial :: SerialT IO a -> IO [a]
toListSerial = S.toList . S.fromSerial

-- XXX need to test that we have promptly cleaned up everything after the error
-- XXX We can also check the output that we are expected to get before the
-- error occurs.

newtype ExampleException = ExampleException String deriving (Eq, Show)

instance Exception ExampleException

simpleMonadError :: Spec
simpleMonadError = do
{-
    it "simple runExceptT" $ do
        (runExceptT $ S.drain $ return ())
        `shouldReturn` (Right () :: Either String ())
    it "simple runExceptT with error" $ do
        (runExceptT $ S.drain $ throwError "E") `shouldReturn` Left "E"
        -}
    it "simple try" $
        try (S.drain $ return ())
        `shouldReturn` (Right () :: Either ExampleException ())
    it "simple try with throw error" $
        try (S.drain $ throwM $ ExampleException "E")
        `shouldReturn` (Left (ExampleException "E") :: Either ExampleException ())

_composeWithMonadError
    :: ( IsStream t
       , Semigroup (t (ExceptT String IO) Int)
       , MonadError String (t (ExceptT String IO))
       )
    => (t (ExceptT String IO) Int -> SerialT (ExceptT String IO) Int) -> Spec
_composeWithMonadError t = do
    let tl = S.toList . t
    it "Compose throwError, nil" $
        runExceptT (tl (throwError "E" <> S.nil)) `shouldReturn` Left "E"
    it "Compose nil, error" $
        runExceptT (tl (S.nil <> throwError "E")) `shouldReturn` Left "E"

mixedOps :: Spec
mixedOps =
    it "Compose many ops" $
        (sort <$> toListSerial composeMixed)
            `shouldReturn` ([8,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,11,11
                            ,11,11,11,11,11,11,11,11,12,12,12,12,12,13
                            ] :: [Int])
    where

    composeMixed :: SerialT IO Int
    composeMixed = do
        S.fromEffect $ return ()
        S.fromEffect $ putStr ""
        let x = 1
        let y = 2
        z <- do
                x1 <- S.fromWAsync $ return 1 <> return 2
                S.fromEffect $ return ()
                S.fromEffect $ putStr ""
                y1 <- S.fromAsync $ return 1 <> return 2
                z1 <- do
                    x11 <- return 1 <> return 2
                    y11 <- S.fromAsync $ return 1 <> return 2
                    z11 <- S.fromWSerial $ return 1 <> return 2
                    S.fromEffect $ return ()
                    S.fromEffect $ putStr ""
                    return (x11 + y11 + z11)
                return (x1 + y1 + z1)
        return (x + y + z)

mixedOpsAheadly :: Spec
mixedOpsAheadly =
    it "Compose many ops" $
        (sort <$> toListSerial composeMixed)
            `shouldReturn` ([8,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,11,11
                            ,11,11,11,11,11,11,11,11,12,12,12,12,12,13
                            ] :: [Int])
    where

    composeMixed :: SerialT IO Int
    composeMixed = do
        S.fromEffect $ return ()
        S.fromEffect $ putStr ""
        let x = 1
        let y = 2
        z <- do
                x1 <- S.fromWAsync $ return 1 <> return 2
                S.fromEffect $ return ()
                S.fromEffect $ putStr ""
                y1 <- S.fromAhead $ return 1 <> return 2
                z1 <- do
                    x11 <- return 1 <> return 2
                    y11 <- S.fromAhead $ return 1 <> return 2
                    z11 <- S.fromParallel $ return 1 <> return 2
                    S.fromEffect $ return ()
                    S.fromEffect $ putStr ""
                    return (x11 + y11 + z11)
                return (x1 + y1 + z1)
        return (x + y + z)

-- XXX Merge both the loops.
nestedLoops :: IO ()
nestedLoops = S.drain $ do
    S.fromEffect $ hSetBuffering stdout LineBuffering
    x <- loop "A " 2
    y <- loop "B " 2
    S.fromEffect $ myThreadId >>= putStr . show
             >> putStr " "
             >> print (x, y)

    where

    -- we can just use
    -- fromParallel $ mconcat $ replicate n $ fromEffect (...)
    loop :: String -> Int -> SerialT IO String
    loop name n = do
        rnd <- S.fromEffect (randomIO :: IO Int)
        let result = name <> show rnd
            repeatIt = if n > 1 then loop name (n - 1) else S.nil
         in return result `S.wAsync` repeatIt

parallelLoops :: IO ()
parallelLoops = do
    hSetBuffering stdout LineBuffering
    S.drain $ do
        x <- S.take 10 $ loop "A" `S.parallel` loop "B"
        S.fromEffect $ myThreadId >>= putStr . show
               >> putStr " got "
               >> print x

    where

    -- we can just use
    -- fromParallel $ cycle1 $ fromEffect (...)
    loop :: String -> SerialT IO (String, Int)
    loop name = do
        S.fromEffect $ threadDelay 1000000
        rnd <- S.fromEffect (randomIO :: IO Int)
        S.fromEffect $ myThreadId >>= putStr . show
               >> putStr " yielding "
               >> print rnd
        return (name, rnd) `S.parallel` loop name

moduleName :: String
moduleName = "Prelude"

main :: IO ()
main = hspec $ H.parallel $ do
  describe moduleName $ do
    describe "Miscellaneous combined examples" mixedOps
    describe "Miscellaneous combined examples fromAhead" mixedOpsAheadly
    describe "Simple MonadError and MonadThrow" simpleMonadError

    it "Nested loops" nestedLoops
    it "Parallel loops" parallelLoops
    {-
    describe "Composed MonadError fromSerial" $ composeWithMonadError fromSerial
    describe "Composed MonadError fromWSerial" $ composeWithMonadError fromWSerial
    describe "Composed MonadError fromAsync" $ composeWithMonadError fromAsync
    describe "Composed MonadError fromWAsync" $ composeWithMonadError fromWAsync
    -}