on master help sign in
Fix docs and enable test cases for common FS Event module
commit6b0b50207b9f7c31f0e8ca26ef5e13add7e7d9de authorRanjeet Kumar Ranjan <ranjeetr@composewell.com> dateTue 7 Sep 2021, 21:34 (5 years ago) parenta9589af
4 files changed+328−64
src/Streamly/Internal/FileSystem/Event.hs+49−4 src/Streamly/Internal/FileSystem/Event/Linux.hs+51−1 src/Streamly/Internal/FileSystem/Event/Windows.hs+66−7 test/Streamly/Test/FileSystem/Event.hs+162−52
src/Streamly/Internal/FileSystem/Event.hs+49−4
@@ -104,8 +104,22 @@ import qualified Streamly.Internal.FileSystem.Event.Windows as Event
104104--
105105-- /Pre-release/
106106--
107−watch :: NonEmpty (Array Word8) -> SerialT IO Event.Event
107+watch :: NonEmpty (Array Word8) -> SerialT IO Event
108+#if defined(CABAL_OS_WINDOWS)
108109watch = Event.watch
110+#elif defined(CABAL_OS_LINUX)
111+watch = Event.watchWithFlags
112+ [ 0x00000002 -- File was modified.
113+ , 0x00000004 -- Metadata changed.
114+ , 0x00000100 -- Subfile was created.
115+ , 0x00000200 -- Subfile was deleted.
116+ , 0x00000400 -- Root was deleted.
117+ , 0x00000040 -- File was moved from X.
118+ , 0x00000080 -- File was moved to Y.
119+ ]
120+#elif defined(CABAL_OS_DARWIN)
121+watch = Event.watch
122+#endif
109123
110124-- | Like 'watch' except that if a watched path is a directory the whole
111125-- directory tree under it is watched recursively.
@@ -115,7 +129,21 @@ watch = Event.watch
115129-- /Pre-release/
116130--
117131watchRecursive :: NonEmpty (Array Word8) -> SerialT IO Event
132+#if defined(CABAL_OS_WINDOWS)
133+watchRecursive = Event.watchRecursive
134+#elif defined(CABAL_OS_LINUX)
135+watchRecursive = Event.watchRecursiveWithFlags
136+ [ 0x00000002 -- File was modified.
137+ , 0x00000004 -- Metadata changed.
138+ , 0x00000100 -- Subfile was created.
139+ , 0x00000200 -- Subfile was deleted.
140+ , 0x00000400 -- Root was deleted.
141+ , 0x00000040 -- File was moved from X.
142+ , 0x00000080 -- File was moved to Y.
143+ ]
144+#elif defined(CABAL_OS_DARWIN)
118145watchRecursive = Event.watchRecursive
146+#endif
119147
120148-------------------------------------------------------------------------------
121149-- Handling Events
@@ -134,7 +162,8 @@ getAbsPath = Event.getAbsPath
134162-- | Determine whether the event indicates creation of an object within the
135163-- monitored path. This event is generated when any file system object other
136164-- than a hard link is created.
137−--
165+-- Hard link behaviours:
166+-- On Linux and Windows hard lnik creation generates 'Created' event.
138167-- /Pre-release/
139168--
140169isCreated :: Event -> Bool
@@ -143,8 +172,20 @@ isCreated = Event.isCreated
143172-- XXX Test and document the hard link deletion on each platform.
144173--
145174-- | Determine whether the event indicates deletion of an object within the
146−-- monitored path. This is true when a file or a hardlink is deleted.
147−--
175+-- monitored path. This is true when a file or a hardlink is deleted.--
176+-- Hard link behaviours:
177+-- On Linux and Windows hard lnik deletion generates 'Deleted' event
178+-- @
179+-- Deletion of Root path:
180+--
181+-- In 'Linux' the deletion of root path only generates 'RootDeleted' event for
182+-- the root path, whereas the nested directory inside the root path gets
183+-- 'Deleted' and 'RootDeleted' events.
184+--
185+-- In Windows from GUI the deletion of root path is not allowed , from CLI the
186+-- content inside the root path is deleted and 'Deleted' event is generated
187+-- for each nested paths.There is no event generated for root path itself.
188+-- @
148189-- /Pre-release/
149190--
150191isDeleted :: Event -> Bool
@@ -156,6 +197,8 @@ isDeleted = Event.isDeleted
156197-- monitored path. This event is generated when an object is renamed within the
157198-- watched directory or if it is moved out of or in the watched directory.
158199--
200+-- Hard link behaviours:
201+-- On Linux and Windows hard lnik move generates 'MovedFrom' and ' MovedTo' events
159202-- /Pre-release/
160203--
161204isMoved :: Event -> Bool
@@ -163,6 +206,8 @@ isMoved = Event.isMoved
163206
164207-- | Determine whether the event indicates modification of an object within the
165208-- monitored path. This event is generated only for files and not directories.
209+-- Unlike Linux in Windows in recursive mode when a file is created or deleted the Modified
210+-- event is generated for the parent directory as well.
166211--
167212-- /Pre-release/
168213--
src/Streamly/Internal/FileSystem/Event/Linux.hs+51−1
@@ -66,6 +66,7 @@ module Streamly.Internal.FileSystem.Event.Linux
6666 Config (..)
6767 , Toggle (..)
6868 , defaultConfig
69+ , setRecursiveMode
6970
7071 -- ** Watch Behavior
7172 , setFollowSymLinks
@@ -80,6 +81,7 @@ module Streamly.Internal.FileSystem.Event.Linux
8081 , setRootDeleted
8182 , setRootMoved
8283 -- XXX make a setRootPathEvents to include all root events
84+ , setRootPathEvents
8385
8486 -- *** Item Level Metadata change
8587 , setMetadataChanged
@@ -103,6 +105,8 @@ module Streamly.Internal.FileSystem.Event.Linux
103105 , watch
104106 , watchRecursive
105107 , watchWith
108+ , watchWithFlags
109+ , watchRecursiveWithFlags
106110
107111 -- Low level watch APIs
108112 , addToWatch
@@ -117,6 +121,7 @@ module Streamly.Internal.FileSystem.Event.Linux
117121
118122 -- ** Root Level Events
119123 -- XXX create a isRootPathEvent similar to macOS.
124+ , isRootPathEvent
120125 , isRootUnwatched
121126 , isRootDeleted
122127 , isRootMoved
@@ -360,6 +365,15 @@ foreign import capi
360365setRootMoved :: Toggle -> Config -> Config
361366setRootMoved = setFlag iN_MOVE_SELF
362367
368+-- | Report when the watched root path itself gets deleted or renamed.
369+--
370+-- /default: On/
371+--
372+-- /Pre-release/
373+--
374+setRootPathEvents :: Toggle -> Config -> Config
375+setRootPathEvents = setFlag (iN_DELETE_SELF .|. iN_MOVE_SELF)
376+
363377foreign import capi
364378 "sys/inotify.h value IN_ATTRIB" iN_ATTRIB :: Word32
365379
@@ -904,6 +918,31 @@ watchRecursive = watchWith id
904918watch :: NonEmpty (Array Word8) -> SerialT IO Event
905919watch = watchWith (setRecursiveMode False)
906920
921+-- | In recursive mode when a nested directory is created,
922+-- 'Created' event is generated only for the top directory.
923+-- No recursive Created events are generated for nested contents.
924+-- When a nested directory is deleted 'Deleted' events are generated
925+-- for the nested content recursively.
926+-- The nested directories are added into watch list for future events.
927+-- [flags] are used to filter the events with specific event flag.
928+--
929+-- /Pre-release/
930+--
931+watchRecursiveWithFlags ::
932+ [Word32] -> NonEmpty (Array Word8) -> SerialT IO Event
933+watchRecursiveWithFlags flags =
934+ S.filter (\ev -> any (flip getFlag ev) flags) . watchRecursive
935+
936+-- | When a nested directory is created,
937+-- Created event is generated only for the top directory.
938+-- [flags] are used to filter the events with specific event flag.
939+--
940+-- /Pre-release/
941+--
942+watchWithFlags :: [Word32] -> NonEmpty (Array Word8) -> SerialT IO Event
943+watchWithFlags flags =
944+ S.filter (\ev -> any (flip getFlag ev) flags) . watch
945+
907946-------------------------------------------------------------------------------
908947-- Examine event stream
909948-------------------------------------------------------------------------------
@@ -942,7 +981,7 @@ getRelPath Event{..} = eventRelPath
942981-- /Pre-release/
943982--
944983getAbsPath :: Event -> Array Word8
945−getAbsPath ev = getRoot ev <> A.fromCString# "/"# <> getRelPath ev
984+getAbsPath ev = ensureTrailingSlash (getRoot ev) <> getRelPath ev
946985
947986-- XXX should we use a Maybe?
948987-- | Cookie is set when a rename occurs. The cookie value can be used to
@@ -1037,6 +1076,17 @@ foreign import capi
10371076isRootUnmounted :: Event -> Bool
10381077isRootUnmounted = getFlag iN_UNMOUNT
10391078
1079+-- | Determine whether the event indicates a change of path of the monitored
1080+-- object itself. Note that the object may become unreachable or deleted after
1081+-- a change of path.
1082+--
1083+-- /Occurs only for a watched path/
1084+--
1085+-- /Pre-release/
1086+--
1087+isRootPathEvent :: Event -> Bool
1088+isRootPathEvent = getFlag (iN_DELETE_SELF .|. iN_MOVE_SELF .|. iN_UNMOUNT)
1089+
10401090-------------------------------------------------------------------------------
10411091-- Metadata change Events
10421092-------------------------------------------------------------------------------
src/Streamly/Internal/FileSystem/Event/Windows.hs+66−7
@@ -64,11 +64,11 @@ module Streamly.Internal.FileSystem.Event.Windows
6464
6565 -- ** Watch APIs
6666 , watch
67− , watchTrees
68− , watchTreesWith
67+ , watchRecursive
68+ , watchWith
6969
7070 -- * Handling Events
71− , Event
71+ , Event(..)
7272 , getRoot
7373 , getRelPath
7474 , getAbsPath
@@ -76,9 +76,10 @@ module Streamly.Internal.FileSystem.Event.Windows
7676 -- ** Item CRUD events
7777 , isCreated
7878 , isDeleted
79+ , isModified
80+ , isMoved
7981 , isMovedFrom
8082 , isMovedTo
81− , isModified
8283
8384 -- ** Exception Conditions
8485 , isEventsLost
@@ -410,7 +411,7 @@ closePathHandleStream = S.mapM_ (\(h, _, _) -> closeHandle h)
410411-- path is a directory, only the files and directories directly under the
411412-- watched directory are monitored, contents of subdirectories are not
412413-- monitored. Monitoring starts from the current time onwards. The paths are
413−-- specified as UTF-8 encoded 'Array' of 'Word8'.
414+-- specified as encoded 'Array' of 'Word8'.
414415--
415416-- @
416417-- watchWith
@@ -468,11 +469,46 @@ getRoot :: Event -> Array Word8
468469getRoot Event{..} = (UTF8.toArray . UTF8.pack) eventRootPath
469470
470471getAbsPath :: Event -> Array Word8
471−getAbsPath ev = getRoot ev <> getRelPath ev
472+getAbsPath ev = getRoot ev <> A.fromCString# "\\"# <> (getRelPath ev)
472473
473474-- XXX need to document the exact semantics of these.
474475--
475476-- | File/directory created in watched directory.
477+-- ** In non-recursive mode
478+-- @
479+-- When a file or directory is created, Created and Modified events are
480+-- generated for corresponding file or directory. Parent directory will not
481+-- generate any events.
482+--
483+-- When a nested directory is created following events are generated:
484+-- Created event for the directory created.
485+-- Modified event for the directory created.
486+-- Modified event for each entry inside it.
487+-- If a nested directory contains 5 entries on top level then we will get
488+-- one Created and six Modified events for the created top directory path.
489+-- @
490+--
491+-- ** In recursive mode
492+-- @
493+-- When a file is created we get following events:
494+-- Created event for the file got created.
495+-- Modified event for the file got created.
496+-- Modified event for the parent directory.
497+-- @
498+--
499+-- @
500+-- When an empty directory is created we get following events:
501+-- Created event for the directory got created.
502+-- Modified event for the parent directory.
503+-- @
504+--
505+-- @
506+-- When a nested directory is created we get
507+-- following events:
508+-- Created and Modified events for all the files and subdirectories
509+-- recursively inside the given directory.
510+-- Modified event for the parent directory.
511+-- @
476512--
477513-- /Pre-release/
478514--
@@ -480,7 +516,13 @@ isCreated :: Event -> Bool
480516isCreated = getFlag fILE_ACTION_ADDED
481517
482518-- | File/directory deleted from watched directory.
519+-- ** In non-recursive mode:
520+-- Deleted event is generated for object deleted from watched directory.
521+-- No events are generated for nested objects.
483522--
523+-- ** In recursive mode:
524+-- If a nested directory is deleted Modified events are received for nested
525+-- sub-directories recursively. No events are generated for nested files.
484526-- /Pre-release/
485527--
486528isDeleted :: Event -> Bool
@@ -488,6 +530,7 @@ isDeleted = getFlag fILE_ACTION_REMOVED
488530
489531-- | Generated for the original path when an object is moved from under a
490532-- monitored directory.
533+-- In recursive mode Modified events is generated for parent directory.
491534--
492535-- /Pre-release/
493536--
@@ -496,17 +539,32 @@ isMovedFrom = getFlag fILE_ACTION_RENAMED_OLD_NAME
496539
497540-- | Generated for the new path when an object is moved under a monitored
498541-- directory.
542+-- In recursive mode Modified events is generated for parent directory.
499543--
500544-- /Pre-release/
501545--
502546isMovedTo :: Event -> Bool
503547isMovedTo = getFlag fILE_ACTION_RENAMED_NEW_NAME
504548
549+-- | Generated for a path that is moved from or moved to the monitored
550+-- directory.
551+--
552+-- >>> isMoved ev = isMovedFrom ev || isMovedTo ev
553+--
554+-- /Occurs only for an object inside the watched directory/
555+--
556+-- /Pre-release/
557+--
558+isMoved :: Event -> Bool
559+isMoved ev = isMovedFrom ev || isMovedTo ev
560+
505561-- XXX This event is generated only for files and not directories?
506562--
507563-- | Determine whether the event indicates modification of an object within the
508564-- monitored path.
509−--
565+-- In non-recursive mode Modified event is not generated for a directory.
566+-- In recursive mode Modified event is generated for parent directory if a file
567+-- or directory is created or renamed.
510568-- /Pre-release/
511569--
512570isModified :: Event -> Bool
@@ -531,6 +589,7 @@ showEvent ev@Event{..} =
531589 "--------------------------"
532590 ++ "\nRoot = " ++ utf8ToString (getRoot ev)
533591 ++ "\nPath = " ++ utf8ToString (getRelPath ev)
592+ ++ "\ngetAbsPath = " ++ utf8ToString (getAbsPath ev)
534593 ++ "\nFlags " ++ show eventFlags
535594 ++ showev isEventsLost "Overflow"
536595 ++ showev isCreated "Created"
test/Streamly/Test/FileSystem/Event.hs+162−52
@@ -18,6 +18,7 @@ import Data.Maybe (fromJust)
1818import Data.Word (Word8)
1919import System.Directory
2020 ( createDirectoryIfMissing
21+ , createDirectoryLink
2122 , removeFile
2223 , removeDirectory
2324 , removePathForcibly
@@ -33,6 +34,7 @@ import System.IO.Unsafe (unsafePerformIO)
3334import Streamly.Internal.Data.Array.Foreign (Array)
3435
3536import qualified Data.List.NonEmpty as NonEmpty
37+import Data.List.NonEmpty (NonEmpty)
3638import qualified Data.Set as Set
3739import qualified Streamly.Unicode.Stream as Unicode
3840import qualified Streamly.Internal.Data.Array.Foreign as Array
@@ -40,6 +42,7 @@ import qualified Streamly.Internal.Data.Fold as FL
4042import qualified Streamly.Internal.Data.Parser as PR
4143import qualified Streamly.Internal.Data.Stream.IsStream as S
4244import qualified Streamly.Internal.Unicode.Stream as U
45+import qualified Streamly.Internal.FileSystem.Event as CommonEvent
4346#if defined(CABAL_OS_DARWIN)
4447import qualified Streamly.Internal.FileSystem.Event.Darwin as Event
4548#elif defined(CABAL_OS_LINUX)
@@ -74,11 +77,7 @@ eoTask = "EOTask"
7477-- XXX Make the getRelPath type same on windows and other platforms
7578eventPredicate :: Event.Event -> Bool
7679eventPredicate ev =
77−#if defined(CABAL_OS_WINDOWS)
78− if (utf8ToString $ Event.getRelPath ev) == eoTask
79−#else
8080 if (utf8ToString $ Event.getRelPath ev) == eoTask
81−#endif
8281 then False
8382 else True
8483
@@ -122,18 +121,37 @@ showEventShort ev@Event.Event{..} =
122121#error "Unsupported OS"
123122#endif
124123
124+rootPathRemovedEventCount :: Int
125+#if defined(CABAL_OS_WINDOWS)
126+rootPathRemovedEventCount = 3
127+#else
128+rootPathRemovedEventCount = 10
129+eventListWithFixLenSymLink :: ToEventList
130+eventListWithFixLenSymLink = S.toList . S.take 1
131+#endif
132+
125133-------------------------------------------------------------------------------
126134-- Event Watcher
127135-------------------------------------------------------------------------------
128136
129−checkEvents :: FilePath -> MVar () -> [String] -> IO String
130−checkEvents rootPath m matchList = do
137+type EventChecker = FilePath -> MVar () -> [String] -> IO String
138+type EventWatch = NonEmpty (Array Word8) -> S.SerialT IO Event.Event
139+type ToEventList = S.SerialT IO Event.Event -> IO [Event.Event]
140+
141+eventListWithFixLen :: ToEventList
142+eventListWithFixLen = S.toList . S.take rootPathRemovedEventCount
143+
144+eventListWithEOtask :: ToEventList
145+eventListWithEOtask = S.parse (PR.takeWhile eventPredicate FL.toList)
146+
147+checkEvents :: ToEventList -> EventWatch -> EventChecker
148+checkEvents toEL ew rootPath m matchList = do
131149 let args = [rootPath]
132150 paths <- mapM toUtf8 args
133151 putStrLn ("Watch started !!!! on Path " ++ rootPath)
134− events <- S.parse (PR.takeWhile eventPredicate FL.toList)
152+ events <- toEL
135153 $ S.before (putMVar m ())
136− $ Event.watchTrees (NonEmpty.fromList paths)
154+ $ ew (NonEmpty.fromList paths)
137155 let eventStr = map showEventShort events
138156 let baseSet = Set.fromList matchList
139157 resultSet = Set.fromList eventStr
@@ -150,9 +168,9 @@ checkEvents rootPath m matchList = do
150168-------------------------------------------------------------------------------
151169
152170checker :: S.IsStream t =>
153− FilePath -> MVar () -> [String] -> t IO String
154−checker rootPath synch matchList =
155− S.fromEffect (checkEvents rootPath synch matchList)
171+ EventChecker -> FilePath -> MVar () -> [String] -> t IO String
172+checker ec rootPath synch matchList =
173+ S.fromEffect (ec rootPath synch matchList)
156174 `S.parallelFst`
157175 S.fromEffect timeout
158176
@@ -161,30 +179,41 @@ checker rootPath synch matchList =
161179-------------------------------------------------------------------------------
162180
163181driver ::
164− ( String
182+ EventChecker
183+ -> ( String
165184 , FilePath -> IO ()
166185 , FilePath -> IO ()
167186 , [String]
187+ , Bool
168188 )
169189 -> SpecWith ()
170−driver (desc, pre, ops, events) = it desc $ runTest `shouldReturn` "PASS"
190+driver ec (desc, pre, ops, events, sym) = it desc $ runTest `shouldReturn` "PASS"
171191
172192 where
173193
174194 runTest = do
175195 sync <- newEmptyMVar
176− withSystemTempDirectory fseventDir $ \fp -> do
177− pre fp
178− let eventStream = checker fp sync events
179− fsOps = S.fromEffect $ runFSOps fp sync
180− fmap fromJust $ S.head $ eventStream `S.parallelFst` fsOps
196+ withSystemTempDirectory fseventDir $ \fp ->
197+ if sym
198+ then do
199+ createDirectoryLink fp (fp ++ "SymLink")
200+ pre (fp ++ "SymLink")
201+ let eventStream = checker ec (fp ++ "SymLink") sync events
202+ fsOps = S.fromEffect $ runFSOps (fp ++ "SymLink") sync
203+ fmap fromJust $ S.head $ eventStream `S.parallelFst` fsOps
204+ else do
205+ pre fp
206+ let eventStream = checker ec fp sync events
207+ fsOps = S.fromEffect $ runFSOps fp sync
208+ fmap fromJust $ S.head $ eventStream `S.parallelFst` fsOps
181209
182210 runFSOps fp sync = do
183211 _ <- takeMVar sync
184212 threadDelay 200000
185213 ops fp
214+ -- 'EOTask Created dir' event gets out of order so need to wait here
186215 threadDelay 200000 -- Why this delay?
187− createDirectoryIfMissing True (fp </> "EOTask")
216+ createDirectoryIfMissing True (fp </> eoTask)
188217 threadDelay 10000000
189218 error "fs ops timed out"
190219
@@ -192,11 +221,12 @@ driver (desc, pre, ops, events) = it desc $ runTest `shouldReturn` "PASS"
192221-- Main
193222-------------------------------------------------------------------------------
194223
195−testDesc ::
224+testDesc, testDescRootDir ::
196225 [ ( String -- test description
197226 , FilePath -> IO () -- pre test operation
198227 , FilePath -> IO () -- file system actions
199− , [String]) -- expected events
228+ , [String] -- expected events
229+ , Bool ) -- SymLink
200230 ]
201231testDesc =
202232 [
@@ -206,12 +236,9 @@ testDesc =
206236#if defined(CABAL_OS_WINDOWS)
207237 , [ "dir1Single_1" ]
208238#elif defined(CABAL_OS_LINUX)
209− , [ "dir1Single_1073742080_Dir"
210− , "dir1Single_1073741856_Dir"
211− , "dir1Single_1073741825_Dir"
212− , "dir1Single_1073741840_Dir"
213− ]
239+ , ["dir1Single_1073742080_Dir"]
214240#endif
241+ , False
215242 )
216243 , ( "Remove a single directory"
217244 , \fp -> createDirectoryIfMissing True (fp </> "dir1Single")
@@ -219,10 +246,11 @@ testDesc =
219246#if defined(CABAL_OS_WINDOWS)
220247 , [ "dir1Single_2" ]
221248#elif defined(CABAL_OS_LINUX)
222− , [ "dir1Single_1024"
223− , "dir1Single_32768"
249+ , [
250+ "dir1Single_1073742336_Dir"
224251 ]
225252#endif
253+ , False
226254 )
227255 , ( "Rename a single directory"
228256 , \fp -> createDirectoryIfMissing True (fp </> "dir1Single")
@@ -239,6 +267,7 @@ testDesc =
239267 , "dir1SingleRenamed_1073741952_Dir"
240268 ]
241269#endif
270+ , False
242271 )
243272 , ( "Create a nested directory"
244273 , const (return ())
@@ -251,28 +280,9 @@ testDesc =
251280 ]
252281#elif defined(CABAL_OS_LINUX)
253282 , [ "dir1_1073742080_Dir"
254− , "dir1_1073741856_Dir"
255− , "dir1_1073741825_Dir"
256− , "dir1_1073741840_Dir"
257− ]
258−#endif
259− )
260− , ( "Remove a nested directory"
261− , \fp ->
262− createDirectoryIfMissing True (fp </> "dir1" </> "dir2" </> "dir3")
263− , \fp -> removePathForcibly (fp </> "dir1")
264−#if defined(CABAL_OS_WINDOWS)
265− , [ "dir1_3"
266− , "dir1\\dir2_3"
267− , "dir1\\dir2\\dir3_2"
268− , "dir1\\dir2_2","dir1_2"
269− ]
270−#elif defined(CABAL_OS_LINUX)
271− , [ "dir1/dir2/dir3_1073742336_Dir"
272− , "dir1/dir2_1073742336_Dir"
273− , "dir1_1073742336_Dir"
274283 ]
275284#endif
285+ , False
276286 )
277287 , ( "Rename a nested directory"
278288 , \fp -> createDirectoryIfMissing True
@@ -292,6 +302,7 @@ testDesc =
292302 , "dir1/dir2/dir3Renamed_1073741952_Dir"
293303 ]
294304#endif
305+ , False
295306 )
296307 , ( "Create a file in root Dir"
297308 , const (return ())
@@ -303,10 +314,10 @@ testDesc =
303314 ]
304315#elif defined(CABAL_OS_LINUX)
305316 , [ "FileCreated.txt_256"
306− , "FileCreated.txt_32"
307317 , "FileCreated.txt_2"
308318 ]
309319#endif
320+ , False
310321 )
311322 , ( "Remove a file in root Dir"
312323 , \fp -> writeFile (fp </> "FileCreated.txt") "Test Data"
@@ -316,6 +327,7 @@ testDesc =
316327#elif defined(CABAL_OS_LINUX)
317328 , [ "FileCreated.txt_512" ]
318329#endif
330+ , False
319331 )
320332 , ( "Rename a file in root Dir"
321333 , \fp -> writeFile (fp </> "FileCreated.txt") "Test Data"
@@ -332,6 +344,7 @@ testDesc =
332344 , "FileRenamed.txt_128"
333345 ]
334346#endif
347+ , False
335348 )
336349 , ( "Create a file in a nested Dir"
337350 , \fp ->
@@ -345,11 +358,10 @@ testDesc =
345358 ]
346359#elif defined(CABAL_OS_LINUX)
347360 , [ "dir1/dir2/dir3/FileCreated.txt_256"
348− , "dir1/dir2/dir3/FileCreated.txt_32"
349361 , "dir1/dir2/dir3/FileCreated.txt_2"
350− , "dir1/dir2/dir3/FileCreated.txt_8"
351362 ]
352363#endif
364+ , False
353365 )
354366 , ( "Remove a file in a nested Dir"
355367 , \fp ->
@@ -366,6 +378,7 @@ testDesc =
366378#elif defined(CABAL_OS_LINUX)
367379 , ["dir1/dir2/dir3/FileCreated.txt_512"]
368380#endif
381+ , False
369382 )
370383 , ( "Rename a file in a nested Dir"
371384 , \fp ->
@@ -388,13 +401,110 @@ testDesc =
388401 , "dir1/dir2/dir3/FileRenamed.txt_128"
389402 ]
390403#endif
404+ , False
405+ )
406+ , ( "Remove the nested directory"
407+ , \fp ->
408+ createDirectoryIfMissing True (fp </> "dir1" </> "dir2" </> "dir3")
409+ , \fp -> removePathForcibly (fp </> "dir1")
410+#if defined(CABAL_OS_WINDOWS)
411+ , [ "dir1_3"
412+ , "dir1\\dir2_3"
413+ , "dir1\\dir2\\dir3_2"
414+ , "dir1\\dir2_2","dir1_2"
415+ ]
416+#elif defined(CABAL_OS_LINUX)
417+ , [ "dir1_1073741828_Dir"
418+ , "dir1_1073741828_Dir"
419+ , "dir1/dir2_1073741828_Dir"
420+ , "dir1/dir2_1073741828_Dir"
421+ , "dir1/dir2/dir3_1073741828_Dir"
422+ , "dir1/dir2/dir3_1073741828_Dir"
423+ , "dir1/dir2/dir3_1024"
424+ , "dir1/dir2/dir3_1073742336_Dir"
425+ , "dir1/dir2_1024"
426+ , "dir1/dir2_1073742336_Dir"
427+ , "dir1_1024"
428+ , "dir1_1073742336_Dir"
429+ ]
430+#endif
431+ , False
391432 )
392433 ]
393434
435+testDescRootDir =
436+ [ ( "Remove the root directory"
437+ , \fp ->
438+ createDirectoryIfMissing True (fp </> "dir1" </> "dir2")
439+ , \fp -> removePathForcibly fp
440+#if defined(CABAL_OS_WINDOWS)
441+ , ["dir1_2", "dir1_3", "dir1\\dir2_2"]
442+#elif defined(CABAL_OS_LINUX)
443+ , [ "_1073741828_Dir"
444+ , "dir1_1073741828_Dir"
445+ , "dir1_1073741828_Dir"
446+ , "dir1/dir2_1073741828_Dir"
447+ , "dir1/dir2_1073741828_Dir"
448+ , "dir1/dir2_1024"
449+ , "dir1/dir2_1073742336_Dir"
450+ , "dir1_1024"
451+ , "dir1_1073742336_Dir"
452+ , "_1024"
453+ ]
454+#endif
455+ , False
456+ )
457+ ]
458+
459+#if defined(CABAL_OS_LINUX)
460+testDescRootDirSymLink ::
461+ [ ( String -- test description
462+ , FilePath -> IO () -- pre test operation
463+ , FilePath -> IO () -- file system actions
464+ , [String] -- expected events
465+ , Bool ) -- SymLink
466+ ]
467+testDescRootDirSymLink =
468+ [ ( "Remove the root directory as SymLink"
469+ , \fp ->
470+ createDirectoryIfMissing True (fp </> "dir1" </> "dir2")
471+ , \fp -> removePathForcibly fp
472+ , ["_1073741828_Dir"]
473+ , True
474+ )
475+ ]
476+#endif
477+
394478moduleName :: String
395479moduleName = "FileSystem.Event"
396480
397481main :: IO ()
398482main = do
399483 hSetBuffering stdout NoBuffering
400− hspec $ describe moduleName $ mapM_ driver testDesc
484+#if defined(CABAL_OS_LINUX)
485+ hspec
486+ $ describe moduleName
487+ $ mapM_
488+ (driver $ checkEvents eventListWithEOtask Event.watchRecursive)
489+ testDesc
490+ hspec
491+ $ describe moduleName
492+ $ mapM_
493+ (driver $ checkEvents eventListWithEOtask CommonEvent.watchRecursive)
494+ (map (\(a, b, c, d, _) -> (a, b, c, d, True)) testDesc)
495+ hspec
496+ $ describe moduleName
497+ $ mapM_
498+ (driver $ checkEvents eventListWithFixLenSymLink CommonEvent.watchRecursive)
499+ testDescRootDirSymLink
500+#endif
501+ hspec
502+ $ describe moduleName
503+ $ mapM_
504+ (driver $ checkEvents eventListWithEOtask CommonEvent.watchRecursive)
505+ testDesc
506+ hspec
507+ $ describe moduleName
508+ $ mapM_
509+ (driver $ checkEvents eventListWithFixLen CommonEvent.watchRecursive)
510+ testDescRootDir