Skip to content

Commit 5c3af2e

Browse files
committed
Simplify prop_numOpenHandles and prop_numDirEntries
* Replace `UniqueList` by `Set` * Replace `Path` by the new `FsPathComponent`
1 parent b09100f commit 5c3af2e

File tree

2 files changed

+85
-38
lines changed

2 files changed

+85
-38
lines changed

test/Test/FS.hs

Lines changed: 18 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ module Test.FS (tests) where
66
import Control.Concurrent.Class.MonadSTM (MonadSTM (atomically))
77
import Control.Concurrent.Class.MonadSTM.Strict.TMVar
88
import Control.Monad
9+
import Control.Monad.Class.MonadThrow
910
import Control.Monad.IOSim (runSimOrThrow)
1011
import Control.Monad.ST (runST)
1112
import Data.Bit (cloneFromByteString, cloneToByteString, flipBit)
1213
import Data.ByteString (ByteString)
1314
import qualified Data.ByteString as BS
14-
import Data.Char (isAsciiLower, isAsciiUpper)
15-
import qualified Data.List as List
16-
import qualified Data.Text as Text
15+
import Data.Set (Set)
16+
import qualified Data.Set as Set
1717
import Data.Vector.Unboxed (thaw, unsafeFreeze)
1818
import GHC.Generics (Generic)
1919
import System.FS.API
@@ -49,43 +49,16 @@ tests = testGroup "Test.FS" [
4949
Simulated file system properties
5050
-------------------------------------------------------------------------------}
5151

52-
newtype Path = Path FsPath
53-
deriving stock (Show, Eq)
54-
55-
newtype UniqueList a = UniqueList [a]
56-
deriving stock Show
57-
58-
instance (Arbitrary a, Eq a) => Arbitrary (UniqueList a) where
59-
arbitrary = do
60-
xs <- arbitrary
61-
pure (UniqueList (List.nub xs))
62-
shrink (UniqueList []) = []
63-
shrink (UniqueList xs) = UniqueList . List.nub <$> shrink xs
64-
65-
instance Arbitrary Path where
66-
arbitrary = Path . mkFsPath . (:[]) <$> ((:) <$> genChar <*> listOf genChar)
67-
where
68-
genChar = elements (['A'..'Z'] ++ ['a'..'z'])
69-
shrink (Path p) = case fsPathToList p of
70-
[] -> []
71-
t:_ -> [
72-
Path p'
73-
| t' <- shrink t
74-
, let t'' = Text.filter (\c -> isAsciiUpper c || isAsciiLower c) t'
75-
, not (Text.null t'')
76-
, let p' = fsPathFromList [t']
77-
]
78-
7952
-- | Sanity check for 'propNoOpenHandles' and 'propNumOpenHandles'
80-
prop_numOpenHandles :: UniqueList Path -> Property
81-
prop_numOpenHandles (UniqueList paths) = runSimOrThrow $
53+
prop_numOpenHandles :: Set FsPathComponent -> Property
54+
prop_numOpenHandles (Set.toList -> paths) = runSimOrThrow $
8255
withSimHasFS propTrivial MockFS.empty $ \hfs fsVar -> do
8356
-- No open handles initially
8457
fs <- atomically $ readTMVar fsVar
8558
let prop = propNoOpenHandles fs
8659

8760
-- Open n handles
88-
hs <- forM paths $ \(Path p) -> hOpen hfs p (WriteMode MustBeNew)
61+
hs <- forM paths $ \(fsPathComponentFsPath -> p) -> hOpen hfs p (WriteMode MustBeNew)
8962

9063
-- Now there should be precisely n open handles
9164
fs' <- atomically $ readTMVar fsVar
@@ -103,8 +76,12 @@ prop_numOpenHandles (UniqueList paths) = runSimOrThrow $
10376
n = length paths
10477

10578
-- | Sanity check for 'propNoDirEntries' and 'propNumDirEntries'
106-
prop_numDirEntries :: Path -> InfiniteList Bool -> UniqueList Path -> Property
107-
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 $
10885
withSimHasFS propTrivial MockFS.empty $ \hfs fsVar -> do
10986
createDirectoryIfMissing hfs False dir
11087

@@ -113,17 +90,17 @@ prop_numDirEntries (Path dir) isFiles (UniqueList paths) = runSimOrThrow $
11390
let prop = propNoDirEntries dir fs
11491

11592
-- Create n entries
116-
forM_ xs $ \(isFile, Path p) ->
93+
forM_ xs $ \(isFile, fsPathComponentFsPath -> p) ->
11794
if isFile
118-
then withFile hfs (dir </> p) (WriteMode MustBeNew) $ \_ -> pure ()
95+
then createFile hfs (dir </> p)
11996
else createDirectory hfs (dir </> p)
12097

12198
-- Now there should be precisely n entries
12299
fs' <- atomically $ readTMVar fsVar
123100
let prop' = propNumDirEntries dir n fs'
124101

125102
-- Remove n entries
126-
forM_ xs $ \(isFile, Path p) ->
103+
forM_ xs $ \(isFile, fsPathComponentFsPath -> p) ->
127104
if isFile
128105
then removeFile hfs (dir </> p)
129106
else removeDirectoryRecursive hfs (dir </> p)
@@ -137,6 +114,9 @@ prop_numDirEntries (Path dir) isFiles (UniqueList paths) = runSimOrThrow $
137114
n = length paths
138115
xs = zip (getInfiniteList isFiles) paths
139116

