问题
I am working on integration testing some part of my code that creates directory trees under SVN. This requires me to test if the directory structure and the files within are what I expect them to be.
On one hand I have the expected directory tree with the files I want and on the other, an export of the files from SVN (prefer svn export
over svn co
to avoid the .svn
noise).
However, is there any library that can assert two directory trees? The last resort I have in mind is to do an iterative comparison myself.
Basically I am looking for an API that can just accept two directories and tell me if they are equal or not.
Something on the lines of
boolean areDirectoriesEqual(File dir1, File dir2)
回答1:
I am not using a third party lib but standard jdk lib.
private static void verifyDirsAreEqual(Path one, Path other) throws IOException {
Files.walkFileTree(one, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file,
BasicFileAttributes attrs)
throws IOException {
FileVisitResult result = super.visitFile(file, attrs);
// get the relative file name from path "one"
Path relativize = one.relativize(file);
// construct the path for the counterpart file in "other"
Path fileInOther = other.resolve(relativize);
log.debug("=== comparing: {} to {}", file, fileInOther);
byte[] otherBytes = Files.readAllBytes(fileInOther);
byte[] theseBytes = Files.readAllBytes(file);
if (!Arrays.equals(otherBytes, theseBytes)) {
throw new AssertionFailedError(file + " is not equal to " + fileInOther);
}
return result;
}
});
}
Note: this is just comparing actual files under two folders. If you have empty folders etc you want to compare too, you may need to do some extra things.
回答2:
I am not aware of any areDirsEqual
library; the closest I can think of is the listFiles method in Commons FileUtils.
If you put the resulting collections in a HashSet
, you should be able to compare the two sets efficiently. And it can be done in 2 lines, maybe even a one-liner.
Something on this line:
public static boolean areDirsEqual(File dir, File dir2) {
return (new HashSet<File>(FileUtils.listFiles(dir1,..))).
containsAll(FileUtils.listFiles(dir2, ..))
}
回答3:
I had same problem and following Patrick and Lorenzo Dematté I found a solution that works for me. The following code walks through folder and:
- for each sub-folder checks if file lists are the same
- for each file compares the contents (in my case I have to compare two folders that contain csv files)
I tested it on linux.
private static void verifyDirsAreEqual(File expected, File generated)
throws IOException {
// Checks parameters
assertTrue("Generated Folder doesn't exist: " + generated,generated.exists());
assertTrue("Generated is not a folder?!?!: " + generated,generated.isDirectory());
assertTrue("Expected Folder doesn't exist: " + expected,expected.exists());
assertTrue("Expected is not a folder?!?!: " + expected,expected.isDirectory());
Files.walkFileTree(expected.toPath(), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir,
BasicFileAttributes attrs)
throws IOException {
FileVisitResult result = super.preVisitDirectory(dir, attrs);
// get the relative file name from path "expected"
Path relativize = expected.toPath().relativize(dir);
// construct the path for the counterpart file in "generated"
File otherDir = generated.toPath().resolve(relativize).toFile();
log.debug("=== preVisitDirectory === compare " + dir + " to " + otherDir);
assertEquals("Folders doesn't contain same file!?!?",
Arrays.toString(dir.toFile().list()),
Arrays.toString(otherDir.list()));
return result;
}
@Override
public FileVisitResult visitFile(Path file,
BasicFileAttributes attrs)
throws IOException {
FileVisitResult result = super.visitFile(file, attrs);
// get the relative file name from path "expected"
Path relativize = expected.toPath().relativize(file);
// construct the path for the counterpart file in "generated"
File fileInOther = generated.toPath().resolve(relativize).toFile();
log.debug("=== comparing: " + file + " to " + fileInOther);
String expectedContents = FileUtils.readFileToString(file.toFile());
String generatedContents = FileUtils.readFileToString(fileInOther);
assertEquals("("+fileInOther+") csv standard doesn't match expected ("+file+")!", expectedContents, generatedContents);
return result;
}
});
}
回答4:
OK so I don't know of any ready piece of code that does that and search didn't help either. So here is how I would implement it
- iterate recursively on all folders and files
- save all file names with relative path from root in a hashset, where the relative path is the key/value
- iterate recursively on the second directory structure and create a from each path to match the keys in the hashet (if the folder/file exists)
If you just want to mark the tree as changed/not changed, you can save the hash of each file, then you need a hashmap instead of hashset, where the hash of the contents of each file is the value of the hashmap
hope this helps
回答5:
This is a simple iterative solution using the Java NIO package (without using the Visitor pattern, so it can be adapted for earlier Java versions as well).
Of course it could be tuned, but for now this is an easy solution checking from the view of both directories if every file occurs and optionally compare the file content using the Apache Commons FileUtils.
/**
* checks if the directory file lists and file content is equal
*
* @param directory
* the directory
* @param compareDirectory
* the directory to compare with
* @param checkFileContent
* also compare file content
* @return true if directory and compareDirectory are equal
* @throws IOException
*/
public static boolean isEqualDirectories(Path directory, Path compareDirectory, boolean checkFileContent) throws IOException {
boolean check = isEverythingInCompareDirectory(directory, compareDirectory, checkFileContent);
boolean checkOpposite = check && isEverythingInCompareDirectory(directory, compareDirectory, checkFileContent);
return check && checkOpposite;
}
/**
* checks if the directory file lists and file content is equal
*
* @param directory
* the directory
* @param compareDirectory
* the directory to compare with
* @param checkFileContent
* also compare file content
* @return true if directory and compareDirectory are equal
* @throws IOException
*/
public static boolean isEverythingInCompareDirectory(Path directory, Path compareDirectory, boolean checkFileContent)
throws IOException {
try {
LOGGER.info("checking directory " + directory);
File directoryFile = directory.toFile();
File compareFile = compareDirectory.toFile();
// check, if there is the same number of files/subdirectories
File[] directoryFiles = directoryFile.listFiles();
File[] compareFiles = compareFile.listFiles();
if (directoryFiles.length == compareFiles.length) {
return compareDirectoryContents(directory, compareDirectory, checkFileContent);
} else {
LOGGER.info("number of files in directory are different " + directoryFiles.length + " vs compareDirectory: " + compareFiles.length);
return false;
}
} catch (IOException e) {
throw new RuntimeException("Failed to assert that all files are equal", e);
}
}
public static boolean compareDirectoryContents(Path directory, Path compareDirectory, boolean checkFileContent) throws IOException {
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory)) {
for (Path directoryFilePath : directoryStream) {
// search for directoryFile in the compareDirectory
Path compareFilePath = compareDirectory.resolve(directoryFilePath.getFileName());
if (compareFilePath != null) {
File directoryFile = directoryFilePath.toFile();
if (directoryFile.isFile()) {
LOGGER.info("checking file " + directoryFilePath);
if (checkFileContent && !FileUtils.contentEquals(compareFilePath.toFile(), directoryFile)) {
LOGGER.info("files not equal: compare: " + compareFilePath.toFile() + ", directory: " + directoryFilePath.getFileName() + "!");
return false;
}
} else {
LOGGER.info("going into recursion with directory " + directoryFilePath);
boolean result = isEverythingInCompareDirectory(directoryFilePath, compareFilePath, checkFileContent);
// cancel if not equal, otherwise continue processing
if (!result) {
return false;
}
}
} else {
LOGGER.info(directoryFilePath.toString() + ": compareFilepath not found");
return false;
}
}
}
return true;
}
回答6:
for me the solution of Patrick seems to be a good solution, but in combination with camel (Fuse ESB) I had the problem that the last parent folder was still blocked by Fuse process => for me the follow solutions semms to be better way. I iterate over ther directories by SimpleVistor and made a comparable Set of booth directories
public boolean compareFolders(final Path pathOne, final Path pathSecond) throws IOException {
// get content of first directory
final TreeSet<String> treeOne = new TreeSet();
Files.walkFileTree(pathOne, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Path relPath = pathOne.relativize(file);
String entry = relPath.toString();
treeOne.add(entry);
return FileVisitResult.CONTINUE;
}
});
// get content of second directory
final TreeSet<String> treeSecond = new TreeSet();
Files.walkFileTree(pathSecond, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Path relPath = pathSecond.relativize(file);
String entry = relPath.toString();
treeSecond.add(entry);
return FileVisitResult.CONTINUE;
}
});
return treeOne.equals(treeSecond);
}
回答7:
I wrote this small code in Kotlin. It does not check the contents of the files, but relies completely on md5 from apache.
import org.apache.commons.codec.digest.DigestUtils
fun File.calcMD5() = DigestUtils.md5Hex(FileUtils.readFileToByteArray(this))
fun compareTwoDirs(dir1: File, dir2: File): Boolean {
val files1 = dir1.listFiles().sorted()
val files2 = dir2.listFiles().sorted()
if (files1.size != files2.size) return false
return files1.zip(files2).all { equate(it.first, it.second) }
}
fun equate(fl: File, fl2: File): Boolean {
if (fl.isFile && fl2.isFile) return fl.calcMD5() == fl2.calcMD5()
if (fl.isDirectory && fl2.isDirectory) return compareTwoDirs(fl, fl2)
return false
}
回答8:
I know this is an old question but I will try to solve it. Here is a code that opens 2 directories and compares their contents. It returns false if the files found in one are missing in the other or if their contents are distinct. To do this, I browse the content of the 2 directories and I recursively compare the files and directories found (in alphabetical order).
// Comparison of 2 files
private static boolean compareFiles(File f1, File f2) {
long i1 = f1.length();
long i2 = f2.length();
if (i1 != i2) {
return false;
}
try {
byte b1, b2;
DataInputStream dis = new DataInputStream(
new BufferedInputStream(
new FileInputStream(f1)));
DataInputStream dis1 = new DataInputStream(
new BufferedInputStream(
new FileInputStream(f2)));
while (true) {
b1 = dis.readByte();
b2 = dis1.readByte();
if (b1 != b2) {
return false;
}
}
} catch (IOException ex) {
return true;
}
}
// Recursive comparison of 2 files.
// The idea is that if 2 directories are the same,
// they will have the same files and the same under directories
private static boolean areDirsEqual(File dir1, File dir2) {
File[] list1 = dir1.listFiles(), list2 = dir2.listFiles();
if (list1.length != list2.length) {
return false;
}
for (int i = 0; i < list2.length; i++) {
if (list1[i].isFile() && list2[i].isFile()) {
if (!compareFiles(list1[i], list2[i])) {
return false;
}
} else if (list1[i].isDirectory() && list2[i].isDirectory()) {
if (!areDirsEqual(list1[i], list2[i])) {
return false;
}
} else {
return false;
}
}
return true;
}
回答9:
import java.io.File;
/**
*
* FileUtils is a collection of routines for common file system operations.
*
* @author Dan Jemiolo (danj)
*
*/
public final class FileUtils {
/**
*
* This is a convenience method that calls find(File, String, boolean) with
* the last parameter set to "false" (does not match directories).
*
* @see #find(File, String, boolean)
*
*/
public static File find(File contextRoot, String fileName) {
return find(contextRoot, fileName, false);
}
/**
*
* Searches through the directory tree under the given context directory and
* finds the first file that matches the file name. If the third parameter is
* true, the method will also try to match directories, not just "regular"
* files.
*
* @param contextRoot
* The directory to start the search from.
*
* @param fileName
* The name of the file (or directory) to search for.
*
* @param matchDirectories
* True if the method should try and match the name against directory
* names, not just file names.
*
* @return The java.io.File representing the <em>first</em> file or
* directory with the given name, or null if it was not found.
*
*/
public static File find(File contextRoot, String fileName, boolean matchDirectories) {
if (contextRoot == null)
throw new NullPointerException("NullContextRoot");
if (fileName == null)
throw new NullPointerException("NullFileName");
if (!contextRoot.isDirectory()) {
Object[] filler = { contextRoot.getAbsolutePath() };
String message = "NotDirectory";
throw new IllegalArgumentException(message);
}
File[] files = contextRoot.listFiles();
//
// for all children of the current directory...
//
for (int n = 0; n < files.length; ++n) {
String nextName = files[n].getName();
//
// if we find a directory, there are two possibilities:
//
// 1. the names match, AND we are told to match directories.
// in this case we're done
//
// 2. not told to match directories, so recurse
//
if (files[n].isDirectory()) {
if (nextName.equals(fileName) && matchDirectories)
return files[n];
File match = find(files[n], fileName);
if (match != null)
return match;
}
//
// in the case of regular files, just check the names
//
else if (nextName.equals(fileName))
return files[n];
}
return null;
}
}
来源:https://stackoverflow.com/questions/14522239/test-two-directory-trees-for-equality