@@ -5,20 +5,24 @@ module Main (main) where
55import Control.Concurrent (modifyMVar_ , newMVar , threadDelay ,
66 withMVar )
77import Control.Concurrent.Async
8- import Control.Exception (SomeException (SomeException ), bracket , try )
8+ import Control.Exception (Exception (.. ),
9+ SomeException (SomeException ), bracket , try )
910import Control.Monad
1011import Control.Monad.Primitive
1112import Data.ByteString (ByteString )
1213import qualified Data.ByteString as BS
1314import qualified Data.ByteString.Char8 as BSC
15+ import qualified Data.ByteString.Lazy as LBS
1416import Data.Foldable (traverse_ )
1517import Data.Functor.Compose (Compose (Compose ))
18+ import qualified Data.List as List
1619import Data.Maybe (catMaybes )
1720import Data.Primitive.ByteArray
1821import Data.Typeable
1922import qualified Data.Vector as V
2023import qualified Data.Vector.Unboxed as VU
2124import System.FS.API
25+ import qualified System.FS.API.Lazy as FS
2226import qualified System.FS.API.Strict as FS
2327import System.FS.API.Strict (hPutAllStrict )
2428import qualified System.FS.BlockIO.API as FS
@@ -40,7 +44,18 @@ tests = testGroup "blockio:test" [
4044 , testCase " example_closeIsIdempotent" example_closeIsIdempotent
4145 , testProperty " prop_readWrite" prop_readWrite
4246 , testProperty " prop_submitToClosedCtx" prop_submitToClosedCtx
47+
48+ -- Context
49+ , testProperty " prop_submitIO_contextClosed" prop_submitIO_contextClosed
50+
51+ -- Pinned vs. unpinned buffers
52+ , testProperty " prop_submitIO_buffersPinned" prop_submitIO_buffersPinned
53+ , testProperty " prop_submitIO_buffersUnpinned" prop_submitIO_buffersUnpinned
54+
55+ -- File locks
4356 , testProperty " prop_tryLockFileExclusiveTwice" prop_tryLockFileExclusiveTwice
57+
58+ -- Storage synchronisation
4459 , testProperty " prop_synchronise" prop_synchronise
4560 , testProperty " prop_synchroniseFile_fileDoesNotExist"
4661 prop_synchroniseFile_fileDoesNotExist
@@ -118,6 +133,104 @@ prop_submitToClosedCtx bs = ioProperty $ withSystemTempDirectory "prop_a" $ \dir
118133 Left _ -> Just $ tabulate " submitIO successful" [show False ] $ counterexample " expected failure, but got success" (b === True )
119134 Right _ -> Just $ tabulate " submitIO successful" [show True ] $ counterexample " expected success, but got failure" (b === False )
120135
136+ {- ------------------------------------------------------------------------------
137+ Closed context
138+ -------------------------------------------------------------------------------}
139+
140+ -- | Test that 'submitIO' on a closed context returns a "context closed" error
141+ prop_submitIO_contextClosed :: Property
142+ prop_submitIO_contextClosed =
143+ ioProperty $
144+ withTempIOHasBlockIO " prop_submitIO_unpinnedBuffers" $ \ hfs hbio ->
145+ FS. withFile hfs path (FS. ReadWriteMode FS. MustBeNew ) $ \ h -> do
146+ void $ FS. hPutAll hfs h $ LBS. pack [1 .. 100 ]
147+ buf <- newByteArray 17
148+ let ioops = V. fromList [
149+ IOOpWrite h 0 buf 0 17
150+ , IOOpRead h 0 buf 0 17
151+ ]
152+ close hbio
153+ eith <- try @ FsError $ submitIO hbio ioops
154+ pure $ case eith of
155+ Left e
156+ | isClosedError e
157+ -> property True
158+ | otherwise
159+ -> counterexample (" Unexpected error: " <> displayException e) False
160+ Right _
161+ -> counterexample (" Unexpected success" ) False
162+ where
163+ path = FS. mkFsPath [" temp-file" ]
164+
165+ -- TODO: add a property that checks @isClosedError . mkClosedError = True@
166+ isClosedError :: FsError -> Bool
167+ isClosedError e
168+ -- TODO: add an FsResourceVanished constructor to FsErrorType?
169+ | fsErrorType e == FsOther
170+ , " HasBlockIO closed: " `List.isPrefixOf` (fsErrorString e)
171+ = True
172+ | otherwise
173+ = False
174+
175+ {- ------------------------------------------------------------------------------
176+ Pinned vs. unpinned buffers
177+ -------------------------------------------------------------------------------}
178+
179+ -- | Test that 'submitIO' using pinned buffers returns /no/ "unpinned buffers"
180+ -- error
181+ prop_submitIO_buffersPinned :: Property
182+ prop_submitIO_buffersPinned =
183+ ioProperty $
184+ withTempIOHasBlockIO " prop_submitIO_pinnedBuffers" $ \ hfs hbio ->
185+ FS. withFile hfs path (FS. ReadWriteMode FS. MustBeNew ) $ \ h -> do
186+ void $ FS. hPutAll hfs h $ LBS. pack [1 .. 100 ]
187+ buf <- newPinnedByteArray 17
188+ let ioops = V. fromList [
189+ IOOpWrite h 0 buf 0 17
190+ , IOOpRead h 0 buf 0 17
191+ ]
192+ eith <- try @ FsError $ submitIO hbio ioops
193+ pure $ case eith of
194+ Left e
195+ -> counterexample (" Unexpected error: " <> displayException e) False
196+ Right _
197+ -> property True
198+ where
199+ path = FS. mkFsPath [" temp-file" ]
200+
201+ -- | Test that 'submitIO' using unpinned buffers returns an "unpinned buffers" error
202+ prop_submitIO_buffersUnpinned :: Property
203+ prop_submitIO_buffersUnpinned =
204+ ioProperty $
205+ withTempIOHasBlockIO " prop_submitIO_unpinnedBuffers" $ \ hfs hbio ->
206+ FS. withFile hfs path (FS. ReadWriteMode FS. MustBeNew ) $ \ h -> do
207+ void $ FS. hPutAll hfs h $ LBS. pack [1 .. 100 ]
208+ buf <- newByteArray 17
209+ let ioops = V. fromList [
210+ IOOpWrite h 0 buf 0 17
211+ , IOOpRead h 0 buf 0 17
212+ ]
213+ eith <- try @ FsError $ submitIO hbio ioops
214+ pure $ case eith of
215+ Left e
216+ | isNotPinnedError e
217+ -> property True
218+ | otherwise
219+ -> counterexample (" Unexpected error: " <> displayException e) False
220+ Right _
221+ -> counterexample (" Unexpected success" ) False
222+ where
223+ path = FS. mkFsPath [" temp-file" ]
224+
225+ -- TODO: add a property that checks @isNotPinnedError . mkNotPinnedError = True@
226+ isNotPinnedError :: FsError -> Bool
227+ isNotPinnedError e
228+ | fsErrorType e == FsInvalidArgument
229+ , " MutableByteArray is unpinned: " `List.isPrefixOf` (fsErrorString e)
230+ = True
231+ | otherwise
232+ = False
233+
121234{- ------------------------------------------------------------------------------
122235 File locks
123236-------------------------------------------------------------------------------}
0 commit comments