{-# LANGUAGE CPP, ScopedTypeVariables, DataKinds, TypeSynonymInstances #-} module Main ( main ) where #if MIN_VERSION_base(4,8,0) #define HAS_NATURAL #endif #if MIN_VERSION_base(4,7,0) #define HAS_FIXED_CONSTRUCTOR #endif import Control.Applicative import Control.Exception as C (SomeException, catch, evaluate) import Control.Monad (unless, liftM2) import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy.Internal as L #if MIN_VERSION_bytestring(0,10,4) import Data.ByteString.Short (ShortByteString) #endif import Data.Int import Data.Ratio import Data.Typeable import System.IO.Unsafe import Data.Orphans () #ifdef HAS_NATURAL import Numeric.Natural #endif import GHC.Fingerprint import qualified Data.Fixed as Fixed import Test.Framework import Test.Framework.Providers.QuickCheck2 import Test.QuickCheck hiding (total) import qualified Action (tests) import Arbitrary () import Data.Binary import Data.Binary.Get import Data.Binary.Put ------------------------------------------------------------------------ roundTrip :: (Eq a, Binary a) => a -> (L.ByteString -> L.ByteString) -> Bool roundTrip a f = a == {-# SCC "decode.refragment.encode" #-} decode (f (encode a)) roundTripWith :: Eq a => (a -> Put) -> Get a -> a -> Property roundTripWith putter getter x = forAll positiveList $ \xs -> x == runGet getter (refragment xs (runPut (putter x))) -- make sure that a test fails mustThrowError :: B a mustThrowError a = unsafePerformIO $ C.catch (do _ <- C.evaluate a return False) (\(_e :: SomeException) -> return True) -- low level ones: -- -- Words prop_Word8 :: Word8 -> Property prop_Word8 = roundTripWith putWord8 getWord8 prop_Word16be :: Word16 -> Property prop_Word16be = roundTripWith putWord16be getWord16be prop_Word16le :: Word16 -> Property prop_Word16le = roundTripWith putWord16le getWord16le prop_Word16host :: Word16 -> Property prop_Word16host = roundTripWith putWord16host getWord16host prop_Word32be :: Word32 -> Property prop_Word32be = roundTripWith putWord32be getWord32be prop_Word32le :: Word32 -> Property prop_Word32le = roundTripWith putWord32le getWord32le prop_Word32host :: Word32 -> Property prop_Word32host = roundTripWith putWord32host getWord32host prop_Word64be :: Word64 -> Property prop_Word64be = roundTripWith putWord64be getWord64be prop_Word64le :: Word64 -> Property prop_Word64le = roundTripWith putWord64le getWord64le prop_Word64host :: Word64 -> Property prop_Word64host = roundTripWith putWord64host getWord64host prop_Wordhost :: Word -> Property prop_Wordhost = roundTripWith putWordhost getWordhost -- Ints prop_Int8 :: Int8 -> Property prop_Int8 = roundTripWith putInt8 getInt8 prop_Int16be :: Int16 -> Property prop_Int16be = roundTripWith putInt16be getInt16be prop_Int16le :: Int16 -> Property prop_Int16le = roundTripWith putInt16le getInt16le prop_Int16host :: Int16 -> Property prop_Int16host = roundTripWith putInt16host getInt16host prop_Int32be :: Int32 -> Property prop_Int32be = roundTripWith putInt32be getInt32be prop_Int32le :: Int32 -> Property prop_Int32le = roundTripWith putInt32le getInt32le prop_Int32host :: Int32 -> Property prop_Int32host = roundTripWith putInt32host getInt32host prop_Int64be :: Int64 -> Property prop_Int64be = roundTripWith putInt64be getInt64be prop_Int64le :: Int64 -> Property prop_Int64le = roundTripWith putInt64le getInt64le prop_Int64host :: Int64 -> Property prop_Int64host = roundTripWith putInt64host getInt64host prop_Inthost :: Int -> Property prop_Inthost = roundTripWith putInthost getInthost -- Floats and Doubles prop_Floatbe :: Float -> Property prop_Floatbe = roundTripWith putFloatbe getFloatbe prop_Floatle :: Float -> Property prop_Floatle = roundTripWith putFloatle getFloatle prop_Floathost :: Float -> Property prop_Floathost = roundTripWith putFloathost getFloathost prop_Doublebe :: Double -> Property prop_Doublebe = roundTripWith putDoublebe getDoublebe prop_Doublele :: Double -> Property prop_Doublele = roundTripWith putDoublele getDoublele prop_Doublehost :: Double -> Property prop_Doublehost = roundTripWith putDoublehost getDoublehost #if MIN_VERSION_base(4,10,0) testTypeable :: Test testTypeable = testProperty "TypeRep" prop_TypeRep prop_TypeRep :: TypeRep -> Property prop_TypeRep = roundTripWith put get atomicTypeReps :: [TypeRep] atomicTypeReps = [ typeRep (Proxy :: Proxy ()) , typeRep (Proxy :: Proxy String) , typeRep (Proxy :: Proxy Int) , typeRep (Proxy :: Proxy (,)) , typeRep (Proxy :: Proxy ((,) (Maybe Int))) , typeRep (Proxy :: Proxy Maybe) , typeRep (Proxy :: Proxy 'Nothing) , typeRep (Proxy :: Proxy 'Left) , typeRep (Proxy :: Proxy "Hello") , typeRep (Proxy :: Proxy 42) , typeRep (Proxy :: Proxy '[1,2,3,4]) , typeRep (Proxy :: Proxy ('Left Int)) , typeRep (Proxy :: Proxy (Either Int String)) , typeRep (Proxy :: Proxy (() -> ())) ] instance Arbitrary TypeRep where arbitrary = oneof (map pure atomicTypeReps) #else testTypeable :: Test testTypeable = testGroup "Skipping Typeable tests" [] #endif -- done, partial and fail -- | Test partial results. -- May or may not use the whole input, check conditions for the different -- outcomes. prop_partial :: L.ByteString -> Property prop_partial lbs = forAll (choose (0, L.length lbs * 2)) $ \skipN -> let result = pushChunks (runGetIncremental decoder) lbs decoder = do s <- getByteString (fromIntegral skipN) return (L.fromChunks [s]) in case result of Partial _ -> L.length lbs < skipN Done unused _pos value -> and [ L.length value == skipN , L.append value (L.fromChunks [unused]) == lbs ] Fail _ _ _ -> False -- | Fail a decoder and make sure the result is sane. prop_fail :: L.ByteString -> String -> Property prop_fail lbs msg = forAll (choose (0, L.length lbs)) $ \pos -> let result = pushChunks (runGetIncremental decoder) lbs decoder = do -- use part of the input... _ <- getByteString (fromIntegral pos) -- ... then fail fail msg in case result of Fail unused pos' msg' -> and [ pos == pos' , msg == msg' , L.length lbs - pos == fromIntegral (B.length unused) , L.fromChunks [unused] `L.isSuffixOf` lbs ] _ -> False -- wuut? -- read negative length prop_getByteString_negative :: Int -> Property prop_getByteString_negative n = n < 1 ==> runGet (getByteString n) L.empty == B.empty prop_bytesRead :: L.ByteString -> Property prop_bytesRead lbs = forAll (makeChunks 0 totalLength) $ \chunkSizes -> let result = pushChunks (runGetIncremental decoder) lbs decoder = do -- Read some data and invoke bytesRead several times. -- Each time, check that the values are what we expect. flip mapM_ chunkSizes $ \(total, step) -> do _ <- getByteString (fromIntegral step) n <- bytesRead unless (n == total) $ fail "unexpected position" bytesRead in case result of Done unused pos value -> and [ value == totalLength , pos == value , B.null unused ] Partial _ -> False Fail _ _ _ -> False where totalLength = L.length lbs makeChunks total i | i == 0 = return [] | otherwise = do n <- choose (0,i) let total' = total + n rest <- makeChunks total' (i - n) return ((total',n):rest) -- | We're trying to guarantee that the Decoder will not ask for more input -- with Partial if it has been given Nothing once. -- In this test we're making the decoder return 'Partial' to get more -- input, and to get knownledge of the current position using 'BytesRead'. -- Both of these operations, when used with the <|> operator, result internally -- in that the decoder return with Partial and BytesRead multiple times, -- in which case we need to keep track of if the user has passed Nothing to a -- Partial in the past. prop_partialOnlyOnce :: Property prop_partialOnlyOnce = property $ let result = runGetIncremental (decoder <|> decoder) decoder = do 0 <- bytesRead _ <- getWord8 -- this will make the decoder return with Partial return "shouldn't get here" in case result of -- we expect Partial followed by Fail Partial k -> case k Nothing of -- push down a Nothing Fail _ _ _ -> True Partial _ -> error $ "partial twice! oh noes!" Done _ _ _ -> error $ "we're not supposed to be done." _ -> error $ "not partial, error!" -- read too much prop_readTooMuch :: (Eq a, Binary a) => a -> Bool prop_readTooMuch x = mustThrowError $ x == a && x /= b where -- encode 'a', but try to read 'b' too (a,b) = decode (encode x) _types = [a,b] -- In binary-0.5 the Get monad looked like -- -- > data S = S {-# UNPACK #-} !B.ByteString -- > L.ByteString -- > {-# UNPACK #-} !Int64 -- > -- > newtype Get a = Get { unGet :: S -> (# a, S #) } -- -- with a helper function -- -- > mkState :: L.ByteString -> Int64 -> S -- > mkState l = case l of -- > L.Empty -> S B.empty L.empty -- > L.Chunk x xs -> S x xs -- -- Note that mkState is strict in its first argument. This goes wrong in this -- function: -- -- > getBytes :: Int -> Get B.ByteString -- > getBytes n = do -- > S s ss bytes <- traceNumBytes n $ get -- > if n <= B.length s -- > then do let (consume,rest) = B.splitAt n s -- > put $! S rest ss (bytes + fromIntegral n) -- > return $! consume -- > else -- > case L.splitAt (fromIntegral n) (s `join` ss) of -- > (consuming, rest) -> -- > do let now = B.concat . L.toChunks $ consuming -- > put $ mkState rest (bytes + fromIntegral n) -- > -- forces the next chunk before this one is returned -- > if (B.length now < n) -- > then -- > fail "too few bytes" -- > else -- > return now -- -- Consider the else-branch of this function; suppose we ask for n bytes; -- the call to L.splitAt gives us a lazy bytestring 'consuming' of precisely @n@ -- bytes (unless we don't have enough data, in which case we fail); but then -- the strict evaluation of mkState on 'rest' means we look ahead too far. -- -- Although this is all done completely differently in binary-0.7 it is -- important that the same bug does not get introduced in some other way. The -- test is basically the same test that already exists in this test suite, -- verifying that -- -- > decode . refragment . encode == id -- -- However, we use a different 'refragment', one that introduces an exception -- as the tail of the bytestring after rechunking. If we don't look ahead too -- far then this should make no difference, but if we do then this will throw -- an exception (for instance, in binary-0.5, this will throw an exception for -- certain rechunkings, but not for others). -- -- To make sure that the property holds no matter what refragmentation we use, -- we test exhaustively for a single chunk, and all ways to break the string -- into 2, 3 and 4 chunks. prop_lookAheadIndepOfChunking :: (Eq a, Binary a) => a -> Property prop_lookAheadIndepOfChunking testInput = forAll (testCuts (L.length (encode testInput))) $ roundTrip testInput . rechunk where testCuts :: forall a. (Num a, Enum a) => a -> Gen [a] testCuts len = elements $ [ [] ] ++ [ [i] | i <- [0 .. len] ] ++ [ [i, j] | i <- [0 .. len] , j <- [0 .. len - i] ] ++ [ [i, j, k] | i <- [0 .. len] , j <- [0 .. len - i] , k <- [0 .. len - i - j] ] -- Rechunk a bytestring, leaving the tail as an exception rather than Empty rechunk :: forall a. Integral a => [a] -> L.ByteString -> L.ByteString rechunk cuts = fromChunks . cut cuts . B.concat . L.toChunks where cut :: [a] -> B.ByteString -> [B.ByteString] cut [] bs = [bs] cut (i:is) bs = let (bs0, bs1) = B.splitAt (fromIntegral i) bs in bs0 : cut is bs1 fromChunks :: [B.ByteString] -> L.ByteString fromChunks [] = error "Binary should not have to ask for this chunk!" fromChunks (bs:bss) = L.Chunk bs (fromChunks bss) -- String utilities prop_getLazyByteString :: L.ByteString -> Property prop_getLazyByteString lbs = forAll (choose (0, 2 * L.length lbs)) $ \len -> let result = pushChunks (runGetIncremental decoder) lbs decoder = getLazyByteString len in case result of Done unused _pos value -> and [ value == L.take len lbs , L.fromChunks [unused] == L.drop len lbs ] Partial _ -> len > L.length lbs _ -> False prop_getLazyByteStringNul :: Word16 -> [Int] -> Property prop_getLazyByteStringNul count0 fragments = count >= 0 ==> forAll (choose (0, count)) $ \pos -> let lbs = case L.splitAt pos (L.replicate count 65) of (start,end) -> refragment fragments $ L.concat [start, L.singleton 0, end] result = pushEndOfInput $ pushChunks (runGetIncremental getLazyByteStringNul) lbs in case result of Done unused pos' value -> and [ value == L.take pos lbs , pos + 1 == pos' -- 1 for the NUL , L.fromChunks [unused] == L.drop (pos + 1) lbs ] _ -> False where count = fromIntegral count0 -- to make the generated numbers a bit smaller -- | Same as prop_getLazyByteStringNul, but without any NULL in the string. prop_getLazyByteStringNul_noNul :: Word16 -> [Int] -> Property prop_getLazyByteStringNul_noNul count0 fragments = count >= 0 ==> let lbs = refragment fragments $ L.replicate count 65 result = pushEndOfInput $ pushChunks (runGetIncremental getLazyByteStringNul) lbs in case result of Fail _ _ _ -> True _ -> False where count = fromIntegral count0 -- to make the generated numbers a bit smaller prop_getRemainingLazyByteString :: L.ByteString -> Property prop_getRemainingLazyByteString lbs = property $ let result = pushEndOfInput $ pushChunks (runGetIncremental getRemainingLazyByteString) lbs in case result of Done unused pos value -> and [ value == lbs , B.null unused , fromIntegral pos == L.length lbs ] _ -> False -- sanity: invariant_lbs :: L.ByteString -> Bool invariant_lbs (L.Empty) = True invariant_lbs (L.Chunk x xs) = not (B.null x) && invariant_lbs xs prop_invariant :: (Binary a) => a -> Bool prop_invariant = invariant_lbs . encode -- refragment a lazy bytestring's chunks refragment :: [Int] -> L.ByteString -> L.ByteString refragment [] lbs = lbs refragment (x:xs) lbs = let x' = fromIntegral . (+1) . abs $ x rest = refragment xs (L.drop x' lbs) in L.append (L.fromChunks [B.concat . L.toChunks . L.take x' $ lbs]) rest -- check identity of refragmentation prop_refragment :: L.ByteString -> [Int] -> Bool prop_refragment lbs xs = lbs == refragment xs lbs -- check that refragmention still hold invariant prop_refragment_inv :: L.ByteString -> [Int] -> Bool prop_refragment_inv lbs xs = invariant_lbs $ refragment xs lbs main :: IO () main = defaultMain tests ------------------------------------------------------------------------ genInteger :: Gen Integer genInteger = do b <- arbitrary if b then genIntegerSmall else genIntegerSmall genIntegerSmall :: Gen Integer genIntegerSmall = arbitrary genIntegerBig :: Gen Integer genIntegerBig = do x <- arbitrarySizedIntegral :: Gen Integer -- arbitrarySizedIntegral generates numbers smaller than -- (maxBound :: Word32), so let's make them bigger to better test -- the Binary instance. return (x + fromIntegral (maxBound :: Word32)) #ifdef HAS_NATURAL genNatural :: Gen Natural genNatural = do b <- arbitrary if b then genNaturalSmall else genNaturalBig genNaturalSmall :: Gen Natural genNaturalSmall = arbitrarySizedNatural genNaturalBig :: Gen Natural genNaturalBig = do x <- arbitrarySizedNatural :: Gen Natural -- arbitrarySizedNatural generates numbers smaller than -- (maxBound :: Word64), so let's make them bigger to better test -- the Binary instance. return (x + fromIntegral (maxBound :: Word64)) #endif ------------------------------------------------------------------------ genFingerprint :: Gen Fingerprint genFingerprint = liftM2 Fingerprint arbitrary arbitrary ------------------------------------------------------------------------ #ifdef HAS_FIXED_CONSTRUCTOR fixedPut :: forall a. Fixed.HasResolution a => Fixed.Fixed a -> Put fixedPut x = put (truncate (x * fromInteger (Fixed.resolution (undefined :: Maybe a))) :: Integer) fixedGet :: forall a. Fixed.HasResolution a => Get (Fixed.Fixed a) fixedGet = (\x -> fromInteger x / fromInteger (Fixed.resolution (undefined :: Maybe a))) `liftA` get -- | Serialise using base >=4.7 and <4.7 methods agree prop_fixed_ser :: Fixed.Fixed Fixed.E3 -> Bool prop_fixed_ser x = runPut (put x) == runPut (fixedPut x) -- | Serialised with base >=4.7, unserialised with base <4.7 method roundtrip prop_fixed_constr_resolution :: Fixed.Fixed Fixed.E3 -> Bool prop_fixed_constr_resolution x = runGet fixedGet (runPut (put x)) == x -- | Serialised with base <4.7, unserialised with base >=4.7 method roundtrip prop_fixed_resolution_constr :: Fixed.Fixed Fixed.E3 -> Bool prop_fixed_resolution_constr x = runGet get (runPut (fixedPut x)) == x #endif ------------------------------------------------------------------------ type T a = a -> Property type B a = a -> Bool p :: (Testable p) => p -> Property p = property test :: (Eq a, Binary a) => a -> Property test a = forAll positiveList (roundTrip a . refragment) test' :: (Show a, Arbitrary a) => String -> (a -> Property) -> ([a] -> Property) -> Test test' desc prop propList = testGroup desc [ testProperty desc prop, testProperty ("[" ++ desc ++ "]") propList ] testWithGen :: (Show a, Eq a, Binary a) => String -> Gen a -> Test testWithGen desc gen = testGroup desc [ testProperty desc (forAll gen test), testProperty ("[" ++ desc ++ "]") (forAll (listOf gen) test) ] positiveList :: Gen [Int] positiveList = fmap (filter (/=0) . map abs) $ arbitrary tests :: [Test] tests = [ testGroup "Utils" [ testProperty "refragment id" (p prop_refragment) , testProperty "refragment invariant" (p prop_refragment_inv) ] , testGroup "Boundaries" [ testProperty "read to much" (p (prop_readTooMuch :: B Word8)) , testProperty "read negative length" (p (prop_getByteString_negative :: T Int)) , -- Arbitrary test input let testInput :: [Int] ; testInput = [0 .. 10] in testProperty "look-ahead independent of chunking" (p (prop_lookAheadIndepOfChunking testInput)) ] , testGroup "Partial" [ testProperty "partial" (p prop_partial) , testProperty "fail" (p prop_fail) , testProperty "bytesRead" (p prop_bytesRead) , testProperty "partial only once" (p prop_partialOnlyOnce) ] , testGroup "Model" Action.tests , testGroup "Primitives" [ testProperty "Word8" (p prop_Word8) , testProperty "Word16be" (p prop_Word16be) , testProperty "Word16le" (p prop_Word16le) , testProperty "Word16host" (p prop_Word16host) , testProperty "Word32be" (p prop_Word32be) , testProperty "Word32le" (p prop_Word32le) , testProperty "Word32host" (p prop_Word32host) , testProperty "Word64be" (p prop_Word64be) , testProperty "Word64le" (p prop_Word64le) , testProperty "Word64host" (p prop_Word64host) , testProperty "Wordhost" (p prop_Wordhost) -- Int , testProperty "Int8" (p prop_Int8) , testProperty "Int16be" (p prop_Int16be) , testProperty "Int16le" (p prop_Int16le) , testProperty "Int16host" (p prop_Int16host) , testProperty "Int32be" (p prop_Int32be) , testProperty "Int32le" (p prop_Int32le) , testProperty "Int32host" (p prop_Int32host) , testProperty "Int64be" (p prop_Int64be) , testProperty "Int64le" (p prop_Int64le) , testProperty "Int64host" (p prop_Int64host) , testProperty "Inthost" (p prop_Inthost) -- Float/Double , testProperty "Floatbe" (p prop_Floatbe) , testProperty "Floatle" (p prop_Floatle) , testProperty "Floathost" (p prop_Floathost) , testProperty "Doublebe" (p prop_Doublebe) , testProperty "Doublele" (p prop_Doublele) , testProperty "Doublehost" (p prop_Doublehost) ] , testGroup "String utils" [ testProperty "getLazyByteString" prop_getLazyByteString , testProperty "getLazyByteStringNul" prop_getLazyByteStringNul , testProperty "getLazyByteStringNul No Null" prop_getLazyByteStringNul_noNul , testProperty "getRemainingLazyByteString" prop_getRemainingLazyByteString ] , testGroup "Using Binary class, refragmented ByteString" [ test' "()" (test :: T () ) test , test' "Bool" (test :: T Bool ) test , test' "Char" (test :: T Char ) test , test' "Ordering" (test :: T Ordering ) test , test' "Ratio Int" (test :: T (Ratio Int)) test , test' "Word" (test :: T Word ) test , test' "Word8" (test :: T Word8 ) test , test' "Word16" (test :: T Word16) test , test' "Word32" (test :: T Word32) test , test' "Word64" (test :: T Word64) test , test' "Int" (test :: T Int ) test , test' "Int8" (test :: T Int8 ) test , test' "Int16" (test :: T Int16) test , test' "Int32" (test :: T Int32) test , test' "Int64" (test :: T Int64) test , testWithGen "Integer mixed" genInteger , testWithGen "Integer small" genIntegerSmall , testWithGen "Integer big" genIntegerBig , test' "Fixed" (test :: T (Fixed.Fixed Fixed.E3) ) test #ifdef HAS_NATURAL , testWithGen "Natural mixed" genNatural , testWithGen "Natural small" genNaturalSmall , testWithGen "Natural big" genNaturalBig #endif , testWithGen "GHC.Fingerprint" genFingerprint , test' "Float" (test :: T Float ) test , test' "Double" (test :: T Double) test , test' "((), ())" (test :: T ((), ()) ) test , test' "(Word8, Word32)" (test :: T (Word8, Word32) ) test , test' "(Int8, Int32)" (test :: T (Int8, Int32) ) test , test' "(Int32, [Int])" (test :: T (Int32, [Int]) ) test , test' "Maybe Int8" (test :: T (Maybe Int8) ) test , test' "Either Int8 Int16" (test :: T (Either Int8 Int16) ) test , test' "(Int, ByteString)" (test :: T (Int, B.ByteString) ) test , test' "[(Int, ByteString)]" (test :: T [(Int, B.ByteString)] ) test , test' "(Maybe Int64, Bool, [Int])" (test :: T (Maybe Int64, Bool, [Int])) test , test' "(Maybe Word8, Bool, [Int], Either Bool Word8)" (test :: T (Maybe Word8, Bool, [Int], Either Bool Word8)) test , test' "(Maybe Word16, Bool, [Int], Either Bool Word16, Int)" (test :: T (Maybe Word16, Bool, [Int], Either Bool Word16, Int)) test , test' "(Int,Int,Int,Int,Int,Int)" (test :: T (Int,Int,Int,Int,Int,Int)) test , test' "(Int,Int,Int,Int,Int,Int,Int)" (test :: T (Int,Int,Int,Int,Int,Int,Int)) test , test' "(Int,Int,Int,Int,Int,Int,Int,Int)" (test :: T (Int,Int,Int,Int,Int,Int,Int,Int)) test , test' "(Int,Int,Int,Int,Int,Int,Int,Int,Int)" (test :: T (Int,Int,Int,Int,Int,Int,Int,Int,Int)) test , test' "(Int,Int,Int,Int,Int,Int,Int,Int,Int,Int)" (test :: T (Int,Int,Int,Int,Int,Int,Int,Int,Int,Int)) test , test' "B.ByteString" (test :: T B.ByteString) test , test' "L.ByteString" (test :: T L.ByteString) test #if MIN_VERSION_bytestring(0,10,4) , test' "ShortByteString" (test :: T ShortByteString) test #endif ] , testGroup "Invariants" $ map (uncurry testProperty) [ ("B.ByteString invariant", p (prop_invariant :: B B.ByteString )) , ("[B.ByteString] invariant", p (prop_invariant :: B [B.ByteString] )) , ("L.ByteString invariant", p (prop_invariant :: B L.ByteString )) , ("[L.ByteString] invariant", p (prop_invariant :: B [L.ByteString] )) #if MIN_VERSION_bytestring(0,10,4) , ("ShortByteString invariant", p (prop_invariant :: B ShortByteString )) , ("[ShortByteString] invariant", p (prop_invariant :: B [ShortByteString] )) #endif ] #ifdef HAS_FIXED_CONSTRUCTOR , testGroup "Fixed" [ testProperty "Serialisation same" $ p prop_fixed_ser , testProperty "MkFixed -> HasResolution" $ p prop_fixed_constr_resolution , testProperty "HasResolution -> MkFixed" $ p prop_fixed_resolution_constr ] #endif , testTypeable ]