DirectoryScannerTest.java
package org.codehaus.plexus.util;
/*
* Copyright The Codehaus Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
/**
* Base class for testcases doing tests with files.
*
* @author Dan T. Tran
* @version $Id: $Id
* @since 3.4.0
*/
class DirectoryScannerTest extends FileBasedTestCase {
private static final String testDir = getTestDirectory().getPath();
/**
* <p>setUp.</p>
*/
@BeforeEach
void setUp() {
try {
FileUtils.deleteDirectory(testDir);
} catch (IOException e) {
fail("Could not delete directory " + testDir);
}
}
/**
* <p>testCrossPlatformIncludesString.</p>
*
* @throws java.io.IOException if any.
* @throws java.net.URISyntaxException if any.
*/
@Test
void crossPlatformIncludesString() throws IOException, URISyntaxException {
DirectoryScanner ds = new DirectoryScanner();
ds.setBasedir(new File(getTestResourcesDir() + File.separator + "directory-scanner").getCanonicalFile());
String fs;
if (File.separatorChar == '/') {
fs = "\\";
} else {
fs = "/";
}
ds.setIncludes(new String[] {"foo" + fs});
ds.addDefaultExcludes();
ds.scan();
String[] files = ds.getIncludedFiles();
assertEquals(1, files.length);
}
/**
* <p>testCrossPlatformExcludesString.</p>
*
* @throws java.io.IOException if any.
* @throws java.net.URISyntaxException if any.
*/
@Test
void crossPlatformExcludesString() throws IOException, URISyntaxException {
DirectoryScanner ds = new DirectoryScanner();
ds.setBasedir(new File(getTestResourcesDir() + File.separator + "directory-scanner").getCanonicalFile());
ds.setIncludes(new String[] {"**"});
String fs;
if (File.separatorChar == '/') {
fs = "\\";
} else {
fs = "/";
}
ds.setExcludes(new String[] {"foo" + fs});
ds.addDefaultExcludes();
ds.scan();
String[] files = ds.getIncludedFiles();
assertEquals(0, files.length);
}
private String getTestResourcesDir() throws URISyntaxException {
ClassLoader cloader = Thread.currentThread().getContextClassLoader();
URL resource = cloader.getResource("test.txt");
if (resource == null) {
fail("Cannot locate test-resources directory containing 'test.txt' in the classloader.");
}
File file = new File(new URI(resource.toExternalForm()).normalize().getPath());
return file.getParent();
}
private void createTestFiles() throws IOException {
FileUtils.mkdir(testDir);
this.createFile(new File(testDir + "/scanner1.dat"), 0);
this.createFile(new File(testDir + "/scanner2.dat"), 0);
this.createFile(new File(testDir + "/scanner3.dat"), 0);
this.createFile(new File(testDir + "/scanner4.dat"), 0);
this.createFile(new File(testDir + "/scanner5.dat"), 0);
}
/**
* Check if 'src/test/resources/symlinks/src/sym*' test files (start with 'sym') exist and are symlinks.<br>
* On some OS (like Windows 10), the 'git clone' requires to be executed with admin permissions and the
* 'core.symlinks=true' git option.
*
* @return true If files here and symlinks, false otherwise
*/
private boolean checkTestFilesSymlinks() {
File symlinksDirectory = new File("src/test/resources/symlinks/src");
try {
List<String> symlinks =
FileUtils.getFileAndDirectoryNames(symlinksDirectory, "sym*", null, true, true, true, true);
if (symlinks.isEmpty()) {
throw new IOException("Symlinks files/directories are not present");
}
for (String symLink : symlinks) {
if (!Files.isSymbolicLink(Paths.get(symLink))) {
throw new IOException(String.format("Path is not a symlink: %s", symLink));
}
}
return true;
} catch (IOException e) {
System.err.printf(
"The unit test '%s.%s' will be skipped, reason: %s%n",
this.getClass().getSimpleName(), getTestMethodName(), e.getMessage());
System.out.printf("This test requires symlinks files in '%s' directory.%n", symlinksDirectory.getPath());
System.out.println("On some OS (like Windows 10), files are present only if the clone/checkout is done"
+ " in administrator mode, and correct (symlinks and not flat file/directory)"
+ " if symlinks option are used (for git: git clone -c core.symlinks=true [url])");
return false;
}
}
/**
* <p>testGeneral.</p>
*
* @throws java.io.IOException if any.
*/
@Test
void general() throws IOException {
this.createTestFiles();
String includes = "scanner1.dat,scanner2.dat,scanner3.dat,scanner4.dat,scanner5.dat";
String excludes = "scanner1.dat,scanner2.dat";
List<File> fileNames = FileUtils.getFiles(new File(testDir), includes, excludes, false);
assertEquals(3, fileNames.size(), "Wrong number of results.");
assertTrue(fileNames.contains(new File("scanner3.dat")), "3 not found.");
assertTrue(fileNames.contains(new File("scanner4.dat")), "4 not found.");
assertTrue(fileNames.contains(new File("scanner5.dat")), "5 not found.");
}
/**
* <p>testIncludesExcludesWithWhiteSpaces.</p>
*
* @throws java.io.IOException if any.
*/
@Test
void includesExcludesWithWhiteSpaces() throws IOException {
this.createTestFiles();
String includes = "scanner1.dat,\n \n,scanner2.dat \n\r, scanner3.dat\n, \tscanner4.dat,scanner5.dat\n,";
String excludes = "scanner1.dat,\n \n,scanner2.dat \n\r,,";
List<File> fileNames = FileUtils.getFiles(new File(testDir), includes, excludes, false);
assertEquals(3, fileNames.size(), "Wrong number of results.");
assertTrue(fileNames.contains(new File("scanner3.dat")), "3 not found.");
assertTrue(fileNames.contains(new File("scanner4.dat")), "4 not found.");
assertTrue(fileNames.contains(new File("scanner5.dat")), "5 not found.");
}
/**
* <p>testFollowSymlinksFalse.</p>
*/
@Test
void followSymlinksFalse() {
assumeTrue(checkTestFilesSymlinks());
DirectoryScanner ds = new DirectoryScanner();
ds.setBasedir(new File("src/test/resources/symlinks/src/"));
ds.setFollowSymlinks(false);
ds.scan();
List<String> included = Arrays.asList(ds.getIncludedFiles());
assertAlwaysIncluded(included);
assertEquals(9, included.size());
List<String> includedDirs = Arrays.asList(ds.getIncludedDirectories());
assertTrue(includedDirs.contains("")); // w00t !
assertTrue(includedDirs.contains("aRegularDir"));
assertTrue(includedDirs.contains("symDir"));
assertTrue(includedDirs.contains("symLinkToDirOnTheOutside"));
assertTrue(includedDirs.contains("targetDir"));
assertEquals(5, includedDirs.size());
}
private void assertAlwaysIncluded(List<String> included) {
assertTrue(included.contains("aRegularDir" + File.separator + "aRegularFile.txt"));
assertTrue(included.contains("targetDir" + File.separator + "targetFile.txt"));
assertTrue(included.contains("fileR.txt"));
assertTrue(included.contains("fileW.txt"));
assertTrue(included.contains("fileX.txt"));
assertTrue(included.contains("symR"));
assertTrue(included.contains("symW"));
assertTrue(included.contains("symX"));
assertTrue(included.contains("symLinkToFileOnTheOutside"));
}
/**
* <p>testFollowSymlinks.</p>
*/
@Test
void followSymlinks() {
assumeTrue(checkTestFilesSymlinks());
DirectoryScanner ds = new DirectoryScanner();
ds.setBasedir(new File("src/test/resources/symlinks/src/"));
ds.setFollowSymlinks(true);
ds.scan();
List<String> included = Arrays.asList(ds.getIncludedFiles());
assertAlwaysIncluded(included);
assertTrue(included.contains("symDir" + File.separator + "targetFile.txt"));
assertTrue(included.contains("symLinkToDirOnTheOutside" + File.separator + "FileInDirOnTheOutside.txt"));
assertEquals(11, included.size());
List<String> includedDirs = Arrays.asList(ds.getIncludedDirectories());
assertTrue(includedDirs.contains("")); // w00t !
assertTrue(includedDirs.contains("aRegularDir"));
assertTrue(includedDirs.contains("symDir"));
assertTrue(includedDirs.contains("symLinkToDirOnTheOutside"));
assertTrue(includedDirs.contains("targetDir"));
assertEquals(5, includedDirs.size());
}
private void createTestDirectories() throws IOException {
FileUtils.mkdir(testDir + File.separator + "directoryTest");
FileUtils.mkdir(testDir + File.separator + "directoryTest" + File.separator + "testDir123");
FileUtils.mkdir(testDir + File.separator + "directoryTest" + File.separator + "test_dir_123");
FileUtils.mkdir(testDir + File.separator + "directoryTest" + File.separator + "test-dir-123");
this.createFile(
new File(testDir + File.separator + "directoryTest" + File.separator + "testDir123" + File.separator
+ "file1.dat"),
0);
this.createFile(
new File(testDir + File.separator + "directoryTest" + File.separator + "test_dir_123" + File.separator
+ "file1.dat"),
0);
this.createFile(
new File(testDir + File.separator + "directoryTest" + File.separator + "test-dir-123" + File.separator
+ "file1.dat"),
0);
}
/**
* <p>testDirectoriesWithHyphens.</p>
*
* @throws java.io.IOException if any.
*/
@Test
void directoriesWithHyphens() throws IOException {
this.createTestDirectories();
DirectoryScanner ds = new DirectoryScanner();
String[] includes = {"**/*.dat"};
String[] excludes = {""};
ds.setIncludes(includes);
ds.setExcludes(excludes);
ds.setBasedir(new File(testDir + File.separator + "directoryTest"));
ds.setCaseSensitive(true);
ds.scan();
String[] files = ds.getIncludedFiles();
assertEquals(3, files.length, "Wrong number of results.");
}
/**
* <p>testAntExcludesOverrideIncludes.</p>
*
* @throws java.io.IOException if any.
*/
@Test
void antExcludesOverrideIncludes() throws IOException {
printTestHeader();
File dir = new File(testDir, "regex-dir");
dir.mkdirs();
String[] excludedPaths = {"target/foo.txt"};
createFiles(dir, excludedPaths);
String[] includedPaths = {"src/main/resources/project/target/foo.txt"};
createFiles(dir, includedPaths);
DirectoryScanner ds = new DirectoryScanner();
String[] includes = {"**/target/*"};
String[] excludes = {"target/*"};
// This doesn't work, since excluded patterns refine included ones, meaning they operate on
// the list of paths that passed the included patterns, and can override them.
// String[] includes = {"**src/**/target/**/*" };
// String[] excludes = { "**/target/**/*" };
ds.setIncludes(includes);
ds.setExcludes(excludes);
ds.setBasedir(dir);
ds.scan();
assertInclusionsAndExclusions(ds.getIncludedFiles(), excludedPaths, includedPaths);
}
/**
* <p>testAntExcludesOverrideIncludesWithExplicitAntPrefix.</p>
*
* @throws java.io.IOException if any.
*/
@Test
void antExcludesOverrideIncludesWithExplicitAntPrefix() throws IOException {
printTestHeader();
File dir = new File(testDir, "regex-dir");
dir.mkdirs();
String[] excludedPaths = {"target/foo.txt"};
createFiles(dir, excludedPaths);
String[] includedPaths = {"src/main/resources/project/target/foo.txt"};
createFiles(dir, includedPaths);
DirectoryScanner ds = new DirectoryScanner();
String[] includes = {SelectorUtils.ANT_HANDLER_PREFIX + "**/target/**/*" + SelectorUtils.PATTERN_HANDLER_SUFFIX
};
String[] excludes = {SelectorUtils.ANT_HANDLER_PREFIX + "target/**/*" + SelectorUtils.PATTERN_HANDLER_SUFFIX};
// This doesn't work, since excluded patterns refine included ones, meaning they operate on
// the list of paths that passed the included patterns, and can override them.
// String[] includes = {"**src/**/target/**/*" };
// String[] excludes = { "**/target/**/*" };
ds.setIncludes(includes);
ds.setExcludes(excludes);
ds.setBasedir(dir);
ds.scan();
assertInclusionsAndExclusions(ds.getIncludedFiles(), excludedPaths, includedPaths);
}
/**
* <p>testRegexIncludeWithExcludedPrefixDirs.</p>
*
* @throws java.io.IOException if any.
*/
@Test
void regexIncludeWithExcludedPrefixDirs() throws IOException {
printTestHeader();
File dir = new File(testDir, "regex-dir");
dir.mkdirs();
String[] excludedPaths = {"src/main/foo.txt"};
createFiles(dir, excludedPaths);
String[] includedPaths = {"src/main/resources/project/target/foo.txt"};
createFiles(dir, includedPaths);
String regex = ".+/target.*";
DirectoryScanner ds = new DirectoryScanner();
String includeExpr = SelectorUtils.REGEX_HANDLER_PREFIX + regex + SelectorUtils.PATTERN_HANDLER_SUFFIX;
String[] includes = {includeExpr};
ds.setIncludes(includes);
ds.setBasedir(dir);
ds.scan();
assertInclusionsAndExclusions(ds.getIncludedFiles(), excludedPaths, includedPaths);
}
/**
* <p>testRegexExcludeWithNegativeLookahead.</p>
*
* @throws java.io.IOException if any.
*/
@Test
void regexExcludeWithNegativeLookahead() throws IOException {
printTestHeader();
File dir = new File(testDir, "regex-dir");
try {
FileUtils.deleteDirectory(dir);
} catch (IOException ignored) {
}
dir.mkdirs();
String[] excludedPaths = {"target/foo.txt"};
createFiles(dir, excludedPaths);
String[] includedPaths = {"src/main/resources/project/target/foo.txt"};
createFiles(dir, includedPaths);
String regex = "(?!.*src/).*target.*";
DirectoryScanner ds = new DirectoryScanner();
String excludeExpr = SelectorUtils.REGEX_HANDLER_PREFIX + regex + SelectorUtils.PATTERN_HANDLER_SUFFIX;
String[] excludes = {excludeExpr};
ds.setExcludes(excludes);
ds.setBasedir(dir);
ds.scan();
assertInclusionsAndExclusions(ds.getIncludedFiles(), excludedPaths, includedPaths);
}
/**
* <p>testRegexWithSlashInsideCharacterClass.</p>
*
* @throws java.io.IOException if any.
*/
@Test
void regexWithSlashInsideCharacterClass() throws IOException {
printTestHeader();
File dir = new File(testDir, "regex-dir");
try {
FileUtils.deleteDirectory(dir);
} catch (IOException ignored) {
}
dir.mkdirs();
String[] excludedPaths = {"target/foo.txt", "target/src/main/target/foo.txt"};
createFiles(dir, excludedPaths);
String[] includedPaths = {"module/src/main/target/foo.txt"};
createFiles(dir, includedPaths);
// NOTE: The portion "[^/]" is the interesting part of this pattern.
String regex = "(?!((?!target/)[^/]+/)*src/).*target.*";
DirectoryScanner ds = new DirectoryScanner();
String excludeExpr = SelectorUtils.REGEX_HANDLER_PREFIX + regex + SelectorUtils.PATTERN_HANDLER_SUFFIX;
String[] excludes = {excludeExpr};
ds.setExcludes(excludes);
ds.setBasedir(dir);
ds.scan();
assertInclusionsAndExclusions(ds.getIncludedFiles(), excludedPaths, includedPaths);
}
/**
* Test that the directory scanning does not enter into not matching directories.
*
* @see <a href="https://github.com/codehaus-plexus/plexus-utils/issues/63">Issue #63</a>
* @throws java.io.IOException if occurs an I/O error.
*/
@Test
void doNotScanUnnecesaryDirectories() throws IOException {
createTestDirectories();
// create additional directories 'anotherDir1', 'anotherDir2' and 'anotherDir3' with a 'file1.dat' file
FileUtils.mkdir(testDir + File.separator + "directoryTest" + File.separator + "testDir123" + File.separator
+ "anotherDir1");
FileUtils.mkdir(testDir + File.separator + "directoryTest" + File.separator + "test_dir_123" + File.separator
+ "anotherDir2");
FileUtils.mkdir(testDir + File.separator + "directoryTest" + File.separator + "test-dir-123" + File.separator
+ "anotherDir3");
this.createFile(
new File(testDir + File.separator + "directoryTest" + File.separator + "testDir123" + File.separator
+ "anotherDir1" + File.separator + "file1.dat"),
0);
this.createFile(
new File(testDir + File.separator + "directoryTest" + File.separator + "test_dir_123" + File.separator
+ "anotherDir2" + File.separator + "file1.dat"),
0);
this.createFile(
new File(testDir + File.separator + "directoryTest" + File.separator + "test-dir-123" + File.separator
+ "anotherDir3" + File.separator + "file1.dat"),
0);
String[] excludedPaths = {
"directoryTest" + File.separator + "testDir123" + File.separator + "anotherDir1" + File.separator
+ "file1.dat",
"directoryTest" + File.separator + "test_dir_123" + File.separator + "anotherDir2" + File.separator
+ "file1.dat",
"directoryTest" + File.separator + "test-dir-123" + File.separator + "anotherDir3" + File.separator
+ "file1.dat"
};
String[] includedPaths = {
"directoryTest" + File.separator + "testDir123" + File.separator + "file1.dat",
"directoryTest" + File.separator + "test_dir_123" + File.separator + "file1.dat",
"directoryTest" + File.separator + "test-dir-123" + File.separator + "file1.dat"
};
final Set<String> scannedDirSet = new HashSet<>();
DirectoryScanner ds = new DirectoryScanner() {
@Override
protected void scandir(File dir, String vpath, boolean fast) {
scannedDirSet.add(dir.getName());
super.scandir(dir, vpath, fast);
}
};
// one '*' matches only ONE directory level
String[] includes = {"directoryTest" + File.separator + "*" + File.separator + "file1.dat"};
ds.setIncludes(includes);
ds.setBasedir(new File(testDir));
ds.scan();
assertInclusionsAndExclusions(ds.getIncludedFiles(), excludedPaths, includedPaths);
Set<String> expectedScannedDirSet =
new HashSet<>(Arrays.asList("io", "directoryTest", "testDir123", "test_dir_123", "test-dir-123"));
assertEquals(expectedScannedDirSet, scannedDirSet);
}
/**
* <p>testIsSymbolicLink.</p>
*
* @throws java.io.IOException if any.
*/
@Test
void isSymbolicLink() throws IOException {
assumeTrue(checkTestFilesSymlinks());
final File directory = new File("src/test/resources/symlinks/src");
DirectoryScanner ds = new DirectoryScanner();
assertTrue(ds.isSymbolicLink(directory, "symR"));
assertTrue(ds.isSymbolicLink(directory, "symDir"));
assertFalse(ds.isSymbolicLink(directory, "fileR.txt"));
assertFalse(ds.isSymbolicLink(directory, "aRegularDir"));
}
/**
* <p>testIsParentSymbolicLink.</p>
*
* @throws java.io.IOException if any.
*/
@Test
void isParentSymbolicLink() throws IOException {
assumeTrue(checkTestFilesSymlinks());
final File directory = new File("src/test/resources/symlinks/src");
DirectoryScanner ds = new DirectoryScanner();
assertFalse(ds.isParentSymbolicLink(directory, "symR"));
assertFalse(ds.isParentSymbolicLink(directory, "symDir"));
assertFalse(ds.isParentSymbolicLink(directory, "fileR.txt"));
assertFalse(ds.isParentSymbolicLink(directory, "aRegularDir"));
assertFalse(ds.isParentSymbolicLink(new File(directory, "aRegularDir"), "aRegulatFile.txt"));
assertTrue(ds.isParentSymbolicLink(new File(directory, "symDir"), "targetFile.txt"));
assertTrue(
ds.isParentSymbolicLink(new File(directory, "symLinkToDirOnTheOutside"), "FileInDirOnTheOutside.txt"));
}
private void printTestHeader() {
StackTraceElement ste = new Throwable().getStackTrace()[1];
System.out.println("Test: " + ste.getMethodName());
}
private void assertInclusionsAndExclusions(String[] files, String[] excludedPaths, String... includedPaths) {
Arrays.sort(files);
System.out.println("Included files: ");
for (String file : files) {
System.out.println(file);
}
List<String> failedToExclude = new ArrayList<>();
for (String excludedPath : excludedPaths) {
String alt = excludedPath.replace('/', '\\');
System.out.println("Searching for exclusion as: " + excludedPath + "\nor: " + alt);
if (Arrays.binarySearch(files, excludedPath) > -1 || Arrays.binarySearch(files, alt) > -1) {
failedToExclude.add(excludedPath);
}
}
List<String> failedToInclude = new ArrayList<>();
for (String includedPath : includedPaths) {
String alt = includedPath.replace('/', '\\');
System.out.println("Searching for inclusion as: " + includedPath + "\nor: " + alt);
if (Arrays.binarySearch(files, includedPath) < 0 && Arrays.binarySearch(files, alt) < 0) {
failedToInclude.add(includedPath);
}
}
StringBuilder buffer = new StringBuilder();
if (!failedToExclude.isEmpty()) {
buffer.append("Should NOT have included:\n").append(StringUtils.join(failedToExclude.iterator(), "\n\t- "));
}
if (!failedToInclude.isEmpty()) {
if (buffer.length() > 0) {
buffer.append("\n\n");
}
buffer.append("Should have included:\n").append(StringUtils.join(failedToInclude.iterator(), "\n\t- "));
}
if (buffer.length() > 0) {
fail(buffer.toString());
}
}
private void createFiles(File dir, String... paths) throws IOException {
for (String path1 : paths) {
String path = path1.replace('/', File.separatorChar).replace('\\', File.separatorChar);
File file = new File(dir, path);
if (path.endsWith(File.separator)) {
file.mkdirs();
} else {
if (file.getParentFile() != null) {
file.getParentFile().mkdirs();
}
createFile(file, 0);
}
}
}
}