1 #![deny(warnings)]
2
3
4 // This test suite is incomplete and doesn't cover all available functionality.
5 // Contributions to improve test coverage would be highly appreciated!
6
7 use inotify::{
8 Inotify,
9 WatchMask
10 };
11 use std::fs::File;
12 use std::io::{
13 Write,
14 ErrorKind,
15 };
16 use std::os::unix::io::{
17 AsRawFd,
18 FromRawFd,
19 IntoRawFd,
20 };
21 use std::path::PathBuf;
22 use tempfile::TempDir;
23
24 #[cfg(feature = "stream")]
25 use maplit::hashmap;
26 #[cfg(feature = "stream")]
27 use inotify::EventMask;
28 #[cfg(feature = "stream")]
29 use rand::{thread_rng, prelude::SliceRandom};
30 #[cfg(feature = "stream")]
31 use std::sync::{Mutex, Arc};
32 #[cfg(feature = "stream")]
33 use futures_util::StreamExt;
34
35
36 #[test]
it_should_watch_a_file()37 fn it_should_watch_a_file() {
38 let mut testdir = TestDir::new();
39 let (path, mut file) = testdir.new_file();
40
41 let mut inotify = Inotify::init().unwrap();
42 let watch = inotify.watches().add(&path, WatchMask::MODIFY).unwrap();
43
44 write_to(&mut file);
45
46 let mut buffer = [0; 1024];
47 let events = inotify.read_events_blocking(&mut buffer).unwrap();
48
49 let mut num_events = 0;
50 for event in events {
51 assert_eq!(watch, event.wd);
52 num_events += 1;
53 }
54 assert!(num_events > 0);
55 }
56
57 #[cfg(feature = "stream")]
58 #[tokio::test]
it_should_watch_a_file_async()59 async fn it_should_watch_a_file_async() {
60 let mut testdir = TestDir::new();
61 let (path, mut file) = testdir.new_file();
62
63 let inotify = Inotify::init().unwrap();
64
65 // Hold ownership of `watches` for this test, so that the underlying file descriptor has
66 // at least one reference to keep it alive, and we can inspect the WatchDescriptors below.
67 // Otherwise the `Weak<FdGuard>` contained in the WatchDescriptors will be invalidated
68 // when `inotify` is consumed by `into_event_stream()` and the EventStream is dropped
69 // during `await`.
70 let mut watches = inotify.watches();
71
72 let watch = watches.add(&path, WatchMask::MODIFY).unwrap();
73
74 write_to(&mut file);
75
76 let mut buffer = [0; 1024];
77
78 use futures_util::StreamExt;
79 let events = inotify
80 .into_event_stream(&mut buffer[..])
81 .unwrap()
82 .take(1)
83 .collect::<Vec<_>>()
84 .await;
85
86 let mut num_events = 0;
87 for event in events {
88 if let Ok(event) = event {
89 assert_eq!(watch, event.wd);
90 num_events += 1;
91 }
92 }
93 assert!(num_events > 0);
94 }
95
96 #[cfg(feature = "stream")]
97 #[tokio::test]
it_should_watch_a_file_from_eventstream_watches()98 async fn it_should_watch_a_file_from_eventstream_watches() {
99 let mut testdir = TestDir::new();
100 let (path, mut file) = testdir.new_file();
101
102 let inotify = Inotify::init().unwrap();
103
104 let mut buffer = [0; 1024];
105
106 use futures_util::StreamExt;
107 let stream = inotify.into_event_stream(&mut buffer[..]).unwrap();
108
109 // Hold ownership of `watches` for this test, so that the underlying file descriptor has
110 // at least one reference to keep it alive, and we can inspect the WatchDescriptors below.
111 // Otherwise the `Weak<FdGuard>` contained in the WatchDescriptors will be invalidated
112 // when `stream` is dropped during `await`.
113 let mut watches = stream.watches();
114
115 let watch = watches.add(&path, WatchMask::MODIFY).unwrap();
116 write_to(&mut file);
117
118 let events = stream
119 .take(1)
120 .collect::<Vec<_>>()
121 .await;
122
123 let mut num_events = 0;
124 for event in events {
125 if let Ok(event) = event {
126 assert_eq!(watch, event.wd);
127 num_events += 1;
128 }
129 }
130 assert!(num_events > 0);
131 }
132
133 #[cfg(feature = "stream")]
134 #[tokio::test]
it_should_watch_a_file_after_converting_back_from_eventstream()135 async fn it_should_watch_a_file_after_converting_back_from_eventstream() {
136 let mut testdir = TestDir::new();
137 let (path, mut file) = testdir.new_file();
138
139 let inotify = Inotify::init().unwrap();
140
141 let mut buffer = [0; 1024];
142 let stream = inotify.into_event_stream(&mut buffer[..]).unwrap();
143 let mut inotify = stream.into_inotify();
144
145 let watch = inotify.watches().add(&path, WatchMask::MODIFY).unwrap();
146
147 write_to(&mut file);
148
149 let events = inotify.read_events_blocking(&mut buffer).unwrap();
150
151 let mut num_events = 0;
152 for event in events {
153 assert_eq!(watch, event.wd);
154 num_events += 1;
155 }
156 assert!(num_events > 0);
157 }
158
159 #[test]
it_should_return_immediately_if_no_events_are_available()160 fn it_should_return_immediately_if_no_events_are_available() {
161 let mut inotify = Inotify::init().unwrap();
162
163 let mut buffer = [0; 1024];
164 assert_eq!(inotify.read_events(&mut buffer).unwrap_err().kind(), ErrorKind::WouldBlock);
165 }
166
167 #[test]
it_should_convert_the_name_into_an_os_str()168 fn it_should_convert_the_name_into_an_os_str() {
169 let mut testdir = TestDir::new();
170 let (path, mut file) = testdir.new_file();
171
172 let mut inotify = Inotify::init().unwrap();
173 inotify.watches().add(&path.parent().unwrap(), WatchMask::MODIFY).unwrap();
174
175 write_to(&mut file);
176
177 let mut buffer = [0; 1024];
178 let mut events = inotify.read_events_blocking(&mut buffer).unwrap();
179
180 if let Some(event) = events.next() {
181 assert_eq!(path.file_name(), event.name);
182 }
183 else {
184 panic!("Expected inotify event");
185 }
186 }
187
188 #[test]
it_should_set_name_to_none_if_it_is_empty()189 fn it_should_set_name_to_none_if_it_is_empty() {
190 let mut testdir = TestDir::new();
191 let (path, mut file) = testdir.new_file();
192
193 let mut inotify = Inotify::init().unwrap();
194 inotify.watches().add(&path, WatchMask::MODIFY).unwrap();
195
196 write_to(&mut file);
197
198 let mut buffer = [0; 1024];
199 let mut events = inotify.read_events_blocking(&mut buffer).unwrap();
200
201 if let Some(event) = events.next() {
202 assert_eq!(event.name, None);
203 }
204 else {
205 panic!("Expected inotify event");
206 }
207 }
208
209 #[test]
it_should_not_accept_watchdescriptors_from_other_instances()210 fn it_should_not_accept_watchdescriptors_from_other_instances() {
211 let mut testdir = TestDir::new();
212 let (path, _) = testdir.new_file();
213
214 let inotify = Inotify::init().unwrap();
215 let _ = inotify.watches().add(&path, WatchMask::ACCESS).unwrap();
216
217 let second_inotify = Inotify::init().unwrap();
218 let wd2 = second_inotify.watches().add(&path, WatchMask::ACCESS).unwrap();
219
220 assert_eq!(inotify.watches().remove(wd2).unwrap_err().kind(), ErrorKind::InvalidInput);
221 }
222
223 #[test]
watch_descriptors_from_different_inotify_instances_should_not_be_equal()224 fn watch_descriptors_from_different_inotify_instances_should_not_be_equal() {
225 let mut testdir = TestDir::new();
226 let (path, _) = testdir.new_file();
227
228 let inotify_1 = Inotify::init()
229 .unwrap();
230 let inotify_2 = Inotify::init()
231 .unwrap();
232
233 let wd_1 = inotify_1
234 .watches()
235 .add(&path, WatchMask::ACCESS)
236 .unwrap();
237 let wd_2 = inotify_2
238 .watches()
239 .add(&path, WatchMask::ACCESS)
240 .unwrap();
241
242 // As far as inotify is concerned, watch descriptors are just integers that
243 // are scoped per inotify instance. This means that multiple instances will
244 // produce the same watch descriptor number, a case we want inotify-rs to
245 // detect.
246 assert!(wd_1 != wd_2);
247 }
248
249 #[test]
watch_descriptor_equality_should_not_be_confused_by_reused_fds()250 fn watch_descriptor_equality_should_not_be_confused_by_reused_fds() {
251 let mut testdir = TestDir::new();
252 let (path, _) = testdir.new_file();
253
254 // When a new inotify instance is created directly after closing another
255 // one, it is possible that the file descriptor is reused immediately, and
256 // we end up with a new instance that has the same file descriptor as the
257 // old one.
258 // This is quite likely, but it doesn't happen every time. Therefore we may
259 // need a few tries until we find two instances where that is the case.
260 let (wd_1, inotify_2) = loop {
261 let inotify_1 = Inotify::init()
262 .unwrap();
263
264 let wd_1 = inotify_1
265 .watches()
266 .add(&path, WatchMask::ACCESS)
267 .unwrap();
268 let fd_1 = inotify_1.as_raw_fd();
269
270 inotify_1
271 .close()
272 .unwrap();
273 let inotify_2 = Inotify::init()
274 .unwrap();
275
276 if fd_1 == inotify_2.as_raw_fd() {
277 break (wd_1, inotify_2);
278 }
279 };
280
281 let wd_2 = inotify_2
282 .watches()
283 .add(&path, WatchMask::ACCESS)
284 .unwrap();
285
286 // The way we engineered this situation, both `WatchDescriptor` instances
287 // have the same fields. They still come from different inotify instances
288 // though, so they shouldn't be equal.
289 assert!(wd_1 != wd_2);
290
291 inotify_2
292 .close()
293 .unwrap();
294
295 // A little extra gotcha: If both inotify instances are closed, and the `Eq`
296 // implementation naively compares the weak pointers, both will be `None`,
297 // making them equal. Let's make sure this isn't the case.
298 assert!(wd_1 != wd_2);
299 }
300
301 #[test]
it_should_implement_raw_fd_traits_correctly()302 fn it_should_implement_raw_fd_traits_correctly() {
303 let fd = Inotify::init()
304 .expect("Failed to initialize inotify instance")
305 .into_raw_fd();
306
307 // If `IntoRawFd` has been implemented naively, `Inotify`'s `Drop`
308 // implementation will have closed the inotify instance at this point. Let's
309 // make sure this didn't happen.
310 let mut inotify = unsafe { <Inotify as FromRawFd>::from_raw_fd(fd) };
311
312 let mut buffer = [0; 1024];
313 if let Err(error) = inotify.read_events(&mut buffer) {
314 if error.kind() != ErrorKind::WouldBlock {
315 panic!("Failed to add watch: {}", error);
316 }
317 }
318 }
319
320 #[test]
it_should_watch_correctly_with_a_watches_clone()321 fn it_should_watch_correctly_with_a_watches_clone() {
322 let mut testdir = TestDir::new();
323 let (path, mut file) = testdir.new_file();
324
325 let mut inotify = Inotify::init().unwrap();
326 let mut watches1 = inotify.watches();
327 let mut watches2 = watches1.clone();
328 let watch1 = watches1.add(&path, WatchMask::MODIFY).unwrap();
329 let watch2 = watches2.add(&path, WatchMask::MODIFY).unwrap();
330
331 // same path and same Inotify should return same descriptor
332 assert_eq!(watch1, watch2);
333
334 write_to(&mut file);
335
336 let mut buffer = [0; 1024];
337 let events = inotify.read_events_blocking(&mut buffer).unwrap();
338
339 let mut num_events = 0;
340 for event in events {
341 assert_eq!(watch2, event.wd);
342 num_events += 1;
343 }
344 assert!(num_events > 0);
345 }
346
347 #[cfg(feature = "stream")]
348 #[tokio::test]
349 /// Testing if two files with the same name but different directories
350 /// (e.g. "file_a" and "another_dir/file_a") are distinguished when _randomly_
351 /// triggering a DELETE_SELF for the two files.
it_should_distinguish_event_for_files_with_same_name()352 async fn it_should_distinguish_event_for_files_with_same_name() {
353 let mut testdir = TestDir::new();
354 let testdir_path = testdir.dir.path().to_owned();
355 let file_order = Arc::new(Mutex::new(vec!["file_a", "another_dir/file_a"]));
356 file_order.lock().unwrap().shuffle(&mut thread_rng());
357 let file_order_clone = file_order.clone();
358
359 let inotify = Inotify::init().expect("Failed to initialize inotify instance");
360
361 // creating file_a inside `TestDir.dir`
362 let (path_1, _) = testdir.new_file_with_name("file_a");
363 // creating a directory inside `TestDir.dir`
364 testdir.new_directory_with_name("another_dir");
365 // creating a file inside `TestDir.dir/another_dir`
366 let (path_2, _) = testdir.new_file_in_directory_with_name("another_dir", "file_a");
367
368 // watching both files for `DELETE_SELF`
369 let wd_1 = inotify.watches().add(&path_1, WatchMask::DELETE_SELF).unwrap();
370 let wd_2 = inotify.watches().add(&path_2, WatchMask::DELETE_SELF).unwrap();
371
372 let expected_ids = hashmap! {
373 wd_1.get_watch_descriptor_id() => "file_a",
374 wd_2.get_watch_descriptor_id() => "another_dir/file_a"
375 };
376 let mut buffer = [0; 1024];
377
378 let file_removal_handler = tokio::spawn(async move {
379 for file in file_order.lock().unwrap().iter() {
380 testdir.delete_file(file);
381 }
382 });
383
384 let event_handle = tokio::spawn(async move {
385 let mut events = inotify.into_event_stream(&mut buffer).unwrap();
386 while let Some(Ok(event)) = events.next().await {
387 if event.mask == EventMask::DELETE_SELF {
388 let id = event.wd.get_watch_descriptor_id();
389 let file = expected_ids.get(&id).unwrap();
390 let full_path = testdir_path.join(*file);
391 println!("file {:?} was deleted", full_path);
392 file_order_clone.lock().unwrap().retain(|&x| x != *file);
393
394 if file_order_clone.lock().unwrap().is_empty() {
395 break;
396 }
397 }
398 }
399 });
400
401 let () = event_handle.await.unwrap();
402 let () = file_removal_handler.await.unwrap();
403 }
404
405 struct TestDir {
406 dir: TempDir,
407 counter: u32,
408 }
409
410 impl TestDir {
new() -> TestDir411 fn new() -> TestDir {
412 TestDir {
413 dir: TempDir::new().unwrap(),
414 counter: 0,
415 }
416 }
417
418 #[cfg(feature = "stream")]
new_file_with_name(&mut self, file_name: &str) -> (PathBuf, File)419 fn new_file_with_name(&mut self, file_name: &str) -> (PathBuf, File) {
420 self.counter += 1;
421
422 let path = self.dir.path().join(file_name);
423 let file = File::create(&path)
424 .unwrap_or_else(|error| panic!("Failed to create temporary file: {}", error));
425
426 (path, file)
427 }
428
429 #[cfg(feature = "stream")]
delete_file(&mut self, relative_path_to_file: &str)430 fn delete_file(&mut self, relative_path_to_file: &str) {
431 let path = &self.dir.path().join(relative_path_to_file);
432 std::fs::remove_file(path).unwrap();
433 }
434
435 #[cfg(feature = "stream")]
new_file_in_directory_with_name( &mut self, dir_name: &str, file_name: &str, ) -> (PathBuf, File)436 fn new_file_in_directory_with_name(
437 &mut self,
438 dir_name: &str,
439 file_name: &str,
440 ) -> (PathBuf, File) {
441 self.counter += 1;
442
443 let path = self.dir.path().join(dir_name).join(file_name);
444 let file = File::create(&path)
445 .unwrap_or_else(|error| panic!("Failed to create temporary file: {}", error));
446
447 (path, file)
448 }
449
450 #[cfg(feature = "stream")]
new_directory_with_name(&mut self, dir_name: &str) -> PathBuf451 fn new_directory_with_name(&mut self, dir_name: &str) -> PathBuf {
452 let path = self.dir.path().join(dir_name);
453 let () = std::fs::create_dir(&path).unwrap();
454 path.to_path_buf()
455 }
456
new_file(&mut self) -> (PathBuf, File)457 fn new_file(&mut self) -> (PathBuf, File) {
458 let id = self.counter;
459 self.counter += 1;
460
461 let path = self.dir.path().join("file-".to_string() + &id.to_string());
462 let file = File::create(&path)
463 .unwrap_or_else(|error| panic!("Failed to create temporary file: {}", error));
464
465 (path, file)
466 }
467 }
468
write_to(file: &mut File)469 fn write_to(file: &mut File) {
470 file
471 .write(b"This should trigger an inotify event.")
472 .unwrap_or_else(|error|
473 panic!("Failed to write to file: {}", error)
474 );
475 }
476