@@ -18,6 +18,7 @@ import Data.Maybe (fromJust)
18import Data.Word (Word8)
18import Data.Word (Word8)
19import System.Directory
19import System.Directory
20 ( createDirectoryIfMissing
20 ( createDirectoryIfMissing
21+ , createDirectoryLink
21 , removeFile
22 , removeFile
22 , removeDirectory
23 , removeDirectory
23 , removePathForcibly
24 , removePathForcibly
@@ -33,6 +34,7 @@ import System.IO.Unsafe (unsafePerformIO)
33import Streamly.Internal.Data.Array.Foreign (Array)
34import Streamly.Internal.Data.Array.Foreign (Array)
35import qualified Data.List.NonEmpty as NonEmpty
36import qualified Data.List.NonEmpty as NonEmpty
37+import Data.List.NonEmpty (NonEmpty)
36import qualified Data.Set as Set
38import qualified Data.Set as Set
37import qualified Streamly.Unicode.Stream as Unicode
39import qualified Streamly.Unicode.Stream as Unicode
38import qualified Streamly.Internal.Data.Array.Foreign as Array
40import qualified Streamly.Internal.Data.Array.Foreign as Array
@@ -40,6 +42,7 @@ import qualified Streamly.Internal.Data.Fold as FL
40import qualified Streamly.Internal.Data.Parser as PR
42import qualified Streamly.Internal.Data.Parser as PR
41import qualified Streamly.Internal.Data.Stream.IsStream as S
43import qualified Streamly.Internal.Data.Stream.IsStream as S
42import qualified Streamly.Internal.Unicode.Stream as U
44import qualified Streamly.Internal.Unicode.Stream as U
45+import qualified Streamly.Internal.FileSystem.Event as CommonEvent
43#if defined(CABAL_OS_DARWIN)
46#if defined(CABAL_OS_DARWIN)
44import qualified Streamly.Internal.FileSystem.Event.Darwin as Event
47import qualified Streamly.Internal.FileSystem.Event.Darwin as Event
45#elif defined(CABAL_OS_LINUX)
48#elif defined(CABAL_OS_LINUX)
@@ -74,11 +77,7 @@ eoTask = "EOTask"
74-- XXX Make the getRelPath type same on windows and other platforms
77-- XXX Make the getRelPath type same on windows and other platforms
75eventPredicate :: Event.Event -> Bool
78eventPredicate :: Event.Event -> Bool
76eventPredicate ev =
79eventPredicate ev =
77−#if defined(CABAL_OS_WINDOWS)
78− if (utf8ToString $ Event.getRelPath ev) == eoTask
80 if (utf8ToString $ Event.getRelPath ev) == eoTask
80 if (utf8ToString $ Event.getRelPath ev) == eoTask
82 then False
81 then False
@@ -122,18 +121,37 @@ showEventShort ev@Event.Event{..} =
122#error "Unsupported OS"
121#error "Unsupported OS"
124+rootPathRemovedEventCount :: Int
125+#if defined(CABAL_OS_WINDOWS)
126+rootPathRemovedEventCount = 3
128+rootPathRemovedEventCount = 10
129+eventListWithFixLenSymLink :: ToEventList
130+eventListWithFixLenSymLink = S.toList . S.take 1
125-------------------------------------------------------------------------------
133-------------------------------------------------------------------------------
126-- Event Watcher
134-- Event Watcher
127-------------------------------------------------------------------------------
135-------------------------------------------------------------------------------
129−checkEvents :: FilePath -> MVar () -> [String] -> IO String
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]
141+eventListWithFixLen :: ToEventList
142+eventListWithFixLen = S.toList . S.take rootPathRemovedEventCount
144+eventListWithEOtask :: ToEventList
145+eventListWithEOtask = S.parse (PR.takeWhile eventPredicate FL.toList)
147+checkEvents :: ToEventList -> EventWatch -> EventChecker
130−checkEvents rootPath m matchList = do
148+checkEvents toEL ew rootPath m matchList = do
131 let args = [rootPath]
149 let args = [rootPath]
132 paths <- mapM toUtf8 args
150 paths <- mapM toUtf8 args
133 putStrLn ("Watch started !!!! on Path " ++ rootPath)
151 putStrLn ("Watch started !!!! on Path " ++ rootPath)
134− events <- S.parse (PR.takeWhile eventPredicate FL.toList)
152+ events <- toEL
135 $ S.before (putMVar m ())
153 $ S.before (putMVar m ())
136− $ Event.watchTrees (NonEmpty.fromList paths)
154+ $ ew (NonEmpty.fromList paths)
137 let eventStr = map showEventShort events
155 let eventStr = map showEventShort events
138 let baseSet = Set.fromList matchList
156 let baseSet = Set.fromList matchList
139 resultSet = Set.fromList eventStr
157 resultSet = Set.fromList eventStr
@@ -150,9 +168,9 @@ checkEvents rootPath m matchList = do
150-------------------------------------------------------------------------------
168-------------------------------------------------------------------------------
152checker :: S.IsStream t =>
170checker :: S.IsStream t =>
153− FilePath -> MVar () -> [String] -> t IO String
171+ EventChecker -> FilePath -> MVar () -> [String] -> t IO String
154−checker rootPath synch matchList =
172+checker ec rootPath synch matchList =
155− S.fromEffect (checkEvents rootPath synch matchList)
173+ S.fromEffect (ec rootPath synch matchList)
156 `S.parallelFst`
174 `S.parallelFst`
157 S.fromEffect timeout
175 S.fromEffect timeout
@@ -161,30 +179,41 @@ checker rootPath synch matchList =
161-------------------------------------------------------------------------------
179-------------------------------------------------------------------------------
164− ( String
183+ -> ( String
165 , FilePath -> IO ()
184 , FilePath -> IO ()
166 , FilePath -> IO ()
185 , FilePath -> IO ()
167 , [String]
186 , [String]
169 -> SpecWith ()
189 -> 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"
174 runTest = do
194 runTest = do
175 sync <- newEmptyMVar
195 sync <- newEmptyMVar
176− withSystemTempDirectory fseventDir $ \fp -> do
196+ withSystemTempDirectory fseventDir $ \fp ->
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
178− let eventStream = checker fp sync events
206+ let eventStream = checker ec fp sync events
179− fsOps = S.fromEffect $ runFSOps fp sync
207+ fsOps = S.fromEffect $ runFSOps fp sync
180− fmap fromJust $ S.head $ eventStream `S.parallelFst` fsOps
208+ fmap fromJust $ S.head $ eventStream `S.parallelFst` fsOps
182 runFSOps fp sync = do
210 runFSOps fp sync = do
183 _ <- takeMVar sync
211 _ <- takeMVar sync
184 threadDelay 200000
212 threadDelay 200000
214+ -- 'EOTask Created dir' event gets out of order so need to wait here
186 threadDelay 200000 -- Why this delay?
215 threadDelay 200000 -- Why this delay?
187− createDirectoryIfMissing True (fp </> "EOTask")
216+ createDirectoryIfMissing True (fp </> eoTask)
188 threadDelay 10000000
217 threadDelay 10000000
189 error "fs ops timed out"
218 error "fs ops timed out"
@@ -192,11 +221,12 @@ driver (desc, pre, ops, events) = it desc $ runTest `shouldReturn` "PASS"
193-------------------------------------------------------------------------------
222-------------------------------------------------------------------------------
195−testDesc ::
224+testDesc, testDescRootDir ::
196 [ ( String -- test description
225 [ ( String -- test description
197 , FilePath -> IO () -- pre test operation
226 , FilePath -> IO () -- pre test operation
198 , FilePath -> IO () -- file system actions
227 , FilePath -> IO () -- file system actions
199− , [String]) -- expected events
228+ , [String] -- expected events
201testDesc =
231testDesc =
@@ -206,12 +236,9 @@ testDesc =
206#if defined(CABAL_OS_WINDOWS)
236#if defined(CABAL_OS_WINDOWS)
207 , [ "dir1Single_1" ]
237 , [ "dir1Single_1" ]
208#elif defined(CABAL_OS_LINUX)
238#elif defined(CABAL_OS_LINUX)
209− , [ "dir1Single_1073742080_Dir"
239+ , ["dir1Single_1073742080_Dir"]
210− , "dir1Single_1073741856_Dir"
211− , "dir1Single_1073741825_Dir"
212− , "dir1Single_1073741840_Dir"
216 , ( "Remove a single directory"
243 , ( "Remove a single directory"
217 , \fp -> createDirectoryIfMissing True (fp </> "dir1Single")
244 , \fp -> createDirectoryIfMissing True (fp </> "dir1Single")
@@ -219,10 +246,11 @@ testDesc =
219#if defined(CABAL_OS_WINDOWS)
246#if defined(CABAL_OS_WINDOWS)
220 , [ "dir1Single_2" ]
247 , [ "dir1Single_2" ]
221#elif defined(CABAL_OS_LINUX)
248#elif defined(CABAL_OS_LINUX)
222− , [ "dir1Single_1024"
249+ , [
223− , "dir1Single_32768"
250+ "dir1Single_1073742336_Dir"
227 , ( "Rename a single directory"
255 , ( "Rename a single directory"
228 , \fp -> createDirectoryIfMissing True (fp </> "dir1Single")
256 , \fp -> createDirectoryIfMissing True (fp </> "dir1Single")
@@ -239,6 +267,7 @@ testDesc =
239 , "dir1SingleRenamed_1073741952_Dir"
267 , "dir1SingleRenamed_1073741952_Dir"
243 , ( "Create a nested directory"
272 , ( "Create a nested directory"
244 , const (return ())
273 , const (return ())
@@ -251,28 +280,9 @@ testDesc =
252#elif defined(CABAL_OS_LINUX)
281#elif defined(CABAL_OS_LINUX)
253 , [ "dir1_1073742080_Dir"
282 , [ "dir1_1073742080_Dir"
254− , "dir1_1073741856_Dir"
255− , "dir1_1073741825_Dir"
256− , "dir1_1073741840_Dir"
260− , ( "Remove a nested directory"
262− createDirectoryIfMissing True (fp </> "dir1" </> "dir2" </> "dir3")
263− , \fp -> removePathForcibly (fp </> "dir1")
264−#if defined(CABAL_OS_WINDOWS)
267− , "dir1\\dir2\\dir3_2"
268− , "dir1\\dir2_2","dir1_2"
270−#elif defined(CABAL_OS_LINUX)
271− , [ "dir1/dir2/dir3_1073742336_Dir"
272− , "dir1/dir2_1073742336_Dir"
273− , "dir1_1073742336_Dir"
277 , ( "Rename a nested directory"
287 , ( "Rename a nested directory"
278 , \fp -> createDirectoryIfMissing True
288 , \fp -> createDirectoryIfMissing True
@@ -292,6 +302,7 @@ testDesc =
292 , "dir1/dir2/dir3Renamed_1073741952_Dir"
302 , "dir1/dir2/dir3Renamed_1073741952_Dir"
296 , ( "Create a file in root Dir"
307 , ( "Create a file in root Dir"
297 , const (return ())
308 , const (return ())
@@ -303,10 +314,10 @@ testDesc =
304#elif defined(CABAL_OS_LINUX)
315#elif defined(CABAL_OS_LINUX)
305 , [ "FileCreated.txt_256"
316 , [ "FileCreated.txt_256"
306− , "FileCreated.txt_32"
307 , "FileCreated.txt_2"
317 , "FileCreated.txt_2"
311 , ( "Remove a file in root Dir"
322 , ( "Remove a file in root Dir"
312 , \fp -> writeFile (fp </> "FileCreated.txt") "Test Data"
323 , \fp -> writeFile (fp </> "FileCreated.txt") "Test Data"
@@ -316,6 +327,7 @@ testDesc =
316#elif defined(CABAL_OS_LINUX)
327#elif defined(CABAL_OS_LINUX)
317 , [ "FileCreated.txt_512" ]
328 , [ "FileCreated.txt_512" ]
320 , ( "Rename a file in root Dir"
332 , ( "Rename a file in root Dir"
321 , \fp -> writeFile (fp </> "FileCreated.txt") "Test Data"
333 , \fp -> writeFile (fp </> "FileCreated.txt") "Test Data"
@@ -332,6 +344,7 @@ testDesc =
332 , "FileRenamed.txt_128"
344 , "FileRenamed.txt_128"
336 , ( "Create a file in a nested Dir"
349 , ( "Create a file in a nested Dir"
@@ -345,11 +358,10 @@ testDesc =
346#elif defined(CABAL_OS_LINUX)
359#elif defined(CABAL_OS_LINUX)
347 , [ "dir1/dir2/dir3/FileCreated.txt_256"
360 , [ "dir1/dir2/dir3/FileCreated.txt_256"
348− , "dir1/dir2/dir3/FileCreated.txt_32"
349 , "dir1/dir2/dir3/FileCreated.txt_2"
361 , "dir1/dir2/dir3/FileCreated.txt_2"
350− , "dir1/dir2/dir3/FileCreated.txt_8"
354 , ( "Remove a file in a nested Dir"
366 , ( "Remove a file in a nested Dir"
@@ -366,6 +378,7 @@ testDesc =
366#elif defined(CABAL_OS_LINUX)
378#elif defined(CABAL_OS_LINUX)
367 , ["dir1/dir2/dir3/FileCreated.txt_512"]
379 , ["dir1/dir2/dir3/FileCreated.txt_512"]
370 , ( "Rename a file in a nested Dir"
383 , ( "Rename a file in a nested Dir"
@@ -388,13 +401,110 @@ testDesc =
388 , "dir1/dir2/dir3/FileRenamed.txt_128"
401 , "dir1/dir2/dir3/FileRenamed.txt_128"
406+ , ( "Remove the nested directory"
408+ createDirectoryIfMissing True (fp </> "dir1" </> "dir2" </> "dir3")
409+ , \fp -> removePathForcibly (fp </> "dir1")
410+#if defined(CABAL_OS_WINDOWS)
413+ , "dir1\\dir2\\dir3_2"
414+ , "dir1\\dir2_2","dir1_2"
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"
426+ , "dir1/dir2_1073742336_Dir"
428+ , "dir1_1073742336_Dir"
436+ [ ( "Remove the root directory"
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"
449+ , "dir1/dir2_1073742336_Dir"
451+ , "dir1_1073742336_Dir"
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
467+testDescRootDirSymLink =
468+ [ ( "Remove the root directory as SymLink"
470+ createDirectoryIfMissing True (fp </> "dir1" </> "dir2")
471+ , \fp -> removePathForcibly fp
472+ , ["_1073741828_Dir"]
394moduleName :: String
478moduleName :: String
395moduleName = "FileSystem.Event"
479moduleName = "FileSystem.Event"
397main :: IO ()
481main :: IO ()
399 hSetBuffering stdout NoBuffering
483 hSetBuffering stdout NoBuffering
484+#if defined(CABAL_OS_LINUX)
486+ $ describe moduleName
488+ (driver $ checkEvents eventListWithEOtask Event.watchRecursive)
400− hspec $ describe moduleName $ mapM_ driver testDesc
491+ $ describe moduleName
493+ (driver $ checkEvents eventListWithEOtask CommonEvent.watchRecursive)
494+ (map (\(a, b, c, d, _) -> (a, b, c, d, True)) testDesc)
496+ $ describe moduleName
498+ (driver $ checkEvents eventListWithFixLenSymLink CommonEvent.watchRecursive)
499+ testDescRootDirSymLink
502+ $ describe moduleName
504+ (driver $ checkEvents eventListWithEOtask CommonEvent.watchRecursive)
507+ $ describe moduleName
509+ (driver $ checkEvents eventListWithFixLen CommonEvent.watchRecursive)