railroad-0.3.0.0: test/Railroad/BifurcateSpec.hs
{-# LANGUAGE TypeFamilies #-}
module Railroad.BifurcateSpec where
import Control.Monad.State (modify, runState)
import Data.Bifunctor (first)
import Data.Either (isLeft, isRight)
import Data.Functor.Identity
import Data.Maybe (listToMaybe)
import Data.Proxy (Proxy (..))
import Data.Validation (Validation (..))
import Model
import Railroad.Bifurcate
import Test.Hspec
import Test.QuickCheck hiding (Result (..))
spec :: Spec
spec = do
describe "bifurcate" $ do
it "Bool" $ property $ \b ->
bifurcate b === (if b then Right () else Left ())
it "Maybe" $ property $ \(m :: Maybe Int) ->
bifurcate m === maybe (Left ()) Right m
it "Either is the identity" $ property $ \(e :: Either Int Int) ->
bifurcate e === e
it "Validation" $ property $ \(v :: Validation Int Int) ->
bifurcate v === (case v of Failure e -> Left e; Success a -> Right a)
it "a traversable of Bool or Maybe succeeds only if every element does" $ property $
\(bs :: [Bool]) (ms :: [Maybe Int]) ->
(bifurcate bs === (if and bs then Right (map (const ()) bs) else Left ()))
.&&. (bifurcate ms === maybe (Left ()) Right (sequence ms))
it "a traversable of Either stops at the first error" $ property $ \(xs :: [Either Int Int]) ->
bifurcate xs === maybe (Right [a | Right a <- xs]) Left (listToMaybe [e | Left e <- xs])
it "a traversable of Validation accumulates every error" $ property $ \(xs :: [Validation [Int] Int]) ->
bifurcate xs === (case [e | Failure e <- xs] of
[] -> Right [a | Success a <- xs]
es -> Left (concat es))
it "unwraps only the outermost base layer" $ property $
\(e :: Either Int (Maybe Int)) (m :: Maybe (Maybe Int)) (b :: Either Int Bool) ->
(bifurcate e === e) .&&. (bifurcate m === maybe (Left ()) Right m) .&&. (bifurcate b === b)
describe "?>" $ do
it "tags by the predicate, keeping the value on both sides" $ property $ \(a :: Int) ->
runIdentity (pure a ?> even) === (if even a then Right a else Left a)
forShapes "?~ and ??~ recover from a failure" $ \(_ :: Proxy a) -> property $
\(x :: a) (d :: CRes a) (Fn f :: Fun (CErr a) (CRes a)) ->
(runIdentity (pure x ?~ d) === either (const d) id (bifurcate x))
.&&. (runIdentity (pure x ??~ f) === either f id (bifurcate x))
forShapes "?<* runs the handler on a failure only and passes the value through" $ \(_ :: Proxy a) -> property $
\(x :: a) ->
let (y, logged) = runState (pure x ?<* (\e -> modify (e :))) ([] :: [CErr a])
in (bifurcate y === bifurcate x) .&&. (logged === either pure (const []) (bifurcate x))
describe "?| (fallback, last error kept)" $ do
let law :: forall a b. (Shape a, Shape b, CRes a ~ CRes b) => Proxy a -> Proxy b -> Property
law _ _ = property $ \(x :: a) (y :: b) ->
runIdentity (pure x ?| pure y) === either (const (bifurcate y)) Right (bifurcate x)
shortCircuits :: forall a b. (Shape a, Shape b, CRes a ~ CRes b) => Proxy a -> Proxy b -> Property
shortCircuits _ _ = property $ \(x :: a) (y :: b) ->
snd (runState (pure x ?| (modify (+ (1 :: Int)) >> pure y)) 0)
=== (if isLeft (bifurcate x) then 1 else 0)
it "Maybe, Either" $ law (Proxy @(Maybe Int)) (Proxy @(Either Int Int))
it "Either, Maybe" $ law (Proxy @(Either Int Int)) (Proxy @(Maybe Int))
it "Validation, Either" $ law (Proxy @(Validation Int Int)) (Proxy @(Either Int Int))
it "[Maybe], [Either]" $ law (Proxy @[Maybe Int]) (Proxy @[Either Int Int])
it "runs the right action only when the left fails" $ shortCircuits (Proxy @(Either Int Int)) (Proxy @(Maybe Int))
it "is associative" $ property $ \(a :: Either Int Int) (b :: Either Int Int) (c :: Either Int Int) ->
runIdentity ((pure a ?| pure b) ?| pure c) === runIdentity (pure a ?| (pure b ?| pure c))
describe "?|<> (fallback, errors combined with <>)" $ do
let law :: forall a b. (Shape a, Shape b, CRes a ~ CRes b, CErr a ~ CErr b, Semigroup (CErr a))
=> Proxy a -> Proxy b -> Property
law _ _ = property $ \(x :: a) (y :: b) ->
runIdentity (pure x ?|<> pure y)
=== either (\ex -> first (ex <>) (bifurcate y)) Right (bifurcate x)
it "Either, Validation" $ law (Proxy @(Either [Int] Int)) (Proxy @(Validation [Int] Int))
it "Validation, Either" $ law (Proxy @(Validation [Int] Int)) (Proxy @(Either [Int] Int))
it "Maybe, Maybe" $ law (Proxy @(Maybe Int)) (Proxy @(Maybe Int))
it "[Either], [Either]" $ law (Proxy @[Either [Int] Int]) (Proxy @[Either [Int] Int])
it "is associative" $ property $ \(a :: Either [Int] Int) (b :: Either [Int] Int) (c :: Either [Int] Int) ->
runIdentity ((pure a ?|<> pure b) ?|<> pure c) === runIdentity (pure a ?|<> (pure b ?|<> pure c))
describe "tagBy" $ do
it "keeps the value on both sides" $ property $ \(p :: Fun Int Bool) (x :: Int) ->
either id id (tagBy (applyFun p) x) === x
it "is Right exactly when the predicate holds" $ property $ \(p :: Fun Int Bool) (x :: Int) ->
isRight (tagBy (applyFun p) x) === applyFun p x
describe "partitionBifurcate, successes, failures" $ do
let laws :: forall a. (Shape a, Bifurcate [a], CErr [a] ~ CErr a, CRes [a] ~ [CRes a])
=> Proxy a -> Property
laws _ = property $ \(xs :: [a]) (ys :: [a]) ->
let (es, rs) = partitionBifurcate xs
in (length es + length rs === length xs)
.&&. (partitionBifurcate (xs ++ ys) === (es <> failures ys, rs <> successes ys))
.&&. (isRight (bifurcate xs) === null es)
.&&. (either (const True) (== rs) (bifurcate xs))
.&&. (successes xs === rs)
.&&. (failures xs === es)
single :: forall a. Shape a => Proxy a -> Property
single _ = property $ \(x :: a) ->
(successes [x] === either (const []) pure (bifurcate x))
.&&. (failures [x] === either pure (const []) (bifurcate x))
it "Bool" $ laws (Proxy @Bool)
it "Maybe" $ laws (Proxy @(Maybe Int))
it "Either" $ laws (Proxy @(Either Int Int))
it "Validation" $ laws (Proxy @(Validation [Int] Int))
forShapes "a singleton splits as its element does" single
describe "anySucceeds" $ do
let law :: forall a. (Shape a) => Proxy a -> Property
law _ = property $ \(xs :: [a]) ->
let r = anySucceeds xs
in (isRight r === any (isRight . bifurcate) xs)
.&&. (r === (if any (isRight . bifurcate) xs then Right (successes xs) else Left (failures xs)))
.&&. (either length (const 0) r === (if any (isRight . bifurcate) xs then 0 else length xs))
it "Bool" $ law (Proxy @Bool)
it "Maybe" $ law (Proxy @(Maybe Int))
it "Either" $ law (Proxy @(Either Int Int))
it "Validation" $ law (Proxy @(Validation [Int] Int))
it "fails on an empty collection with no failures" $
anySucceeds ([] :: [Maybe Int]) `shouldBe` Left []
describe "neg" $ do
it "Bool and Maybe swap to the unit failure" $ do
neg True `shouldBe` Left ()
neg False `shouldBe` Right ()
neg (Just 'x') `shouldBe` Left 'x'
neg (Nothing :: Maybe Char) `shouldBe` Right ()
forShapes "swaps failure and success" $ \(_ :: Proxy a) -> property $
\(x :: a) -> neg x === either Right Left (bifurcate x)
forShapes "is an involution: neg (neg x) = bifurcate x" $ \(_ :: Proxy a) -> property $
\(x :: a) -> neg (neg x) === bifurcate x
it "of a traversable passes exactly when some element failed" $ property $
\(xs :: [Either Int Int]) -> isRight (neg xs) === any (isLeft . bifurcate) xs
it "carries the first failure of a traversable" $ property $ \(xs :: [Either Int Int]) ->
neg xs === (case [e | Left e <- xs] of { e : _ -> Right e; [] -> Left [a | Right a <- xs] })
it "of atLeastOne is noneAtAll" $ property $ \(xs :: [Int]) ->
neg (atLeastOne xs) === noneAtAll xs
it "turns anySucceeds into all-failed" $ property $ \(xs :: [Either Int Int]) ->
isRight (neg (anySucceeds xs)) === all (isLeft . bifurcate) xs
describe "atLeastOne" $ do
it "classifies a list by emptiness, keeping the list" $ property $ \(xs :: [Int]) ->
atLeastOne xs === (if null xs then Nothing else Just xs)
it "works on any Foldable" $ do
atLeastOne (Just 'a') `shouldBe` Just (Just 'a')
atLeastOne (Nothing :: Maybe Char) `shouldBe` Nothing
describe "exactlyOne" $ do
it "returns the element of a singleton, else says why not" $ property $ \(xs :: [Int]) ->
exactlyOne xs === (case xs of { [] -> Left IsEmpty; [x] -> Right x; _ -> Left (TooMany xs) })
it "works on any Foldable" $ do
exactlyOne (Just 'a') `shouldBe` Right 'a'
exactlyOne (Nothing :: Maybe Char) `shouldBe` Left IsEmpty
describe "noneAtAll" $ do
it "succeeds on an empty collection, else returns it as the failure" $ property $ \(xs :: [Int]) ->
noneAtAll xs === (if null xs then Right () else Left xs)
describe "the three agree with each other" $ do
it "exactlyOne reports IsEmpty exactly when noneAtAll succeeds" $ property $ \(xs :: [Int]) ->
(exactlyOne xs == Left IsEmpty) === (noneAtAll xs == Right ())
it "a list of one element satisfies atLeastOne and exactlyOne but not noneAtAll" $ property $ \(x :: Int) ->
(atLeastOne [x], exactlyOne [x], noneAtAll [x]) === (Just [x], Right x, Left [x])
describe "CardinalityError" $ do
it "cardinalityErr is the eliminator" $ property $ \(n :: Int) (xs :: [Int]) ->
(cardinalityErr n length (IsEmpty :: CardinalityError [Int]) === n)
.&&. (cardinalityErr n length (TooMany xs) === length xs)