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