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 tempdir::TempDir;
23 
24 
25 #[test]
it_should_watch_a_file()26 fn it_should_watch_a_file() {
27     let mut testdir = TestDir::new();
28     let (path, mut file) = testdir.new_file();
29 
30     let mut inotify = Inotify::init().unwrap();
31     let watch = inotify.add_watch(&path, WatchMask::MODIFY).unwrap();
32 
33     write_to(&mut file);
34 
35     let mut buffer = [0; 1024];
36     let events = inotify.read_events_blocking(&mut buffer).unwrap();
37 
38     let mut num_events = 0;
39     for event in events {
40         assert_eq!(watch, event.wd);
41         num_events += 1;
42     }
43     assert!(num_events > 0);
44 }
45 
46 #[cfg(feature = "stream")]
47 #[tokio::test]
it_should_watch_a_file_async()48 async fn it_should_watch_a_file_async() {
49     let mut testdir = TestDir::new();
50     let (path, mut file) = testdir.new_file();
51 
52     let mut inotify = Inotify::init().unwrap();
53     let watch = inotify.add_watch(&path, WatchMask::MODIFY).unwrap();
54 
55     write_to(&mut file);
56 
57     let mut buffer = [0; 1024];
58 
59     use futures_util::StreamExt;
60     let events = inotify
61         .event_stream(&mut buffer[..])
62         .unwrap()
63         .take(1)
64         .collect::<Vec<_>>()
65         .await;
66 
67     let mut num_events = 0;
68     for event in events {
69         if let Ok(event) = event {
70             assert_eq!(watch, event.wd);
71             num_events += 1;
72         }
73     }
74     assert!(num_events > 0);
75 }
76 
77 #[test]
it_should_return_immediately_if_no_events_are_available()78 fn it_should_return_immediately_if_no_events_are_available() {
79     let mut inotify = Inotify::init().unwrap();
80 
81     let mut buffer = [0; 1024];
82     assert_eq!(0, inotify.read_events(&mut buffer).unwrap().count());
83 }
84 
85 #[test]
it_should_convert_the_name_into_an_os_str()86 fn it_should_convert_the_name_into_an_os_str() {
87     let mut testdir = TestDir::new();
88     let (path, mut file) = testdir.new_file();
89 
90     let mut inotify = Inotify::init().unwrap();
91     inotify.add_watch(&path.parent().unwrap(), WatchMask::MODIFY).unwrap();
92 
93     write_to(&mut file);
94 
95     let mut buffer = [0; 1024];
96     let mut events = inotify.read_events_blocking(&mut buffer).unwrap();
97 
98     if let Some(event) = events.next() {
99         assert_eq!(path.file_name(), event.name);
100     }
101     else {
102         panic!("Expected inotify event");
103     }
104 }
105 
106 #[test]
it_should_set_name_to_none_if_it_is_empty()107 fn it_should_set_name_to_none_if_it_is_empty() {
108     let mut testdir = TestDir::new();
109     let (path, mut file) = testdir.new_file();
110 
111     let mut inotify = Inotify::init().unwrap();
112     inotify.add_watch(&path, WatchMask::MODIFY).unwrap();
113 
114     write_to(&mut file);
115 
116     let mut buffer = [0; 1024];
117     let mut events = inotify.read_events_blocking(&mut buffer).unwrap();
118 
119     if let Some(event) = events.next() {
120         assert_eq!(event.name, None);
121     }
122     else {
123         panic!("Expected inotify event");
124     }
125 }
126 
127 #[test]
it_should_not_accept_watchdescriptors_from_other_instances()128 fn it_should_not_accept_watchdescriptors_from_other_instances() {
129     let mut testdir = TestDir::new();
130     let (path, _) = testdir.new_file();
131 
132     let mut inotify = Inotify::init().unwrap();
133     let _ = inotify.add_watch(&path, WatchMask::ACCESS).unwrap();
134 
135     let mut second_inotify = Inotify::init().unwrap();
136     let wd2 = second_inotify.add_watch(&path, WatchMask::ACCESS).unwrap();
137 
138     assert_eq!(inotify.rm_watch(wd2).unwrap_err().kind(), ErrorKind::InvalidInput);
139 }
140 
141 #[test]
watch_descriptors_from_different_inotify_instances_should_not_be_equal()142 fn watch_descriptors_from_different_inotify_instances_should_not_be_equal() {
143     let mut testdir = TestDir::new();
144     let (path, _) = testdir.new_file();
145 
146     let mut inotify_1 = Inotify::init()
147         .unwrap();
148     let mut inotify_2 = Inotify::init()
149         .unwrap();
150 
151     let wd_1 = inotify_1
152         .add_watch(&path, WatchMask::ACCESS)
153         .unwrap();
154     let wd_2 = inotify_2
155         .add_watch(&path, WatchMask::ACCESS)
156         .unwrap();
157 
158     // As far as inotify is concerned, watch descriptors are just integers that
159     // are scoped per inotify instance. This means that multiple instances will
160     // produce the same watch descriptor number, a case we want inotify-rs to
161     // detect.
162     assert!(wd_1 != wd_2);
163 }
164 
165 #[test]
watch_descriptor_equality_should_not_be_confused_by_reused_fds()166 fn watch_descriptor_equality_should_not_be_confused_by_reused_fds() {
167     let mut testdir = TestDir::new();
168     let (path, _) = testdir.new_file();
169 
170     // When a new inotify instance is created directly after closing another
171     // one, it is possible that the file descriptor is reused immediately, and
172     // we end up with a new instance that has the same file descriptor as the
173     // old one.
174     // This is quite likely, but it doesn't happen every time. Therefore we may
175     // need a few tries until we find two instances where that is the case.
176     let (wd_1, mut inotify_2) = loop {
177         let mut inotify_1 = Inotify::init()
178             .unwrap();
179 
180         let wd_1 = inotify_1
181             .add_watch(&path, WatchMask::ACCESS)
182             .unwrap();
183         let fd_1 = inotify_1.as_raw_fd();
184 
185         inotify_1
186             .close()
187             .unwrap();
188         let inotify_2 = Inotify::init()
189             .unwrap();
190 
191         if fd_1 == inotify_2.as_raw_fd() {
192             break (wd_1, inotify_2);
193         }
194     };
195 
196     let wd_2 = inotify_2
197         .add_watch(&path, WatchMask::ACCESS)
198         .unwrap();
199 
200     // The way we engineered this situation, both `WatchDescriptor` instances
201     // have the same fields. They still come from different inotify instances
202     // though, so they shouldn't be equal.
203     assert!(wd_1 != wd_2);
204 
205     inotify_2
206         .close()
207         .unwrap();
208 
209     // A little extra gotcha: If both inotify instances are closed, and the `Eq`
210     // implementation naively compares the weak pointers, both will be `None`,
211     // making them equal. Let's make sure this isn't the case.
212     assert!(wd_1 != wd_2);
213 }
214 
215 #[test]
it_should_implement_raw_fd_traits_correctly()216 fn it_should_implement_raw_fd_traits_correctly() {
217     let fd = Inotify::init()
218         .expect("Failed to initialize inotify instance")
219         .into_raw_fd();
220 
221     // If `IntoRawFd` has been implemented naively, `Inotify`'s `Drop`
222     // implementation will have closed the inotify instance at this point. Let's
223     // make sure this didn't happen.
224     let mut inotify = unsafe { <Inotify as FromRawFd>::from_raw_fd(fd) };
225 
226     let mut buffer = [0; 1024];
227     if let Err(error) = inotify.read_events(&mut buffer) {
228         panic!("Failed to add watch: {}", error);
229     }
230 }
231 
232 
233 struct TestDir {
234     dir: TempDir,
235     counter: u32,
236 }
237 
238 impl TestDir {
new() -> TestDir239     fn new() -> TestDir {
240         TestDir {
241             dir: TempDir::new("inotify-rs-test").unwrap(),
242             counter: 0,
243         }
244     }
245 
new_file(&mut self) -> (PathBuf, File)246     fn new_file(&mut self) -> (PathBuf, File) {
247         let id = self.counter;
248         self.counter += 1;
249 
250         let path = self.dir.path().join("file-".to_string() + &id.to_string());
251         let file = File::create(&path)
252             .unwrap_or_else(|error| panic!("Failed to create temporary file: {}", error));
253 
254         (path, file)
255     }
256 }
257 
write_to(file: &mut File)258 fn write_to(file: &mut File) {
259     file
260         .write(b"This should trigger an inotify event.")
261         .unwrap_or_else(|error|
262             panic!("Failed to write to file: {}", error)
263         );
264 }
265