Skip to content

Commit a7fbc36

Browse files
committed
WIP
1 parent ccaa346 commit a7fbc36

File tree

2 files changed

+129
-84
lines changed

2 files changed

+129
-84
lines changed

tests/Tests/Properties/Builder.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ tb_formatRealFloat :: (RealFloat a, Show a) =>
9090
tb_formatRealFloat a fmt prec = cond ==>
9191
TB.formatRealFloat fmt p a ===
9292
TB.fromString (showFloat fmt p a "")
93-
where p = precision a prec
93+
where p = unPrecision prec
9494
cond = case (p,fmt) of
9595
#if MIN_VERSION_base(4,12,0)
9696
(Just 0, TB.Generic) -> False -- skipping due to gh-231

tests/Tests/QuickCheckUtils.hs

Lines changed: 128 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
--
55
{-# LANGUAGE FlexibleInstances #-}
66
{-# LANGUAGE DeriveFunctor #-}
7+
{-# LANGUAGE TypeApplications #-}
8+
{-# LANGUAGE ScopedTypeVariables #-}
79

810
{-# OPTIONS_GHC -fno-warn-orphans #-}
911

@@ -29,13 +31,14 @@ module Tests.QuickCheckUtils
2931
) where
3032

3133
import Control.Arrow ((***))
32-
import Control.DeepSeq (NFData (..), deepseq)
33-
import Control.Exception (bracket)
34+
import Control.DeepSeq (NFData(..), deepseq)
3435
import Data.Char (isSpace)
36+
import Data.Coerce (coerce)
3537
import Data.Text.Foreign (I8)
3638
import Data.Text.Lazy.Builder.RealFloat (FPFormat(..))
3739
import Data.Word (Word8, Word16)
38-
import Test.QuickCheck hiding (Fixed(..), Small (..), (.&.))
40+
import GHC.Num (integerLog2)
41+
import Test.QuickCheck hiding (Fixed(..), Small(..), (.&.))
3942
import Tests.Utils
4043
import qualified Data.ByteString as B
4144
import qualified Data.ByteString.Lazy as BL
@@ -47,6 +50,7 @@ import qualified Data.Text.Internal.Lazy as TL
4750
import qualified Data.Text.Internal.Lazy.Fusion as TLF
4851
import qualified Data.Text.Lazy as TL
4952
import qualified System.IO as IO
53+
import Control.Applicative (liftA2, liftA3)
5054

5155
genWord8 :: Gen Word8
5256
genWord8 = chooseAny
@@ -56,7 +60,7 @@ instance Arbitrary I8 where
5660
shrink = shrinkIntegral
5761

5862
instance Arbitrary B.ByteString where
59-
arbitrary = B.pack `fmap` listOf genWord8
63+
arbitrary = B.pack <$> listOf genWord8
6064
shrink = map B.pack . shrink . B.unpack
6165

6266
instance Arbitrary BL.ByteString where
@@ -66,64 +70,84 @@ instance Arbitrary BL.ByteString where
6670
, BL.fromChunks . map B.singleton <$> listOf genWord8
6771
-- so that a code point with 4 byte long utf8 representation
6872
-- could appear split over 3 non-singleton chunks
69-
, (\a b c -> BL.fromChunks [a, b, c])
70-
<$> arbitrary
71-
<*> ((\a b -> B.pack [a, b]) <$> genWord8 <*> genWord8)
72-
<*> arbitrary
73+
, liftA3 (\a b c -> BL.fromChunks [a, b, c])
74+
arbitrary
75+
(liftA2 (\a b -> B.pack [a, b])
76+
genWord8
77+
genWord8
78+
)
79+
arbitrary
7380
]
7481
shrink xs = BL.fromChunks <$> shrink (BL.toChunks xs)
7582

7683
-- | For tests that have O(n^2) running times or input sizes, resize
7784
-- their inputs to the square root of the originals.
7885
newtype Sqrt a = Sqrt { unSqrt :: a }
79-
deriving (Eq, Show)
86+
deriving (Eq, Show)
8087

8188
instance Arbitrary a => Arbitrary (Sqrt a) where
82-
arbitrary = fmap Sqrt $ sized $ \n -> resize (smallish n) arbitrary
83-
where
84-
smallish = round . (sqrt :: Double -> Double) . fromIntegral . abs
85-
shrink = map Sqrt . shrink . unSqrt
89+
arbitrary = coerce $ sized $ \n -> resize (smallish n) $ arbitrary @a
90+
where
91+
smallish = round . sqrt @Double . fromIntegral . abs
92+
shrink = coerce (shrink @a)
8693

8794
instance Arbitrary T.Text where
88-
arbitrary = (T.pack . getUnicodeString) `fmap` arbitrary
95+
arbitrary = T.pack <$> listOf arbitraryUnicodeChar -- without surrogates
8996
shrink = map T.pack . shrink . T.unpack
9097

9198
instance Arbitrary TL.Text where
92-
arbitrary = (TL.fromChunks . map notEmpty . unSqrt) `fmap` arbitrary
99+
arbitrary = TL.fromChunks <$> coerce (arbitrary @(Sqrt [NotEmpty T.Text]))
93100
shrink = map TL.pack . shrink . TL.unpack
94101

95102
newtype BigInt = Big Integer
96-
deriving (Eq, Show)
103+
deriving (Eq, Show)
97104

98105
instance Arbitrary BigInt where
99-
arbitrary = choose (1::Int,200) >>= \e -> Big <$> choose (10^(e-1),10^e)
100-
shrink (Big a) = [Big (a `div` 2^(l-e)) | e <- shrink l]
101-
where l = truncate (log (fromIntegral a) / log 2 :: Double) :: Integer
106+
arbitrary = do
107+
e <- choose @Int (1,200)
108+
coerce $ choose @Integer (10^(e-1),10^e)
109+
110+
shrink ba = [coerce (a `div` 2^(l-e)) | e <- shrink l]
111+
where
112+
a :: Integer
113+
a = coerce ba
114+
l :: Word
115+
l = integerLog2 a
102116

103117
newtype NotEmpty a = NotEmpty { notEmpty :: a }
104-
deriving (Eq, Ord, Show)
118+
deriving (Eq, Ord, Show)
119+
120+
toNotEmptyBy :: Functor m => ([Char] -> a) -> m (NonEmptyList Char) -> m (NotEmpty a)
121+
toNotEmptyBy f = fmap (coerce f)
122+
123+
arbitraryNotEmptyBy :: ([Char] -> a) -> Gen (NotEmpty a)
124+
arbitraryNotEmptyBy f = toNotEmptyBy f arbitrary
125+
126+
shrinkNotEmptyBy :: ([Char] -> a) -> (a -> [Char]) -> NotEmpty a -> [NotEmpty a]
127+
shrinkNotEmptyBy g f =
128+
toNotEmptyBy g . shrink . coerce f
105129

106130
instance Arbitrary (NotEmpty T.Text) where
107-
arbitrary = fmap (NotEmpty . T.pack . getNonEmpty) arbitrary
108-
shrink = fmap (NotEmpty . T.pack . getNonEmpty)
109-
. shrink . NonEmpty . T.unpack . notEmpty
131+
arbitrary = arbitraryNotEmptyBy T.pack
132+
shrink = shrinkNotEmptyBy T.pack T.unpack
110133

111134
instance Arbitrary (NotEmpty TL.Text) where
112-
arbitrary = fmap (NotEmpty . TL.pack . getNonEmpty) arbitrary
113-
shrink = fmap (NotEmpty . TL.pack . getNonEmpty)
114-
. shrink . NonEmpty . TL.unpack . notEmpty
135+
arbitrary = arbitraryNotEmptyBy TL.pack
136+
shrink = shrinkNotEmptyBy TL.pack TL.unpack
137+
115138

116139
data DecodeErr = Lenient | Ignore | Strict | Replace
117-
deriving (Show, Eq, Bounded, Enum)
140+
deriving (Show, Eq, Bounded, Enum)
118141

119142
genDecodeErr :: DecodeErr -> Gen T.OnDecodeError
120-
genDecodeErr Lenient = return T.lenientDecode
121-
genDecodeErr Ignore = return T.ignore
122-
genDecodeErr Strict = return T.strictDecode
123-
genDecodeErr Replace = (\c _ _ -> c) <$> frequency
124-
[ (1, return Nothing)
125-
, (50, Just <$> arbitraryUnicodeChar)
126-
]
143+
genDecodeErr Lenient = pure T.lenientDecode
144+
genDecodeErr Ignore = pure T.ignore
145+
genDecodeErr Strict = pure T.strictDecode
146+
genDecodeErr Replace = (\c _ _ -> c) <$>
147+
frequency
148+
[ (1, pure Nothing)
149+
, (50, pure <$> arbitraryUnicodeChar)
150+
]
127151

128152
instance Arbitrary DecodeErr where
129153
arbitrary = arbitraryBoundedEnum
@@ -167,71 +191,84 @@ eq a b s = a s =^= b s
167191
-- What about with the RHS packed?
168192
eqP :: (Eq a, Show a, Stringy s) =>
169193
(String -> a) -> (s -> a) -> String -> Word8 -> Property
170-
eqP f g s w = counterexample "orig" (f s =^= g t) .&&.
171-
counterexample "mini" (f s =^= g mini) .&&.
172-
counterexample "head" (f sa =^= g ta) .&&.
173-
counterexample "tail" (f sb =^= g tb)
174-
where t = packS s
175-
mini = packSChunkSize 10 s
176-
(sa,sb) = splitAt m s
177-
(ta,tb) = splitAtS m t
178-
l = length s
179-
m | l == 0 = n
180-
| otherwise = n `mod` l
181-
n = fromIntegral w
194+
eqP f g s w =
195+
testCounterExample "orig" s t .&&.
196+
testCounterExample "mini" s mini .&&.
197+
testCounterExample "head" sa ta .&&.
198+
testCounterExample "tail" sb tb
199+
where
200+
testCounterExample txt a b = counterexample txt $ f a =^= g b
201+
202+
t = packS s
203+
mini = packSChunkSize 10 s
204+
(sa,sb) = splitAt m s
205+
(ta,tb) = splitAtS m t
206+
207+
m = if l == 0 then n else n `mod` l
208+
where
209+
l = length s
210+
n = fromIntegral w
182211

183212
eqPSqrt :: (Eq a, Show a, Stringy s) =>
184213
(String -> a) -> (s -> a) -> Sqrt String -> Word8 -> Property
185-
eqPSqrt f g s = eqP f g (unSqrt s)
214+
eqPSqrt f g s = eqP f g $ coerce s
186215

187216
instance Arbitrary FPFormat where
188217
arbitrary = arbitraryBoundedEnum
189218

190-
newtype Precision a = Precision (Maybe Int)
191-
deriving (Eq, Show)
219+
newtype Precision a = Precision { unPrecision :: Maybe Int}
220+
deriving (Eq, Show)
192221

222+
-- Deprecated on 2021-10-05
193223
precision :: a -> Precision a -> Maybe Int
194-
precision _ (Precision prec) = prec
224+
precision _ = coerce
225+
{-# DEPRECATED precision "Use @coerce@ with types instead." #-}
195226

196227
arbitraryPrecision :: Int -> Gen (Precision a)
197-
arbitraryPrecision maxDigits = Precision <$> do
198-
n <- choose (-1,maxDigits)
199-
return $ if n == -1
200-
then Nothing
201-
else Just n
228+
arbitraryPrecision maxDigits = do
229+
n <- choose (0,maxDigits)
230+
frequency
231+
[ (1, pure $ coerce $ Nothing @Int)
232+
, (n, pure $ coerce $ Just n)
233+
]
202234

203235
instance Arbitrary (Precision Float) where
204236
arbitrary = arbitraryPrecision 11
205-
shrink = map Precision . shrink . precision undefined
237+
shrink = coerce (shrink @(Maybe Int))
206238

207239
instance Arbitrary (Precision Double) where
208240
arbitrary = arbitraryPrecision 22
209-
shrink = map Precision . shrink . precision undefined
241+
shrink = coerce (shrink @(Maybe Int))
210242

211243
instance Arbitrary IO.Newline where
212-
arbitrary = oneof [return IO.LF, return IO.CRLF]
244+
arbitrary = oneof [pure IO.LF, pure IO.CRLF]
213245

214246
instance Arbitrary IO.NewlineMode where
215-
arbitrary = IO.NewlineMode <$> arbitrary <*> arbitrary
247+
arbitrary =
248+
liftA2 IO.NewlineMode
249+
arbitrary
250+
arbitrary
216251

217252
instance Arbitrary IO.BufferMode where
218-
arbitrary = oneof [ return IO.NoBuffering,
219-
return IO.LineBuffering,
220-
return (IO.BlockBuffering Nothing),
221-
(IO.BlockBuffering . Just . (+1) . fromIntegral) `fmap`
222-
(arbitrary :: Gen Word16) ]
253+
arbitrary =
254+
oneof
255+
[ pure IO.NoBuffering
256+
, pure IO.LineBuffering
257+
, pure (IO.BlockBuffering Nothing)
258+
, IO.BlockBuffering . pure . succ . fromIntegral <$> arbitrary @Word16
259+
]
223260

224261
-- This test harness is complex! What property are we checking?
225262
--
226263
-- Reading after writing a multi-line file should give the same
227264
-- results as were written.
228265
--
229266
-- What do we vary while checking this property?
230-
-- * The lines themselves, scrubbed to contain neither CR nor LF. (By
231-
-- working with a list of lines, we ensure that the data will
232-
-- sometimes contain line endings.)
233-
-- * Newline translation mode.
234-
-- * Buffering.
267+
-- * The lines themselves, scrubbed to contain neither CR nor LF. (By
268+
-- working with a list of lines, we ensure that the data will
269+
-- sometimes contain line endings.)
270+
-- * Newline translation mode.
271+
-- * Buffering.
235272
write_read :: (NFData a, Eq a, Show a)
236273
=> ([b] -> a)
237274
-> ((Char -> Bool) -> a -> b)
@@ -245,18 +282,26 @@ write_read _ _ _ _ (IO.NewlineMode IO.LF IO.CRLF) _ _ = discard
245282
write_read unline filt writer reader nl buf ts = ioProperty $
246283
(===t) <$> act
247284
where
248-
t = unline . map (filt (not . (`elem` "\r\n"))) $ ts
249-
250-
act = withTempFile $ \path h -> do
251-
IO.hSetNewlineMode h nl
252-
IO.hSetBuffering h buf
253-
() <- writer h t
254-
IO.hClose h
255-
bracket (IO.openFile path IO.ReadMode) IO.hClose $ \h' -> do
256-
IO.hSetNewlineMode h' nl
257-
IO.hSetBuffering h' buf
258-
r <- reader h'
259-
r `deepseq` return r
285+
286+
t = unline . map (filt (`notElem` "\r\n")) $ ts
287+
288+
act =
289+
withTempFile roundTrip
290+
where
291+
292+
readBack h' = do
293+
IO.hSetNewlineMode h' nl
294+
IO.hSetBuffering h' buf
295+
r <- reader h'
296+
r `deepseq` pure r
297+
298+
roundTrip path h = do
299+
IO.hSetNewlineMode h nl
300+
IO.hSetBuffering h buf
301+
() <- writer h t
302+
IO.hClose h
303+
304+
IO.withFile path IO.ReadMode readBack
260305

261306
-- Generate various Unicode space characters with high probability
262307
arbitrarySpacyChar :: Gen Char
@@ -269,5 +314,5 @@ newtype SpacyString = SpacyString { getSpacyString :: String }
269314
deriving (Eq, Ord, Show, Read)
270315

271316
instance Arbitrary SpacyString where
272-
arbitrary = SpacyString `fmap` listOf arbitrarySpacyChar
273-
shrink (SpacyString xs) = SpacyString `fmap` shrink xs
317+
arbitrary = coerce $ listOf arbitrarySpacyChar
318+
shrink = coerce (shrink @[Char])

0 commit comments

Comments
 (0)