packages feed

zifter-0.0.0.0: test/Zifter/ZiftSpec.hs

{-# LANGUAGE TypeApplications #-}

module Zifter.ZiftSpec
    ( spec
    ) where

import Test.Hspec
import Test.QuickCheck
import Test.Validity

import Path.IO

import Control.Concurrent.STM

import Zifter.OptParse.Gen ()
import Zifter.OptParse.Types
import Zifter.Zift
import Zifter.Zift.Gen ()

spec :: Spec
spec = do
    describe "ZiftOutput" $ eqSpec @ZiftOutput
    describe "ZiftResult" $ do
        eqSpec @(ZiftResult Int)
        genValiditySpec @(ZiftResult Double)
        functorSpec @ZiftResult
        applicativeSpec @ZiftResult
        monadSpec @ZiftResult
    describe "Zift" $ do
        describe "Monoid Zift" $ do
            describe "mempty" $
                it "succeeds with empty buffer" $
                forAll genUnchecked $ \sets -> do
                    let zf = mempty :: Zift ()
                    (zr, zs) <- runZiftTest sets zf
                    zr `shouldBe` ZiftSuccess ()
                    zs `shouldBe` ZiftState {bufferedOutput = []}
            describe "mappend" $
                it
                    "succeeds with empty buffers but with the output in the channel" $
                forAll genUnchecked $ \sets ->
                    forAll genUnchecked $ \zo1 ->
                        forAll genUnchecked $ \zo2 -> do
                            let zf1 =
                                    Zift $ \_ _ ->
                                        pure
                                            ( ZiftSuccess ()
                                            , ZiftState {bufferedOutput = [zo1]})
                            let zf2 =
                                    Zift $ \_ _ ->
                                        pure
                                            ( ZiftSuccess ()
                                            , ZiftState {bufferedOutput = [zo2]})
                            let zf = zf1 `mappend` zf2
                            pchan <- atomically newTChan
                            (zr, zs) <- runZiftTestWithChan pchan sets zf
                            zr `shouldBe` ZiftSuccess ()
                            zs `shouldBe` ZiftState {bufferedOutput = []}
                            atomically (readTChan pchan) `shouldReturn` zo1
                            atomically (readTChan pchan) `shouldReturn` zo2
        describe "Functor Zift" $
            describe "fmap" $
            it "it succeeds with a flushed buffer" $
            forAll genUnchecked $ \sets ->
                forAll genUnchecked $ \state -> do
                    let zf =
                            fmap (+ 1) $
                            Zift $ \_ st -> pure (ZiftSuccess (0 :: Int), st)
                    pchan <- atomically newTChan
                    (zr, zs) <- runZiftTestWith state pchan sets zf
                    zr `shouldBe` ZiftSuccess 1
                    zs `shouldBe` ZiftState {bufferedOutput = []}
                    buffer <- readAllFrom pchan
                    buffer `shouldBe` reverse (bufferedOutput state)
        describe "Applicative Zift" $ do
            describe "pure" $
                it "succeeds with the same state" $
                forAll genUnchecked $ \sets ->
                    forAll genUnchecked $ \state -> do
                        let zf = pure () :: Zift ()
                        (zr, zs) <- runZiftTestWithState state sets zf
                        zr `shouldBe` ZiftSuccess ()
                        zs `shouldBe` state
            describe "<*>" $
                it "succeeds with a linear flushed buffer" $
                forAll genUnchecked $ \sets ->
                    forAll genUnchecked $ \state ->
                        forAll genUnchecked $ \st1 ->
                            forAll genUnchecked $ \st2 -> do
                                let zf1 =
                                        Zift $ \_ st ->
                                            pure
                                                ( ZiftSuccess (+ 1)
                                                , st `mappend` st1)
                                let zf2 =
                                        Zift $ \_ st ->
                                            pure
                                                ( ZiftSuccess (1 :: Int)
                                                , st `mappend` st2)
                                let zf = zf1 <*> zf2
                                pchan <- atomically newTChan
                                (zr, zs) <- runZiftTestWith state pchan sets zf
                                zr `shouldBe` ZiftSuccess 2
                                zs `shouldBe` ZiftState {bufferedOutput = []}
                                buffer <- readAllFrom pchan
                                buffer `shouldBe`
                                    reverse
                                        (bufferedOutput st2 ++
                                         bufferedOutput st1 ++
                                         bufferedOutput state)
        describe "Monad Zift" $
            describe ">>=" $
            it "succeeds with a flushed buffer after the first output" $
            forAll genUnchecked $ \sets ->
                forAll genUnchecked $ \state ->
                    forAll genUnchecked $ \zo1 ->
                        forAll genUnchecked $ \zo2 -> do
                            let zf1 =
                                    Zift $ \_ st ->
                                        pure
                                            ( ZiftSuccess (2 :: Int)
                                            , st
                                              { bufferedOutput =
                                                    zo1 : bufferedOutput st
                                              })
                            let zf2 n =
                                    Zift $ \_ st ->
                                        pure
                                            ( ZiftSuccess (n + 1)
                                            , st
                                              { bufferedOutput =
                                                    zo2 : bufferedOutput st
                                              })
                            let zf = zf1 >>= zf2
                            pchan <- atomically newTChan
                            (zr, zs) <- runZiftTestWith state pchan sets zf
                            zr `shouldBe` ZiftSuccess 3
                            zs `shouldBe` ZiftState {bufferedOutput = [zo2]}
                            buffer <- readAllFrom pchan
                            buffer `shouldBe`
                                (reverse (bufferedOutput state) ++ [zo1])
        describe "MonadFail Zift" $ do
            describe "fail" $
                it "just results in a ZiftFailed" $
                forAll genUnchecked $ \sets ->
                    forAll genUnchecked $ \state -> do
                        let s = "Test"
                        let zf = fail s :: Zift ()
                        (zr, zs) <- runZiftTestWithState state sets zf
                        zr `shouldBe` ZiftFailed s
                        zs `shouldBe` state
            describe "liftIO . fail" $
                it "just returns ZiftFailed" $
                forAll genUnchecked $ \sets ->
                    forAll genUnchecked $ \state -> do
                        let s = "Test"
                        let zf = liftIO $ fail s :: Zift ()
                        (zr, zs) <- runZiftTestWithState state sets zf
                        zr `shouldBe` ZiftFailed ("user error (" ++ s ++ ")")
                        zs `shouldBe` state

runZiftTest :: Settings -> Zift a -> IO (ZiftResult a, ZiftState)
runZiftTest sets func = do
    pchan <- atomically newTChan
    runZiftTestWithChan pchan sets func

runZiftTestWithChan :: TChan ZiftOutput
                    -> Settings
                    -> Zift a
                    -> IO (ZiftResult a, ZiftState)
runZiftTestWithChan = runZiftTestWith ZiftState {bufferedOutput = []}

runZiftTestWithState :: ZiftState
                     -> Settings
                     -> Zift a
                     -> IO (ZiftResult a, ZiftState)
runZiftTestWithState state sets func = do
    pchan <- atomically newTChan
    runZiftTestWith state pchan sets func

runZiftTestWith
    :: ZiftState
    -> TChan ZiftOutput
    -> Settings
    -> Zift a
    -> IO (ZiftResult a, ZiftState)
runZiftTestWith zs pchan sets func = do
    rd <- getCurrentDir
    let zc =
            ZiftContext
            { rootdir = rd
            , settings = sets
            , printChan = pchan
            , recursionList = []
            }
    zift func zc zs

readAllFrom :: TChan a -> IO [a]
readAllFrom chan = do
    mr <- atomically $ tryReadTChan chan
    case mr of
        Nothing -> pure []
        Just r -> do
            rest <- readAllFrom chan
            pure (r : rest)