packages feed

antigen-0.1.2.0: src/Test/AntiGen.hs

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE UndecidableInstances #-}

module Test.AntiGen (
  AntiGen,
  (|!),
  (||!),
  runAntiGen,
  zapAntiGen,

  -- * AntiGen combinators
  antiNum,
  antiBool,
  antiChoose,
  antiChooseBounded,
  antiTry,
  antiPositive,
  antiNonPositive,
  antiNegative,
  antiNonNegative,
) where

import Control.Monad (join)
import System.Random (Random)
import Test.AntiGen.Internal
import Test.QuickCheck (
  Arbitrary (..),
  Gen,
  Negative (..),
  NonNegative (..),
  NonPositive (..),
  NonZero (..),
  Positive (..),
 )
import Test.QuickCheck.GenT (MonadGen (..), frequency, suchThat)

-- | Returns the provided number. If negated, returns a value that is not equal
-- to the provided number.
antiNum :: (Eq a, Num a, Arbitrary a) => a -> AntiGen a
antiNum n = pure n |! ((n +) . getNonZero <$> arbitrary)

-- | Returns the provided `Bool`. If negated, returns the negation of that
-- `Bool`.
antiBool :: Bool -> AntiGen Bool
antiBool b = pure b |! pure (not b)

-- | In the positive case generates a value from the first range. In the
-- negative case generates a value from the second range excluding the first
-- range.
--
-- Note: The second range must not be a subset of the first range!
antiChoose :: (Integral a, Random a) => (a, a) -> (a, a) -> AntiGen a
antiChoose rng@(lo, hi) (boundLo, boundHi) =
  choose rng
    |! frequency
      ( mconcat
          [ [ (fromIntegral $ lo - boundLo, choose rngLo)
            | lo > boundLo
            ]
          , [ (fromIntegral $ boundHi - hi, choose rngHi)
            | boundHi > hi
            ]
          ]
      )
  where
    rngLo = (boundLo, lo)
    rngHi = (hi, boundHi)

-- | Generates a value from the range. If negated, returns a random value
-- outside the range between `minBound` and `maxBound`.
antiChooseBounded :: (Integral a, Random a, Bounded a) => (a, a) -> AntiGen a
antiChooseBounded rng = antiChoose rng (minBound, maxBound)

-- | Returns the provided value unless negated, in which case it generates an
-- arbitrary value that is different from the provided value. It uses `suchThat`,
-- so using it on small types might end up discarding many values.
antiTry :: (Eq a, Arbitrary a) => a -> AntiGen a
antiTry a = antiTryGen a arbitrary

-- | Returns the provided value unless negated, in which case it uses the
-- generator to generate a random value that is different from the provided
-- value. It uses `suchThat`, so using it on small types might end up 
-- discarding many values.
antiTryGen :: Eq a => a -> Gen a -> AntiGen a
antiTryGen a gen = pure a |! (gen `suchThat` (/= a))

-- | Negatable generator for positive numbers
antiPositive :: (Num a, Ord a, Arbitrary a) => AntiGen a
antiPositive = (getPositive <$> arbitrary) |! (getNonPositive <$> arbitrary)

-- | Negatable generator for non-positive numbers
antiNonPositive :: (Num a, Ord a, Arbitrary a) => AntiGen a
antiNonPositive = (getNonPositive <$> arbitrary) |! (getPositive <$> arbitrary)

-- | Negatable generator for negative numbers
antiNegative :: (Num a, Ord a, Arbitrary a) => AntiGen a
antiNegative = (getNegative <$> arbitrary) |! (getNonNegative <$> arbitrary)

-- | Negatable generator for non-negative numbers
antiNonNegative :: (Num a, Ord a, Arbitrary a) => AntiGen a
antiNonNegative = (getNonNegative <$> arbitrary) |! (getNegative <$> arbitrary)

-- | Create an `AntiGen` from a positive and a negative `AntiGen` generator
(||!) :: AntiGen a -> AntiGen a -> AntiGen a
a ||! b = join $ pure a |! pure b