@@ -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