packages feed

antigen 0.1.1.0 → 0.1.2.0

raw patch · 5 files changed

+194/−70 lines, 5 filesdep +randomPVP ok

version bump matches the API change (PVP)

Dependencies added: random

API changes (from Hackage documentation)

+ Test.AntiGen: (||!) :: AntiGen a -> AntiGen a -> AntiGen a
+ Test.AntiGen: antiBool :: Bool -> AntiGen Bool
+ Test.AntiGen: antiChoose :: (Integral a, Random a) => (a, a) -> (a, a) -> AntiGen a
+ Test.AntiGen: antiChooseBounded :: (Integral a, Random a, Bounded a) => (a, a) -> AntiGen a
+ Test.AntiGen: antiNegative :: (Num a, Ord a, Arbitrary a) => AntiGen a
+ Test.AntiGen: antiNonNegative :: (Num a, Ord a, Arbitrary a) => AntiGen a
+ Test.AntiGen: antiNonPositive :: (Num a, Ord a, Arbitrary a) => AntiGen a
+ Test.AntiGen: antiNum :: (Eq a, Num a, Arbitrary a) => a -> AntiGen a
+ Test.AntiGen: antiPositive :: (Num a, Ord a, Arbitrary a) => AntiGen a
+ Test.AntiGen: antiTry :: (Eq a, Arbitrary a) => a -> AntiGen a

Files

