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 createRandomAccessFile()53 RandomAccessFile createRandomAccessFile() throws FileNotFoundException { 54 return RandomAccessFileMode.READ_WRITE.create(testFile); 55 } 56 newInstance()57 protected FileCleaningTracker newInstance() { 58 return new FileCleaningTracker(); 59 } 60 pauseForDeleteToComplete(File file)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 pauseForDeleteToComplete(Path file)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 setUp()78 public void setUp() { 79 testFile = new File(tempDirFile, "file-test.txt"); 80 testPath = testFile.toPath(); 81 theInstance = newInstance(); 82 } 83 showFailures()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 tearDown()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 testFileCleanerDirectory_ForceStrategy_FileSource()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 testFileCleanerDirectory_ForceStrategy_PathSource()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 testFileCleanerDirectory_NullStrategy()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 testFileCleanerDirectoryFileSource()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 testFileCleanerDirectoryPathSource()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 testFileCleanerExitWhenFinished_NoTrackAfter()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 testFileCleanerExitWhenFinished1()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 testFileCleanerExitWhenFinished2()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 testFileCleanerExitWhenFinishedFirst()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 testFileCleanerFile()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 testFileCleanerNull()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 waitUntilTrackCount()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 } 383