Skip to content

Commit ffca73d

Browse files
LysxiaBodigrim
authored andcommitted
Improve read_write test to check CRLF encoding
1 parent fbfc1b4 commit ffca73d

File tree

3 files changed

+85
-57
lines changed

3 files changed

+85
-57
lines changed

tests/Tests/Properties/LowLevel.hs

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
{-# LANGUAGE CPP #-}
44
{-# LANGUAGE MagicHash #-}
5+
{-# LANGUAGE OverloadedStrings #-}
56
{-# LANGUAGE ScopedTypeVariables #-}
67

78
{-# OPTIONS_GHC -fno-warn-missing-signatures -fno-warn-unused-imports #-}
@@ -16,6 +17,7 @@ module Tests.Properties.LowLevel (testLowLevel) where
1617
import Prelude hiding (head, tail)
1718
import Control.Applicative ((<$>), pure)
1819
import Control.Exception as E (SomeException, catch, evaluate)
20+
import Data.Functor.Identity (Identity(..))
1921
import Data.Int (Int32, Int64)
2022
import Data.Text.Foreign
2123
import Data.Text.Internal (Text(..), mul, mul32, mul64, safe)
@@ -109,14 +111,32 @@ t_literal_foo = T.pack "foo"
109111
-- tl_put_get = write_read TL.unlines TL.filter put get
110112
-- where put h = withRedirect h IO.stdout . TL.putStr
111113
-- get h = withRedirect h IO.stdin TL.getContents
112-
t_write_read = write_read T.unlines T.filter T.hPutStr T.hGetContents unSqrt
113-
tl_write_read = write_read TL.unlines TL.filter TL.hPutStr TL.hGetContents unSqrt
114114

115-
t_write_read_line = write_read (T.concat . take 1) T.filter T.hPutStrLn T.hGetLine (: [])
116-
tl_write_read_line = write_read (TL.concat . take 1) TL.filter TL.hPutStrLn TL.hGetLine (: [])
115+
inputOutput :: TestTree
116+
inputOutput = testGroup "input-output" [
117+
testProperty "t_write_read" $ write_read arbitrary shrink (T.replace "\n" "\r\n") T.hPutStr T.hGetContents,
118+
testProperty "tl_write_read" $ write_read arbitrary shrink (TL.replace "\n" "\r\n") TL.hPutStr TL.hGetContents,
119+
testProperty "t_write_read_line" $ write_read genTLine shrinkTLine (`T.append` "\r") T.hPutStrLn T.hGetLine,
120+
testProperty "tl_write_read_line" $ write_read genTLLine shrinkTLLine (`TL.append` "\r") TL.hPutStrLn TL.hGetLine,
121+
-- Note: Data.Text.IO.Utf8 does NO newline translation
122+
testProperty "utf8_write_read" $ write_read arbitrary shrink id TU.hPutStr TU.hGetContents,
123+
testProperty "utf8_write_read_line" $ write_read genTLine shrinkTLine id TU.hPutStrLn TU.hGetLine
124+
-- These tests are subject to I/O race conditions
125+
-- testProperty "t_put_get" t_put_get,
126+
-- testProperty "tl_put_get" tl_put_get
127+
]
128+
129+
genTLine :: Gen T.Text
130+
genTLine = T.filter (`notElem` ("\r\n" :: String)) <$> arbitrary
131+
132+
genTLLine :: Gen TL.Text
133+
genTLLine = TL.filter (`notElem` ("\r\n" :: String)) <$> arbitrary
134+
135+
shrinkTLine :: T.Text -> [T.Text]
136+
shrinkTLine = filter (T.all (/= '\n')) . shrink
117137

118-
utf8_write_read = write_read T.unlines T.filter TU.hPutStr TU.hGetContents unSqrt
119-
utf8_write_read_line = write_read (T.concat . take 1) T.filter TU.hPutStrLn TU.hGetLine (: [])
138+
shrinkTLLine :: TL.Text -> [TL.Text]
139+
shrinkTLLine = filter (TL.all (/= '\n')) . shrink
120140

121141
testLowLevel :: TestTree
122142
testLowLevel =
@@ -150,15 +170,5 @@ testLowLevel =
150170
#endif
151171
],
152172

153-
testGroup "input-output" [
154-
testGroup "t_write_read" t_write_read,
155-
testGroup "tl_write_read" tl_write_read,
156-
testGroup "t_write_read_line" t_write_read_line,
157-
testGroup "tl_write_read_line" tl_write_read_line,
158-
testGroup "utf8_write_read" utf8_write_read,
159-
testGroup "utf8_write_read_line" utf8_write_read_line
160-
-- These tests are subject to I/O race conditions
161-
-- testProperty "t_put_get" t_put_get,
162-
-- testProperty "tl_put_get" tl_put_get
163-
]
173+
inputOutput
164174
]

tests/Tests/QuickCheckUtils.hs

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,18 @@ module Tests.QuickCheckUtils
3434
) where
3535

3636
import Control.Arrow ((***))
37-
import Data.Bool (bool)
37+
import Control.Monad (when)
3838
import Data.Char (isSpace)
39+
import Data.IORef (writeIORef)
3940
import Data.Text.Foreign (I8)
4041
import Data.Text.Lazy.Builder.RealFloat (FPFormat(..))
4142
import Data.Word (Word8, Word16)
42-
import GHC.IO.Encoding.Types (TextEncoding(TextEncoding,textEncodingName))
43-
import Test.QuickCheck (Arbitrary(..), arbitraryUnicodeChar, arbitraryBoundedEnum, getUnicodeString, arbitrarySizedIntegral, shrinkIntegral, Property, ioProperty, discard, counterexample, scale, (.&&.), NonEmptyList(..), forAll, getPositive)
43+
import qualified GHC.IO.Buffer as GIO
44+
import qualified GHC.IO.Handle.Internals as GIO
45+
import qualified GHC.IO.Handle.Types as GIO
46+
import GHC.IO.Encoding.Types (TextEncoding(textEncodingName))
47+
import Test.QuickCheck (Arbitrary(..), arbitraryUnicodeChar, arbitraryBoundedEnum, getUnicodeString, arbitrarySizedIntegral, shrinkIntegral, Property, ioProperty, counterexample, scale, (.&&.), NonEmptyList(..), forAllShrink)
4448
import Test.QuickCheck.Gen (Gen, choose, chooseAny, elements, frequency, listOf, oneof, resize, sized)
45-
import Test.Tasty (TestTree, testGroup)
46-
import Test.Tasty.QuickCheck (testProperty)
4749
import Tests.Utils
4850
import qualified Data.ByteString as B
4951
import qualified Data.ByteString.Lazy as BL
@@ -248,39 +250,55 @@ instance Arbitrary IO.BufferMode where
248250
-- sometimes contain line endings.)
249251
-- * Newline translation mode.
250252
-- * Buffering.
251-
write_read :: forall a b c.
252-
(Eq a, Show a, Show c, Arbitrary c)
253-
=> ([b] -> a)
254-
-> ((Char -> Bool) -> b -> b)
253+
write_read :: forall a.
254+
(Eq a, Show a)
255+
=> Gen a
256+
-> (a -> [a])
257+
-> (a -> a) -- ^ replace '\n' with '\r\n' (for multiline tests) or append '\r' (for single-line tests)
255258
-> (IO.Handle -> a -> IO ())
256259
-> (IO.Handle -> IO a)
257-
-> (c -> [b])
258-
-> [TestTree]
259-
write_read unline filt writer reader modData
260-
= encodings <&> \enc@TextEncoding {textEncodingName} -> testGroup textEncodingName
261-
[ testProperty "NoBuffering" $ propTest enc (pure IO.NoBuffering)
262-
, testProperty "LineBuffering" $ propTest enc (pure IO.LineBuffering)
263-
, testProperty "BlockBuffering" $ propTest enc blockBuffering
264-
]
260+
-> Property
261+
write_read genTxt shrinkTxt expandNl writer reader
262+
= forAllShrink genEncoding shrinkEncoding propTest
265263
where
266-
propTest :: TextEncoding -> Gen IO.BufferMode -> IO.NewlineMode -> c -> Property
267-
propTest _ _ (IO.NewlineMode IO.LF IO.CRLF) _ = discard
268-
propTest enc genBufferMode nl d = forAll genBufferMode $ \mode -> ioProperty $ withTempFile $ \_ h -> do
269-
let ts = modData d
270-
t = unline . map (filt (not . (`elem` "\r\n"))) $ ts
271-
IO.hSetEncoding h enc
272-
IO.hSetNewlineMode h nl
273-
IO.hSetBuffering h mode
274-
() <- writer h t
275-
IO.hSeek h IO.AbsoluteSeek 0
276-
r <- reader h
277-
let isEq = r == t
278-
seq isEq $ pure $ counterexample (show r ++ bool " /= " " == " isEq ++ show t) isEq
279-
280-
encodings = [IO.utf8, IO.utf8_bom, IO.utf16, IO.utf16le, IO.utf16be, IO.utf32, IO.utf32le, IO.utf32be]
281-
282-
blockBuffering :: Gen IO.BufferMode
283-
blockBuffering = IO.BlockBuffering <$> fmap (fmap $ min 4 . getPositive) arbitrary
264+
propTest :: TextEncoding -> IO.BufferMode -> Property
265+
propTest enc mode = forAllShrink genTxt shrinkTxt $ \txt -> ioProperty $ do
266+
file <- emptyTempFile
267+
let with nl k = IO.withFile file IO.ReadWriteMode $ \h -> do
268+
IO.hSetEncoding h enc
269+
IO.hSetBuffering h mode
270+
IO.hSetNewlineMode h nl
271+
setSmallBuffer h
272+
k h
273+
-- Put a very small buffer in Handle to easily test boundary conditions in `writeBlocks`
274+
setSmallBuffer h = GIO.withHandle_ "setSmallBuffer" h $ \h_ -> do
275+
buf <- GIO.newCharBuffer 9 GIO.WriteBuffer
276+
writeIORef (GIO.haCharBuffer h_) buf
277+
readExpecting h txt' msg = do
278+
out <- reader h
279+
when (txt' /= out) $ error (show txt' ++ " /= " ++ show out ++ msg)
280+
-- 'reader' may be 'hGetContents', which closes the handle
281+
-- So we reopen a new file every time.
282+
283+
-- Test with CRLF encoding
284+
with (IO.NewlineMode IO.CRLF IO.CRLF) $ \h -> do
285+
writer h txt
286+
IO.hSeek h IO.AbsoluteSeek 0
287+
readExpecting h txt " (at location 1)"
288+
289+
-- Re-read without CRLF decoding to check that we did encode CRLF correctly
290+
with (IO.NewlineMode IO.LF IO.LF) $ \h -> do
291+
readExpecting h (expandNl txt) " (at location 2)"
292+
293+
-- Test without CRLF encoding
294+
with (IO.NewlineMode IO.LF IO.LF) $ \h -> do
295+
IO.hSetFileSize h 0
296+
writer h txt
297+
IO.hSeek h IO.AbsoluteSeek 0
298+
readExpecting h txt " (at location 3)"
299+
300+
genEncoding = elements [IO.utf8, IO.utf8_bom, IO.utf16, IO.utf16le, IO.utf16be, IO.utf32, IO.utf32le, IO.utf32be]
301+
shrinkEncoding enc = if textEncodingName enc == textEncodingName IO.utf8 then [] else [IO.utf8]
284302

285303
-- Generate various Unicode space characters with high probability
286304
arbitrarySpacyChar :: Gen Char
@@ -301,7 +319,3 @@ newtype SkewedBool = Skewed { getSkewed :: Bool }
301319

302320
instance Arbitrary SkewedBool where
303321
arbitrary = Skewed <$> frequency [(1, pure False), (5, pure True)]
304-
305-
(<&>) :: [a] -> (a -> b) -> [b]
306-
(<&>) = flip fmap
307-

tests/Tests/Utils.hs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ module Tests.Utils
66
(=^=)
77
, withRedirect
88
, withTempFile
9+
, emptyTempFile
910
) where
1011

1112
import Control.Exception (SomeException, bracket_, evaluate, try)
1213
import Control.Monad (when)
13-
import System.IO.Temp (withSystemTempFile)
14+
import System.IO.Temp (withSystemTempFile, emptySystemTempFile)
1415
import GHC.IO.Handle.Internals (withHandle)
1516
import System.IO (Handle, hFlush, hIsOpen, hIsWritable)
1617
import Test.QuickCheck (Property, ioProperty, property, (===), counterexample)
@@ -33,6 +34,9 @@ infix 4 =^=
3334
withTempFile :: (FilePath -> Handle -> IO a) -> IO a
3435
withTempFile = withSystemTempFile "crashy.txt"
3536

37+
emptyTempFile :: IO FilePath
38+
emptyTempFile = emptySystemTempFile "crashy.txt"
39+
3640
withRedirect :: Handle -> Handle -> IO a -> IO a
3741
withRedirect tmp h = bracket_ swap swap
3842
where

0 commit comments

Comments
 (0)