diff --git a/src/Streamly/Internal/FileSystem/Event.hs b/src/Streamly/Internal/FileSystem/Event.hs index 4f3c19d2..033f2c31 100644 --- a/src/Streamly/Internal/FileSystem/Event.hs +++ b/src/Streamly/Internal/FileSystem/Event.hs @@ -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/ -- diff --git a/src/Streamly/Internal/FileSystem/Event/Linux.hs b/src/Streamly/Internal/FileSystem/Event/Linux.hs index b5dc4a07..a033c3bf 100644 --- a/src/Streamly/Internal/FileSystem/Event/Linux.hs +++ b/src/Streamly/Internal/FileSystem/Event/Linux.hs @@ -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 ------------------------------------------------------------------------------- diff --git a/src/Streamly/Internal/FileSystem/Event/Windows.hs b/src/Streamly/Internal/FileSystem/Event/Windows.hs index bc7fd10f..17d62b5e 100644 --- a/src/Streamly/Internal/FileSystem/Event/Windows.hs +++ b/src/Streamly/Internal/FileSystem/Event/Windows.hs @@ -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" diff --git a/test/Streamly/Test/FileSystem/Event.hs b/test/Streamly/Test/FileSystem/Event.hs index ac19d054..d74cb7a6 100644 --- a/test/Streamly/Test/FileSystem/Event.hs +++ b/test/Streamly/Test/FileSystem/Event.hs @@ -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