From a327761c480f8f127e3663bc5886aca39d1a0682 Mon Sep 17 00:00:00 2001 From: erik Date: Sun, 30 Mar 2008 12:44:05 +0000 Subject: [PATCH] added test cases for directorymonitor. --- .../java/org/wamblee/io/DirectoryMonitor.java | 127 +++--- .../org/wamblee/io/DirectoryMonitorTest.java | 116 +++++ .../java/org/wamblee/io/FileSystemUtils.java | 416 +++++++++--------- .../test/java/org/wamblee/io/TestData.java | 76 +++- 4 files changed, 470 insertions(+), 265 deletions(-) create mode 100644 support/src/test/java/org/wamblee/io/DirectoryMonitorTest.java diff --git a/support/src/main/java/org/wamblee/io/DirectoryMonitor.java b/support/src/main/java/org/wamblee/io/DirectoryMonitor.java index 74723337..0f84183c 100644 --- a/support/src/main/java/org/wamblee/io/DirectoryMonitor.java +++ b/support/src/main/java/org/wamblee/io/DirectoryMonitor.java @@ -12,7 +12,7 @@ * 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. - */ + */ package org.wamblee.io; @@ -21,65 +21,90 @@ import java.io.FileFilter; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** - * Monitors a directory for changes. - * + * Monitors a directory for changes. + * * @author Erik Brakkee */ public class DirectoryMonitor { - - private static final Log LOG = LogFactory.getLog(DirectoryMonitor.class); - - public static interface Listener { - void fileChanged(File aFile); - void fileCreated(File aFile); - void fileDeleted(File aFile); - }; - - private File _directory; - private FileFilter _filter; - private Listener _listener; - private Map _contents; - - public DirectoryMonitor(File aDirectory, FileFilter aFilefilter, Listener aListener) { - _directory = aDirectory; - if ( !_directory.isDirectory()) { - throw new IllegalArgumentException("Directory '" + _directory + "' does not exist"); - } - _filter = aFilefilter; - _listener = aListener; - _contents = new HashMap(); - } - - public void poll() { - LOG.debug("Polling " + _directory); - Map newContents = new HashMap(); - File[] files = _directory.listFiles(_filter); - for (File file: files) { - if ( _contents.containsKey(file)) { - Date oldDate = _contents.get(file); - if (file.lastModified() != oldDate.getTime()) { - _listener.fileChanged(file); - } else { - // No change. - } - _contents.remove(file); - newContents.put(file, new Date(file.lastModified())); - } else { - _listener.fileCreated(file); - newContents.put(file, new Date(file.lastModified())); - } - } - for (File file: _contents.keySet()) { - _listener.fileDeleted(file); - } - _contents = newContents; - } + + private static final Log LOG = LogFactory.getLog(DirectoryMonitor.class); + + public static interface Listener { + + void fileChanged(File aFile); + + void fileCreated(File aFile); + + void fileDeleted(File aFile); + }; + + private File _directory; + private FileFilter _filter; + private Listener _listener; + private Map _contents; + + public DirectoryMonitor(File aDirectory, FileFilter aFilefilter, + Listener aListener) { + _directory = aDirectory; + if (!_directory.isDirectory()) { + throw new IllegalArgumentException("Directory '" + _directory + + "' does not exist"); + } + _filter = aFilefilter; + _listener = aListener; + _contents = new HashMap(); + } + + /** + * Polls the directory for changes and notifies the listener of any changes. + * In case of any exceptions thrown by the listener while handling the changes, + * the next call to this method will invoked the listeners again for the same changes. + */ + public void poll() { + LOG.debug("Polling " + _directory); + Map newContents = new HashMap(); + File[] files = _directory.listFiles(_filter); + + // Check deleted files. + Set deletedFiles = new HashSet(_contents.keySet()); + for (File file : files) { + if (file.isFile()) { + if (_contents.containsKey(file)) { + deletedFiles.remove(file); + } + } + } + for (File file : deletedFiles) { + _listener.fileDeleted(file); + } + + for (File file : files) { + if (file.isFile()) { + if (_contents.containsKey(file)) { + Date oldDate = _contents.get(file); + if (file.lastModified() != oldDate.getTime()) { + _listener.fileChanged(file); + } else { + // No change. + } + newContents.put(file, new Date(file.lastModified())); + } else { + _listener.fileCreated(file); + newContents.put(file, new Date(file.lastModified())); + } + } + } + + _contents = newContents; + } } diff --git a/support/src/test/java/org/wamblee/io/DirectoryMonitorTest.java b/support/src/test/java/org/wamblee/io/DirectoryMonitorTest.java new file mode 100644 index 00000000..b778adac --- /dev/null +++ b/support/src/test/java/org/wamblee/io/DirectoryMonitorTest.java @@ -0,0 +1,116 @@ +package org.wamblee.io; + +import org.apache.oro.io.AwkFilenameFilter; +import org.easymock.EasyMock; + +import junit.framework.TestCase; + +public class DirectoryMonitorTest extends TestCase { + + private static final String REGEX = "^.*\\.txt$"; + private static final String FILE1 = "file1.txt"; + + private TestData _data; + private DirectoryMonitor.Listener _listener; + private DirectoryMonitor _monitor; + + @Override + protected void setUp() throws Exception { + super.setUp(); + _data = new TestData(this); + _data.clean(); + _listener = EasyMock.createStrictMock(DirectoryMonitor.Listener.class); + _monitor = new DirectoryMonitor(_data.getRoot(), new AwkFilenameFilter( + REGEX), _listener); + } + + public void testEmptyDir() { + // Nothing is expected to be called. + for (int i = 0; i < 10; i++) { + EasyMock.replay(_listener); + _monitor.poll(); + EasyMock.verify(_listener); + EasyMock.reset(_listener); + } + } + + public void testFileCreated() { + _listener.fileCreated(_data.getFile(FILE1)); + EasyMock.replay(_listener); + _data.createFile(FILE1, "hello"); + _monitor.poll(); + EasyMock.verify(_listener); + } + + public void testFileDeleted() { + _data.createFile(FILE1, "hello"); + _monitor.poll(); + + EasyMock.reset(_listener); + + _data.deleteFile(FILE1); + _listener.fileDeleted(_data.getFile(FILE1)); + EasyMock.replay(_listener); + _monitor.poll(); + EasyMock.verify(_listener); + } + + public void testFileChanged() throws InterruptedException { + _data.createFile(FILE1, "hello"); + _monitor.poll(); + EasyMock.reset(_listener); + + Thread.sleep(2000); + _data.deleteFile(FILE1); + _data.createFile(FILE1, "bla"); + + _listener.fileChanged(_data.getFile(FILE1)); + EasyMock.replay(_listener); + _monitor.poll(); + EasyMock.verify(_listener); + } + + public void testFileFilterIsUsed() { + _monitor.poll(); + + _data.createFile("file.xml", "hello"); + EasyMock.replay(_listener); + _monitor.poll(); + EasyMock.verify(_listener); + } + + public void testDirectoryIsIgnored() { + _monitor.poll(); + _data.createDir(FILE1); + EasyMock.replay(_listener); + _monitor.poll(); + EasyMock.verify(_listener); + } + + public void testExceptionsWIllLeadToRepeatedNotifications() { + _monitor.poll(); + _data.createFile(FILE1, "hello"); + + _listener.fileCreated(_data.getFile(FILE1)); + EasyMock.expectLastCall().andThrow(new RuntimeException()); + EasyMock.replay(_listener); + try { + _monitor.poll(); + } catch (RuntimeException e) { + EasyMock.verify(_listener); + EasyMock.reset(_listener); + + // polling again should lead to the same filecreated call. + // this time no exception is thrown. + + _listener.fileCreated(_data.getFile(FILE1)); + EasyMock.replay(_listener); + _monitor.poll(); + EasyMock.verify(_listener); + return; + } + fail(); // should not get here. + + + } +} diff --git a/support/src/test/java/org/wamblee/io/FileSystemUtils.java b/support/src/test/java/org/wamblee/io/FileSystemUtils.java index 3d0f3b3b..ad3b4e20 100644 --- a/support/src/test/java/org/wamblee/io/FileSystemUtils.java +++ b/support/src/test/java/org/wamblee/io/FileSystemUtils.java @@ -36,205 +36,225 @@ import org.apache.commons.logging.LogFactory; /** * File system utilities. - * + * * @author Erik Brakkee */ public final class FileSystemUtils { - private static final Log LOG = LogFactory.getLog(FileSystemUtils.class); - - /** - * Test output directory relative to the sub project. - */ - private static final String TEST_OUTPUT_DIR = "../target/testoutput"; - - /** - * Test input directory relative to the sub project. - */ - private static final String TEST_INPUT_DIR = "../src/test/resources"; - - /* - * Disabled. - * - */ - private FileSystemUtils() { - // Empty - } - - /** - * Deletes a directory recursively. The test case will fail if the directory - * does not exist or if deletion fails. - * - * @param aDir - * Directory to delete. - */ - public static void deleteDirRecursively(String aDir) { - deleteDirRecursively(new File(aDir)); - } - - /** - * Deletes a directory recursively. See {@link #deleteDirRecursively}. - * - * @param aDir - * Directory. - */ - public static void deleteDirRecursively(File aDir) { - TestCase.assertTrue(aDir.isDirectory()); - - for (File file : aDir.listFiles()) { - if (file.isDirectory()) { - deleteDirRecursively(file); - } else { - delete(file); - } - } - - delete(aDir); - } - - /** - * Deletes a file or directory. The test case will fail if the file or - * directory does not exist or if deletion fails. Deletion of a non-empty - * directory will always fail. - * - * @param aFile - * File or directory to delete. - */ - public static void delete(File aFile) { - TestCase.assertTrue(aFile.delete()); - } - - /** - * Gets a path relative to a sub project. This utility should be used to - * easily access file paths within a subproject without requiring any - * specific Eclipse configuration. - * - * @param aRelativePath - * Relative path. - * @param aTestClass - * Test class. - */ - public static File getPath(String aRelativePath, Class aTestClass) { - CodeSource source = aTestClass.getProtectionDomain().getCodeSource(); - if (source == null) { - LOG.warn("Could not obtain path for '" + aRelativePath - + "' for class " + aTestClass - + ", using relative path as is"); - return new File(aRelativePath); - } - URL location = source.getLocation(); - String protocol = location.getProtocol(); - if (!protocol.equals("file")) { - LOG.warn("protocol is not 'file': " + location); - return new File(aRelativePath); - } - - String path = location.getPath(); - try { - path = URLDecoder.decode(location.getPath(), "UTF-8"); - } catch (UnsupportedEncodingException e) { - // ignore it.. just don't decode - LOG.warn("Decoding path failed: '" + location.getPath() + "'", e ); - } - - return new File(new File(path).getParentFile(), aRelativePath); - } - - /** - * Ensures that a directory hierarchy exists (recursively if needed). If it - * is not possible to create the directory, then the test case will fail. - * - * @param aDir - * Directory to create. - */ - public static void createDir(File aDir) { - if (aDir.exists() && !aDir.isDirectory()) { - TestCase.fail("'" + aDir - + "' already exists and is not a directory"); - } - if (aDir.exists()) { - return; - } - createDir(aDir.getParentFile()); - TestCase.assertTrue("Could not create '" + aDir + "'", aDir.mkdir()); - } - - /** - * Gets the test output directory for a specific test class. - * - * @param aTestClass - * Test class. - * @return Test output directory. - */ - public static File getTestOutputDir(Class aTestClass) { - File file = getPath(TEST_OUTPUT_DIR, aTestClass); - String packageName = aTestClass.getPackage().getName(); - String packagePath = packageName.replaceAll("\\.", "/"); - return new File(file, packagePath); - } - - /** - * Gets the test input directory for a specific test class. - * - * @param aTestClass - * Test class. - * @return Test input directory. - */ - public static File getTestInputDir(Class aTestClass) { - File file = getPath(TEST_INPUT_DIR, aTestClass); - String packageName = aTestClass.getPackage().getName(); - String packagePath = packageName.replaceAll("\\.", "/"); - return new File(file, packagePath); - } - - /** - * Creates a directory hierarchy for the output directory of a test class if - * needed. - * - * @param aTestClass - * Test class - * @return Test directory. - */ - public static File createTestOutputDir(Class aTestClass) { - File file = getTestOutputDir(aTestClass); - createDir(file); - return file; - } - - /** - * Gets a test output file name. This returns a File object representing the - * output file and ensures that the directory where the file will be created - * already exists. - * - * @param aName - * Name of the file. - * @param aTestClass - * Test class. - * @return File. - */ - public static File getTestOutputFile(String aName, Class aTestClass) { - File file = new File(getTestOutputDir(aTestClass), aName); - createDir(file.getParentFile()); - return file; - } - - public static String read(InputStream aIs) throws IOException { - try { - StringBuffer buffer = new StringBuffer(); - int c; - while ((c = aIs.read()) != -1) { - buffer.append((char)c); - } - return buffer.toString(); - } finally { - aIs.close(); - } - } - - /** - * Copies an input stream to an output stream. - * @param aIs Input stream. - * @param aOs Output stream. + private static final Log LOG = LogFactory.getLog(FileSystemUtils.class); + + /** + * Test output directory relative to the sub project. + */ + private static final String TEST_OUTPUT_DIR = "../target/testoutput"; + + /** + * Test input directory relative to the sub project. + */ + private static final String TEST_INPUT_DIR = "../src/test/resources"; + + /* + * Disabled. + * + */ + private FileSystemUtils() { + // Empty + } + + /** + * Deletes a directory recursively. The test case will fail if the directory + * does not exist or if deletion fails. + * + * @param aDir + * Directory to delete. + */ + public static void deleteDirRecursively(String aDir) { + deleteDirRecursively(new File(aDir)); + } + + /** + * Deletes a directory recursively. See {@link #deleteDirRecursively}. + * + * @param aDir + * Directory. + */ + public static void deleteDirRecursively(File aDir) { + TestCase.assertTrue(aDir.isDirectory()); + + for (File file : aDir.listFiles()) { + if (file.isDirectory()) { + deleteDirRecursively(file); + } else { + delete(file); + } + } + + delete(aDir); + } + + /** + * Deletes a file or directory. The test case will fail if the file or + * directory does not exist or if deletion fails. Deletion of a non-empty + * directory will always fail. + * + * @param aFile + * File or directory to delete. + */ + public static void delete(File aFile) { + TestCase.assertTrue(aFile.delete()); + } + + /** + * Gets a path relative to a sub project. This utility should be used to + * easily access file paths within a subproject without requiring any + * specific Eclipse configuration. + * + * @param aRelativePath + * Relative path. + * @param aTestClass + * Test class. + */ + public static File getPath(String aRelativePath, Class aTestClass) { + CodeSource source = aTestClass.getProtectionDomain().getCodeSource(); + if (source == null) { + LOG.warn("Could not obtain path for '" + aRelativePath + + "' for class " + aTestClass + + ", using relative path as is"); + return new File(aRelativePath); + } + URL location = source.getLocation(); + String protocol = location.getProtocol(); + if (!protocol.equals("file")) { + LOG.warn("protocol is not 'file': " + location); + return new File(aRelativePath); + } + + String path = location.getPath(); + try { + path = URLDecoder.decode(location.getPath(), "UTF-8"); + } catch (UnsupportedEncodingException e) { + // ignore it.. just don't decode + LOG.warn("Decoding path failed: '" + location.getPath() + "'", e); + } + + return new File(new File(path).getParentFile(), aRelativePath); + } + + /** + * Ensures that a directory hierarchy exists (recursively if needed). If it + * is not possible to create the directory, then the test case will fail. + * + * @param aDir + * Directory to create. + */ + public static void createDir(File aDir) { + if (aDir.exists() && !aDir.isDirectory()) { + TestCase.fail("'" + aDir + + "' already exists and is not a directory"); + } + if (aDir.exists()) { + return; + } + createDir(aDir.getParentFile()); + TestCase.assertTrue("Could not create '" + aDir + "'", aDir.mkdir()); + } + + /** + * Creates a file in a directory. + * @param aDir Directory path. Will be created if it does not exist. + * @param aName Filename. + * @param aContents Contents. + */ + public static void createFile(File aDir, String aName, InputStream aContents) { + createDir(aDir); + try { + OutputStream os = new FileOutputStream(new File(aDir, aName)); + copyStream(aContents, os); + } catch (IOException e) { + e.printStackTrace(); + TestCase.fail(e.getMessage()); + } + } + + /** + * Gets the test output directory for a specific test class. + * + * @param aTestClass + * Test class. + * @return Test output directory. + */ + public static File getTestOutputDir(Class aTestClass) { + File file = getPath(TEST_OUTPUT_DIR, aTestClass); + String className = aTestClass.getName(); + String classRelPath = className.replaceAll("\\.", "/"); + return new File(file, classRelPath); + } + + /** + * Gets the test input directory for a specific test class. + * + * @param aTestClass + * Test class. + * @return Test input directory. + */ + public static File getTestInputDir(Class aTestClass) { + File file = getPath(TEST_INPUT_DIR, aTestClass); + String packageName = aTestClass.getPackage().getName(); + String packagePath = packageName.replaceAll("\\.", "/"); + return new File(file, packagePath); + } + + /** + * Creates a directory hierarchy for the output directory of a test class if + * needed. + * + * @param aTestClass + * Test class + * @return Test directory. + */ + public static File createTestOutputDir(Class aTestClass) { + File file = getTestOutputDir(aTestClass); + createDir(file); + return file; + } + + /** + * Gets a test output file name. This returns a File object representing the + * output file and ensures that the directory where the file will be created + * already exists. + * + * @param aName + * Name of the file. + * @param aTestClass + * Test class. + * @return File. + */ + public static File getTestOutputFile(String aName, Class aTestClass) { + File file = new File(getTestOutputDir(aTestClass), aName); + createDir(file.getParentFile()); + return file; + } + + public static String read(InputStream aIs) throws IOException { + try { + StringBuffer buffer = new StringBuffer(); + int c; + while ((c = aIs.read()) != -1) { + buffer.append((char) c); + } + return buffer.toString(); + } finally { + aIs.close(); + } + } + + /** + * Copies an input stream to an output stream. + * + * @param aIs + * Input stream. + * @param aOs + * Output stream. */ public static void copyStream(InputStream aIs, OutputStream aOs) { try { @@ -243,7 +263,7 @@ public final class FileSystemUtils { aOs.write(c); } aIs.close(); - aOs.close(); + aOs.close(); } catch (IOException e) { e.printStackTrace(); Assert.fail(e.getMessage()); @@ -313,7 +333,7 @@ public final class FileSystemUtils { + aTarget.getPath() + " failed.", false); } } - + /** * Remove all files within a given directory including the directory itself. * This only attempts to remove regular files and not directories within the @@ -327,7 +347,7 @@ public final class FileSystemUtils { cleanDir(aDir); delete(aDir); } - + /** * Remove all regular files within a given directory. * diff --git a/support/src/test/java/org/wamblee/io/TestData.java b/support/src/test/java/org/wamblee/io/TestData.java index cc815c1b..58bea68e 100644 --- a/support/src/test/java/org/wamblee/io/TestData.java +++ b/support/src/test/java/org/wamblee/io/TestData.java @@ -15,6 +15,7 @@ */ package org.wamblee.io; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -27,7 +28,7 @@ import java.nio.channels.FileChannel; import junit.framework.Assert; /** - * TestData provides a convenient interface for managing test output files. + * TestData provides a convenient interface for managing test output files. * * @author Erik Brakkee */ @@ -37,13 +38,14 @@ public final class TestData { private File _root; /** - * Test data to be constructed in the setUp of a test. - * {@link #clean()} must be called to make sure that this - * directory is empty before executing a test. + * Test data to be constructed in the setUp of a test. {@link #clean()} must + * be called to make sure that this directory is empty before executing a + * test. */ public TestData(Object aTestcase) { _testcase = aTestcase; _root = getTestRootDir(aTestcase); + FileSystemUtils.createDir(_root); } /** @@ -55,6 +57,28 @@ public final class TestData { return FileSystemUtils.getTestOutputDir(aTestcase.getClass()); } + public void createFile(String aRelative, String aFile, InputStream aContents) { + FileSystemUtils + .createFile(new File(_root, aRelative), aFile, aContents); + } + + public void createFile(String aFile, String aContents) { + createFile(".", aFile, aContents); + } + + public void createFile(String aRelative, String aFile, String aContents) { + InputStream is = new ByteArrayInputStream(aContents.getBytes()); + FileSystemUtils.createFile(new File(_root, aRelative), aFile, is); + } + + public void deleteFile(String aFile) { + deleteFile(".", aFile); + } + + public void deleteFile(String aRelative, String aFile) { + FileSystemUtils.delete(new File(_root, aFile)); + } + /** * Returns a temporary directory. * @@ -69,6 +93,7 @@ public final class TestData { */ public void clean() { FileSystemUtils.deleteDirRecursively(_root); + FileSystemUtils.createDir(_root); } /** @@ -80,7 +105,7 @@ public final class TestData { public void copyDir(File aSrc) { FileSystemUtils.copyDir(aSrc, _root); } - + /** * Copies a classpath resource to a relative path in the test output * directory. @@ -104,13 +129,19 @@ public final class TestData { /** * Copies a resource to the root directory of the test output. - * @param aResource Resource. + * + * @param aResource + * Resource. */ public void copyResource(String aResource) { String basename = new File(aResource).getName(); copyResource(aResource, basename); } + public void createDir(String aRelative) { + FileSystemUtils.createDir(new File(_root, aRelative)); + } + /** * Deletes a file or directory relative to the test output root. * @@ -123,25 +154,38 @@ public final class TestData { } /** - * Deletes a directory including its contents. - * @param aRelative Relative path. + * Deletes a directory including its contents. + * + * @param aRelative + * Relative path. */ - public void deleteDir(String aRelative) { + public void deleteDir(String aRelative) { FileSystemUtils.deleteDir(new File(_root, aRelative)); } /** - * Deletes a directory recursively. - * @param aRelative Relative path. + * Deletes a directory recursively. + * + * @param aRelative + * Relative path. */ - public void deleteDirRecursively(String aRelative) { + public void deleteDirRecursively(String aRelative) { FileSystemUtils.deleteDir(new File(_root, aRelative)); } - + + /** + * Gets the root of the test output directory. + * + * @return Root of the test output. + */ + public File getRoot() { + return _root; + } + /** - * Gets a file object for a relative path. + * Gets a file object for a relative path. */ - public File getFile(String aRelative) { - return new File(_root, aRelative); + public File getFile(String aRelative) { + return new File(_root, aRelative); } } -- 2.31.1