1 package org.codehaus.plexus.util;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.net.URI;
22 import java.net.URISyntaxException;
23 import java.net.URL;
24 import java.nio.file.Files;
25 import java.nio.file.Paths;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.HashSet;
29 import java.util.List;
30 import java.util.Set;
31
32 import org.junit.jupiter.api.BeforeEach;
33 import org.junit.jupiter.api.Test;
34
35 import static org.junit.jupiter.api.Assertions.assertEquals;
36 import static org.junit.jupiter.api.Assertions.assertFalse;
37 import static org.junit.jupiter.api.Assertions.assertTrue;
38 import static org.junit.jupiter.api.Assertions.fail;
39 import static org.junit.jupiter.api.Assumptions.assumeTrue;
40
41
42
43
44
45
46
47
48 public class DirectoryScannerTest extends FileBasedTestCase {
49 private static String testDir = getTestDirectory().getPath();
50
51
52
53
54 @BeforeEach
55 public void setUp() {
56 try {
57 FileUtils.deleteDirectory(testDir);
58 } catch (IOException e) {
59 fail("Could not delete directory " + testDir);
60 }
61 }
62
63
64
65
66
67
68
69 @Test
70 public void testCrossPlatformIncludesString() throws IOException, URISyntaxException {
71 DirectoryScanner ds = new DirectoryScanner();
72 ds.setBasedir(new File(getTestResourcesDir() + File.separator + "directory-scanner").getCanonicalFile());
73
74 String fs;
75 if (File.separatorChar == '/') {
76 fs = "\\";
77 } else {
78 fs = "/";
79 }
80
81 ds.setIncludes(new String[] {"foo" + fs});
82 ds.addDefaultExcludes();
83 ds.scan();
84
85 String[] files = ds.getIncludedFiles();
86 assertEquals(1, files.length);
87 }
88
89
90
91
92
93
94
95 @Test
96 public void testCrossPlatformExcludesString() throws IOException, URISyntaxException {
97 DirectoryScanner ds = new DirectoryScanner();
98 ds.setBasedir(new File(getTestResourcesDir() + File.separator + "directory-scanner").getCanonicalFile());
99 ds.setIncludes(new String[] {"**"});
100
101 String fs;
102 if (File.separatorChar == '/') {
103 fs = "\\";
104 } else {
105 fs = "/";
106 }
107
108 ds.setExcludes(new String[] {"foo" + fs});
109 ds.addDefaultExcludes();
110 ds.scan();
111
112 String[] files = ds.getIncludedFiles();
113 assertEquals(0, files.length);
114 }
115
116 private String getTestResourcesDir() throws URISyntaxException {
117 ClassLoader cloader = Thread.currentThread().getContextClassLoader();
118 URL resource = cloader.getResource("test.txt");
119 if (resource == null) {
120 fail("Cannot locate test-resources directory containing 'test.txt' in the classloader.");
121 }
122
123 File file = new File(new URI(resource.toExternalForm()).normalize().getPath());
124
125 return file.getParent();
126 }
127
128 private void createTestFiles() throws IOException {
129 FileUtils.mkdir(testDir);
130 this.createFile(new File(testDir + "/scanner1.dat"), 0);
131 this.createFile(new File(testDir + "/scanner2.dat"), 0);
132 this.createFile(new File(testDir + "/scanner3.dat"), 0);
133 this.createFile(new File(testDir + "/scanner4.dat"), 0);
134 this.createFile(new File(testDir + "/scanner5.dat"), 0);
135 }
136
137
138
139
140
141
142
143
144 private boolean checkTestFilesSymlinks() {
145 File symlinksDirectory = new File("src/test/resources/symlinks/src");
146 try {
147 List<String> symlinks =
148 FileUtils.getFileAndDirectoryNames(symlinksDirectory, "sym*", null, true, true, true, true);
149 if (symlinks.isEmpty()) {
150 throw new IOException("Symlinks files/directories are not present");
151 }
152 for (String symLink : symlinks) {
153 if (!Files.isSymbolicLink(Paths.get(symLink))) {
154 throw new IOException(String.format("Path is not a symlink: %s", symLink));
155 }
156 }
157 return true;
158 } catch (IOException e) {
159 System.err.println(String.format(
160 "The unit test '%s.%s' will be skipped, reason: %s",
161 this.getClass().getSimpleName(), getTestMethodName(), e.getMessage()));
162 System.out.println(
163 String.format("This test requires symlinks files in '%s' directory.", symlinksDirectory.getPath()));
164 System.out.println("On some OS (like Windows 10), files are present only if the clone/checkout is done"
165 + " in administrator mode, and correct (symlinks and not flat file/directory)"
166 + " if symlinks option are used (for git: git clone -c core.symlinks=true [url])");
167 return false;
168 }
169 }
170
171
172
173
174
175
176 @Test
177 public void testGeneral() throws IOException {
178 this.createTestFiles();
179
180 String includes = "scanner1.dat,scanner2.dat,scanner3.dat,scanner4.dat,scanner5.dat";
181 String excludes = "scanner1.dat,scanner2.dat";
182
183 List<File> fileNames = FileUtils.getFiles(new File(testDir), includes, excludes, false);
184
185 assertEquals(3, fileNames.size(), "Wrong number of results.");
186 assertTrue(fileNames.contains(new File("scanner3.dat")), "3 not found.");
187 assertTrue(fileNames.contains(new File("scanner4.dat")), "4 not found.");
188 assertTrue(fileNames.contains(new File("scanner5.dat")), "5 not found.");
189 }
190
191
192
193
194
195
196 @Test
197 public void testIncludesExcludesWithWhiteSpaces() throws IOException {
198 this.createTestFiles();
199
200 String includes = "scanner1.dat,\n \n,scanner2.dat \n\r, scanner3.dat\n, \tscanner4.dat,scanner5.dat\n,";
201
202 String excludes = "scanner1.dat,\n \n,scanner2.dat \n\r,,";
203
204 List<File> fileNames = FileUtils.getFiles(new File(testDir), includes, excludes, false);
205
206 assertEquals(3, fileNames.size(), "Wrong number of results.");
207 assertTrue(fileNames.contains(new File("scanner3.dat")), "3 not found.");
208 assertTrue(fileNames.contains(new File("scanner4.dat")), "4 not found.");
209 assertTrue(fileNames.contains(new File("scanner5.dat")), "5 not found.");
210 }
211
212
213
214
215 @Test
216 public void testFollowSymlinksFalse() {
217 assumeTrue(checkTestFilesSymlinks());
218
219 DirectoryScanner ds = new DirectoryScanner();
220 ds.setBasedir(new File("src/test/resources/symlinks/src/"));
221 ds.setFollowSymlinks(false);
222 ds.scan();
223 List<String> included = Arrays.asList(ds.getIncludedFiles());
224 assertAlwaysIncluded(included);
225 assertEquals(9, included.size());
226 List<String> includedDirs = Arrays.asList(ds.getIncludedDirectories());
227 assertTrue(includedDirs.contains(""));
228 assertTrue(includedDirs.contains("aRegularDir"));
229 assertTrue(includedDirs.contains("symDir"));
230 assertTrue(includedDirs.contains("symLinkToDirOnTheOutside"));
231 assertTrue(includedDirs.contains("targetDir"));
232 assertEquals(5, includedDirs.size());
233 }
234
235 private void assertAlwaysIncluded(List<String> included) {
236 assertTrue(included.contains("aRegularDir" + File.separator + "aRegularFile.txt"));
237 assertTrue(included.contains("targetDir" + File.separator + "targetFile.txt"));
238 assertTrue(included.contains("fileR.txt"));
239 assertTrue(included.contains("fileW.txt"));
240 assertTrue(included.contains("fileX.txt"));
241 assertTrue(included.contains("symR"));
242 assertTrue(included.contains("symW"));
243 assertTrue(included.contains("symX"));
244 assertTrue(included.contains("symLinkToFileOnTheOutside"));
245 }
246
247
248
249
250 @Test
251 public void testFollowSymlinks() {
252 assumeTrue(checkTestFilesSymlinks());
253
254 DirectoryScanner ds = new DirectoryScanner();
255 ds.setBasedir(new File("src/test/resources/symlinks/src/"));
256 ds.setFollowSymlinks(true);
257 ds.scan();
258 List<String> included = Arrays.asList(ds.getIncludedFiles());
259 assertAlwaysIncluded(included);
260 assertTrue(included.contains("symDir" + File.separator + "targetFile.txt"));
261 assertTrue(included.contains("symLinkToDirOnTheOutside" + File.separator + "FileInDirOnTheOutside.txt"));
262 assertEquals(11, included.size());
263
264 List<String> includedDirs = Arrays.asList(ds.getIncludedDirectories());
265 assertTrue(includedDirs.contains(""));
266 assertTrue(includedDirs.contains("aRegularDir"));
267 assertTrue(includedDirs.contains("symDir"));
268 assertTrue(includedDirs.contains("symLinkToDirOnTheOutside"));
269 assertTrue(includedDirs.contains("targetDir"));
270 assertEquals(5, includedDirs.size());
271 }
272
273 private void createTestDirectories() throws IOException {
274 FileUtils.mkdir(testDir + File.separator + "directoryTest");
275 FileUtils.mkdir(testDir + File.separator + "directoryTest" + File.separator + "testDir123");
276 FileUtils.mkdir(testDir + File.separator + "directoryTest" + File.separator + "test_dir_123");
277 FileUtils.mkdir(testDir + File.separator + "directoryTest" + File.separator + "test-dir-123");
278 this.createFile(
279 new File(testDir + File.separator + "directoryTest" + File.separator + "testDir123" + File.separator
280 + "file1.dat"),
281 0);
282 this.createFile(
283 new File(testDir + File.separator + "directoryTest" + File.separator + "test_dir_123" + File.separator
284 + "file1.dat"),
285 0);
286 this.createFile(
287 new File(testDir + File.separator + "directoryTest" + File.separator + "test-dir-123" + File.separator
288 + "file1.dat"),
289 0);
290 }
291
292
293
294
295
296
297 @Test
298 public void testDirectoriesWithHyphens() throws IOException {
299 this.createTestDirectories();
300
301 DirectoryScanner ds = new DirectoryScanner();
302 String[] includes = {"**/*.dat"};
303 String[] excludes = {""};
304 ds.setIncludes(includes);
305 ds.setExcludes(excludes);
306 ds.setBasedir(new File(testDir + File.separator + "directoryTest"));
307 ds.setCaseSensitive(true);
308 ds.scan();
309
310 String[] files = ds.getIncludedFiles();
311 assertEquals(3, files.length, "Wrong number of results.");
312 }
313
314
315
316
317
318
319 @Test
320 public void testAntExcludesOverrideIncludes() throws IOException {
321 printTestHeader();
322
323 File dir = new File(testDir, "regex-dir");
324 dir.mkdirs();
325
326 String[] excludedPaths = {"target/foo.txt"};
327
328 createFiles(dir, excludedPaths);
329
330 String[] includedPaths = {"src/main/resources/project/target/foo.txt"};
331
332 createFiles(dir, includedPaths);
333
334 DirectoryScanner ds = new DirectoryScanner();
335
336 String[] includes = {"**/target/*"};
337 String[] excludes = {"target/*"};
338
339
340
341
342
343
344 ds.setIncludes(includes);
345 ds.setExcludes(excludes);
346 ds.setBasedir(dir);
347 ds.scan();
348
349 assertInclusionsAndExclusions(ds.getIncludedFiles(), excludedPaths, includedPaths);
350 }
351
352
353
354
355
356
357 @org.junit.jupiter.api.Test
358 public void testAntExcludesOverrideIncludesWithExplicitAntPrefix() throws IOException {
359 printTestHeader();
360
361 File dir = new File(testDir, "regex-dir");
362 dir.mkdirs();
363
364 String[] excludedPaths = {"target/foo.txt"};
365
366 createFiles(dir, excludedPaths);
367
368 String[] includedPaths = {"src/main/resources/project/target/foo.txt"};
369
370 createFiles(dir, includedPaths);
371
372 DirectoryScanner ds = new DirectoryScanner();
373
374 String[] includes = {SelectorUtils.ANT_HANDLER_PREFIX + "**/target/**/*" + SelectorUtils.PATTERN_HANDLER_SUFFIX
375 };
376 String[] excludes = {SelectorUtils.ANT_HANDLER_PREFIX + "target/**/*" + SelectorUtils.PATTERN_HANDLER_SUFFIX};
377
378
379
380
381
382
383 ds.setIncludes(includes);
384 ds.setExcludes(excludes);
385 ds.setBasedir(dir);
386 ds.scan();
387
388 assertInclusionsAndExclusions(ds.getIncludedFiles(), excludedPaths, includedPaths);
389 }
390
391
392
393
394
395
396 @org.junit.jupiter.api.Test
397 public void testRegexIncludeWithExcludedPrefixDirs() throws IOException {
398 printTestHeader();
399
400 File dir = new File(testDir, "regex-dir");
401 dir.mkdirs();
402
403 String[] excludedPaths = {"src/main/foo.txt"};
404
405 createFiles(dir, excludedPaths);
406
407 String[] includedPaths = {"src/main/resources/project/target/foo.txt"};
408
409 createFiles(dir, includedPaths);
410
411 String regex = ".+/target.*";
412
413 DirectoryScanner ds = new DirectoryScanner();
414
415 String includeExpr = SelectorUtils.REGEX_HANDLER_PREFIX + regex + SelectorUtils.PATTERN_HANDLER_SUFFIX;
416
417 String[] includes = {includeExpr};
418 ds.setIncludes(includes);
419 ds.setBasedir(dir);
420 ds.scan();
421
422 assertInclusionsAndExclusions(ds.getIncludedFiles(), excludedPaths, includedPaths);
423 }
424
425
426
427
428
429
430 @org.junit.jupiter.api.Test
431 public void testRegexExcludeWithNegativeLookahead() throws IOException {
432 printTestHeader();
433
434 File dir = new File(testDir, "regex-dir");
435 try {
436 FileUtils.deleteDirectory(dir);
437 } catch (IOException e) {
438 }
439
440 dir.mkdirs();
441
442 String[] excludedPaths = {"target/foo.txt"};
443
444 createFiles(dir, excludedPaths);
445
446 String[] includedPaths = {"src/main/resources/project/target/foo.txt"};
447
448 createFiles(dir, includedPaths);
449
450 String regex = "(?!.*src/).*target.*";
451
452 DirectoryScanner ds = new DirectoryScanner();
453
454 String excludeExpr = SelectorUtils.REGEX_HANDLER_PREFIX + regex + SelectorUtils.PATTERN_HANDLER_SUFFIX;
455
456 String[] excludes = {excludeExpr};
457 ds.setExcludes(excludes);
458 ds.setBasedir(dir);
459 ds.scan();
460
461 assertInclusionsAndExclusions(ds.getIncludedFiles(), excludedPaths, includedPaths);
462 }
463
464
465
466
467
468
469 @Test
470 public void testRegexWithSlashInsideCharacterClass() throws IOException {
471 printTestHeader();
472
473 File dir = new File(testDir, "regex-dir");
474 try {
475 FileUtils.deleteDirectory(dir);
476 } catch (IOException e) {
477 }
478
479 dir.mkdirs();
480
481 String[] excludedPaths = {"target/foo.txt", "target/src/main/target/foo.txt"};
482
483 createFiles(dir, excludedPaths);
484
485 String[] includedPaths = {"module/src/main/target/foo.txt"};
486
487 createFiles(dir, includedPaths);
488
489
490 String regex = "(?!((?!target/)[^/]+/)*src/).*target.*";
491
492 DirectoryScanner ds = new DirectoryScanner();
493
494 String excludeExpr = SelectorUtils.REGEX_HANDLER_PREFIX + regex + SelectorUtils.PATTERN_HANDLER_SUFFIX;
495
496 String[] excludes = {excludeExpr};
497 ds.setExcludes(excludes);
498 ds.setBasedir(dir);
499 ds.scan();
500
501 assertInclusionsAndExclusions(ds.getIncludedFiles(), excludedPaths, includedPaths);
502 }
503
504
505
506
507
508
509
510 @Test
511 public void testDoNotScanUnnecesaryDirectories() throws IOException {
512 createTestDirectories();
513
514
515 FileUtils.mkdir(testDir + File.separator + "directoryTest" + File.separator + "testDir123" + File.separator
516 + "anotherDir1");
517 FileUtils.mkdir(testDir + File.separator + "directoryTest" + File.separator + "test_dir_123" + File.separator
518 + "anotherDir2");
519 FileUtils.mkdir(testDir + File.separator + "directoryTest" + File.separator + "test-dir-123" + File.separator
520 + "anotherDir3");
521
522 this.createFile(
523 new File(testDir + File.separator + "directoryTest" + File.separator + "testDir123" + File.separator
524 + "anotherDir1" + File.separator + "file1.dat"),
525 0);
526 this.createFile(
527 new File(testDir + File.separator + "directoryTest" + File.separator + "test_dir_123" + File.separator
528 + "anotherDir2" + File.separator + "file1.dat"),
529 0);
530 this.createFile(
531 new File(testDir + File.separator + "directoryTest" + File.separator + "test-dir-123" + File.separator
532 + "anotherDir3" + File.separator + "file1.dat"),
533 0);
534
535 String[] excludedPaths = {
536 "directoryTest" + File.separator + "testDir123" + File.separator + "anotherDir1" + File.separator
537 + "file1.dat",
538 "directoryTest" + File.separator + "test_dir_123" + File.separator + "anotherDir2" + File.separator
539 + "file1.dat",
540 "directoryTest" + File.separator + "test-dir-123" + File.separator + "anotherDir3" + File.separator
541 + "file1.dat"
542 };
543
544 String[] includedPaths = {
545 "directoryTest" + File.separator + "testDir123" + File.separator + "file1.dat",
546 "directoryTest" + File.separator + "test_dir_123" + File.separator + "file1.dat",
547 "directoryTest" + File.separator + "test-dir-123" + File.separator + "file1.dat"
548 };
549
550 final Set<String> scannedDirSet = new HashSet<String>();
551
552 DirectoryScanner ds = new DirectoryScanner() {
553 @Override
554 protected void scandir(File dir, String vpath, boolean fast) {
555 scannedDirSet.add(dir.getName());
556 super.scandir(dir, vpath, fast);
557 }
558 };
559
560
561 String[] includes = {"directoryTest" + File.separator + "*" + File.separator + "file1.dat"};
562 ds.setIncludes(includes);
563 ds.setBasedir(new File(testDir));
564 ds.scan();
565
566 assertInclusionsAndExclusions(ds.getIncludedFiles(), excludedPaths, includedPaths);
567
568 Set<String> expectedScannedDirSet =
569 new HashSet<String>(Arrays.asList("io", "directoryTest", "testDir123", "test_dir_123", "test-dir-123"));
570
571 assertEquals(expectedScannedDirSet, scannedDirSet);
572 }
573
574
575
576
577
578
579 @Test
580 public void testIsSymbolicLink() throws IOException {
581 assumeTrue(checkTestFilesSymlinks());
582
583 final File directory = new File("src/test/resources/symlinks/src");
584 DirectoryScanner ds = new DirectoryScanner();
585 assertTrue(ds.isSymbolicLink(directory, "symR"));
586 assertTrue(ds.isSymbolicLink(directory, "symDir"));
587 assertFalse(ds.isSymbolicLink(directory, "fileR.txt"));
588 assertFalse(ds.isSymbolicLink(directory, "aRegularDir"));
589 }
590
591
592
593
594
595
596 @Test
597 public void testIsParentSymbolicLink() throws IOException {
598 assumeTrue(checkTestFilesSymlinks());
599
600 final File directory = new File("src/test/resources/symlinks/src");
601 DirectoryScanner ds = new DirectoryScanner();
602 assertFalse(ds.isParentSymbolicLink(directory, "symR"));
603 assertFalse(ds.isParentSymbolicLink(directory, "symDir"));
604 assertFalse(ds.isParentSymbolicLink(directory, "fileR.txt"));
605 assertFalse(ds.isParentSymbolicLink(directory, "aRegularDir"));
606 assertFalse(ds.isParentSymbolicLink(new File(directory, "aRegularDir"), "aRegulatFile.txt"));
607 assertTrue(ds.isParentSymbolicLink(new File(directory, "symDir"), "targetFile.txt"));
608 assertTrue(
609 ds.isParentSymbolicLink(new File(directory, "symLinkToDirOnTheOutside"), "FileInDirOnTheOutside.txt"));
610 }
611
612 private void printTestHeader() {
613 StackTraceElement ste = new Throwable().getStackTrace()[1];
614 System.out.println("Test: " + ste.getMethodName());
615 }
616
617 private void assertInclusionsAndExclusions(String[] files, String[] excludedPaths, String... includedPaths) {
618 Arrays.sort(files);
619
620 System.out.println("Included files: ");
621 for (String file : files) {
622 System.out.println(file);
623 }
624
625 List<String> failedToExclude = new ArrayList<String>();
626 for (String excludedPath : excludedPaths) {
627 String alt = excludedPath.replace('/', '\\');
628 System.out.println("Searching for exclusion as: " + excludedPath + "\nor: " + alt);
629 if (Arrays.binarySearch(files, excludedPath) > -1 || Arrays.binarySearch(files, alt) > -1) {
630 failedToExclude.add(excludedPath);
631 }
632 }
633
634 List<String> failedToInclude = new ArrayList<String>();
635 for (String includedPath : includedPaths) {
636 String alt = includedPath.replace('/', '\\');
637 System.out.println("Searching for inclusion as: " + includedPath + "\nor: " + alt);
638 if (Arrays.binarySearch(files, includedPath) < 0 && Arrays.binarySearch(files, alt) < 0) {
639 failedToInclude.add(includedPath);
640 }
641 }
642
643 StringBuilder buffer = new StringBuilder();
644 if (!failedToExclude.isEmpty()) {
645 buffer.append("Should NOT have included:\n").append(StringUtils.join(failedToExclude.iterator(), "\n\t- "));
646 }
647
648 if (!failedToInclude.isEmpty()) {
649 if (buffer.length() > 0) {
650 buffer.append("\n\n");
651 }
652
653 buffer.append("Should have included:\n").append(StringUtils.join(failedToInclude.iterator(), "\n\t- "));
654 }
655
656 if (buffer.length() > 0) {
657 fail(buffer.toString());
658 }
659 }
660
661 private void createFiles(File dir, String... paths) throws IOException {
662 for (String path1 : paths) {
663 String path = path1.replace('/', File.separatorChar).replace('\\', File.separatorChar);
664 File file = new File(dir, path);
665
666 if (path.endsWith(File.separator)) {
667 file.mkdirs();
668 } else {
669 if (file.getParentFile() != null) {
670 file.getParentFile().mkdirs();
671 }
672
673 createFile(file, 0);
674 }
675 }
676 }
677 }