CHANGELOG.md view
@@ -1,5 +1,21 @@ # Revision history for antigen -## 0.1.0.0 -- YYYY-mm-dd+## 0.1.2.0++* Fixed `sized` and `resize` implementations for `AntiGen`+* Added utility functions:+  - `(||!)`+  - `antiNum`+  - `antiBool`+  - `antiChoose`+  - `antiChooseBounded`+  - `antiTry`+  - `antiTryGen`+  - `antiPositive`+  - `antiNonPositive`+  - `antiNegative`+  - `antiNonNegative`++## 0.1.0.0  * First version. Released on an unsuspecting world.
antigen.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               antigen-version:            0.1.1.0+version:            0.1.2.0 synopsis:           Negatable QuickCheck generators  description:   AntiGen is a library that helps with generating negative examples from a @@ -15,8 +15,11 @@ category:           Testing build-type:         Simple extra-doc-files:    CHANGELOG.md--- extra-source-files: +source-repository head+  type: git+  location: https://github.com/input-output-hk/antigen+ common warnings     ghc-options: -Wall @@ -26,10 +29,11 @@       Test.AntiGen       Test.AntiGen.Internal     build-depends:    -      base ^>=4.20.2.0,-      QuickCheck >= 2.16.0 && < 2.17,+      base >=4.18 && <5,+      QuickCheck >= 2.16.0 && < 2.18,       free >= 5.2 && < 5.3,       mtl >= 2.3.1 && < 2.4,+      random >= 1.2 && < 1.4,       quickcheck-transformer >= 0.3.1 && < 0.4,     hs-source-dirs:   src     default-language: Haskell2010
src/Test/AntiGen.hs view
@@ -12,8 +12,102 @@ 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
src/Test/AntiGen/Internal.hs view
@@ -26,7 +26,7 @@ import Control.Monad.Free.Class (wrapT) import Control.Monad.State.Strict (MonadState (..), StateT (..), evalStateT, modify) import Control.Monad.Trans (MonadTrans (..))-import Test.QuickCheck (Gen)+import Test.QuickCheck (Gen, getSize) import Test.QuickCheck.GenT (GenT (..), MonadGen (..), runGenT)  data BiGen next where@@ -45,17 +45,11 @@ instance MonadGen AntiGen where   liftGen g = AntiGen $ F $ \p b -> b $ BiGen g Nothing p   variant n = mapGen (variant n)-  sized f = AntiGen $ F $ \p b ->-    let-      pos = sized $ \sz ->-        let AntiGen (F m) = f sz-         in m pure $ \(BiGen ps _ c) -> ps >>= c-     in-      b $ BiGen pos Nothing p-  resize n = mapGen (resize n)+  sized f = wrap $ BiGen (f <$> getSize) Nothing id+  resize n m = mapGen (resize n) m   choose = liftGen . choose --- | Create a negatable generator by providing a positive and a negative +-- | Create a negatable generator by providing a positive and a negative -- generator (|!) :: Gen a -> Gen a -> AntiGen a pos |! neg = AntiGen $ F $ \p b -> b $ BiGen pos (Just neg) p@@ -126,9 +120,9 @@ evalPartial :: PartialGen a -> a evalPartial (PartialGen (F m)) = m id continue --- | Create a negative generator from an `AntiGen` by introducing at most +-- | Create a negative generator from an `AntiGen` by introducing at most -- `n` mistakes. If there are no negatable generators in the `AntiGen`, it will--- return a positive generator. Also if the number of negatable generators in +-- return a positive generator. Also if the number of negatable generators in -- the `AntiGen` is lower than `n`, then the number of negations will be less -- than `n`. zapAntiGen :: Int -> AntiGen a -> Gen a
test/Main.hs view
@@ -7,7 +7,7 @@ import Data.Data (Proxy (..)) import Test.AntiGen (AntiGen, runAntiGen, zapAntiGen, (|!)) import Test.AntiGen.Internal (countDecisionPoints, evalToPartial)-import Test.Hspec (describe, hspec, shouldBe)+import Test.Hspec (Spec, describe, hspec, shouldBe) import Test.Hspec.QuickCheck (prop) import Test.QuickCheck (   Arbitrary (..),@@ -21,9 +21,11 @@   counterexample,   forAll,   forAllBlind,+  getSize,   label,   scale,   suchThat,+  vector,   (.&&.),   (.||.),   (===),@@ -88,6 +90,61 @@         pure $ f <$> x <*> y     ] +zapAntiGenSpec :: Spec+zapAntiGenSpec =+  describe "zapAntiGen" $ do+    prop "zapping `antiGenPositive` once generates negative examples" $ do+      x <- zapAntiGen 1 antiGenPositive+      pure $ x <= 0+    prop "zapping `antiGenPositive` zero times generates a positive example" $ do+      x <- zapAntiGen 0 antiGenPositive+      pure $ x > 0+    prop "zapping `antiGenTuple` once results in a single non-positive Int" $ do+      (x, y) <- zapAntiGen 1 antiGenTuple+      pure $+        label "x is non-positive" (x <= 0) .||. label "y is non-positive" (y <= 0)+    prop "zapping `antiGenTuple` twice results in two non-positive Ints" $ do+      (x, y) <- zapAntiGen 2 antiGenTuple+      pure $+        counterexample ("x = " <> show x <> " is positive") (x <= 0)+          .&&. counterexample ("y = " <> show y <> " is positive") (y <= 0)+    prop+      "zapping the length of the string propagates to the string generator"+      . forAll (zapAntiGen 1 antiGenLengthStringStatic)+      $ \(l, s) -> length s === l+    prop+      "zapping `antiGenLengthString` either generates invalid Int or a string of invalid length"+      . forAll (zapAntiGen 1 antiGenLengthString)+      $ \(l, s) ->+        exactlyOne+          [ ("l > 5", l > 5)+          , ("length s /= l", length s /= l)+          ]+    prop+      "zapping `antiGenEither` once gives a nice distribution"+      . forAll (zapAntiGen 1 antiGenEither)+      $ \x ->+        exactlyOne+          [+            ( "Left v <= 0"+            , case x of+                Right _ -> False+                Left v -> v <= 0+            )+          ,+            ( "Right length (filter not v) == 1"+            , case x of+                Left _ -> False+                Right v -> length (filter not v) == 1+            )+          ,+            ( "Right length > 5"+            , case x of+                Left _ -> False+                Right v -> length v > 5+            )+          ]+ main :: IO () main = hspec $ do   describe "AntiGen" $ do@@ -101,6 +158,7 @@         pt <- evalToPartial m         pt' <- evalToPartial antiGenPositive         pure $ countDecisionPoints pt === countDecisionPoints pt' .&&. countDecisionPoints pt === 1+    zapAntiGenSpec     describe "runAntiGen" $ do       prop "runAntiGen . liftGen == id" $         \(seed :: Int) -> forAllBlind (someGen $ Proxy @Int) $ \g -> do@@ -108,55 +166,13 @@           res <- variant seed g           res' <- variant seed g'           pure $ res === res'-    describe "zapAntiGen" $ do-      prop "zapping `antiGenPositive` once generates negative examples" $ do-        x <- zapAntiGen 1 antiGenPositive-        pure $ x <= 0-      prop "zapping `antiGenPositive` zero times generates a positive example" $ do-        x <- zapAntiGen 0 antiGenPositive-        pure $ x > 0-      prop "zapping `antiGenTuple` once results in a single non-positive Int" $ do-        (x, y) <- zapAntiGen 1 antiGenTuple-        pure $-          label "x is non-positive" (x <= 0) .||. label "y is non-positive" (y <= 0)-      prop "zapping `antiGenTuple` twice results in two non-positive Ints" $ do-        (x, y) <- zapAntiGen 2 antiGenTuple-        pure $-          counterexample ("x = " <> show x <> " is positive") (x <= 0)-            .&&. counterexample ("y = " <> show y <> " is positive") (y <= 0)-      prop-        "zapping the length of the string propagates to the string generator"-        . forAll (zapAntiGen 1 antiGenLengthStringStatic)-        $ \(l, s) -> length s === l-      prop-        "zapping `antiGenLengthString` either generates invalid Int or a string of invalid length"-        . forAll (zapAntiGen 1 antiGenLengthString)-        $ \(l, s) ->-          exactlyOne-            [ ("l > 5", l > 5)-            , ("length s /= l", length s /= l)-            ]-      prop-        "zapping `antiGenEither` once gives a nice distribution"-        . forAll (zapAntiGen 1 antiGenEither)-        $ \x ->-          exactlyOne-            [-              ( "Left v <= 0"-              , case x of-                  Right _ -> False-                  Left v -> v <= 0-              )-            ,-              ( "Right length (filter not v) == 1"-              , case x of-                  Left _ -> False-                  Right v -> length (filter not v) == 1-              )-            ,-              ( "Right length > 5"-              , case x of-                  Left _ -> False-                  Right v -> length v > 5-              )-            ]+    describe "MonadGen" $ do+      prop "applying `sized` to a negatable generator preserves negation" $ do+        size <- getSize+        val <- zapAntiGen 1 . sized $ \sz -> pure sz |! pure (-sz)+        pure $ val === -size+      prop "`resize` has an effect when zapping" $ do+        sz <- choose (0, 10)+        val :: [Bool] <- zapAntiGen 1 . resize sz . sized $ \s ->+          liftGen (vector $ 2 * s) |! liftGen (vector s)+        pure $ length val === sz