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    *      https://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.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  import static org.junit.jupiter.api.Assertions.fail;
25  
26  import java.io.File;
27  import java.io.IOException;
28  import java.nio.charset.StandardCharsets;
29  import java.nio.file.Files;
30  import java.nio.file.Path;
31  import java.util.ArrayList;
32  import java.util.Collection;
33  import java.util.Iterator;
34  import java.util.List;
35  import java.util.UUID;
36  import java.util.concurrent.CompletableFuture;
37  import java.util.concurrent.ExecutionException;
38  import java.util.stream.Collectors;
39  import java.util.stream.Stream;
40  
41  import org.apache.commons.io.file.PathUtils;
42  import org.apache.commons.io.filefilter.FileFilterUtils;
43  import org.apache.commons.io.filefilter.IOFileFilter;
44  import org.apache.commons.io.function.Uncheck;
45  import org.apache.commons.lang3.function.Consumers;
46  import org.junit.jupiter.api.BeforeEach;
47  import org.junit.jupiter.api.Test;
48  import org.junit.jupiter.api.io.TempDir;
49  
50  /**
51   * Tests FileUtils.listFiles() methods.
52   */
53  class FileUtilsListFilesTest {
54  
55      @TempDir
56      public File temporaryFolder;
57  
58      @SuppressWarnings("ResultOfMethodCallIgnored")
59      @BeforeEach
60      public void setUp() throws Exception {
61          File dir = temporaryFolder;
62          File file = new File(dir, "dummy-build.xml");
63          FileUtils.touch(file);
64          file = new File(dir, "README");
65          FileUtils.touch(file);
66  
67          dir = new File(dir, "subdir1");
68          dir.mkdirs();
69          file = new File(dir, "dummy-build.xml");
70          FileUtils.touch(file);
71          file = new File(dir, "dummy-readme.txt");
72          FileUtils.touch(file);
73  
74          dir = new File(dir, "subsubdir1");
75          dir.mkdirs();
76          file = new File(dir, "dummy-file.txt");
77          FileUtils.touch(file);
78          file = new File(dir, "dummy-index.html");
79          FileUtils.touch(file);
80          file = new File(dir, "dummy-indexhtml");
81          FileUtils.touch(file);
82  
83          dir = dir.getParentFile();
84          dir = new File(dir, "CVS");
85          dir.mkdirs();
86          file = new File(dir, "Entries");
87          FileUtils.touch(file);
88          file = new File(dir, "Repository");
89          FileUtils.touch(file);
90      }
91  
92      @Test
93      void testIterateFilesByExtension() {
94          final String[] extensions = { "xml", "txt" };
95  
96          Iterator<File> files = FileUtils.iterateFiles(temporaryFolder, extensions, false);
97          try {
98              final Collection<String> fileNames = toFileNames(files);
99              assertEquals(1, fileNames.size());
100             assertTrue(fileNames.contains("dummy-build.xml"));
101             assertFalse(fileNames.contains("README"));
102             assertFalse(fileNames.contains("dummy-file.txt"));
103         } finally {
104             // Backstop in case filesToFilenames() failure.
105             files.forEachRemaining(Consumers.nop());
106         }
107 
108         try {
109             files = FileUtils.iterateFiles(temporaryFolder, extensions, true);
110             final Collection<String> fileNames = toFileNames(files);
111             assertEquals(4, fileNames.size());
112             assertTrue(fileNames.contains("dummy-file.txt"));
113             assertFalse(fileNames.contains("dummy-index.html"));
114         } finally {
115             // Backstop in case filesToFilenames() failure.
116             files.forEachRemaining(Consumers.nop());
117         }
118 
119         files = FileUtils.iterateFiles(temporaryFolder, null, false);
120         try {
121             final Collection<String> fileNames = toFileNames(files);
122             assertEquals(2, fileNames.size());
123             assertTrue(fileNames.contains("dummy-build.xml"));
124             assertTrue(fileNames.contains("README"));
125             assertFalse(fileNames.contains("dummy-file.txt"));
126         } finally {
127             // Backstop in case filesToFilenames() failure.
128             files.forEachRemaining(Consumers.nop());
129         }
130     }
131 
132     @Test
133     void testListFiles() {
134         Collection<File> files;
135         Collection<String> fileNames;
136         IOFileFilter fileFilter;
137         IOFileFilter dirFilter;
138         //
139         // First, find non-recursively
140         fileFilter = FileFilterUtils.trueFileFilter();
141         files = FileUtils.listFiles(temporaryFolder, fileFilter, null);
142         fileNames = toFileNames(files);
143         assertTrue(fileNames.contains("dummy-build.xml"), "'dummy-build.xml' is missing");
144         assertFalse(fileNames.contains("dummy-index.html"), "'dummy-index.html' shouldn't be found");
145         assertFalse(fileNames.contains("Entries"), "'Entries' shouldn't be found");
146         //
147         // Second, find recursively
148         fileFilter = FileFilterUtils.trueFileFilter();
149         dirFilter = FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter("CVS"));
150         files = FileUtils.listFiles(temporaryFolder, fileFilter, dirFilter);
151         fileNames = toFileNames(files);
152         assertTrue(fileNames.contains("dummy-build.xml"), "'dummy-build.xml' is missing");
153         assertTrue(fileNames.contains("dummy-index.html"), "'dummy-index.html' is missing");
154         assertFalse(fileNames.contains("Entries"), "'Entries' shouldn't be found");
155         //
156         // Do the same as above but now with the filter coming from FileFilterUtils
157         fileFilter = FileFilterUtils.trueFileFilter();
158         dirFilter = FileFilterUtils.makeCVSAware(null);
159         files = FileUtils.listFiles(temporaryFolder, fileFilter, dirFilter);
160         fileNames = toFileNames(files);
161         assertTrue(fileNames.contains("dummy-build.xml"), "'dummy-build.xml' is missing");
162         assertTrue(fileNames.contains("dummy-index.html"), "'dummy-index.html' is missing");
163         assertFalse(fileNames.contains("Entries"), "'Entries' shouldn't be found");
164         //
165         // Again with the CVS filter but now with a non-null parameter
166         fileFilter = FileFilterUtils.trueFileFilter();
167         dirFilter = FileFilterUtils.prefixFileFilter("sub");
168         dirFilter = FileFilterUtils.makeCVSAware(dirFilter);
169         files = FileUtils.listFiles(temporaryFolder, fileFilter, dirFilter);
170         fileNames = toFileNames(files);
171         assertTrue(fileNames.contains("dummy-build.xml"), "'dummy-build.xml' is missing");
172         assertTrue(fileNames.contains("dummy-index.html"), "'dummy-index.html' is missing");
173         assertFalse(fileNames.contains("Entries"), "'Entries' shouldn't be found");
174         // Edge case
175         assertThrows(NullPointerException.class, () -> FileUtils.listFiles(temporaryFolder, null, null));
176     }
177 
178     @Test
179     void testListFilesByExtension() {
180         final String[] extensions = {"xml", "txt"};
181 
182         Collection<File> files = FileUtils.listFiles(temporaryFolder, extensions, false);
183         assertEquals(1, files.size());
184         Collection<String> fileNames = toFileNames(files);
185         assertTrue(fileNames.contains("dummy-build.xml"));
186         assertFalse(fileNames.contains("README"));
187         assertFalse(fileNames.contains("dummy-file.txt"));
188 
189         files = FileUtils.listFiles(temporaryFolder, extensions, true);
190         fileNames = toFileNames(files);
191         assertEquals(4, fileNames.size(), fileNames::toString);
192         assertTrue(fileNames.contains("dummy-file.txt"));
193         assertFalse(fileNames.contains("dummy-index.html"));
194 
195         files = FileUtils.listFiles(temporaryFolder, null, false);
196         assertEquals(2, files.size(), files::toString);
197         fileNames = toFileNames(files);
198         assertTrue(fileNames.contains("dummy-build.xml"));
199         assertTrue(fileNames.contains("README"));
200         assertFalse(fileNames.contains("dummy-file.txt"));
201 
202         final File directory = new File(temporaryFolder, "subdir1/subsubdir1");
203         files = FileUtils.listFiles(directory, new String[] { "html" }, false);
204         fileNames = toFileNames(files);
205         assertFalse(files.isEmpty(), directory::toString);
206         assertTrue(fileNames.contains("dummy-index.html"));
207         assertFalse(fileNames.contains("dummy-indexhtml"));
208         files = FileUtils.listFiles(temporaryFolder, new String[] { "html" }, true);
209         fileNames = toFileNames(files);
210         assertFalse(files.isEmpty(), temporaryFolder::toString);
211         assertTrue(fileNames.contains("dummy-index.html"));
212         assertFalse(fileNames.contains("dummy-indexhtml"));
213     }
214 
215     @Test
216     void testListFilesMissing() {
217         assertTrue(FileUtils.listFiles(new File(temporaryFolder, "dir/does/not/exist/at/all"), null, false).isEmpty());
218     }
219 
220     @Test
221     void testListFilesWithDeletion() throws IOException {
222         final String[] extensions = {"xml", "txt"};
223         final List<File> list;
224         final File xFile = new File(temporaryFolder, "x.xml");
225         if (!xFile.createNewFile()) {
226             fail("could not create test file: " + xFile);
227         }
228         final Collection<File> files = FileUtils.listFiles(temporaryFolder, extensions, true);
229         assertEquals(5, files.size());
230         try (Stream<File> stream = Uncheck.get(() -> FileUtils.streamFiles(temporaryFolder, true, extensions))) {
231             assertTrue(xFile.delete());
232             list = stream.collect(Collectors.toList());
233             assertFalse(list.contains(xFile), list::toString);
234         }
235         assertEquals(4, list.size());
236     }
237 
238     /**
239      * Tests <a href="https://issues.apache.org/jira/browse/IO-856">IO-856</a> ListFiles should not fail on vanishing files.
240      */
241     @Test
242     void testListFilesWithDeletionThreaded() throws ExecutionException, InterruptedException {
243         // test for IO-856
244         // create random directory in tmp, create the directory if it does not exist
245         final Path tempPath = PathUtils.getTempDirectory().resolve("IO-856");
246         final File tempDir = tempPath.toFile();
247         if (!tempDir.exists() && !tempDir.mkdirs()) {
248             fail("Could not create file path: " + tempDir.getAbsolutePath());
249         }
250         final int waitTime = 10_000;
251         final int maxFiles = 500;
252         final byte[] bytes = "TEST".getBytes(StandardCharsets.UTF_8);
253         final CompletableFuture<Void> c1 = CompletableFuture.runAsync(() -> {
254             final long endTime = System.currentTimeMillis() + waitTime;
255             int count = 0;
256             while (System.currentTimeMillis() < endTime && count < maxFiles) {
257                 final File file = new File(tempDir.getAbsolutePath(), UUID.randomUUID() + ".deletetester");
258                 file.deleteOnExit();
259                 try {
260                     Files.write(file.toPath(), bytes);
261                     count++;
262                 } catch (final Exception e) {
263                     fail("Could not create test file: '" + file.getAbsolutePath() + "': " + e, e);
264                 }
265                 if (!file.delete()) {
266                     fail("Could not delete test file: '" + file.getAbsolutePath() + "'");
267                 }
268             }
269             // System.out.printf("Created %,d%n", count);
270         });
271         final CompletableFuture<Void> c2 = CompletableFuture.runAsync(() -> {
272             final long endTime = System.currentTimeMillis() + waitTime;
273             int max = 0;
274             try {
275                 while (System.currentTimeMillis() < endTime) {
276                     final Collection<File> files = FileUtils.listFiles(tempDir, new String[] { ".deletetester" }, false);
277                     assertNotNull(files);
278                     max = Math.max(max, files.size());
279                 }
280             } catch (final Exception e) {
281                 System.out.printf("List size max %,d%n", max);
282                 fail("IO-856 test failure: " + e, e);
283                 // The exception can be hidden.
284                 e.printStackTrace();
285             }
286             // System.out.printf("List size max %,d%n", max);
287         });
288         // wait for the threads to finish
289         c1.get();
290         c2.get();
291     }
292 
293     private Collection<String> toFileNames(final Collection<File> files) {
294         return files.stream().map(File::getName).collect(Collectors.toList());
295     }
296 
297     /**
298      * Consumes and closes the underlying stream.
299      *
300      * @param files The iterator to consume.
301      * @return a new collection.
302      */
303     private Collection<String> toFileNames(final Iterator<File> files) {
304         final Collection<String> fileNames = new ArrayList<>();
305         // Iterator.forEachRemaining() closes the underlying stream.
306         files.forEachRemaining(f -> fileNames.add(f.getName()));
307         return fileNames;
308     }
309 
310 }