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