package org.rubypeople.rdt.core.tests.util; import java.io.File; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; public class Util { // Trace for delete operation /* * Maximum time wasted repeating delete operations while running RDT/Core tests. */ private static int DELETE_MAX_TIME = 0; /** * Trace deletion operations while running RDT/Core tests. */ public static boolean DELETE_DEBUG = false; /** * Maximum of time in ms to wait in deletion operation while running RDT/Core tests. * Default is 10 seconds. This number cannot exceed 1 minute (ie. 60000). * <br> * To avoid too many loops while waiting, the ten first ones are done waiting * 10ms before repeating, the ten loops after are done waiting 100ms and * the other loops are done waiting 1s... */ public static int DELETE_MAX_WAIT = 10000; public static String convertToIndependantLineDelimiter(String source) { if (source.indexOf('\n') == -1 && source.indexOf('\r') == -1) return source; StringBuffer buffer = new StringBuffer(); for (int i = 0, length = source.length(); i < length; i++) { char car = source.charAt(i); if (car == '\r') { buffer.append('\n'); if (i < length-1 && source.charAt(i+1) == '\n') { i++; // skip \n after \r } } else { buffer.append(car); } } return buffer.toString(); } /** * Delete a file or directory and insure that the file is no longer present * on file system. In case of directory, delete all the hierarchy underneath. * * @param resource The resource to delete * @return true iff the file was really delete, false otherwise */ public static boolean delete(IResource resource) { try { resource.delete(true, null); if (isResourceDeleted(resource)) { return true; } } catch (CoreException e) { // skip } return waitUntilResourceDeleted(resource); } /** * Returns whether a resource is really deleted or not. * Does not only rely on {@link IResource#isAccessible()} method but also * look if it's not in its parent children {@link #getParentChildResource(IResource)}. * * @param resource The resource to test if deleted * @return true if the resource is not accessible and was not found in its parent children. */ public static boolean isResourceDeleted(IResource resource) { return !resource.isAccessible() && getParentChildResource(resource) == null; } /** * Returns parent's child resource matching the given resource or null if not found. * * @param resource The searched file in parent * @return The parent's child matching the given file or null if not found. */ private static IResource getParentChildResource(IResource resource) { IContainer parent = resource.getParent(); if (parent == null || !parent.exists()) return null; try { IResource[] members = parent.members(); int length = members ==null ? 0 : members.length; if (length > 0) { for (int i=0; i<length; i++) { if (members[i] == resource) { return members[i]; } else if (members[i].equals(resource)) { return members[i]; } else if (members[i].getFullPath().equals(resource.getFullPath())) { return members[i]; } } } } catch (CoreException ce) { // skip } return null; } /** * Wait until a resource is _really_ deleted on file system. * * @param resource Deleted resource * @return true if the file was finally deleted, false otherwise */ private static boolean waitUntilResourceDeleted(IResource resource) { File file = resource.getLocation().toFile(); if (DELETE_DEBUG) { System.out.println(); System.out.println("WARNING in test: "+getTestName()); System.out.println(" - problems occured while deleting resource "+resource); printRdtCoreStackTrace(null, 1); printFileInfo(file.getParentFile(), 1, -1); // display parent with its children System.out.print(" - wait for ("+DELETE_MAX_WAIT+"ms max): "); } int count = 0; int delay = 10; // ms int maxRetry = DELETE_MAX_WAIT / delay; int time = 0; while (count < maxRetry) { try { count++; Thread.sleep(delay); time += delay; if (time > DELETE_MAX_TIME) DELETE_MAX_TIME = time; if (DELETE_DEBUG) System.out.print('.'); if (resource.isAccessible()) { try { resource.delete(true, null); if (isResourceDeleted(resource) && isFileDeleted(file)) { // SUCCESS if (DELETE_DEBUG) { System.out.println(); System.out.println(" => resource really removed after "+time+"ms (max="+DELETE_MAX_TIME+"ms)"); System.out.println(); } return true; } } catch (CoreException e) { // skip } } if (isResourceDeleted(resource) && isFileDeleted(file)) { // SUCCESS if (DELETE_DEBUG) { System.out.println(); System.out.println(" => resource disappeared after "+time+"ms (max="+DELETE_MAX_TIME+"ms)"); System.out.println(); } return true; } // Increment waiting delay exponentially if (count >= 10 && delay <= 100) { count = 1; delay *= 10; maxRetry = DELETE_MAX_WAIT / delay; if ((DELETE_MAX_WAIT%delay) != 0) { maxRetry++; } } } catch (InterruptedException ie) { break; // end loop } } if (!DELETE_DEBUG) { System.out.println(); System.out.println("WARNING in test: "+getTestName()); System.out.println(" - problems occured while deleting resource "+resource); printRdtCoreStackTrace(null, 1); printFileInfo(file.getParentFile(), 1, -1); // display parent with its children } System.out.println(); System.out.println(" !!! ERROR: "+resource+" was never deleted even after having waited "+DELETE_MAX_TIME+"ms!!!"); System.out.println(); return false; } /** * Returns the test name from stack elements info. * * @return The name of the test currently running */ private static String getTestName() { StackTraceElement[] elements = new Exception().getStackTrace(); int idx = 0, length=elements.length; while (idx<length && !elements[idx++].getClassName().startsWith("org.rubypeoplee.rdt")) { // loop until RDT/Core class appears in the stack } if (idx<length) { StackTraceElement testElement = null; while (idx<length && elements[idx].getClassName().startsWith("org.rubypeople.rdt")) { testElement = elements[idx++]; } if (testElement != null) { return testElement.getClassName() + " - " + testElement.getMethodName(); } } return "?"; } /** * Returns whether a file is really deleted or not. * Does not only rely on {@link File#exists()} method but also * look if it's not in its parent children {@link #getParentChildFile(File)}. * * @param file The file to test if deleted * @return true if the file does not exist and was not found in its parent children. */ public static boolean isFileDeleted(File file) { return !file.exists() && getParentChildFile(file) == null; } private static File getParentChildFile(File file) { File parent = file.getParentFile(); if (parent == null || !parent.exists()) return null; File[] files = parent.listFiles(); int length = files==null ? 0 : files.length; if (length > 0) { for (int i=0; i<length; i++) { if (files[i] == file) { return files[i]; } else if (files[i].equals(file)) { return files[i]; } else if (files[i].getPath().equals(file.getPath())) { return files[i]; } } } return null; } /** * Print given file information with specified indentation. * These information are:<ul> * <li>read {@link File#canRead()}</li> * <li>write {@link File#canWrite()}</li> * <li>exists {@link File#exists()}</li> * <li>is file {@link File#isFile()}</li> * <li>is directory {@link File#isDirectory()}</li> * <li>is hidden {@link File#isHidden()}</li> * </ul> * May recurse several level in parents hierarchy. * May also display children, but then will not recusre in parent * hierarchy to avoid infinite loop... * * @param file The file to display information * @param indent Number of tab to print before the information * @param recurse Display also information on <code>recurse</code>th parents in hierarchy. * If negative then display children information instead. */ private static void printFileInfo(File file, int indent, int recurse) { String tab = ""; for (int i=0; i<indent; i++) tab+="\t"; System.out.print(tab+"- "+file.getName()+" file info: "); String sep = ""; if (file.canRead()) { System.out.print("read"); sep = ", "; } if (file.canWrite()) { System.out.print(sep+"write"); sep = ", "; } if (file.exists()) { System.out.print(sep+"exist"); sep = ", "; } if (file.isDirectory()) { System.out.print(sep+"dir"); sep = ", "; } if (file.isFile()) { System.out.print(sep+"file"); sep = ", "; } if (file.isHidden()) { System.out.print(sep+"hidden"); sep = ", "; } System.out.println(); File[] files = file.listFiles(); int length = files==null ? 0 : files.length; if (length > 0) { boolean children = recurse < 0; System.out.print(tab+" + children: "); if (children) System.out.println(); for (int i=0; i<length; i++) { if (children) { // display children printFileInfo(files[i], indent+2, -1); } else { if (i>0) System.out.print(", "); System.out.print(files[i].getName()); if (files[i].isDirectory()) System.out.print("[dir]"); else if (files[i].isFile()) System.out.print("[file]"); else System.out.print("[?]"); } } if (!children) System.out.println(); } if (recurse > 0) { File parent = file.getParentFile(); if (parent != null) printFileInfo(parent, indent+1, recurse-1); } } /** * Print stack trace with only RDT/Core elements. * * @param exception Exception of the stack trace. May be null, then a fake exception is used. * @param indent Number of tab to display before the stack elements to display. */ private static void printRdtCoreStackTrace(Exception exception, int indent) { String tab = ""; for (int i=0; i<indent; i++) tab+="\t"; StackTraceElement[] elements = (exception==null?new Exception():exception).getStackTrace(); int idx = 0, length=elements.length; while (idx<length && !elements[idx++].getClassName().startsWith("org.rubypeople.rdt")) { // loop until JDT/Core class appears in the stack } if (idx<length) { System.out.print(tab+"- stack trace"); if (exception == null) System.out.println(":"); else System.out.println(" for exception "+exception+":"); while (idx<length && elements[idx].getClassName().startsWith("org.rubypeople.rdt")) { StackTraceElement testElement = elements[idx++]; System.out.println(tab+" -> "+testElement); } } else { exception.printStackTrace(System.out); } } public static String displayString(String inputString, int indent) { return displayString(inputString, indent, false); } public static String displayString(String inputString){ return displayString(inputString, 0); } public static String displayString(String inputString, int indent, boolean shift) { if (inputString == null) return "null"; int length = inputString.length(); StringBuffer buffer = new StringBuffer(length); java.util.StringTokenizer tokenizer = new java.util.StringTokenizer(inputString, "\n\r", true); for (int i = 0; i < indent; i++) buffer.append("\t"); if (shift) indent++; buffer.append("\""); while (tokenizer.hasMoreTokens()){ String token = tokenizer.nextToken(); if (token.equals("\r")) { buffer.append("\\r"); if (tokenizer.hasMoreTokens()) { token = tokenizer.nextToken(); if (token.equals("\n")) { buffer.append("\\n"); if (tokenizer.hasMoreTokens()) { buffer.append("\" + \n"); for (int i = 0; i < indent; i++) buffer.append("\t"); buffer.append("\""); } continue; } buffer.append("\" + \n"); for (int i = 0; i < indent; i++) buffer.append("\t"); buffer.append("\""); } else { continue; } } else if (token.equals("\n")) { buffer.append("\\n"); if (tokenizer.hasMoreTokens()) { buffer.append("\" + \n"); for (int i = 0; i < indent; i++) buffer.append("\t"); buffer.append("\""); } continue; } StringBuffer tokenBuffer = new StringBuffer(); for (int i = 0; i < token.length(); i++){ char c = token.charAt(i); switch (c) { case '\r' : tokenBuffer.append("\\r"); break; case '\n' : tokenBuffer.append("\\n"); break; case '\b' : tokenBuffer.append("\\b"); break; case '\t' : tokenBuffer.append("\t"); break; case '\f' : tokenBuffer.append("\\f"); break; case '\"' : tokenBuffer.append("\\\""); break; case '\'' : tokenBuffer.append("\\'"); break; case '\\' : tokenBuffer.append("\\\\"); break; default : tokenBuffer.append(c); } } buffer.append(tokenBuffer.toString()); } buffer.append("\""); return buffer.toString(); } /** * Delete a file or directory and insure that the file is no longer present * on file system. In case of directory, delete all the hierarchy underneath. * * @param file The file or directory to delete * @return true iff the file was really delete, false otherwise */ public static boolean delete(File file) { // flush all directory content if (file.isDirectory()) { flushDirectoryContent(file); } // remove file file.delete(); if (isFileDeleted(file)) { return true; } return waitUntilFileDeleted(file); } /** * Flush content of a given directory (leaving it empty), * no-op if not a directory. */ public static void flushDirectoryContent(File dir) { File[] files = dir.listFiles(); if (files == null) return; for (int i = 0, max = files.length; i < max; i++) { delete(files[i]); } } /** * Wait until the file is _really_ deleted on file system. * * @param file Deleted file * @return true if the file was finally deleted, false otherwise */ private static boolean waitUntilFileDeleted(File file) { if (DELETE_DEBUG) { System.out.println(); System.out.println("WARNING in test: "+getTestName()); System.out.println(" - problems occured while deleting "+file); printRdtCoreStackTrace(null, 1); printFileInfo(file.getParentFile(), 1, -1); // display parent with its children System.out.print(" - wait for ("+DELETE_MAX_WAIT+"ms max): "); } int count = 0; int delay = 10; // ms int maxRetry = DELETE_MAX_WAIT / delay; int time = 0; while (count < maxRetry) { try { count++; Thread.sleep(delay); time += delay; if (time > DELETE_MAX_TIME) DELETE_MAX_TIME = time; if (DELETE_DEBUG) System.out.print('.'); if (file.exists()) { if (file.delete()) { // SUCCESS if (DELETE_DEBUG) { System.out.println(); System.out.println(" => file really removed after "+time+"ms (max="+DELETE_MAX_TIME+"ms)"); System.out.println(); } return true; } } if (isFileDeleted(file)) { // SUCCESS if (DELETE_DEBUG) { System.out.println(); System.out.println(" => file disappeared after "+time+"ms (max="+DELETE_MAX_TIME+"ms)"); System.out.println(); } return true; } // Increment waiting delay exponentially if (count >= 10 && delay <= 100) { count = 1; delay *= 10; maxRetry = DELETE_MAX_WAIT / delay; if ((DELETE_MAX_WAIT%delay) != 0) { maxRetry++; } } } catch (InterruptedException ie) { break; // end loop } } if (!DELETE_DEBUG) { System.out.println(); System.out.println("WARNING in test: "+getTestName()); System.out.println(" - problems occured while deleting "+file); printRdtCoreStackTrace(null, 1); printFileInfo(file.getParentFile(), 1, -1); // display parent with its children } System.out.println(); System.out.println(" !!! ERROR: "+file+" was never deleted even after having waited "+DELETE_MAX_TIME+"ms!!!"); System.out.println(); return false; } }