View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.shared.utils.io;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.List;
26  
27  import org.apache.maven.shared.utils.Os;
28  import org.apache.maven.shared.utils.testhelpers.FileTestHelper;
29  import org.junit.Assert;
30  import org.junit.Ignore;
31  import org.junit.Rule;
32  import org.junit.Test;
33  import org.junit.rules.ExpectedException;
34  import org.junit.rules.TemporaryFolder;
35  
36  import static org.junit.Assert.assertEquals;
37  import static org.junit.Assert.assertTrue;
38  import static org.junit.Assume.assumeFalse;
39  
40  @SuppressWarnings("deprecation")
41  public class DirectoryScannerTest {
42      private static final String[] NONE = new String[0];
43  
44      @Rule
45      public TemporaryFolder tempFolder = new TemporaryFolder();
46  
47      private void createTestData() throws IOException {
48          File rootDir = tempFolder.getRoot();
49          File folder1 = new File(rootDir, "folder1");
50          if (!folder1.mkdirs()) {
51              Assert.fail();
52          }
53  
54          FileTestHelper.generateTestFile(new File(rootDir, "file1.txt"), 11);
55          FileTestHelper.generateTestFile(new File(rootDir, "file2.txt"), 12);
56          FileTestHelper.generateTestFile(new File(rootDir, "file3.dat"), 13);
57  
58          FileTestHelper.generateTestFile(new File(folder1, "file4.txt"), 14);
59          FileTestHelper.generateTestFile(new File(folder1, "file5.dat"), 15);
60  
61          File folder2 = new File(folder1, "ignorefolder");
62          if (!folder2.mkdirs()) {
63              Assert.fail();
64          }
65          FileTestHelper.generateTestFile(new File(folder2, "file7.txt"), 17);
66      }
67  
68      @Test
69      public void testSimpleScan() throws Exception {
70          createTestData();
71  
72          fitScanTest(
73                  true,
74                  true,
75                  true,
76                  /* includes */ null,
77                  /* excludes */ null,
78                  /* expInclFiles */
79                  new String[] {"file1.txt", "file2.txt", "file3.dat", "folder1/file4.txt", "folder1/file5.dat"},
80                  /* expInclDirs */ new String[] {"", "folder1"},
81                  /* expNotInclFiles */ NONE,
82                  /* expNotInclDirs  */ NONE,
83                  /* expNotExclFiles */ NONE,
84                  /* expNotExclDirs  */ NONE);
85  
86          // same without followSymlinks
87          fitScanTest(
88                  true,
89                  false,
90                  true,
91                  /* includes */ null,
92                  /* excludes */ null,
93                  /* expInclFiles */
94                  new String[] {"file1.txt", "file2.txt", "file3.dat", "folder1/file4.txt", "folder1/file5.dat"},
95                  /* expInclDirs */ new String[] {"", "folder1"},
96                  /* expNotInclFiles */ NONE,
97                  /* expNotInclDirs  */ NONE,
98                  /* expNotExclFiles */ NONE,
99                  /* expNotExclDirs  */ NONE);
100     }
101 
102     @Test
103     public void testSimpleIncludes() throws Exception {
104         createTestData();
105 
106         fitScanTest(
107                 true,
108                 true,
109                 true,
110                 /* includes        */ new String[] {"**/*.dat", "*.somethingelse"},
111                 /* excludes        */ null,
112                 /* expInclFiles    */ new String[] {"file3.dat", "folder1/file5.dat"},
113                 /* expInclDirs     */ NONE,
114                 /* expNotInclFiles */ new String[] {"file1.txt", "file2.txt", "folder1/file4.txt"},
115                 /* expNotInclDirs  */ new String[] {"", "folder1"},
116                 /* expExclFiles    */ NONE,
117                 /* expExclDirs     */ NONE);
118 
119         // same without followSymlinks
120         fitScanTest(
121                 true,
122                 false,
123                 true,
124                 /* includes        */ new String[] {"**/*.dat", "*.somethingelse"},
125                 /* excludes        */ null,
126                 /* expInclFiles    */ new String[] {"file3.dat", "folder1/file5.dat"},
127                 /* expInclDirs     */ NONE,
128                 /* expNotInclFiles */ new String[] {"file1.txt", "file2.txt", "folder1/file4.txt"},
129                 /* expNotInclDirs  */ new String[] {"", "folder1"},
130                 /* expExclFiles    */ NONE,
131                 /* expExclDirs     */ NONE);
132     }
133 
134     @Rule
135     public ExpectedException xcludesNPExRule = ExpectedException.none();
136 
137     @Test
138     public void testIncludesWithNull() throws Exception {
139         testXcludesWithNull(new String[] {null}, null, "includes");
140     }
141 
142     @Test
143     public void testExcludesWithNull() throws Exception {
144         testXcludesWithNull(null, new String[] {null}, "excludes");
145     }
146 
147     private void testXcludesWithNull(String[] includes, String[] excludes, String listName) throws Exception {
148         createTestData();
149         xcludesNPExRule.expect(NullPointerException.class);
150         xcludesNPExRule.expectMessage("If a non-null " + listName + " list is given, all elements must be non-null");
151 
152         fitScanTest(
153                 true,
154                 true,
155                 true,
156                 /* includes        */ includes,
157                 /* excludes        */ excludes,
158                 /* expInclFiles    */ new String[] {"file3.dat", "folder1/file5.dat"},
159                 /* expInclDirs     */ NONE,
160                 /* expNotInclFiles */ new String[] {"file1.txt", "file2.txt", "folder1/file4.txt"},
161                 /* expNotInclDirs  */ new String[] {"", "folder1"},
162                 /* expExclFiles    */ NONE,
163                 /* expExclDirs     */ NONE);
164     }
165 
166     @Test
167     public void checkSymlinkBehaviour() {
168         DirectoryScanner ds = new DirectoryScanner();
169         ds.setBasedir(new File("src/test/resources/symlinks/src"));
170         ds.setFollowSymlinks(false);
171         ds.scan();
172 
173         String[] includedDirectories = ds.getIncludedDirectories();
174         // FIXME 3 (Windows) and 5 (Linux) are both wrong. The correct answer is 4.
175         // This method is broken in different ways on different operating systems.
176         assertTrue(includedDirectories.length == 3 || includedDirectories.length == 5);
177 
178         String[] files = ds.getIncludedFiles();
179         assertAlwaysIncluded(Arrays.asList(files));
180 
181         // FIXME getIncludedFiles is broken on Windows; correct answer is 9
182         assertTrue("files.length is " + files.length, files.length == 9 || files.length == 11);
183     }
184 
185     @Test
186     public void followSymlinksFalse() throws IOException {
187         assumeFalse(Os.isFamily(Os.FAMILY_WINDOWS));
188 
189         File testDir = SymlinkTestSetup.createStandardSymlinkTestDir(new File("target/test/symlinkTestCase"));
190 
191         DirectoryScanner ds = new DirectoryScanner();
192         ds.setBasedir(testDir);
193         ds.setFollowSymlinks(false);
194         ds.scan();
195         List<String> included = Arrays.asList(ds.getIncludedFiles());
196         assertAlwaysIncluded(included);
197         assertEquals(9, included.size());
198         List<String> includedDirs = Arrays.asList(ds.getIncludedDirectories());
199         assertTrue(includedDirs.contains(""));
200         assertTrue(includedDirs.contains("aRegularDir"));
201         assertTrue(includedDirs.contains("symDir"));
202         assertTrue(includedDirs.contains("symLinkToDirOnTheOutside"));
203         assertTrue(includedDirs.contains("targetDir"));
204         assertEquals(5, includedDirs.size());
205     }
206 
207     private void assertAlwaysIncluded(List<String> included) {
208         assertTrue(included.contains("aRegularDir" + File.separator + "aRegularFile.txt"));
209         assertTrue(included.contains("targetDir" + File.separator + "targetFile.txt"));
210         assertTrue(included.contains("fileR.txt"));
211         assertTrue(included.contains("fileW.txt"));
212         assertTrue(included.contains("fileX.txt"));
213         assertTrue(included.contains("symR"));
214         assertTrue(included.contains("symW"));
215         assertTrue(included.contains("symX"));
216         assertTrue(included.contains("symLinkToFileOnTheOutside"));
217     }
218 
219     @Test
220     public void followSymlinks() throws IOException {
221         assumeFalse(Os.isFamily(Os.FAMILY_WINDOWS));
222 
223         DirectoryScanner ds = new DirectoryScanner();
224         File testDir = SymlinkTestSetup.createStandardSymlinkTestDir(new File("target/test/symlinkTestCase"));
225 
226         ds.setBasedir(testDir);
227         ds.setFollowSymlinks(true);
228         ds.scan();
229         List<String> included = Arrays.asList(ds.getIncludedFiles());
230         assertAlwaysIncluded(included);
231         assertTrue(included.contains("symDir/targetFile.txt"));
232         assertTrue(included.contains("symLinkToDirOnTheOutside/FileInDirOnTheOutside.txt"));
233         assertEquals(11, included.size());
234 
235         List<String> includedDirs = Arrays.asList(ds.getIncludedDirectories());
236         assertTrue(includedDirs.contains("")); // w00t !
237         assertTrue(includedDirs.contains("aRegularDir"));
238         assertTrue(includedDirs.contains("symDir"));
239         assertTrue(includedDirs.contains("symLinkToDirOnTheOutside"));
240         assertTrue(includedDirs.contains("targetDir"));
241         assertEquals(5, includedDirs.size());
242     }
243 
244     /*
245        Creates a standard directory layout with symlinks and files.
246     */
247     @Test
248     public void testSimpleExcludes() throws Exception {
249         createTestData();
250 
251         fitScanTest(
252                 true,
253                 true,
254                 true,
255                 /* includes        */ null,
256                 /* excludes        */ new String[] {"**/*.dat", "*.somethingelse"},
257                 /* expInclFiles    */ new String[] {"file1.txt", "file2.txt", "folder1/file4.txt"},
258                 /* expInclDirs     */ new String[] {"", "folder1"},
259                 /* expNotInclFiles */ NONE,
260                 /* expNotInclDirs  */ NONE,
261                 /* expExclFiles    */ new String[] {"file3.dat", "folder1/file5.dat"},
262                 /* expExclDirs     */ NONE);
263 
264         // same without followSymlinks
265         fitScanTest(
266                 true,
267                 false,
268                 true,
269                 /* includes        */ null,
270                 /* excludes        */ new String[] {"**/*.dat", "*.somethingelse"},
271                 /* expInclFiles    */ new String[] {"file1.txt", "file2.txt", "folder1/file4.txt"},
272                 /* expInclDirs     */ new String[] {"", "folder1"},
273                 /* expNotInclFiles */ NONE,
274                 /* expNotInclDirs  */ NONE,
275                 /* expExclFiles    */ new String[] {"file3.dat", "folder1/file5.dat"},
276                 /* expExclDirs     */ NONE);
277     }
278 
279     public void testIsSymbolicLink() throws IOException {
280         File file = new File(".");
281         DirectoryScanner ds = new DirectoryScanner();
282         ds.isSymbolicLink(file, "abc");
283     }
284 
285     /**
286      * Performs a scan and test for the given parameters if not null.
287      */
288     private void fitScanTest(
289             boolean caseSensitive,
290             boolean followSymLinks,
291             boolean addDefaultExcludes,
292             String[] includes,
293             String[] excludes,
294             String[] expectedIncludedFiles,
295             String[] expectedIncludedDirectories,
296             String[] expectedNotIncludedFiles,
297             String[] expectedNotIncludedDirectories,
298             String[] expectedExcludedFiles,
299             String[] expectedExcludedDirectories) {
300         DirectoryScanner ds = new DirectoryScanner();
301         ds.setBasedir(tempFolder.getRoot());
302 
303         ds.setCaseSensitive(caseSensitive);
304         ds.setFollowSymlinks(followSymLinks);
305 
306         if (addDefaultExcludes) {
307             ds.addDefaultExcludes();
308         }
309         if (includes != null) {
310             ds.setIncludes(includes);
311         }
312         if (excludes != null) {
313             ds.setExcludes(excludes);
314         }
315 
316         TestScanConductor scanConductor = new TestScanConductor();
317 
318         ds.setScanConductor(scanConductor);
319 
320         ds.scan();
321 
322         checkFiles("expectedIncludedFiles", expectedIncludedFiles, ds.getIncludedFiles());
323         checkFiles("expectedIncludedDirectories", expectedIncludedDirectories, ds.getIncludedDirectories());
324         checkFiles("expectedNotIncludedFiles", expectedNotIncludedFiles, ds.getNotIncludedFiles());
325         checkFiles("expectedNotIncludedDirectories", expectedNotIncludedDirectories, ds.getNotIncludedDirectories());
326         checkFiles("expectedExcludedFiles", expectedExcludedFiles, ds.getExcludedFiles());
327         checkFiles("expectedExcludedDirectories", expectedExcludedDirectories, ds.getExcludedDirectories());
328 
329         checkFiles(
330                 "visitedFiles",
331                 expectedIncludedFiles,
332                 scanConductor.visitedFiles.toArray(new String[scanConductor.visitedFiles.size()]));
333     }
334 
335     /**
336      * Check if the resolved files match the rules of the expected files.
337      *
338      * @param expectedFiles
339      * @param resolvedFiles
340      */
341     private void checkFiles(String category, String[] expectedFiles, String[] resolvedFiles) {
342         if (expectedFiles != null) {
343             String msg = category + " expected: " + Arrays.toString(expectedFiles) + " but got: "
344                     + Arrays.toString(resolvedFiles);
345             Assert.assertNotNull(msg, resolvedFiles);
346             assertEquals(msg, expectedFiles.length, resolvedFiles.length);
347 
348             Arrays.sort(expectedFiles);
349             Arrays.sort(resolvedFiles);
350 
351             for (int i = 0; i < resolvedFiles.length; i++) {
352                 assertEquals(msg, expectedFiles[i], resolvedFiles[i].replace("\\", "/"));
353             }
354         }
355     }
356 
357     private static class TestScanConductor implements ScanConductor {
358         final List<String> visitedFiles = new ArrayList<String>();
359 
360         public ScanConductor.ScanAction visitDirectory(String name, File directory) {
361             assertTrue(directory.isDirectory());
362 
363             if (directory.getName().equals("ignorefolder")) {
364                 return ScanAction.NO_RECURSE;
365             }
366 
367             return ScanAction.CONTINUE;
368         }
369 
370         public ScanConductor.ScanAction visitFile(String name, File file) {
371             assertTrue(file.isFile());
372             visitedFiles.add(name);
373             return ScanAction.CONTINUE;
374         }
375     }
376 
377     private void removeAndAddSomeFiles() throws IOException {
378         File rootDir = tempFolder.getRoot();
379         File file2 = new File(rootDir, "file2.txt");
380         file2.delete();
381 
382         FileTestHelper.generateTestFile(new File(rootDir, "folder1/file9.txt"), 15);
383 
384         File folder2 = new File(rootDir, "folder1/ignorefolder");
385         FileUtils.deleteDirectory(folder2);
386     }
387 
388     @Test
389     public void testScanDiff() throws Exception {
390         createTestData();
391 
392         DirectoryScanner dss = new DirectoryScanner();
393         dss.setBasedir(tempFolder.getRoot());
394         Assert.assertNotNull(dss);
395 
396         // we take the initial snapshot
397         dss.scan();
398         String[] oldFiles = dss.getIncludedFiles();
399 
400         // now we change 3 files. add one and remove
401         removeAndAddSomeFiles();
402 
403         dss.scan();
404 
405         DirectoryScanResult dsr = dss.diffIncludedFiles(oldFiles);
406 
407         String[] addedFiles = dsr.getFilesAdded();
408         String[] removedFiles = dsr.getFilesRemoved();
409         Assert.assertNotNull(addedFiles);
410         Assert.assertNotNull(removedFiles);
411         assertEquals(1, addedFiles.length);
412         assertEquals(2, removedFiles.length);
413     }
414 
415     @Ignore("Enable this test to run performance checks")
416     @Test
417     public void performanceTest() throws Exception {
418 
419         File rootFolder = tempFolder.getRoot();
420 
421         // do some warmup
422         for (int i = 1; i < 200; i++) {
423             createTestData();
424             removeAndAddSomeFiles();
425             FileUtils.deleteDirectory(rootFolder);
426         }
427 
428         int cycles = 2000;
429 
430         // and now we take the time _without_
431         long startTime = System.nanoTime();
432         for (int i = 1; i < cycles; i++) {
433             createTestData();
434             removeAndAddSomeFiles();
435             FileUtils.deleteDirectory(rootFolder);
436             rootFolder.mkdir();
437         }
438         long endTime = System.nanoTime();
439 
440         long durationEmptyRun = endTime - startTime;
441         System.out.println("durationEmptyRun            [ns]: " + durationEmptyRun);
442 
443         startTime = System.nanoTime();
444         for (int i = 1; i < cycles; i++) {
445             createTestData();
446             DirectoryScanner directoryScanner = new DirectoryScanner();
447             directoryScanner.setBasedir(rootFolder);
448             directoryScanner.scan();
449             String[] oldFiles = directoryScanner.getIncludedFiles();
450 
451             removeAndAddSomeFiles();
452 
453             directoryScanner.scan();
454 
455             DirectoryScanResult directoryScanResult = directoryScanner.diffIncludedFiles(oldFiles);
456             Assert.assertNotNull(directoryScanResult);
457 
458             FileUtils.deleteDirectory(rootFolder);
459             rootFolder.mkdir();
460         }
461         endTime = System.nanoTime();
462 
463         long durationWithSnapshotScanner = endTime - startTime;
464         System.out.println("durationWithSnapshotScanner [ns]: " + durationWithSnapshotScanner);
465 
466         long dirScannerOverhead = durationWithSnapshotScanner - durationEmptyRun;
467 
468         System.out.println("Overhead for n cycles [ns]: " + dirScannerOverhead);
469     }
470 }