1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 package org.apache.commons.io.monitor;
18 
19 import static org.junit.jupiter.api.Assertions.assertEquals;
20 import static org.junit.jupiter.api.Assertions.assertFalse;
21 import static org.junit.jupiter.api.Assertions.assertThrows;
22 import static org.junit.jupiter.api.Assertions.assertTrue;
23 import static org.junit.jupiter.api.Assertions.fail;
24 
25 import java.io.File;
26 import java.time.Duration;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.Iterator;
31 import java.util.concurrent.Executors;
32 import java.util.concurrent.ThreadFactory;
33 
34 import org.apache.commons.io.ThreadUtils;
35 import org.apache.commons.io.test.TestUtils;
36 import org.junit.jupiter.api.Test;
37 
38 /**
39  * {@link FileAlterationMonitor} Test Case.
40  */
41 public class FileAlterationMonitorTest extends AbstractMonitorTest {
42 
43     /**
44      * Construct a new test case.
45      *
46      */
FileAlterationMonitorTest()47     public FileAlterationMonitorTest() {
48         listener = new CollectionFileListener(false);
49     }
50 
51     /**
52      * Check all the File Collections have the expected sizes.
53      */
checkFile(final String label, final File file, final Collection<File> files)54     private void checkFile(final String label, final File file, final Collection<File> files) {
55         for (int i = 0; i < 20; i++) {
56             if (files.contains(file)) {
57                 return; // found, test passes
58             }
59             TestUtils.sleepQuietly(pauseTime);
60         }
61         fail(label + " " + file + " not found");
62     }
63 
64     /**
65      * Test add/remove observers.
66      */
67     @Test
testAddRemoveObservers()68     public void testAddRemoveObservers() {
69         FileAlterationObserver[] observers = null;
70 
71         // Null Observers
72         FileAlterationMonitor monitor = new FileAlterationMonitor(123, observers);
73         assertEquals(123, monitor.getInterval(), "Interval");
74         assertFalse(monitor.getObservers().iterator().hasNext(), "Observers[1]");
75 
76         // Null Observer
77         observers = new FileAlterationObserver[1]; // observer is null
78         monitor = new FileAlterationMonitor(456, observers);
79         assertFalse(monitor.getObservers().iterator().hasNext(), "Observers[2]");
80 
81         // Null Observer
82         monitor.addObserver(null);
83         assertFalse(monitor.getObservers().iterator().hasNext(), "Observers[3]");
84         monitor.removeObserver(null);
85 
86         // Add Observer
87         final FileAlterationObserver observer = new FileAlterationObserver("foo");
88         monitor.addObserver(observer);
89         final Iterator<FileAlterationObserver> it = monitor.getObservers().iterator();
90         assertTrue(it.hasNext(), "Observers[4]");
91         assertEquals(observer, it.next(), "Added");
92         assertFalse(it.hasNext(), "Observers[5]");
93 
94         // Remove Observer
95         monitor.removeObserver(observer);
96         assertFalse(monitor.getObservers().iterator().hasNext(), "Observers[6]");
97     }
98 
99     @Test
testCollectionConstructor()100     public void testCollectionConstructor() {
101         observer = new FileAlterationObserver("foo");
102         final Collection<FileAlterationObserver> observers = Arrays.asList(observer);
103         final FileAlterationMonitor monitor = new FileAlterationMonitor(0, observers);
104         final Iterator<FileAlterationObserver> iterator = monitor.getObservers().iterator();
105         assertEquals(observer, iterator.next());
106     }
107 
108     @Test
testCollectionConstructorShouldDoNothingWithNullCollection()109     public void testCollectionConstructorShouldDoNothingWithNullCollection() {
110         final Collection<FileAlterationObserver> observers = null;
111         final FileAlterationMonitor monitor = new FileAlterationMonitor(0, observers);
112         assertFalse(monitor.getObservers().iterator().hasNext());
113     }
114 
115     @Test
testCollectionConstructorShouldDoNothingWithNullObservers()116     public void testCollectionConstructorShouldDoNothingWithNullObservers() {
117         final Collection<FileAlterationObserver> observers = new ArrayList<>(5);
118         final FileAlterationMonitor monitor = new FileAlterationMonitor(0, observers);
119         assertFalse(monitor.getObservers().iterator().hasNext());
120     }
121 
122     /**
123      * Test default constructor.
124      */
125     @Test
testDefaultConstructor()126     public void testDefaultConstructor() {
127         final FileAlterationMonitor monitor = new FileAlterationMonitor();
128         assertEquals(10000, monitor.getInterval(), "Interval");
129     }
130 
131     /**
132      * Test checkAndNotify() method
133      * @throws Exception
134      */
135     @Test
testMonitor()136     public void testMonitor() throws Exception {
137         final long interval = 100;
138         listener.clear();
139         final FileAlterationMonitor monitor = new FileAlterationMonitor(interval, observer);
140         assertEquals(interval, monitor.getInterval(), "Interval");
141         monitor.start();
142 
143         // try and start again
144         assertThrows(IllegalStateException.class, () -> monitor.start());
145 
146         // Create a File
147         checkCollectionsEmpty("A");
148         File file1 = touch(new File(testDir, "file1.java"));
149         checkFile("Create", file1, listener.getCreatedFiles());
150         listener.clear();
151 
152         // Update a file
153         checkCollectionsEmpty("B");
154         file1 = touch(file1);
155         checkFile("Update", file1, listener.getChangedFiles());
156         listener.clear();
157 
158         // Delete a file
159         checkCollectionsEmpty("C");
160         file1.delete();
161         checkFile("Delete", file1, listener.getDeletedFiles());
162         listener.clear();
163 
164         // Stop monitoring
165         monitor.stop();
166 
167         // try and stop again
168         assertThrows(IllegalStateException.class, () -> monitor.stop());
169     }
170 
171     /**
172      * Test case for IO-535
173      *
174      * Verify that {@link FileAlterationMonitor#stop()} stops the created thread
175      */
176     @Test
testStopWhileWaitingForNextInterval()177     public void testStopWhileWaitingForNextInterval() throws Exception {
178         final Collection<Thread> createdThreads = new ArrayList<>(1);
179         final ThreadFactory threadFactory = new ThreadFactory() {
180             private final ThreadFactory delegate = Executors.defaultThreadFactory();
181 
182             @Override
183             public Thread newThread(final Runnable r) {
184                 final Thread thread = delegate.newThread(r);
185                 thread.setDaemon(true); //do not leak threads if the test fails
186                 createdThreads.add(thread);
187                 return thread;
188             }
189         };
190 
191         final FileAlterationMonitor monitor = new FileAlterationMonitor(1_000);
192         monitor.setThreadFactory(threadFactory);
193 
194         monitor.start();
195         assertFalse(createdThreads.isEmpty());
196 
197         ThreadUtils.sleep(Duration.ofMillis(10)); // wait until the watcher thread enters Thread.sleep()
198         monitor.stop(100);
199 
200         createdThreads.forEach(thread -> assertFalse(thread.isAlive(), "The FileAlterationMonitor did not stop the threads it created."));
201     }
202 
203     /**
204      * Test using a thread factory.
205      * @throws Exception
206      */
207     @Test
testThreadFactory()208     public void testThreadFactory() throws Exception {
209         final long interval = 100;
210         listener.clear();
211         final FileAlterationMonitor monitor = new FileAlterationMonitor(interval, observer);
212         monitor.setThreadFactory(Executors.defaultThreadFactory());
213         assertEquals(interval, monitor.getInterval(), "Interval");
214         monitor.start();
215 
216         // Create a File
217         checkCollectionsEmpty("A");
218         final File file2 = touch(new File(testDir, "file2.java"));
219         checkFile("Create", file2, listener.getCreatedFiles());
220         listener.clear();
221 
222         // Delete a file
223         checkCollectionsEmpty("B");
224         file2.delete();
225         checkFile("Delete", file2, listener.getDeletedFiles());
226         listener.clear();
227 
228         // Stop monitoring
229         monitor.stop();
230     }
231 }
232