View Javadoc
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;
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.assertNull;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  
25  import java.io.BufferedOutputStream;
26  import java.io.File;
27  import java.io.FileNotFoundException;
28  import java.io.IOException;
29  import java.io.RandomAccessFile;
30  import java.lang.ref.ReferenceQueue;
31  import java.nio.file.Files;
32  import java.nio.file.Path;
33  import java.nio.file.Paths;
34  import java.util.ArrayList;
35  import java.util.List;
36  
37  import org.apache.commons.io.file.AbstractTempDirTest;
38  import org.apache.commons.io.test.TestUtils;
39  import org.junit.jupiter.api.AfterEach;
40  import org.junit.jupiter.api.BeforeEach;
41  import org.junit.jupiter.api.Test;
42  
43  /**
44   * Tests {@link FileCleaningTracker}.
45   */
46  public class FileCleaningTrackerTest extends AbstractTempDirTest {
47  
48      private File testFile;
49      private Path testPath;
50  
51      private FileCleaningTracker theInstance;
52  
53      RandomAccessFile createRandomAccessFile() throws FileNotFoundException {
54          return RandomAccessFileMode.READ_WRITE.create(testFile);
55      }
56  
57      protected FileCleaningTracker newInstance() {
58          return new FileCleaningTracker();
59      }
60  
61      private void pauseForDeleteToComplete(File file) {
62          int count = 0;
63          while (file.exists() && count++ < 40) {
64              TestUtils.sleepQuietly(500L);
65              file = new File(file.getPath());
66          }
67      }
68  
69      private void pauseForDeleteToComplete(Path file) {
70          int count = 0;
71          while (Files.exists(file) && count++ < 40) {
72              TestUtils.sleepQuietly(500L);
73              file = Paths.get(file.toAbsolutePath().toString());
74          }
75      }
76  
77      @BeforeEach
78      public void setUp() {
79          testFile = new File(tempDirFile, "file-test.txt");
80          testPath = testFile.toPath();
81          theInstance = newInstance();
82      }
83  
84      private String showFailures() {
85          if (theInstance.deleteFailures.size() == 1) {
86              return "[Delete Failed: " + theInstance.deleteFailures.get(0) + "]";
87          }
88          return "[Delete Failures: " + theInstance.deleteFailures.size() + "]";
89      }
90  
91      @AfterEach
92      public void tearDown() {
93  
94          // reset file cleaner class, so as not to break other tests
95  
96          /**
97           * The following block of code can possibly be removed when the deprecated {@link FileCleaner} is gone. The
98           * question is, whether we want to support reuse of {@link FileCleaningTracker} instances, which we should, IMO,
99           * not.
100          */
101         {
102             if (theInstance != null) {
103                 theInstance.q = new ReferenceQueue<>();
104                 theInstance.trackers.clear();
105                 theInstance.deleteFailures.clear();
106                 theInstance.exitWhenFinished = false;
107                 theInstance.reaper = null;
108             }
109         }
110 
111         theInstance = null;
112     }
113 
114     @Test
115     public void testFileCleanerDirectory_ForceStrategy_FileSource() throws Exception {
116         if (!testFile.getParentFile().exists()) {
117             throw new IOException("Cannot create file " + testFile
118                     + " as the parent directory does not exist");
119         }
120         try (BufferedOutputStream output =
121                 new BufferedOutputStream(Files.newOutputStream(testFile.toPath()))) {
122             TestUtils.generateTestData(output, 100);
123         }
124         assertTrue(testFile.exists());
125         assertTrue(tempDirFile.exists());
126 
127         Object obj = new Object();
128         assertEquals(0, theInstance.getTrackCount());
129         theInstance.track(tempDirFile, obj, FileDeleteStrategy.FORCE);
130         assertEquals(1, theInstance.getTrackCount());
131 
132         obj = null;
133 
134         waitUntilTrackCount();
135         pauseForDeleteToComplete(testFile.getParentFile());
136 
137         assertEquals(0, theInstance.getTrackCount());
138         assertFalse(new File(testFile.getPath()).exists(), showFailures());
139         assertFalse(testFile.getParentFile().exists(), showFailures());
140     }
141 
142     @Test
143     public void testFileCleanerDirectory_ForceStrategy_PathSource() throws Exception {
144         if (!Files.exists(testPath.getParent())) {
145             throw new IOException("Cannot create file " + testPath
146                     + " as the parent directory does not exist");
147         }
148         try (BufferedOutputStream output =
149                 new BufferedOutputStream(Files.newOutputStream(testPath))) {
150             TestUtils.generateTestData(output, 100);
151         }
152         assertTrue(Files.exists(testPath));
153         assertTrue(Files.exists(tempDirPath));
154 
155         Object obj = new Object();
156         assertEquals(0, theInstance.getTrackCount());
157         theInstance.track(tempDirPath, obj, FileDeleteStrategy.FORCE);
158         assertEquals(1, theInstance.getTrackCount());
159 
160         obj = null;
161 
162         waitUntilTrackCount();
163         pauseForDeleteToComplete(testPath.getParent());
164 
165         assertEquals(0, theInstance.getTrackCount());
166         assertFalse(Files.exists(testPath), showFailures());
167         assertFalse(Files.exists(testPath.getParent()), showFailures());
168     }
169 
170     @Test
171     public void testFileCleanerDirectory_NullStrategy() throws Exception {
172         TestUtils.createFile(testFile, 100);
173         assertTrue(testFile.exists());
174         assertTrue(tempDirFile.exists());
175 
176         Object obj = new Object();
177         assertEquals(0, theInstance.getTrackCount());
178         theInstance.track(tempDirFile, obj, null);
179         assertEquals(1, theInstance.getTrackCount());
180 
181         obj = null;
182 
183         waitUntilTrackCount();
184 
185         assertEquals(0, theInstance.getTrackCount());
186         assertTrue(testFile.exists());  // not deleted, as dir not empty
187         assertTrue(testFile.getParentFile().exists());  // not deleted, as dir not empty
188     }
189 
190     @Test
191     public void testFileCleanerDirectoryFileSource() throws Exception {
192         TestUtils.createFile(testFile, 100);
193         assertTrue(testFile.exists());
194         assertTrue(tempDirFile.exists());
195 
196         Object obj = new Object();
197         assertEquals(0, theInstance.getTrackCount());
198         theInstance.track(tempDirFile, obj);
199         assertEquals(1, theInstance.getTrackCount());
200 
201         obj = null;
202 
203         waitUntilTrackCount();
204 
205         assertEquals(0, theInstance.getTrackCount());
206         assertTrue(testFile.exists());  // not deleted, as dir not empty
207         assertTrue(testFile.getParentFile().exists());  // not deleted, as dir not empty
208     }
209 
210     @Test
211     public void testFileCleanerDirectoryPathSource() throws Exception {
212         TestUtils.createFile(testPath, 100);
213         assertTrue(Files.exists(testPath));
214         assertTrue(Files.exists(tempDirPath));
215 
216         Object obj = new Object();
217         assertEquals(0, theInstance.getTrackCount());
218         theInstance.track(tempDirPath, obj);
219         assertEquals(1, theInstance.getTrackCount());
220 
221         obj = null;
222 
223         waitUntilTrackCount();
224 
225         assertEquals(0, theInstance.getTrackCount());
226         assertTrue(Files.exists(testPath));  // not deleted, as dir not empty
227         assertTrue(Files.exists(testPath.getParent()));  // not deleted, as dir not empty
228     }
229 
230     @Test
231     public void testFileCleanerExitWhenFinished_NoTrackAfter() {
232         assertFalse(theInstance.exitWhenFinished);
233         theInstance.exitWhenFinished();
234         assertTrue(theInstance.exitWhenFinished);
235         assertNull(theInstance.reaper);
236 
237         final String path = testFile.getPath();
238         final Object marker = new Object();
239 
240         assertThrows(IllegalStateException.class, () -> theInstance.track(path, marker));
241         assertTrue(theInstance.exitWhenFinished);
242         assertNull(theInstance.reaper);
243     }
244 
245     @Test
246     public void testFileCleanerExitWhenFinished1() throws Exception {
247         final String path = testFile.getPath();
248 
249         assertFalse(testFile.exists(), "1-testFile exists: " + testFile);
250 
251         // Do NOT used a try-with-resources statement here or the test will fail.
252         RandomAccessFile raf = createRandomAccessFile();
253         assertTrue(testFile.exists(), "2-testFile exists");
254 
255         assertEquals(0, theInstance.getTrackCount(), "3-Track Count");
256         theInstance.track(path, raf);
257         assertEquals(1, theInstance.getTrackCount(), "4-Track Count");
258         assertFalse(theInstance.exitWhenFinished, "5-exitWhenFinished");
259         assertTrue(theInstance.reaper.isAlive(), "6-reaper.isAlive");
260 
261         assertFalse(theInstance.exitWhenFinished, "7-exitWhenFinished");
262         theInstance.exitWhenFinished();
263         assertTrue(theInstance.exitWhenFinished, "8-exitWhenFinished");
264         assertTrue(theInstance.reaper.isAlive(), "9-reaper.isAlive");
265 
266         raf.close();
267         testFile = null;
268         raf = null;
269 
270         waitUntilTrackCount();
271         pauseForDeleteToComplete(new File(path));
272 
273         assertEquals(0, theInstance.getTrackCount(), "10-Track Count");
274         assertFalse(new File(path).exists(), "11-testFile exists " + showFailures());
275         assertTrue(theInstance.exitWhenFinished, "12-exitWhenFinished");
276         assertFalse(theInstance.reaper.isAlive(), "13-reaper.isAlive");
277     }
278 
279     @Test
280     public void testFileCleanerExitWhenFinished2() throws Exception {
281         final String path = testFile.getPath();
282 
283         assertFalse(testFile.exists());
284         RandomAccessFile r = createRandomAccessFile();
285         assertTrue(testFile.exists());
286 
287         assertEquals(0, theInstance.getTrackCount());
288         theInstance.track(path, r);
289         assertEquals(1, theInstance.getTrackCount());
290         assertFalse(theInstance.exitWhenFinished);
291         assertTrue(theInstance.reaper.isAlive());
292 
293         r.close();
294         testFile = null;
295         r = null;
296 
297         waitUntilTrackCount();
298         pauseForDeleteToComplete(new File(path));
299 
300         assertEquals(0, theInstance.getTrackCount());
301         assertFalse(new File(path).exists(), showFailures());
302         assertFalse(theInstance.exitWhenFinished);
303         assertTrue(theInstance.reaper.isAlive());
304 
305         assertFalse(theInstance.exitWhenFinished);
306         theInstance.exitWhenFinished();
307         for (int i = 0; i < 20 && theInstance.reaper.isAlive(); i++) {
308             TestUtils.sleep(500L);  // allow reaper thread to die
309         }
310         assertTrue(theInstance.exitWhenFinished);
311         assertFalse(theInstance.reaper.isAlive());
312     }
313 
314     @Test
315     public void testFileCleanerExitWhenFinishedFirst() throws Exception {
316         assertFalse(theInstance.exitWhenFinished);
317         theInstance.exitWhenFinished();
318         assertTrue(theInstance.exitWhenFinished);
319         assertNull(theInstance.reaper);
320 
321         waitUntilTrackCount();
322 
323         assertEquals(0, theInstance.getTrackCount());
324         assertTrue(theInstance.exitWhenFinished);
325         assertNull(theInstance.reaper);
326     }
327 
328     @Test
329     public void testFileCleanerFile() throws Exception {
330         final String path = testFile.getPath();
331 
332         assertFalse(testFile.exists());
333         RandomAccessFile r = createRandomAccessFile();
334         assertTrue(testFile.exists());
335 
336         assertEquals(0, theInstance.getTrackCount());
337         theInstance.track(path, r);
338         assertEquals(1, theInstance.getTrackCount());
339 
340         r.close();
341         testFile = null;
342         r = null;
343 
344         waitUntilTrackCount();
345         pauseForDeleteToComplete(new File(path));
346 
347         assertEquals(0, theInstance.getTrackCount());
348         assertFalse(new File(path).exists(), showFailures());
349     }
350     @Test
351     public void testFileCleanerNull() {
352         assertThrows(NullPointerException.class, () -> theInstance.track((File) null, new Object()));
353         assertThrows(NullPointerException.class, () -> theInstance.track((File) null, new Object(), FileDeleteStrategy.NORMAL));
354         assertThrows(NullPointerException.class, () -> theInstance.track((String) null, new Object()));
355         assertThrows(NullPointerException.class, () -> theInstance.track((String) null, new Object(), FileDeleteStrategy.NORMAL));
356     }
357 
358     private void waitUntilTrackCount() throws Exception {
359         System.gc();
360         TestUtils.sleep(500);
361         int count = 0;
362         while (theInstance.getTrackCount() != 0 && count++ < 5) {
363             List<String> list = new ArrayList<>();
364             try {
365                 long i = 0;
366                 while (theInstance.getTrackCount() != 0) {
367                     list.add(
368                         "A Big String A Big String A Big String A Big String A Big String A Big String A Big String A Big String A Big String A Big String "
369                             + i++);
370                 }
371             } catch (final Throwable ignored) {
372             }
373             list = null;
374             System.gc();
375             TestUtils.sleep(1000);
376         }
377         if (theInstance.getTrackCount() != 0) {
378             throw new IllegalStateException("Your JVM is not releasing References, try running the test with less memory (-Xmx)");
379         }
380 
381     }
382 }