117+
createFile :: MonadThrow m => HasFS m h -> FsPath -> m ()
118+
createFile hfs p = withFile hfs p (WriteMode MustBeNew) $ \_ -> pure ()
119+
140120
{-------------------------------------------------------------------------------
141121
Corruption
142122
-------------------------------------------------------------------------------}

test/Test/Util/FS.hs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,14 @@ module Test.Util.FS (
3131
, flipFileBit
3232
, hFlipBit
3333
-- * Arbitrary
34+
, FsPathComponent (..)
35+
, fsPathComponentFsPath
36+
, fsPathComponentString
3437
-- ** Modifiers
3538
, NoCleanupErrors (..)
39+
-- ** Orphans
40+
, isPathChar
41+
, pathChars
3642
) where
3743

3844
import Control.Concurrent.Class.MonadMVar
@@ -43,11 +49,15 @@ import Control.Monad.Class.MonadThrow (MonadCatch, MonadThrow)
4349
import Control.Monad.IOSim (runSimOrThrow)
4450
import Control.Monad.Primitive (PrimMonad)
4551
import Data.Bit (MVector (..), flipBit)
52+
import Data.Char (isAscii, isDigit, isLetter)
4653
import Data.Foldable (foldlM)
54+
import Data.List.NonEmpty (NonEmpty (..))
55+
import qualified Data.List.NonEmpty as NE
4756
import Data.Primitive.ByteArray (newPinnedByteArray, setByteArray)
4857
import Data.Primitive.Types (sizeOf)
4958
import Data.Set (Set)
5059
import qualified Data.Set as Set
60+
import qualified Data.Text as T
5161
import GHC.Stack
5262
import System.FS.API as FS
5363
import System.FS.BlockIO.API
@@ -62,6 +72,7 @@ import qualified System.FS.Sim.Stream as Stream
6272
import System.FS.Sim.Stream (InternalInfo (..), Stream (..))
6373
import System.IO.Temp
6474
import Test.QuickCheck
75+
import Test.QuickCheck.Instances ()
6576
import Text.Printf
6677

6778
{-------------------------------------------------------------------------------
@@ -342,6 +353,36 @@ hFlipBit hfs h bitOffset = do
342353
flipBit bvec i
343354
void $ hPutBufExactlyAt hfs h buf bufOff count off
344355

356+
357+
{-------------------------------------------------------------------------------
358+
Arbitrary
359+
-------------------------------------------------------------------------------}
360+
361+
--
362+
-- FsPathComponent
363+
--
364+
365+
-- | A single component in an 'FsPath'.
366+
--
367+
-- If we have a path @a/b/c/d@, then @a@, @b@ and @c@ are components, but for
368+
-- example @a/b@ is not.
369+
newtype FsPathComponent = FsPathComponent (NonEmpty Char)
370+
deriving stock (Eq, Ord)
371+
372+
instance Show FsPathComponent where
373+
show = show . fsPathComponentFsPath
374+
375+
fsPathComponentFsPath :: FsPathComponent -> FsPath
376+
fsPathComponentFsPath (FsPathComponent s) = FS.mkFsPath [NE.toList s]
377+
378+
fsPathComponentString :: FsPathComponent -> String
379+
fsPathComponentString (FsPathComponent s) = NE.toList s
380+
381+
instance Arbitrary FsPathComponent where
382+
arbitrary = resize 5 $ -- path components don't have to be very long
383+
FsPathComponent <$> liftArbitrary genPathChar
384+
shrink (FsPathComponent s) = FsPathComponent <$> liftShrink shrinkPathChar s
385+
345386
{-------------------------------------------------------------------------------
346387
Arbitrary: modifiers
347388
-------------------------------------------------------------------------------}
@@ -372,6 +413,32 @@ instance Arbitrary NoCleanupErrors where
372413
Arbitrary: orphans
373414
-------------------------------------------------------------------------------}
374415

416+
instance Arbitrary FsPath where
417+
arbitrary = scale (`div` 10) $ -- paths don't have to be very long
418+
FS.mkFsPath <$> listOf (fsPathComponentString <$> arbitrary)
419+
shrink p =
420+
let ss = T.unpack <$> fsPathToList p
421+
in FS.mkFsPath <$> shrinkList shrinkAsComponent ss
422+
where
423+
shrinkAsComponent s = fsPathComponentString <$>
424+
shrink (FsPathComponent $ NE.fromList s)
425+
426+
-- >>> all isPathChar pathChars
427+
-- True
428+
isPathChar :: Char -> Bool
429+
isPathChar c = isAscii c && (isLetter c || isDigit c)
430+
431+
-- >>> pathChars
432+
-- "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
433+
pathChars :: [Char]
434+
pathChars = concat [['a'..'z'], ['A'..'Z'], ['0'..'9']]
435+
436+
genPathChar :: Gen Char
437+
genPathChar = elements pathChars
438+
439+
shrinkPathChar :: Char -> [Char]
440+
shrinkPathChar c = [ c' | c' <- shrink c, isPathChar c']
441+
375442
instance Arbitrary OpenMode where
376443
arbitrary = genOpenMode
377444
shrink = shrinkOpenMode

0 commit comments

Comments
 (0)