Fix docs and enable test cases for common FS Event module

This commit is contained in:
Ranjeet Kumar Ranjan 2021-09-07 21:34:13 +05:30 committed by Harendra Kumar
parent a9589afc3a
commit 6b0b50207b
4 changed files with 327 additions and 63 deletions

View File

@ -104,8 +104,22 @@ import qualified Streamly.Internal.FileSystem.Event.Windows as Event
--
-- /Pre-release/
--
watch :: NonEmpty (Array Word8) -> SerialT IO Event.Event
watch :: NonEmpty (Array Word8) -> SerialT IO Event
#if defined(CABAL_OS_WINDOWS)
watch = Event.watch
#elif defined(CABAL_OS_LINUX)
watch = Event.watchWithFlags
[ 0x00000002 -- File was modified.
, 0x00000004 -- Metadata changed.
, 0x00000100 -- Subfile was created.
, 0x00000200 -- Subfile was deleted.
, 0x00000400 -- Root was deleted.
, 0x00000040 -- File was moved from X.
, 0x00000080 -- File was moved to Y.
]
#elif defined(CABAL_OS_DARWIN)
watch = Event.watch
#endif
-- | Like 'watch' except that if a watched path is a directory the whole
-- directory tree under it is watched recursively.
@ -115,7 +129,21 @@ watch = Event.watch
-- /Pre-release/
--
watchRecursive :: NonEmpty (Array Word8) -> SerialT IO Event
#if defined(CABAL_OS_WINDOWS)
watchRecursive = Event.watchRecursive
#elif defined(CABAL_OS_LINUX)
watchRecursive = Event.watchRecursiveWithFlags
[ 0x00000002 -- File was modified.
, 0x00000004 -- Metadata changed.
, 0x00000100 -- Subfile was created.
, 0x00000200 -- Subfile was deleted.
, 0x00000400 -- Root was deleted.
, 0x00000040 -- File was moved from X.
, 0x00000080 -- File was moved to Y.
]
#elif defined(CABAL_OS_DARWIN)
watchRecursive = Event.watchRecursive
#endif
-------------------------------------------------------------------------------
-- Handling Events
@ -134,7 +162,8 @@ getAbsPath = Event.getAbsPath
-- | Determine whether the event indicates creation of an object within the
-- monitored path. This event is generated when any file system object other
-- than a hard link is created.
--
-- Hard link behaviours:
-- On Linux and Windows hard lnik creation generates 'Created' event.
-- /Pre-release/
--
isCreated :: Event -> Bool
@ -143,8 +172,20 @@ isCreated = Event.isCreated
-- XXX Test and document the hard link deletion on each platform.
--
-- | Determine whether the event indicates deletion of an object within the
-- monitored path. This is true when a file or a hardlink is deleted.
-- monitored path. This is true when a file or a hardlink is deleted.--
-- Hard link behaviours:
-- On Linux and Windows hard lnik deletion generates 'Deleted' event
-- @
-- Deletion of Root path:
--
-- In 'Linux' the deletion of root path only generates 'RootDeleted' event for
-- the root path, whereas the nested directory inside the root path gets
-- 'Deleted' and 'RootDeleted' events.
--
-- In Windows from GUI the deletion of root path is not allowed , from CLI the
-- content inside the root path is deleted and 'Deleted' event is generated
-- for each nested paths.There is no event generated for root path itself.
-- @
-- /Pre-release/
--
isDeleted :: Event -> Bool
@ -156,6 +197,8 @@ isDeleted = Event.isDeleted
-- monitored path. This event is generated when an object is renamed within the
-- watched directory or if it is moved out of or in the watched directory.
--
-- Hard link behaviours:
-- On Linux and Windows hard lnik move generates 'MovedFrom' and ' MovedTo' events
-- /Pre-release/
--
isMoved :: Event -> Bool
@ -163,6 +206,8 @@ isMoved = Event.isMoved
-- | Determine whether the event indicates modification of an object within the
-- monitored path. This event is generated only for files and not directories.
-- Unlike Linux in Windows in recursive mode when a file is created or deleted the Modified
-- event is generated for the parent directory as well.
--
-- /Pre-release/
--

View File

