packages feed

validation-1.3.1: test/hedgehog_tests.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}

import Control.Applicative (liftA3)
import Control.Lens (from, review, (#), (^.), (^?), _Just, _Left, _Right)
import Control.Monad (join, unless)
import Data.Bifunctor (bimap)
import Data.Bifunctor.Swap (swap)
import Data.Functor.Alt (Alt ((<!>)))
import Data.Functor.Apply (Apply ((<.>)))
import Data.Functor.Identity (Identity (..))
import Data.Validation
import Hedgehog
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import System.Exit (exitFailure)
import System.IO (BufferMode (..), hSetBuffering, stderr, stdout)
import Prelude hiding (either, id, (.))
import qualified Prelude

main :: IO ()
main = do
  hSetBuffering stdout LineBuffering
  hSetBuffering stderr LineBuffering

  result <-
    checkParallel $
      Group
        "Validation"
        [ ("prop_semigroup_assoc", prop_semigroup_assoc)
        , ("prop_monoid_assoc", prop_monoid_assoc)
        , ("prop_monoid_left_id", prop_monoid_left_id)
        , ("prop_monoid_right_id", prop_monoid_right_id)
        , ("prop_functor_id", prop_functor_id)
        , ("prop_functor_compose", prop_functor_compose)
        , ("prop_applicative_id", prop_applicative_id)
        , ("prop_applicative_homomorphism", prop_applicative_homomorphism)
        , ("prop_apply_compose", prop_apply_compose)
        , ("prop_alt_assoc", prop_alt_assoc)
        , ("prop_alt_left_catch", prop_alt_left_catch)
        , ("prop_bifunctor_id", prop_bifunctor_id)
        , ("prop_bifunctor_compose", prop_bifunctor_compose)
        , ("prop_foldValidation_failure", prop_foldValidation_failure)
        , ("prop_foldValidation_success", prop_foldValidation_success)
        , ("prop_either_roundtrip", prop_either_roundtrip)
        , ("prop_either_roundtrip_inv", prop_either_roundtrip_inv)
        , ("prop_codiagonal_roundtrip", prop_codiagonal_roundtrip)
        , ("prop_failure_prism_review_preview", prop_failure_prism_review_preview)
        , ("prop_success_prism_review_preview", prop_success_prism_review_preview)
        , ("prop_failure_prism_miss", prop_failure_prism_miss)
        , ("prop_success_prism_miss", prop_success_prism_miss)
        , ("prop_poly_failure_prism", prop_poly_failure_prism)
        , ("prop_poly_success_prism", prop_poly_success_prism)
        , ("prop_swap_failure", prop_swap_failure)
        , ("prop_swap_success", prop_swap_success)
        , ("prop_swap_involution", prop_swap_involution)
        , ("prop_either_reviewFailure", prop_either_reviewFailure)
        , ("prop_either_asFailure_hit", prop_either_asFailure_hit)
        , ("prop_either_asFailure_miss", prop_either_asFailure_miss)
        , ("prop_either_reviewSuccess", prop_either_reviewSuccess)
        , ("prop_either_asSuccess_hit", prop_either_asSuccess_hit)
        , ("prop_either_asSuccess_miss", prop_either_asSuccess_miss)
        , ("prop_either_failure_roundtrip", prop_either_failure_roundtrip)
        , ("prop_either_success_roundtrip", prop_either_success_roundtrip)
        , ("prop_match_hit", prop_match_hit)
        , ("prop_match_miss", prop_match_miss)
        , ("prop_match_validatorProfunctor", prop_match_validatorProfunctor)
        , ("prop_match_validatorMonad", prop_match_validatorMonad)
        , ("prop_match_validatorMonadProfunctor", prop_match_validatorMonadProfunctor)
        , ("prop_match_alt", prop_match_alt)
        , ("prop_matchValidator_alt", prop_matchValidator_alt)
        , ("prop_matchValidatorProfunctor_alt", prop_matchValidatorProfunctor_alt)
        , ("prop_matchValidatorMonad_alt", prop_matchValidatorMonad_alt)
        , ("prop_matchValidatorMonadProfunctor_alt", prop_matchValidatorMonadProfunctor_alt)
        , ("prop_arrow_fmap_match", prop_arrow_fmap_match)
        , ("prop_arrow_id_match", prop_arrow_id_match)
        , ("prop_arrow_validator_alt", prop_arrow_validator_alt)
        , ("prop_arrow_validatorProfunctor_alt", prop_arrow_validatorProfunctor_alt)
        , ("prop_arrow_validatorMonad_alt", prop_arrow_validatorMonad_alt)
        , ("prop_arrow_validatorMonadProfunctor_alt", prop_arrow_validatorMonadProfunctor_alt)
        ]

  unless result exitFailure

-- Generators

genValidation :: Gen e -> Gen a -> Gen (Validation e a)
genValidation e a = Gen.choice [fmap Failure e, fmap Success a]

genInt :: Gen Int
genInt = Gen.int (Range.linear (-100) 100)

genString :: Gen String
genString = Gen.string (Range.linear 0 50) Gen.unicode

genStrings :: Gen [String]
genStrings = Gen.list (Range.linear 1 10) genString

testGen :: Gen (Validation [String] Int)
testGen = genValidation genStrings genInt

-- Semigroup / Monoid

mkAssoc :: (Validation [String] Int -> Validation [String] Int -> Validation [String] Int) -> Property
mkAssoc f =
  let g = forAll testGen
      assoc x y z = ((x `f` y) `f` z) === (x `f` (y `f` z))
   in property $ join (liftA3 assoc g g g)

prop_semigroup_assoc :: Property
prop_semigroup_assoc = mkAssoc (<>)

prop_monoid_assoc :: Property
prop_monoid_assoc = mkAssoc mappend

prop_monoid_left_id :: Property
prop_monoid_left_id =
  property $ do
    x <- forAll testGen
    (mempty `mappend` x) === x

prop_monoid_right_id :: Property
prop_monoid_right_id =
  property $ do
    x <- forAll testGen
    (x `mappend` mempty) === x

-- Functor

prop_functor_id :: Property
prop_functor_id =
  property $ do
    x <- forAll testGen
    fmap Prelude.id x === x

prop_functor_compose :: Property
prop_functor_compose =
  property $ do
    x <- forAll testGen
    let f = (+ 1)
        g = (* 2)
    fmap (f Prelude.. g) x === fmap f (fmap g x)

-- Applicative / Apply

prop_applicative_id :: Property
prop_applicative_id =
  property $ do
    x <- forAll testGen
    (pure Prelude.id <*> x) === x

prop_applicative_homomorphism :: Property
prop_applicative_homomorphism =
  property $ do
    x <- forAll genInt
    let f = (+ 1)
    (pure f <*> pure x :: Validation [String] Int) === pure (f x)

prop_apply_compose :: Property
prop_apply_compose =
  property $ do
    w <- forAll testGen
    let u = Success (+ 1) :: Validation [String] (Int -> Int)
        v = Success (* 2) :: Validation [String] (Int -> Int)
    (fmap (Prelude..) u <.> v <.> w) === (u <.> (v <.> w))

-- Alt

prop_alt_assoc :: Property
prop_alt_assoc =
  property $ do
    x <- forAll testGen
    y <- forAll testGen
    z <- forAll testGen
    ((x <!> y) <!> z) === (x <!> (y <!> z))

prop_alt_left_catch :: Property
prop_alt_left_catch =
  property $ do
    x <- forAll genInt
    y <- forAll testGen
    (Success x <!> y) === (Success x :: Validation [String] Int)

-- Bifunctor

prop_bifunctor_id :: Property
prop_bifunctor_id =
  property $ do
    x <- forAll testGen
    bimap Prelude.id Prelude.id x === x

prop_bifunctor_compose :: Property
prop_bifunctor_compose =
  property $ do
    x <- forAll testGen
    let f = (++ ["x"])
        g = (+ 1)
        h = (++ ["y"])
        k = (* 2)
    bimap (f Prelude.. h) (g Prelude.. k) x === bimap f g (bimap h k x)

-- foldValidation

prop_foldValidation_failure :: Property
prop_foldValidation_failure =
  property $ do
    e <- forAll genStrings
    foldValidation length (const 0) (Failure e :: Validation [String] Int) === length e

prop_foldValidation_success :: Property
prop_foldValidation_success =
  property $ do
    a <- forAll genInt
    foldValidation (const 0) (+ 1) (Success a :: Validation [String] Int) === (a + 1)

-- Iso: either

prop_either_roundtrip :: Property
prop_either_roundtrip =
  property $ do
    x <- forAll testGen
    (x ^. either ^. from either) === x

prop_either_roundtrip_inv :: Property
prop_either_roundtrip_inv =
  property $ do
    x <- forAll testGen
    let e = x ^. either :: Prelude.Either [String] Int
    (e ^. from either) === x

-- Iso: codiagonal

prop_codiagonal_roundtrip :: Property
prop_codiagonal_roundtrip =
  property $ do
    x <- forAll (genValidation genInt genInt)
    (x ^. codiagonal ^. from codiagonal) === x

-- Prisms

prop_failure_prism_review_preview :: Property
prop_failure_prism_review_preview =
  property $ do
    e <- forAll genStrings
    let v = review _Failure e :: Validation [String] Int
    v ^? _Failure === Just e

prop_success_prism_review_preview :: Property
prop_success_prism_review_preview =
  property $ do
    a <- forAll genInt
    let v = review _Success a :: Validation [String] Int
    v ^? _Success === Just a

prop_failure_prism_miss :: Property
prop_failure_prism_miss =
  property $ do
    a <- forAll genInt
    (Success a :: Validation [String] Int) ^? _Failure === Nothing

prop_success_prism_miss :: Property
prop_success_prism_miss =
  property $ do
    e <- forAll genStrings
    (Failure e :: Validation [String] Int) ^? _Success === Nothing

-- Polymorphic prisms

prop_poly_failure_prism :: Property
prop_poly_failure_prism =
  property $ do
    e <- forAll genStrings
    let v = __Failure # e :: Validation [String] Int
    v ^? __Failure === Just e

prop_poly_success_prism :: Property
prop_poly_success_prism =
  property $ do
    a <- forAll genInt
    let v = __Success # a :: Validation [String] Int
    v ^? __Success === Just a

-- Swap

prop_swap_failure :: Property
prop_swap_failure =
  property $ do
    e <- forAll genString
    let v = Failure e :: Validation String Int
    swap v === (Success e :: Validation Int String)

prop_swap_success :: Property
prop_swap_success =
  property $ do
    a <- forAll genInt
    let v = Success a :: Validation String Int
    swap v === (Failure a :: Validation Int String)

prop_swap_involution :: Property
prop_swap_involution =
  property $ do
    x <- forAll testGen
    (swap (swap x)) === x

-- Either instances: ReviewFailure, AsFailure, ReviewSuccess, AsSuccess

genEither :: Gen a -> Gen b -> Gen (Prelude.Either a b)
genEither ga gb = Gen.choice [fmap Left ga, fmap Right gb]

prop_either_reviewFailure :: Property
prop_either_reviewFailure =
  property $ do
    e <- forAll genStrings
    (reviewFailure # e :: Prelude.Either [String] Int) === Left e

prop_either_asFailure_hit :: Property
prop_either_asFailure_hit =
  property $ do
    e <- forAll genStrings
    (Left e :: Prelude.Either [String] Int) ^? _Failure === Just e

prop_either_asFailure_miss :: Property
prop_either_asFailure_miss =
  property $ do
    a <- forAll genInt
    (Right a :: Prelude.Either [String] Int) ^? _Failure === Nothing

prop_either_reviewSuccess :: Property
prop_either_reviewSuccess =
  property $ do
    a <- forAll genInt
    (reviewSuccess # a :: Prelude.Either [String] Int) === Right a

prop_either_asSuccess_hit :: Property
prop_either_asSuccess_hit =
  property $ do
    a <- forAll genInt
    (Right a :: Prelude.Either [String] Int) ^? _Success === Just a

prop_either_asSuccess_miss :: Property
prop_either_asSuccess_miss =
  property $ do
    e <- forAll genStrings
    (Left e :: Prelude.Either [String] Int) ^? _Success === Nothing

prop_either_failure_roundtrip :: Property
prop_either_failure_roundtrip =
  property $ do
    x <- forAll (genEither genStrings genInt)
    let reviewed = x ^? _Failure
    case x of
      Left e -> reviewed === Just e
      Right _ -> reviewed === Nothing

prop_either_success_roundtrip :: Property
prop_either_success_roundtrip =
  property $ do
    x <- forAll (genEither genStrings genInt)
    let reviewed = x ^? _Success
    case x of
      Right a -> reviewed === Just a
      Left _ -> reviewed === Nothing

-- match

matchRight :: Validator (Prelude.Either [String] Int) (Prelude.Either [String] Int) Int
matchRight = match _Right

runValidator :: Validator x err a -> x -> Validation err a
runValidator (Validator f) = f

prop_match_hit :: Property
prop_match_hit =
  property $ do
    a <- forAll genInt
    runValidator matchRight (Right a) === Success a

prop_match_miss :: Property
prop_match_miss =
  property $ do
    e <- forAll genStrings
    runValidator matchRight (Left e) === Failure (Left e)

prop_match_validatorProfunctor :: Property
prop_match_validatorProfunctor =
  property $ do
    x <- forAll (genEither genStrings genInt)
    let ValidatorProfunctor f = match _Right :: ValidatorProfunctor (Prelude.Either [String] Int) (Prelude.Either [String] Int) Int
    f x === runValidator matchRight x

prop_match_validatorMonad :: Property
prop_match_validatorMonad =
  property $ do
    x <- forAll (genEither genStrings genInt)
    let ValidatorMonadT f = match _Right :: ValidatorMonad (Prelude.Either [String] Int) (Prelude.Either [String] Int) Int
        ValidationMonadT (Identity r) = f x
    r === runValidator matchRight x

prop_match_validatorMonadProfunctor :: Property
prop_match_validatorMonadProfunctor =
  property $ do
    x <- forAll (genEither genStrings genInt)
    let ValidatorMonadProfunctorT f = match _Right :: ValidatorMonadProfunctor (Prelude.Either [String] Int) (Prelude.Either [String] Int) Int
        ValidationMonadT (Identity r) = f x
    r === runValidator matchRight x

-- match with (<!>): one prism per constructor, the first match wins

-- | The input used by the (<!>) properties: a Left, a Right Just, or a Right Nothing.
type Input = Prelude.Either String (Maybe Int)

genInput :: Gen Input
genInput = genEither genString (Gen.maybe genInt)

-- | The expected result: Left and Right Just match, Right Nothing matches neither prism.
expected :: Input -> Validation Input String
expected (Left s) = Success s
expected (Right (Just n)) = Success (show n)
expected i@(Right Nothing) = Failure i

prop_match_alt :: Property
prop_match_alt =
  property $ do
    i <- forAll genInput
    let v = match _Left <!> (show <$> match (_Right Prelude.. _Just)) :: Validator Input Input String
    runValidator v i === expected i

prop_matchValidator_alt :: Property
prop_matchValidator_alt =
  property $ do
    i <- forAll genInput
    let v = matchValidator _Left <!> (show <$> matchValidator (_Right Prelude.. _Just))
    runValidator v i === expected i

prop_matchValidatorProfunctor_alt :: Property
prop_matchValidatorProfunctor_alt =
  property $ do
    i <- forAll genInput
    let ValidatorProfunctor f = matchValidatorProfunctor _Left <!> (show <$> matchValidatorProfunctor (_Right Prelude.. _Just))
    f i === expected i

prop_matchValidatorMonad_alt :: Property
prop_matchValidatorMonad_alt =
  property $ do
    i <- forAll genInput
    let ValidatorMonadT f = matchValidatorMonad _Left <!> (show <$> matchValidatorMonad (_Right Prelude.. _Just))
        ValidationMonadT (Identity r) = f i
    r === expected i

prop_matchValidatorMonadProfunctor_alt :: Property
prop_matchValidatorMonadProfunctor_alt =
  property $ do
    i <- forAll genInput
    let ValidatorMonadProfunctorT f = matchValidatorMonadProfunctor _Left <!> (show <$> matchValidatorMonadProfunctor (_Right Prelude.. _Just))
        ValidationMonadT (Identity r) = f i
    r === expected i

-- (-->): match a prism and map its focus, one case per constructor

prop_arrow_fmap_match :: Property
prop_arrow_fmap_match =
  property $ do
    i <- forAll genInput
    let v = _Right Prelude.. _Just --> show :: Validator Input Input String
    runValidator v i === runValidator (show <$> matchValidator (_Right Prelude.. _Just)) i

prop_arrow_id_match :: Property
prop_arrow_id_match =
  property $ do
    i <- forAll genInput
    let v = _Left --> Prelude.id :: Validator Input Input String
    runValidator v i === runValidator (matchValidator _Left) i

prop_arrow_validator_alt :: Property
prop_arrow_validator_alt =
  property $ do
    i <- forAll genInput
    let v = _Left --> Prelude.id <!> _Right Prelude.. _Just --> show :: Validator Input Input String
    runValidator v i === expected i

prop_arrow_validatorProfunctor_alt :: Property
prop_arrow_validatorProfunctor_alt =
  property $ do
    i <- forAll genInput
    let ValidatorProfunctor f = _Left --> Prelude.id <!> _Right Prelude.. _Just --> show :: ValidatorProfunctor Input Input String
    f i === expected i

prop_arrow_validatorMonad_alt :: Property
prop_arrow_validatorMonad_alt =
  property $ do
    i <- forAll genInput
    let ValidatorMonadT f = _Left --> Prelude.id <!> _Right Prelude.. _Just --> show :: ValidatorMonad Input Input String
        ValidationMonadT (Identity r) = f i
    r === expected i

prop_arrow_validatorMonadProfunctor_alt :: Property
prop_arrow_validatorMonadProfunctor_alt =
  property $ do
    i <- forAll genInput
    let ValidatorMonadProfunctorT f = _Left --> Prelude.id <!> _Right Prelude.. _Just --> show :: ValidatorMonadProfunctor Input Input String
        ValidationMonadT (Identity r) = f i
    r === expected i