packages feed

genvalidity 0.7.0.1 → 0.7.0.2

raw patch · 3 files changed

+248/−97 lines, 3 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Data.GenValidity: structurallyValidRecursivelyShrink :: (Generic a, GValidRecursivelyShrink (Rep a)) => a -> [a]
+ Data.GenValidity: shrinkT4 :: (a -> [a]) -> (a, a, a, a) -> [(a, a, a, a)]
+ Data.GenValidity: shuffle :: () => [a] -> Gen [a]

Files

genvalidity.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 8f51510165244a5a76d1cf8b471902a4439454662316b1a9197d65391a0125c2+-- hash: 13e57f7aafc2a2db6d022ffcae38acf8fe9e764b697a451f92daca9e03535e1a  name:           genvalidity-version:        0.7.0.1+version:        0.7.0.2 synopsis:       Testing utilities for the validity library description:    Note: There are companion instance packages for this library:                 .@@ -50,6 +50,7 @@       Data.GenValidity       Data.GenRelativeValidity   other-modules:+      Data.GenValidity.Utils       Paths_genvalidity   hs-source-dirs:       src
src/Data/GenValidity.hs view
@@ -60,8 +60,34 @@ #endif  module Data.GenValidity-    ( module Data.Validity-    , module Data.GenValidity+    ( GenUnchecked(..)+    , GenValid(..)+    , GenInvalid(..)++    -- * Helper functions+    , genValidStructurally+    , genValidStructurallyWithoutExtraChecking+    , shrinkValidStructurally+    , shrinkValidStructurallyWithoutExtraFiltering+    , module Data.GenValidity.Utils++    -- * Re-exports+    , module Data.Validity++    -- * The Generics magic+    , genericGenUnchecked+    , GGenUnchecked(..)+    , genericShrinkUnchecked+    , uncheckedRecursivelyShrink+    , GUncheckedRecursivelyShrink(..)+    , uncheckedSubterms+    , GUncheckedSubterms(..)+    , GUncheckedSubtermsIncl(..)+    , GGenValid(..)+    , GValidRecursivelyShrink(..)+    , structurallyValidSubterms+    , GValidSubterms(..)+    , GValidSubtermsIncl(..)     ) where  import Data.Validity@@ -83,24 +109,23 @@  import Test.QuickCheck hiding (Fixed) -#if !MIN_VERSION_QuickCheck(2,8,0)-import Data.List (sortBy)-import Data.Ord (comparing)-#endif- #if MIN_VERSION_base(4,8,0) import GHC.Natural-import Control.Monad (forM) #else import Control.Applicative ((<*>), (<$>), pure)-import Control.Monad (forM) #endif +import Data.GenValidity.Utils+ {-# ANN module "HLint: ignore Reduce duplication" #-}  -- | A class of types for which truly arbitrary values can be generated. ----- === Automatic instances with 'Generic'+-- === How to instantiate 'GenUnchecked'+--+-- __Step 1__: Try to instantiate 'GenUnchecked' via 'Generic'.+--         __this is probably what you want__+-- -- An instance of this class can be made automatically if the type in question -- has a 'Generic' instance. This instance will try to use 'genUnchecked' to -- generate all structural sub-parts of the value that is being generated.@@ -118,6 +143,21 @@ -- -- > instance GenUnchecked MyType where -- >     genUnchecked = MyType <$> genUnchecked <*> genUnchecked+--+--+-- __Step 2__: If an instatiation via 'Generic' is not possible, then you should emulate what+--         'genericGenUnchecked' does.+--         This means that all sub-parts should be  generated using 'genUnchecked'.+--         Make sure to generate any possible value, valid or not, that can exist at runtime+--         even when taking the existence of 'Unsafe.Coerce.unsafeCoerce' into account.+--+-- === Warning: Invalid values can be funky+--+-- Some types have serious validity constraints. See 'Text' or 'ByteString' for example.+-- These can behave very strangely when they are not valid.+-- In that case, __do not override 'GenUnchecked' such that 'genUnchecked' only generates valid values__.+-- In that case, do not override 'genUnchecked' at all.+-- Instead, use 'genValid' from 'GenValid' (see below) instead. class GenUnchecked a where     genUnchecked :: Gen a     default genUnchecked :: (Generic a, GGenUnchecked (Rep a)) =>@@ -132,11 +172,32 @@  -- | A class of types for which valid values can be generated. --+-- === How to instantiate 'GenValid'+--+-- __Step 1__: Try to instantiate 'GenValid' without overriding any functions.+--             It is possible that, if few values are valid or if validity+--             checking is expensive, that the resulting generator is too slow.+--             In that case, go to Step 2.+--+-- __Step 2__: Try to instantiate 'GenValid' using the helper functions via 'Generic'+--             This involves using 'genValidStructurally' to override 'genValid' and+--             using 'shrinkValidStructurally' to override 'shrinkValid'.+--             __Every time you override 'genValid', you should also override 'shrinkValid'__+--+-- __Step 3__: If the above is not possible due to lack of a 'Generic' instance,+--             then you should emulate what 'genValidStructurally' does.+--             This means that all sub-parts should be generated using 'genValid'.+--             Make sure to generate any possible valid value, but only valid values.+--+-- === A note about 'Arbitrary'+-- -- If you also write @Arbitrary@ instances for @GenValid@ types, it may be--- best to simply write @arbitrary = genValid@.+-- best to simply use+--+-- > arbitrary = genValid+-- > shrink = shrinkValid class (Validity a, GenUnchecked a) =>       GenValid a where-    genValid :: Gen a     -- | Generate a valid datum, this should cover all possible valid values in     -- the type     --@@ -147,10 +208,15 @@     -- To speed up testing, it may be a good idea to implement this yourself.     -- If you do, make sure that it is possible to generate all possible valid     -- data, otherwise your testing may not cover all cases.+    genValid :: Gen a     genValid = genUnchecked `suchThat` isValid      -- | Shrink a valid value.     --+    -- The default implementation is as follows:+    --+    -- >  shrinkValid = filter isValid . shrinkUnchecked+    --     -- It is important that this shrinking function only shrinks values to valid values.     -- If `shrinkValid` ever shrinks a value to an invalid value, the test that is being shrunk for     -- might fail for a different reason than for the reason that it originally failed.@@ -159,6 +225,18 @@     shrinkValid = filter isValid . shrinkUnchecked  -- | A class of types for which invalid values can be generated.+--+-- === How to instantiate 'GenInvalid'+--+-- __Step 1__: Realise that you probably do not want to.+--             It makes no sense, and serves no purpose, to instantiate 'GenInvalid' for types+--             which contain no invalid values. (In fact, the default implementation will go into+--             an infinite loop for such types.)+--             You should only instantiate 'GenInvalid' if you explicitly want to use it+--             to write tests that deal with invalid values, or if you are writing a container+--             for parametric values.+--+-- __Step 2__: Instantiate 'GenInvalid' without overriding any functions. class (Validity a, GenUnchecked a) =>       GenInvalid a where     genInvalid :: Gen a@@ -609,88 +687,6 @@  instance HasResolution a => GenValid (Fixed a) -shrinkT2-  :: (a -> [a])-  -> (a, a) -> [(a, a)]-shrinkT2 s (a, b) = (,) <$> s a <*> s b--shrinkT3-  :: (a -> [a])-  -> (a, a, a) -> [(a, a, a)]-shrinkT3 s (a, b, c) = (,,) <$> s a <*> s b <*> s c----- | 'upTo' generates an integer between 0 (inclusive) and 'n'.-upTo :: Int -> Gen Int-upTo n-    | n <= 0 = pure 0-    | otherwise = choose (0, n)---- | 'genSplit a' generates a tuple '(b, c)' such that 'b + c' equals 'a'.-genSplit :: Int -> Gen (Int, Int)-genSplit n-    | n < 0 = pure (0, 0)-    | otherwise = do-        i <- choose (0, n)-        let j = n - i-        pure (i, j)---- | 'genSplit3 a' generates a triple '(b, c, d)' such that 'b + c + d' equals 'a'.-genSplit3 :: Int -> Gen (Int, Int, Int)-genSplit3 n-    | n < 0 = pure (0, 0, 0)-    | otherwise = do-        (a, z) <- genSplit n-        (b, c) <- genSplit z-        return (a, b, c)---- | 'genSplit4 a' generates a quadruple '(b, c, d, e)' such that 'b + c + d + e' equals 'a'.-genSplit4 :: Int -> Gen (Int, Int, Int, Int)-genSplit4 n-    | n < 0 = pure (0, 0, 0, 0)-    | otherwise = do-        (y, z) <- genSplit n-        (a, b) <- genSplit y-        (c, d) <- genSplit z-        return (a, b, c, d)---- | 'genSplit5 a' generates a quadruple '(b, c, d, e, f)' such that 'b + c + d + e + f' equals 'a'.-genSplit5 :: Int -> Gen (Int, Int, Int, Int, Int)-genSplit5 n-    | n < 0 = pure (0, 0, 0, 0, 0)-    | otherwise = do-        (y, z) <- genSplit n-        (a, b, c) <- genSplit3 y-        (d, e) <- genSplit z-        return (a, b, c, d, e)---- | 'arbPartition n' generates a list 'ls' such that 'sum ls' equals 'n'.-arbPartition :: Int -> Gen [Int]-arbPartition i = go i >>= shuffle-  where-    go k-      | k <= 0 = pure []-      | otherwise = do-          first <- choose (1, k)-          rest <- arbPartition $ k - first-          return $ first : rest--#if !MIN_VERSION_QuickCheck(2,8,0)--- | Generates a random permutation of the given list.-shuffle :: [a] -> Gen [a]-shuffle xs = do-  ns <- vectorOf (length xs) (choose (minBound :: Int, maxBound))-  return (map snd (sortBy (comparing fst) (zip ns xs)))-#endif---- | A version of @listOf@ that takes size into account more accurately.-genListOf :: Gen a -> Gen [a]-genListOf func =-    sized $ \n -> do-        size <- upTo n-        pars <- arbPartition size-        forM pars $ \i -> resize i func- genericGenUnchecked :: (Generic a, GGenUnchecked (Rep a)) => Gen a genericGenUnchecked = to <$> gGenUnchecked @@ -804,9 +800,24 @@   gUncheckedSubtermsIncl (K1 _) = []  +-- | Generate a valid value by generating all the sub parts using the 'Generic' instance,+-- and trying that until a valid value has been generated+--+-- > genValidStructurally = genValidStructurallyWithoutExtraChecking `suchThat` isValid+--+-- This is probably the function that you are looking for.+-- If you do use this function to override `genValid`, you probably also want to use+-- 'shrinkValidStructurally' to override 'shrinkValid'. genValidStructurally :: (Validity a, Generic a, GGenValid (Rep a)) => Gen a genValidStructurally = genValidStructurallyWithoutExtraChecking `suchThat` isValid +-- | Generate a valid value by generating all the sub parts using the 'Generic' instance,+--+-- This generator is _not_ guaranteed to generate a valid value.+--+-- This is probably _not_ the function that you are looking for when overriding+-- `genValid` _unless_ the type in question has no _extra_ validity constraints on top of+-- the validity of its sub parts. genValidStructurallyWithoutExtraChecking :: (Generic a, GGenValid (Rep a)) => Gen a genValidStructurallyWithoutExtraChecking = to <$> gGenValid @@ -830,10 +841,22 @@   -- | Shrink a term to any of its immediate valid subterms,--- and also recursively shrink all subterms.+-- and also recursively shrink all subterms, and then filtering out the results that are not valid.+--+-- > shrinkValidStructurally = filter isValid . shrinkValidStructurallyWithoutExtraFiltering+--+-- This is probably the function that you are looking for. shrinkValidStructurally :: (Validity a, Generic a, GValidRecursivelyShrink (Rep a), GValidSubterms (Rep a) a) => a -> [a] shrinkValidStructurally = filter isValid . shrinkValidStructurallyWithoutExtraFiltering +-- | Shrink a term to any of its immediate valid subterms,+-- and also recursively shrink all subterms.+--+-- This shrinking function is _not_ guaranteed to shrink to valid values.+--+-- This is probably _not_ the function that you are looking for when overriding+-- `shrinkValid` _unless_ the type in question has no _extra_ validity constraints on top of+-- the validity of its sub parts. shrinkValidStructurallyWithoutExtraFiltering :: (Generic a, GValidRecursivelyShrink (Rep a), GValidSubterms (Rep a) a) => a -> [a] shrinkValidStructurallyWithoutExtraFiltering x = structurallyValidSubterms x ++ structurallyValidRecursivelyShrink x 
+ src/Data/GenValidity/Utils.hs view
@@ -0,0 +1,127 @@+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE CPP #-}+#if __GLASGOW_HASKELL__ >= 710+#define OVERLAPPING_ {-# OVERLAPPING #-}+#else+{-# LANGUAGE OverlappingInstances #-}+#define OVERLAPPING_+#endif+#if MIN_VERSION_base(4,9,0)+{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}+#endif+module Data.GenValidity.Utils+    ( -- ** Helper functions for implementing generators+      upTo+    , genSplit+    , genSplit3+    , genSplit4+    , genSplit5+    , arbPartition+    , shuffle+    , genListOf+      -- ** Helper functions for implementing shrinking functions+    , shrinkT2+    , shrinkT3+    , shrinkT4+    ) where++import Test.QuickCheck hiding (Fixed)+#if !MIN_VERSION_QuickCheck(2,8,0)+import Data.List (sortBy)+import Data.Ord (comparing)+#endif++#if MIN_VERSION_base(4,8,0)+import Control.Monad (forM)+#else+import Control.Applicative ((<$>), (<*>), pure)+import Control.Monad (forM)+#endif+-- | 'upTo' generates an integer between 0 (inclusive) and 'n'.+upTo :: Int -> Gen Int+upTo n+    | n <= 0 = pure 0+    | otherwise = choose (0, n)++-- | 'genSplit a' generates a tuple '(b, c)' such that 'b + c' equals 'a'.+genSplit :: Int -> Gen (Int, Int)+genSplit n+    | n < 0 = pure (0, 0)+    | otherwise = do+        i <- choose (0, n)+        let j = n - i+        pure (i, j)++-- | 'genSplit3 a' generates a triple '(b, c, d)' such that 'b + c + d' equals 'a'.+genSplit3 :: Int -> Gen (Int, Int, Int)+genSplit3 n+    | n < 0 = pure (0, 0, 0)+    | otherwise = do+        (a, z) <- genSplit n+        (b, c) <- genSplit z+        return (a, b, c)++-- | 'genSplit4 a' generates a quadruple '(b, c, d, e)' such that 'b + c + d + e' equals 'a'.+genSplit4 :: Int -> Gen (Int, Int, Int, Int)+genSplit4 n+    | n < 0 = pure (0, 0, 0, 0)+    | otherwise = do+        (y, z) <- genSplit n+        (a, b) <- genSplit y+        (c, d) <- genSplit z+        return (a, b, c, d)++-- | 'genSplit5 a' generates a quintuple '(b, c, d, e, f)' such that 'b + c + d + e + f' equals 'a'.+genSplit5 :: Int -> Gen (Int, Int, Int, Int, Int)+genSplit5 n+    | n < 0 = pure (0, 0, 0, 0, 0)+    | otherwise = do+        (y, z) <- genSplit n+        (a, b, c) <- genSplit3 y+        (d, e) <- genSplit z+        return (a, b, c, d, e)++-- | 'arbPartition n' generates a list 'ls' such that 'sum ls' equals 'n'.+arbPartition :: Int -> Gen [Int]+arbPartition i = go i >>= shuffle+  where+    go k+        | k <= 0 = pure []+        | otherwise = do+            first <- choose (1, k)+            rest <- arbPartition $ k - first+            return $ first : rest+#if !MIN_VERSION_QuickCheck(2,8,0)+-- | Generates a random permutation of the given list.+shuffle :: [a] -> Gen [a]+shuffle xs = do+    ns <- vectorOf (length xs) (choose (minBound :: Int, maxBound))+    return (map snd (sortBy (comparing fst) (zip ns xs)))+#endif+-- | A version of @listOf@ that takes size into account more accurately.+--+-- This generator distributes the size that is is given among the values+-- in the list that it generates.+genListOf :: Gen a -> Gen [a]+genListOf func =+    sized $ \n -> do+        size <- upTo n+        pars <- arbPartition size+        forM pars $ \i -> resize i func++-- | Turn a shrinking function into a function that shrinks tuples.+shrinkT2 :: (a -> [a]) -> (a, a) -> [(a, a)]+shrinkT2 s (a, b) = (,) <$> s a <*> s b++-- | Turn a shrinking function into a function that shrinks triples.+shrinkT3 :: (a -> [a]) -> (a, a, a) -> [(a, a, a)]+shrinkT3 s (a, b, c) = (,,) <$> s a <*> s b <*> s c++-- | Turn a shrinking function into a function that shrinks quadruples.+shrinkT4 :: (a -> [a]) -> (a, a, a, a) -> [(a, a, a, a)]+shrinkT4 s (a, b, c, d) = (,,,) <$> s a <*> s b <*> s c <*> s d