@ -66,6 +66,7 @@ module Streamly.Internal.FileSystem.Event.Linux
Config (..)
, Toggle (..)
, defaultConfig
, setRecursiveMode
-- ** Watch Behavior
, setFollowSymLinks
@ -80,6 +81,7 @@ module Streamly.Internal.FileSystem.Event.Linux
, setRootDeleted
, setRootMoved
-- XXX make a setRootPathEvents to include all root events
, setRootPathEvents
-- *** Item Level Metadata change
, setMetadataChanged
@ -103,6 +105,8 @@ module Streamly.Internal.FileSystem.Event.Linux
, watch
, watchRecursive
, watchWith
, watchWithFlags
, watchRecursiveWithFlags
-- Low level watch APIs
, addToWatch
@ -117,6 +121,7 @@ module Streamly.Internal.FileSystem.Event.Linux
-- ** Root Level Events
-- XXX create a isRootPathEvent similar to macOS.
, isRootPathEvent
, isRootUnwatched
, isRootDeleted
, isRootMoved
@ -360,6 +365,15 @@ foreign import capi
setRootMoved :: Toggle -> Config -> Config
setRootMoved = setFlag iN_MOVE_SELF
-- | Report when the watched root path itself gets deleted or renamed.
--
-- /default: On/
--
-- /Pre-release/
--
setRootPathEvents :: Toggle -> Config -> Config
setRootPathEvents = setFlag (iN_DELETE_SELF .|. iN_MOVE_SELF)
foreign import capi
"sys/inotify.h value IN_ATTRIB" iN_ATTRIB :: Word32
@ -904,6 +918,31 @@ watchRecursive = watchWith id
watch :: NonEmpty (Array Word8) -> SerialT IO Event
watch = watchWith (setRecursiveMode False)
-- | In recursive mode when a nested directory is created,
-- 'Created' event is generated only for the top directory.
-- No recursive Created events are generated for nested contents.
-- When a nested directory is deleted 'Deleted' events are generated
-- for the nested content recursively.
-- The nested directories are added into watch list for future events.
-- [flags] are used to filter the events with specific event flag.
--
-- /Pre-release/
--
watchRecursiveWithFlags ::
[Word32] -> NonEmpty (Array Word8) -> SerialT IO Event
watchRecursiveWithFlags flags =
S.filter (\ev -> any (flip getFlag ev) flags) . watchRecursive
-- | When a nested directory is created,
-- Created event is generated only for the top directory.
-- [flags] are used to filter the events with specific event flag.
--
-- /Pre-release/
--
watchWithFlags :: [Word32] -> NonEmpty (Array Word8) -> SerialT IO Event
watchWithFlags flags =
S.filter (\ev -> any (flip getFlag ev) flags) . watch
-------------------------------------------------------------------------------
-- Examine event stream
-------------------------------------------------------------------------------
@ -942,7 +981,7 @@ getRelPath Event{..} = eventRelPath
-- /Pre-release/
--
getAbsPath :: Event -> Array Word8
getAbsPath ev = getRoot ev <> A.fromCString# "/"# <> getRelPath ev
getAbsPath ev = ensureTrailingSlash (getRoot ev) <> getRelPath ev
-- XXX should we use a Maybe?
-- | Cookie is set when a rename occurs. The cookie value can be used to
@ -1037,6 +1076,17 @@ foreign import capi
isRootUnmounted :: Event -> Bool
isRootUnmounted = getFlag iN_UNMOUNT
-- | Determine whether the event indicates a change of path of the monitored
-- object itself. Note that the object may become unreachable or deleted after
-- a change of path.
--
-- /Occurs only for a watched path/
--
-- /Pre-release/
--
isRootPathEvent :: Event -> Bool
isRootPathEvent = getFlag (iN_DELETE_SELF .|. iN_MOVE_SELF .|. iN_UNMOUNT)
-------------------------------------------------------------------------------
-- Metadata change Events
-------------------------------------------------------------------------------

View File

