001/* 002 * #%L 003 * Netarchivesuite - common - test 004 * %% 005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library, 006 * the National Library of France and the Austrian National Library. 007 * %% 008 * This program is free software: you can redistribute it and/or modify 009 * it under the terms of the GNU Lesser General Public License as 010 * published by the Free Software Foundation, either version 2.1 of the 011 * License, or (at your option) any later version. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Lesser Public License for more details. 017 * 018 * You should have received a copy of the GNU General Lesser Public 019 * License along with this program. If not, see 020 * <http://www.gnu.org/licenses/lgpl-2.1.html>. 021 * #L% 022 */ 023 024package dk.netarkivet.testutils; 025 026import java.io.File; 027import java.io.FileFilter; 028import java.io.FileNotFoundException; 029import java.io.FilenameFilter; 030import java.io.IOException; 031import java.util.ArrayList; 032import java.util.HashMap; 033import java.util.List; 034import java.util.Map; 035 036import dk.netarkivet.common.exceptions.IOFailure; 037import dk.netarkivet.common.utils.FileUtils; 038 039/** 040 * File utilities specific to the test classes. 041 */ 042@SuppressWarnings({"unchecked"}) 043public class TestFileUtils { 044 public static final FilenameFilter NON_CVS_DIRS_FILTER = new FilenameFilter() { 045 public boolean accept(File directory, String filename) { 046 return !((filename.equals("CVS") && new File(directory, filename).isDirectory() || (filename.equals(".svn") && new File( 047 directory, filename).isDirectory()))); 048 } 049 }; 050 public static final FileFilter DIRS_ONLY_FILTER = new FileFilter() { 051 public boolean accept(File dir) { 052 return dir.isDirectory(); 053 } 054 }; 055 056 /** 057 * Copy an entire directory from one location to another, skipping CVS directories. Note that this will silently 058 * overwrite old files, just like copyFile(). 059 * 060 * @param from Original directory (or file, for that matter) to copy. 061 * @param to Destination directory, i.e. the 'new name' of the copy of the from directory. 062 * @throws IOFailure 063 */ 064 public static final void copyDirectoryNonCVS(File from, File to) throws IOFailure { 065 if (from.isFile()) { 066 try { 067 FileUtils.copyFile(from, to); 068 } catch (Exception e) { 069 throw new IOFailure("Error copying from " + from.getAbsolutePath() + " to " + to.getAbsolutePath(), e); 070 } 071 } else { 072 if (from.getName().equals("CVS")) { 073 return; 074 } 075 if (from.getName().equals(".svn")) { 076 return; 077 } 078 if (!from.exists()) { 079 throw new IOFailure("Can't find directory " + from); 080 } 081 082 if (!from.isDirectory()) { 083 throw new IOFailure("File is not a directory: " + from); 084 } 085 086 to.mkdir(); 087 088 if (!to.exists()) { 089 throw new IOFailure("Failed to create directory " + to); 090 } 091 092 File[] subfiles = from.listFiles(); 093 094 for (File subfile : subfiles) { 095 copyDirectoryNonCVS(subfile, new File(to, subfile.getName())); 096 } 097 } 098 } 099 100 /** 101 * Compares the content of two directories and report all differences in the returned text string. If no difference 102 * are located, an empty string ("") is returned. All files located in the directories are treated as text files, 103 * and a text comparison is done on a line by line basis. This function will not work if the dirs contain binary 104 * files. No attempt is made to recover from errors. 105 * 106 * @param fstDir The directory to compare with sndDir 107 * @param sndDir The directory to compare with fstDir 108 * @return A text string describing the differences between the two dirs. Empty if no differences are found. 109 * @throws IOFailure if there are problems reading the content of the dirs. 110 */ 111 public static String compareDirsText(File fstDir, File sndDir) throws IOFailure { 112 String result = ""; 113 114 // retrieve lists of all files in the two directories 115 List<File> fstFiles = new ArrayList<File>(); 116 List<File> sndFiles = new ArrayList<File>(); 117 118 FileUtils.getFilesRecursively(fstDir.getPath(), fstFiles, ""); 119 FileUtils.getFilesRecursively(sndDir.getPath(), sndFiles, ""); 120 121 Map<String, File> fstFilesMap = new HashMap<String, File>(); 122 for (File f : fstFiles) { 123 fstFilesMap.put(removePrefixDir(fstDir, f), f); 124 } 125 126 Map<String, File> sndFilesMap = new HashMap<String, File>(); 127 for (File f : sndFiles) { 128 sndFilesMap.put(removePrefixDir(sndDir, f), f); 129 } 130 131 // The two dirs should contain the same files 132 for (String s : fstFilesMap.keySet()) { 133 if (!sndFilesMap.containsKey(s)) { 134 result += "Result file not found in second dir:" + s + "\n"; 135 } 136 } 137 138 // The two dirs should contain the same files 139 for (String s : sndFilesMap.keySet()) { 140 if (!fstFilesMap.containsKey(s)) { 141 result += "Target file not found in result set:" + s + "\n"; 142 } 143 } 144 145 // No reason to continue when sets of files do not match 146 if (result.length() > 0) { 147 return result; 148 } 149 150 // The files in each dir should be identical 151 try { 152 for (String s : fstFilesMap.keySet()) { 153 // Remove all carriage returns to make the comparison work on both Windows and Linux: 154 String fst = FileUtils.readFile(fstFilesMap.get(s)).replaceAll("\r", ""); 155 String snd = FileUtils.readFile(sndFilesMap.get(s)).replaceAll("\r", ""); 156 if (!fst.equals(snd)) { 157 result += "Target and result differs for:" + s + "\n"; 158 result += getDifferences(fst, snd) + "\n"; 159 } 160 } 161 } catch (FileNotFoundException e) { 162 throw new IOFailure("While comparing the files in " + fstFilesMap.keySet(), e); 163 } catch (IOException e) { 164 throw new IOFailure("While comparing the files in " + fstFilesMap.keySet(), e); 165 } 166 167 return result; 168 } 169 170 /** 171 * Strips a path prefix from a file name. 172 * 173 * @param dir The path prefix to remove from the given file's name. 174 * @param f The file to remove the path prefix from. 175 * @return The name of the file without the specified path prefix. 176 */ 177 private static String removePrefixDir(File dir, File f) { 178 return f.getAbsolutePath().replaceAll(dir.getAbsolutePath(), ""); 179 } 180 181 /** 182 * Return textual description of the differences between two strings. 183 * 184 * @param s1 strings to compare 185 * @param s2 strings to compare 186 * @return first line of text that differs 187 */ 188 public static String getDifferences(String s1, String s2) { 189 String[] startStrings = s1.split("\n"); 190 String[] endStrings = s2.split("\n"); 191 List<Difference> differences = new Diff(startStrings, endStrings).diff(); 192 StringBuilder res = new StringBuilder(); 193 194 for (Difference d : differences) { 195 // Would like to do this as a unitfied diff (diff -u) instead, 196 // but that's a little more complex. 197 if (d.getAddedEnd() == -1) { 198 // Deletion 199 res.append("Deleted " + getDifferenceLines(d, false) + "\n"); 200 for (int i = d.getDeletedStart(); i <= d.getDeletedEnd(); i++) { 201 res.append("< " + startStrings[i] + "\n"); 202 } 203 } else if (d.getDeletedEnd() == -1) { 204 // Addition 205 res.append("Added " + getDifferenceLines(d, true) + "\n"); 206 for (int i = d.getAddedStart(); i <= d.getAddedEnd(); i++) { 207 res.append("> " + endStrings[i] + "\n"); 208 } 209 } else { 210 // Modification 211 res.append("Modified " + getDifferenceLines(d, false) + " into " + getDifferenceLines(d, true) + "\n"); 212 for (int i = d.getDeletedStart(); i <= d.getDeletedEnd(); i++) { 213 res.append("< " + startStrings[i] + "\n"); 214 } 215 res.append("---\n"); 216 for (int i = d.getAddedStart(); i <= d.getAddedEnd(); i++) { 217 res.append("> " + endStrings[i] + "\n"); 218 } 219 } 220 } 221 return res.toString(); 222 } 223 224 private static String getDifferenceLines(Difference d, boolean added) { 225 int startLine; 226 int endLine; 227 if (added) { 228 startLine = d.getAddedStart(); 229 endLine = d.getAddedEnd(); 230 } else { 231 startLine = d.getDeletedStart(); 232 endLine = d.getDeletedEnd(); 233 } 234 if (startLine == endLine) { 235 return "line " + (startLine + 1); 236 } else { 237 return "lines " + (startLine + 1) + "-" + (endLine + 1); 238 } 239 } 240 241 /** 242 * Make a temporary directory using File.createTempFile. 243 * 244 * @param prefix 245 * @param suffix 246 * @return a temporary directory using File.createTempFile 247 * @throws IOException 248 */ 249 public static File createTempDir(String prefix, String suffix) throws IOException { 250 File temp = File.createTempFile(prefix, suffix); 251 temp.delete(); 252 temp.mkdir(); 253 return temp; 254 } 255 256 /** 257 * Make a temporary directory using File.createTempFile. 258 * 259 * @param prefix 260 * @param suffix 261 * @param directory 262 * @return a temporary directory using File.createTempFile 263 * @throws IOException 264 */ 265 public static File createTempDir(String prefix, String suffix, File directory) throws IOException { 266 File temp = File.createTempFile(prefix, suffix, directory); 267 temp.delete(); 268 temp.mkdir(); 269 return temp; 270 } 271 272 /** 273 * Find files recursively that match the given filter. 274 * 275 * @param start The directory (or file) to start at. 276 * @param filter Filter of files to include. All files (including directories) are passed to this filter and are 277 * included if filter.accept() returns true. Subdirectories are scanned whether or not filter.accept() returns true 278 * for them. 279 * @return List of files (in no particular order) that match the filter. and reside under start. 280 */ 281 public static List<File> findFiles(File start, FileFilter filter) { 282 List<File> results = new ArrayList<File>(); 283 File[] filesThisLevel = start.listFiles(); 284 for (File f : filesThisLevel) { 285 if (filter.accept(f)) { 286 results.add(f); 287 } 288 if (f.isDirectory()) { 289 results.addAll(findFiles(f, filter)); 290 } 291 } 292 return results; 293 } 294 295}