@@ -6,12 +6,19 @@ module Test.FS (tests) where
66import Control.Concurrent.Class.MonadSTM (MonadSTM (atomically ))
77import Control.Concurrent.Class.MonadSTM.Strict.TMVar
88import Control.Monad
9+ import Control.Monad.Class.MonadThrow
910import Control.Monad.IOSim (runSimOrThrow )
10- import Data.Char (isAsciiLower , isAsciiUpper )
11- import qualified Data.List as List
12- import qualified Data.Text as Text
11+ import Control.Monad.ST (runST )
12+ import Data.Bit (cloneFromByteString , cloneToByteString , flipBit )
13+ import Data.ByteString (ByteString )
14+ import qualified Data.ByteString as BS
15+ import Data.Set (Set )
16+ import qualified Data.Set as Set
17+ import Data.Vector.Unboxed (thaw , unsafeFreeze )
1318import GHC.Generics (Generic )
1419import System.FS.API
20+ import System.FS.API.Lazy
21+ import System.FS.API.Strict
1522import System.FS.Sim.Error
1623import qualified System.FS.Sim.MockFS as MockFS
1724import qualified System.FS.Sim.Stream as S
@@ -29,6 +36,8 @@ tests = testGroup "Test.FS" [
2936 -- * Simulated file system properties
3037 testProperty " prop_numOpenHandles" prop_numOpenHandles
3138 , testProperty " prop_numDirEntries" prop_numDirEntries
39+ -- * Corruption
40+ , testProperty " prop_flipFileBit" prop_flipFileBit
3241 -- * Equality
3342 , testClassLaws " Stream" $
3443 eqLaws (Proxy @ (Stream Int ))
@@ -40,43 +49,16 @@ tests = testGroup "Test.FS" [
4049 Simulated file system properties
4150-------------------------------------------------------------------------------}
4251
43- newtype Path = Path FsPath
44- deriving stock (Show , Eq )
45-
46- newtype UniqueList a = UniqueList [a ]
47- deriving stock Show
48-
49- instance (Arbitrary a , Eq a ) => Arbitrary (UniqueList a ) where
50- arbitrary = do
51- xs <- arbitrary
52- pure (UniqueList (List. nub xs))
53- shrink (UniqueList [] ) = []
54- shrink (UniqueList xs) = UniqueList . List. nub <$> shrink xs
55-
56- instance Arbitrary Path where
57- arbitrary = Path . mkFsPath . (: [] ) <$> ((:) <$> genChar <*> listOf genChar)
58- where
59- genChar = elements ([' A' .. ' Z' ] ++ [' a' .. ' z' ])
60- shrink (Path p) = case fsPathToList p of
61- [] -> []
62- t: _ -> [
63- Path p'
64- | t' <- shrink t
65- , let t'' = Text. filter (\ c -> isAsciiUpper c || isAsciiLower c) t'
66- , not (Text. null t'')
67- , let p' = fsPathFromList [t']
68- ]
69-
7052-- | Sanity check for 'propNoOpenHandles' and 'propNumOpenHandles'
71- prop_numOpenHandles :: UniqueList Path -> Property
72- prop_numOpenHandles (UniqueList paths) = runSimOrThrow $
53+ prop_numOpenHandles :: Set FsPathComponent -> Property
54+ prop_numOpenHandles (Set. toList -> paths) = runSimOrThrow $
7355 withSimHasFS propTrivial MockFS. empty $ \ hfs fsVar -> do
7456 -- No open handles initially
7557 fs <- atomically $ readTMVar fsVar
7658 let prop = propNoOpenHandles fs
7759
7860 -- Open n handles
79- hs <- forM paths $ \ (Path p) -> hOpen hfs p (WriteMode MustBeNew )
61+ hs <- forM paths $ \ (fsPathComponentFsPath -> p) -> hOpen hfs p (WriteMode MustBeNew )
8062
8163 -- Now there should be precisely n open handles
8264 fs' <- atomically $ readTMVar fsVar
@@ -94,8 +76,12 @@ prop_numOpenHandles (UniqueList paths) = runSimOrThrow $
9476 n = length paths
9577
9678-- | Sanity check for 'propNoDirEntries' and 'propNumDirEntries'
97- prop_numDirEntries :: Path -> InfiniteList Bool -> UniqueList Path -> Property
98- prop_numDirEntries (Path dir) isFiles (UniqueList paths) = runSimOrThrow $
79+ prop_numDirEntries ::
80+ FsPathComponent
81+ -> InfiniteList Bool
82+ -> Set FsPathComponent
83+ -> Property
84+ prop_numDirEntries (fsPathComponentFsPath -> dir) isFiles (Set. toList -> paths) = runSimOrThrow $
9985 withSimHasFS propTrivial MockFS. empty $ \ hfs fsVar -> do
10086 createDirectoryIfMissing hfs False dir
10187
@@ -104,17 +90,17 @@ prop_numDirEntries (Path dir) isFiles (UniqueList paths) = runSimOrThrow $
10490 let prop = propNoDirEntries dir fs
10591
10692 -- Create n entries
107- forM_ xs $ \ (isFile, Path p) ->
93+ forM_ xs $ \ (isFile, fsPathComponentFsPath -> p) ->
10894 if isFile
109- then withFile hfs (dir </> p) ( WriteMode MustBeNew ) $ \ _ -> pure ( )
95+ then createFile hfs (dir </> p)
11096 else createDirectory hfs (dir </> p)
11197
11298 -- Now there should be precisely n entries
11399 fs' <- atomically $ readTMVar fsVar
114100 let prop' = propNumDirEntries dir n fs'
115101
116102 -- Remove n entries
117- forM_ xs $ \ (isFile, Path p) ->
103+ forM_ xs $ \ (isFile, fsPathComponentFsPath -> p) ->
118104 if isFile
119105 then removeFile hfs (dir </> p)
120106 else removeDirectoryRecursive hfs (dir </> p)
@@ -128,6 +114,50 @@ prop_numDirEntries (Path dir) isFiles (UniqueList paths) = runSimOrThrow $
128114 n = length paths
129115 xs = zip (getInfiniteList isFiles) paths
130116
117+ createFile :: MonadThrow m => HasFS m h -> FsPath -> m ()
118+ createFile hfs p = withFile hfs p (WriteMode MustBeNew ) $ \ _ -> pure ()
119+
120+ {- ------------------------------------------------------------------------------
121+ Corruption
122+ -------------------------------------------------------------------------------}
123+
124+ data WithBitOffset a = WithBitOffset Int a
125+ deriving stock Show
126+
127+ instance Arbitrary (WithBitOffset ByteString ) where
128+ arbitrary = do
129+ bs <- arbitrary `suchThat` (\ bs -> BS. length bs > 0 )
130+ bitOffset <- chooseInt (0 , BS. length bs - 1 )
131+ pure $ WithBitOffset bitOffset bs
132+ shrink (WithBitOffset bitOffset bs) =
133+ [ WithBitOffset bitOffset' bs'
134+ | bs' <- shrink bs
135+ , BS. length bs' > 0
136+ , let bitOffset' = max 0 $ min (BS. length bs' - 1 ) bitOffset
137+ ] ++ [
138+ WithBitOffset bitOffset' bs
139+ | bitOffset' <- max 0 <$> shrink bitOffset
140+ , bitOffset' >= 0
141+ ]
142+
143+ prop_flipFileBit :: WithBitOffset ByteString -> Property
144+ prop_flipFileBit (WithBitOffset bitOffset bs) =
145+ ioProperty $
146+ withSimHasFS propTrivial MockFS. empty $ \ hfs _fsVar -> do
147+ void $ withFile hfs path (WriteMode MustBeNew ) $ \ h -> hPutAllStrict hfs h bs
148+ flipFileBit hfs path bitOffset
149+ bs' <- withFile hfs path ReadMode $ \ h -> BS. toStrict <$> hGetAll hfs h
150+ pure (spec_flipFileBit bs bitOffset === bs')
151+ where
152+ path = mkFsPath [" file" ]
153+
154+ spec_flipFileBit :: ByteString -> Int -> ByteString
155+ spec_flipFileBit bs bitOffset = runST $ do
156+ mv <- thaw $ cloneFromByteString bs
157+ flipBit mv bitOffset
158+ v <- unsafeFreeze mv
159+ pure $ cloneToByteString v
160+
131161{- ------------------------------------------------------------------------------
132162 Equality
133163-------------------------------------------------------------------------------}
0 commit comments