@ -64,11 +64,11 @@ module Streamly.Internal.FileSystem.Event.Windows
-- ** Watch APIs
, watch
, watchTrees
, watchTreesWith
, watchRecursive
, watchWith
-- * Handling Events
, Event
, Event(..)
, getRoot
, getRelPath
, getAbsPath
@ -76,9 +76,10 @@ module Streamly.Internal.FileSystem.Event.Windows
-- ** Item CRUD events
, isCreated
, isDeleted
, isModified
, isMoved
, isMovedFrom
, isMovedTo
, isModified
-- ** Exception Conditions
, isEventsLost
@ -410,7 +411,7 @@ closePathHandleStream = S.mapM_ (\(h, _, _) -> closeHandle h)
-- path is a directory, only the files and directories directly under the
-- watched directory are monitored, contents of subdirectories are not
-- monitored. Monitoring starts from the current time onwards. The paths are
-- specified as UTF-8 encoded 'Array' of 'Word8'.
-- specified as encoded 'Array' of 'Word8'.
--
-- @
-- watchWith
@ -468,11 +469,46 @@ getRoot :: Event -> Array Word8
getRoot Event{..} = (UTF8.toArray . UTF8.pack) eventRootPath
getAbsPath :: Event -> Array Word8
getAbsPath ev = getRoot ev <> getRelPath ev
getAbsPath ev = getRoot ev <> A.fromCString# "\\"# <> (getRelPath ev)
-- XXX need to document the exact semantics of these.
--
-- | File/directory created in watched directory.
-- ** In non-recursive mode
-- @
-- When a file or directory is created, Created and Modified events are
-- generated for corresponding file or directory. Parent directory will not
-- generate any events.
--
-- When a nested directory is created following events are generated:
-- Created event for the directory created.
-- Modified event for the directory created.
-- Modified event for each entry inside it.
-- If a nested directory contains 5 entries on top level then we will get
-- one Created and six Modified events for the created top directory path.
-- @
--
-- ** In recursive mode
-- @
-- When a file is created we get following events:
-- Created event for the file got created.
-- Modified event for the file got created.
-- Modified event for the parent directory.
-- @
--
-- @
-- When an empty directory is created we get following events:
-- Created event for the directory got created.
-- Modified event for the parent directory.
-- @
--
-- @
-- When a nested directory is created we get
-- following events:
-- Created and Modified events for all the files and subdirectories
-- recursively inside the given directory.
-- Modified event for the parent directory.
-- @
--
-- /Pre-release/
--
@ -480,7 +516,13 @@ isCreated :: Event -> Bool
isCreated = getFlag fILE_ACTION_ADDED
-- | File/directory deleted from watched directory.
-- ** In non-recursive mode:
-- Deleted event is generated for object deleted from watched directory.
-- No events are generated for nested objects.
--
-- ** In recursive mode:
-- If a nested directory is deleted Modified events are received for nested
-- sub-directories recursively. No events are generated for nested files.
-- /Pre-release/
--
isDeleted :: Event -> Bool
@ -488,6 +530,7 @@ isDeleted = getFlag fILE_ACTION_REMOVED
-- | Generated for the original path when an object is moved from under a
-- monitored directory.
-- In recursive mode Modified events is generated for parent directory.
--
-- /Pre-release/
--
@ -496,17 +539,32 @@ isMovedFrom = getFlag fILE_ACTION_RENAMED_OLD_NAME
-- | Generated for the new path when an object is moved under a monitored
-- directory.
-- In recursive mode Modified events is generated for parent directory.
--
-- /Pre-release/
--
isMovedTo :: Event -> Bool
isMovedTo = getFlag fILE_ACTION_RENAMED_NEW_NAME
-- | Generated for a path that is moved from or moved to the monitored
-- directory.
--
-- >>> isMoved ev = isMovedFrom ev || isMovedTo ev
--
-- /Occurs only for an object inside the watched directory/
--
-- /Pre-release/
--
isMoved :: Event -> Bool
isMoved ev = isMovedFrom ev || isMovedTo ev
-- XXX This event is generated only for files and not directories?
--
-- | Determine whether the event indicates modification of an object within the
-- monitored path.
--
-- In non-recursive mode Modified event is not generated for a directory.
-- In recursive mode Modified event is generated for parent directory if a file
-- or directory is created or renamed.
-- /Pre-release/
--
isModified :: Event -> Bool
@ -531,6 +589,7 @@ showEvent ev@Event{..} =
"--------------------------"
++ "\nRoot = " ++ utf8ToString (getRoot ev)
++ "\nPath = " ++ utf8ToString (getRelPath ev)
++ "\ngetAbsPath = " ++ utf8ToString (getAbsPath ev)
++ "\nFlags " ++ show eventFlags
++ showev isEventsLost "Overflow"
++ showev isCreated "Created"

View File

