1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
|
{-# LANGUAGE CPP, MultiParamTypeClasses,
FlexibleInstances, FlexibleContexts,
TypeSynonymInstances #-}
--
-- Uses multi-param type classes
--
module QuickCheckUtils where
import Test.QuickCheck
import Text.Show.Functions
import Control.Monad ( liftM2 )
import Data.Char
import Data.List
import Data.Word
import Data.Int
import System.Random
import System.IO
import Foreign.C (CChar)
import qualified Data.ByteString as P
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Lazy.Internal as L (checkInvariant,ByteString(..))
import qualified Data.ByteString.Char8 as PC
import qualified Data.ByteString.Lazy.Char8 as LC
------------------------------------------------------------------------
integralRandomR :: (Integral a, RandomGen g) => (a,a) -> g -> (a,g)
integralRandomR (a,b) g = case randomR (fromIntegral a :: Integer,
fromIntegral b :: Integer) g of
(x,g) -> (fromIntegral x, g)
sizedByteString n = do m <- choose(0, n)
fmap P.pack $ vectorOf m arbitrary
instance Arbitrary P.ByteString where
arbitrary = do
bs <- sized sizedByteString
n <- choose (0, 2)
return (P.drop n bs) -- to give us some with non-0 offset
instance CoArbitrary P.ByteString where
coarbitrary s = coarbitrary (P.unpack s)
instance Arbitrary L.ByteString where
arbitrary = sized $ \n -> do numChunks <- choose (0, n)
if numChunks == 0
then return L.empty
else fmap (L.checkInvariant .
L.fromChunks .
filter (not . P.null)) $
vectorOf numChunks
(sizedByteString
(n `div` numChunks))
instance CoArbitrary L.ByteString where
coarbitrary s = coarbitrary (L.unpack s)
newtype CByteString = CByteString P.ByteString
deriving Show
instance Arbitrary CByteString where
arbitrary = fmap (CByteString . P.pack . map fromCChar)
arbitrary
where
fromCChar :: NonZero CChar -> Word8
fromCChar = fromIntegral . getNonZero
-- | 'Char', but only representing 8-bit characters.
--
newtype Char8 = Char8 Char
deriving (Eq, Ord, Show)
instance Arbitrary Char8 where
arbitrary = fmap (Char8 . toChar) arbitrary
where
toChar :: Word8 -> Char
toChar = toEnum . fromIntegral
instance CoArbitrary Char8 where
coarbitrary (Char8 c) = coarbitrary c
-- | 'Char', but only representing 8-bit characters.
--
newtype String8 = String8 String
deriving (Eq, Ord, Show)
instance Arbitrary String8 where
arbitrary = fmap (String8 . map toChar) arbitrary
where
toChar :: Word8 -> Char
toChar = toEnum . fromIntegral
------------------------------------------------------------------------
--
-- We're doing two forms of testing here. Firstly, model based testing.
-- For our Lazy and strict bytestring types, we have model types:
--
-- i.e. Lazy == Byte
-- \\ //
-- List
--
-- That is, the Lazy type can be modeled by functions in both the Byte
-- and List type. For each of the 3 models, we have a set of tests that
-- check those types match.
--
-- The Model class connects a type and its model type, via a conversion
-- function.
--
--
class Model a b where
model :: a -> b -- ^ Get the abstract value from a concrete value
-- | Alias for 'model' that's a better name in the situations where we're
-- really just converting functions that take or return Char8.
castFn :: Model a b => a -> b
castFn = model
--
-- Connecting our Lazy and Strict types to their models. We also check
-- the data invariant on Lazy types.
--
-- These instances represent the arrows in the above diagram
--
instance Model B P where model = abstr . checkInvariant
instance Model P [W] where model = P.unpack
instance Model P [Char] where model = PC.unpack
instance Model B [W] where model = L.unpack . checkInvariant
instance Model B [Char] where model = LC.unpack . checkInvariant
instance Model Char8 Char where model (Char8 c) = c
-- Types are trivially modeled by themselves
instance Model Bool Bool where model = id
instance Model Int Int where model = id
instance Model P P where model = id
instance Model B B where model = id
instance Model Int64 Int64 where model = id
instance Model Word8 Word8 where model = id
instance Model Ordering Ordering where model = id
instance Model Char Char where model = id
-- More structured types are modeled recursively, using the NatTrans class from Gofer.
class (Functor f, Functor g) => NatTrans f g where
eta :: f a -> g a
-- The transformation of the same type is identity
instance NatTrans [] [] where eta = id
instance NatTrans Maybe Maybe where eta = id
instance NatTrans ((->) X) ((->) X) where eta = id
instance NatTrans ((->) Char) ((->) Char) where eta = id
instance NatTrans ((->) Char8) ((->) Char) where eta f = f . Char8
instance NatTrans ((->) W) ((->) W) where eta = id
-- We have a transformation of pairs, if the pairs are in Model
instance Model f g => NatTrans ((,) f) ((,) g) where eta (f,a) = (model f, a)
-- And finally, we can take any (m a) to (n b), if we can Model m n, and a b
instance (NatTrans m n, Model a b) => Model (m a) (n b) where model x = fmap model (eta x)
------------------------------------------------------------------------
-- In a form more useful for QC testing (and it's lazy)
checkInvariant :: L.ByteString -> L.ByteString
checkInvariant = L.checkInvariant
abstr :: L.ByteString -> P.ByteString
abstr = P.concat . L.toChunks
-- Some short hand.
type X = Int
type W = Word8
type P = P.ByteString
type B = L.ByteString
------------------------------------------------------------------------
--
-- These comparison functions handle wrapping and equality.
--
-- A single class for these would be nice, but note that they differe in
-- the number of arguments, and those argument types, so we'd need HList
-- tricks. See here: http://okmij.org/ftp/Haskell/vararg-fn.lhs
--
eq1 f g = \a ->
model (f a) == g (model a)
eq2 f g = \a b ->
model (f a b) == g (model a) (model b)
eq3 f g = \a b c ->
model (f a b c) == g (model a) (model b) (model c)
--
-- And for functions that take non-null input
--
eqnotnull1 f g = \x -> (not (isNull x)) ==> eq1 f g x
eqnotnull2 f g = \x y -> (not (isNull y)) ==> eq2 f g x y
eqnotnull3 f g = \x y z -> (not (isNull z)) ==> eq3 f g x y z
class IsNull t where isNull :: t -> Bool
instance IsNull L.ByteString where isNull = L.null
instance IsNull P.ByteString where isNull = P.null
|