@ -18,6 +18,7 @@ import Data.Maybe (fromJust)
import Data.Word (Word8)
import System.Directory
( createDirectoryIfMissing
, createDirectoryLink
, removeFile
, removeDirectory
, removePathForcibly
@ -33,6 +34,7 @@ import System.IO.Unsafe (unsafePerformIO)
import Streamly.Internal.Data.Array.Foreign (Array)
import qualified Data.List.NonEmpty as NonEmpty
import Data.List.NonEmpty (NonEmpty)
import qualified Data.Set as Set
import qualified Streamly.Unicode.Stream as Unicode
import qualified Streamly.Internal.Data.Array.Foreign as Array
@ -40,6 +42,7 @@ import qualified Streamly.Internal.Data.Fold as FL
import qualified Streamly.Internal.Data.Parser as PR
import qualified Streamly.Internal.Data.Stream.IsStream as S
import qualified Streamly.Internal.Unicode.Stream as U
import qualified Streamly.Internal.FileSystem.Event as CommonEvent
#if defined(CABAL_OS_DARWIN)
import qualified Streamly.Internal.FileSystem.Event.Darwin as Event
#elif defined(CABAL_OS_LINUX)
@ -74,11 +77,7 @@ eoTask = "EOTask"
-- XXX Make the getRelPath type same on windows and other platforms
eventPredicate :: Event.Event -> Bool
eventPredicate ev =
#if defined(CABAL_OS_WINDOWS)
if (utf8ToString $ Event.getRelPath ev) == eoTask
#else
if (utf8ToString $ Event.getRelPath ev) == eoTask
#endif
then False
else True
@ -122,18 +121,37 @@ showEventShort ev@Event.Event{..} =
#error "Unsupported OS"
#endif
rootPathRemovedEventCount :: Int
#if defined(CABAL_OS_WINDOWS)
rootPathRemovedEventCount = 3
#else
rootPathRemovedEventCount = 10
eventListWithFixLenSymLink :: ToEventList
eventListWithFixLenSymLink = S.toList . S.take 1
#endif
-------------------------------------------------------------------------------
-- Event Watcher
-------------------------------------------------------------------------------
checkEvents :: FilePath -> MVar () -> [String] -> IO String
checkEvents rootPath m matchList = do
type EventChecker = FilePath -> MVar () -> [String] -> IO String
type EventWatch = NonEmpty (Array Word8) -> S.SerialT IO Event.Event
type ToEventList = S.SerialT IO Event.Event -> IO [Event.Event]
eventListWithFixLen :: ToEventList
eventListWithFixLen = S.toList . S.take rootPathRemovedEventCount
eventListWithEOtask :: ToEventList
eventListWithEOtask = S.parse (PR.takeWhile eventPredicate FL.toList)
checkEvents :: ToEventList -> EventWatch -> EventChecker
checkEvents toEL ew rootPath m matchList = do
let args = [rootPath]
paths <- mapM toUtf8 args
putStrLn ("Watch started !!!! on Path " ++ rootPath)
events <- S.parse (PR.takeWhile eventPredicate FL.toList)
events <- toEL
$ S.before (putMVar m ())
$ Event.watchTrees (NonEmpty.fromList paths)
$ ew (NonEmpty.fromList paths)
let eventStr = map showEventShort events
let baseSet = Set.fromList matchList
resultSet = Set.fromList eventStr
@ -150,9 +168,9 @@ checkEvents rootPath m matchList = do
-------------------------------------------------------------------------------
checker :: S.IsStream t =>
FilePath -> MVar () -> [String] -> t IO String
checker rootPath synch matchList =
S.fromEffect (checkEvents rootPath synch matchList)
EventChecker -> FilePath -> MVar () -> [String] -> t IO String
checker ec rootPath synch matchList =
S.fromEffect (ec rootPath synch matchList)
`S.parallelFst`
S.fromEffect timeout
@ -161,30 +179,41 @@ checker rootPath synch matchList =
-------------------------------------------------------------------------------
driver ::
( String
EventChecker
-> ( String
, FilePath -> IO ()
, FilePath -> IO ()
, [String]
, Bool
)
-> SpecWith ()
driver (desc, pre, ops, events) = it desc $ runTest `shouldReturn` "PASS"
driver ec (desc, pre, ops, events, sym) = it desc $ runTest `shouldReturn` "PASS"
where
runTest = do
sync <- newEmptyMVar
withSystemTempDirectory fseventDir $ \fp -> do
pre fp
let eventStream = checker fp sync events
fsOps = S.fromEffect $ runFSOps fp sync
fmap fromJust $ S.head $ eventStream `S.parallelFst` fsOps
withSystemTempDirectory fseventDir $ \fp ->
if sym
then do
createDirectoryLink fp (fp ++ "SymLink")
pre (fp ++ "SymLink")
let eventStream = checker ec (fp ++ "SymLink") sync events
fsOps = S.fromEffect $ runFSOps (fp ++ "SymLink") sync
fmap fromJust $ S.head $ eventStream `S.parallelFst` fsOps
else do
pre fp
let eventStream = checker ec fp sync events
fsOps = S.fromEffect $ runFSOps fp sync
fmap fromJust $ S.head $ eventStream `S.parallelFst` fsOps
runFSOps fp sync = do
_ <- takeMVar sync
threadDelay 200000
ops fp
-- 'EOTask Created dir' event gets out of order so need to wait here
threadDelay 200000 -- Why this delay?
createDirectoryIfMissing True (fp </> "EOTask")
createDirectoryIfMissing True (fp </> eoTask)
threadDelay 10000000
error "fs ops timed out"
@ -192,11 +221,12 @@ driver (desc, pre, ops, events) = it desc $ runTest `shouldReturn` "PASS"
-- Main
-------------------------------------------------------------------------------
testDesc ::
testDesc, testDescRootDir ::
[ ( String -- test description
, FilePath -> IO () -- pre test operation
, FilePath -> IO () -- file system actions
, [String]) -- expected events
, [String] -- expected events
, Bool ) -- SymLink
]
testDesc =
[
@ -206,12 +236,9 @@ testDesc =
#if defined(CABAL_OS_WINDOWS)
, [ "dir1Single_1" ]
#elif defined(CABAL_OS_LINUX)
, [ "dir1Single_1073742080_Dir"
, "dir1Single_1073741856_Dir"
, "dir1Single_1073741825_Dir"
, "dir1Single_1073741840_Dir"
]
, ["dir1Single_1073742080_Dir"]
#endif
, False
)
, ( "Remove a single directory"
, \fp -> createDirectoryIfMissing True (fp </> "dir1Single")
@ -219,10 +246,11 @@ testDesc =
#if defined(CABAL_OS_WINDOWS)
, [ "dir1Single_2" ]
#elif defined(CABAL_OS_LINUX)
, [ "dir1Single_1024"
, "dir1Single_32768"
, [
"dir1Single_1073742336_Dir"
]
#endif
, False
)
, ( "Rename a single directory"
, \fp -> createDirectoryIfMissing True (fp </> "dir1Single")
@ -239,6 +267,7 @@ testDesc =
, "dir1SingleRenamed_1073741952_Dir"
]
#endif
, False
)
, ( "Create a nested directory"
, const (return ())
@ -251,28 +280,9 @@ testDesc =
]
#elif defined(CABAL_OS_LINUX)
, [ "dir1_1073742080_Dir"
, "dir1_1073741856_Dir"
, "dir1_1073741825_Dir"
, "dir1_1073741840_Dir"
]
#endif
)
, ( "Remove a nested directory"
, \fp ->
createDirectoryIfMissing True (fp </> "dir1" </> "dir2" </> "dir3")
, \fp -> removePathForcibly (fp </> "dir1")
#if defined(CABAL_OS_WINDOWS)
, [ "dir1_3"
, "dir1\\dir2_3"
, "dir1\\dir2\\dir3_2"
, "dir1\\dir2_2","dir1_2"
]
#elif defined(CABAL_OS_LINUX)
, [ "dir1/dir2/dir3_1073742336_Dir"
, "dir1/dir2_1073742336_Dir"
, "dir1_1073742336_Dir"
]
#endif
, False
)
, ( "Rename a nested directory"
, \fp -> createDirectoryIfMissing True
@ -292,6 +302,7 @@ testDesc =
, "dir1/dir2/dir3Renamed_1073741952_Dir"
]
#endif
, False
)
, ( "Create a file in root Dir"
, const (return ())
@ -303,10 +314,10 @@ testDesc =
]
#elif defined(CABAL_OS_LINUX)
, [ "FileCreated.txt_256"
, "FileCreated.txt_32"
, "FileCreated.txt_2"
]
#endif
, False
)
, ( "Remove a file in root Dir"
, \fp -> writeFile (fp </> "FileCreated.txt") "Test Data"
@ -316,6 +327,7 @@ testDesc =
#elif defined(CABAL_OS_LINUX)
, [ "FileCreated.txt_512" ]
#endif
, False
)
, ( "Rename a file in root Dir"
, \fp -> writeFile (fp </> "FileCreated.txt") "Test Data"
@ -332,6 +344,7 @@ testDesc =
, "FileRenamed.txt_128"
]
#endif
, False
)
, ( "Create a file in a nested Dir"
, \fp ->
@ -345,11 +358,10 @@ testDesc =
]
#elif defined(CABAL_OS_LINUX)
, [ "dir1/dir2/dir3/FileCreated.txt_256"
, "dir1/dir2/dir3/FileCreated.txt_32"
, "dir1/dir2/dir3/FileCreated.txt_2"
, "dir1/dir2/dir3/FileCreated.txt_8"
]
#endif
, False
)
, ( "Remove a file in a nested Dir"
, \fp ->
@ -366,6 +378,7 @@ testDesc =
#elif defined(CABAL_OS_LINUX)
, ["dir1/dir2/dir3/FileCreated.txt_512"]
#endif
, False
)
, ( "Rename a file in a nested Dir"
, \fp ->
@ -388,13 +401,110 @@ testDesc =
, "dir1/dir2/dir3/FileRenamed.txt_128"
]
#endif
, False
)
, ( "Remove the nested directory"
, \fp ->
createDirectoryIfMissing True (fp </> "dir1" </> "dir2" </> "dir3")
, \fp -> removePathForcibly (fp </> "dir1")
#if defined(CABAL_OS_WINDOWS)
, [ "dir1_3"
, "dir1\\dir2_3"
, "dir1\\dir2\\dir3_2"
, "dir1\\dir2_2","dir1_2"
]
#elif defined(CABAL_OS_LINUX)
, [ "dir1_1073741828_Dir"
, "dir1_1073741828_Dir"
, "dir1/dir2_1073741828_Dir"
, "dir1/dir2_1073741828_Dir"
, "dir1/dir2/dir3_1073741828_Dir"
, "dir1/dir2/dir3_1073741828_Dir"
, "dir1/dir2/dir3_1024"
, "dir1/dir2/dir3_1073742336_Dir"
, "dir1/dir2_1024"
, "dir1/dir2_1073742336_Dir"
, "dir1_1024"
, "dir1_1073742336_Dir"
]
#endif
, False
)
]
testDescRootDir =
[ ( "Remove the root directory"
, \fp ->
createDirectoryIfMissing True (fp </> "dir1" </> "dir2")
, \fp -> removePathForcibly fp
#if defined(CABAL_OS_WINDOWS)
, ["dir1_2", "dir1_3", "dir1\\dir2_2"]
#elif defined(CABAL_OS_LINUX)
, [ "_1073741828_Dir"
, "dir1_1073741828_Dir"
, "dir1_1073741828_Dir"
, "dir1/dir2_1073741828_Dir"
, "dir1/dir2_1073741828_Dir"
, "dir1/dir2_1024"
, "dir1/dir2_1073742336_Dir"
, "dir1_1024"
, "dir1_1073742336_Dir"
, "_1024"
]
#endif
, False
)
]
#if defined(CABAL_OS_LINUX)
testDescRootDirSymLink ::
[ ( String -- test description
, FilePath -> IO () -- pre test operation
, FilePath -> IO () -- file system actions
, [String] -- expected events
, Bool ) -- SymLink
]
testDescRootDirSymLink =
[ ( "Remove the root directory as SymLink"
, \fp ->
createDirectoryIfMissing True (fp </> "dir1" </> "dir2")
, \fp -> removePathForcibly fp
, ["_1073741828_Dir"]
, True
)
]
#endif
moduleName :: String
moduleName = "FileSystem.Event"
main :: IO ()
main = do
hSetBuffering stdout NoBuffering
hspec $ describe moduleName $ mapM_ driver testDesc
#if defined(CABAL_OS_LINUX)
hspec
$ describe moduleName
$ mapM_
(driver $ checkEvents eventListWithEOtask Event.watchRecursive)
testDesc
hspec
$ describe moduleName
$ mapM_
(driver $ checkEvents eventListWithEOtask CommonEvent.watchRecursive)
(map (\(a, b, c, d, _) -> (a, b, c, d, True)) testDesc)
hspec
$ describe moduleName
$ mapM_
(driver $ checkEvents eventListWithFixLenSymLink CommonEvent.watchRecursive)
testDescRootDirSymLink
#endif
hspec
$ describe moduleName
$ mapM_
(driver $ checkEvents eventListWithEOtask CommonEvent.watchRecursive)
testDesc
hspec
$ describe moduleName
$ mapM_
(driver $ checkEvents eventListWithFixLen CommonEvent.watchRecursive)
testDescRootDir