/**
* Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package org.python.pydev.shared_core.path_watch;
import java.io.File;
import java.io.FileFilter;
import java.nio.file.Paths;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.Watchable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.python.pydev.shared_core.callbacks.ICallback;
import org.python.pydev.shared_core.callbacks.ListenerList;
import org.python.pydev.shared_core.io.FileUtils;
import org.python.pydev.shared_core.string.FastStringBuffer;
import org.python.pydev.shared_core.structure.Tuple;
import org.python.pydev.shared_core.testutils.TestUtils;
import junit.framework.TestCase;
/**
* @author fabioz
*
*/
public class PathWatchTest extends TestCase {
private final class TrackChangesListener implements IFilesystemChangesListener {
@Override
public void removed(File file) {
notifyChange();
}
@Override
public void added(File file) {
notifyChange();
}
}
private final Object lockToSynchWait = new Object();
private final Object lockToChange = new Object();
private volatile boolean changeHappened = false;
private void notifyChange() {
synchronized (lockToChange) {
changeHappened = true;
}
synchronized (lockToSynchWait) {
lockToSynchWait.notifyAll();
}
}
private boolean getChangeHappened() {
synchronized (lockToChange) {
return changeHappened;
}
}
private File baseDir;
private PathWatch pathWatch;
@Override
protected void setUp() throws Exception {
pathWatch = new PathWatch();
pathWatch.log = new FastStringBuffer(1000);
baseDir = new File(FileUtils.getFileAbsolutePath(new File("pathwatchtest.temporary_dir")));
try {
FileUtils.deleteDirectoryTree(baseDir);
} catch (Exception e) {
//ignore
}
}
@Override
protected void tearDown() throws Exception {
//System.out.println(PathWatch.log);
pathWatch.log = null;
pathWatch.dispose();
try {
FileUtils.deleteDirectoryTree(baseDir);
} catch (Exception e) {
//ignore
}
}
public void testEventsStackerRunnable() throws Exception {
pathWatch.log.append("\n\n");
pathWatch.log.appendN('-', 50);
pathWatch.log.append("testEventsStackerRunnable\n");
WatchKey key = new WatchKey() {
@Override
public boolean reset() {
return false;
}
@Override
public List<WatchEvent<?>> pollEvents() {
return null;
}
@Override
public boolean isValid() {
return true;
}
@Override
public void cancel() {
}
@Override
public Watchable watchable() {
throw new RuntimeException("not implemented");
}
};
final List<Tuple<String, File>> changes = new ArrayList<Tuple<String, File>>();
ListenerList<IFilesystemChangesListener> list = new ListenerList<IFilesystemChangesListener>(
IFilesystemChangesListener.class);
list.add(new IFilesystemChangesListener() {
@Override
public void removed(File file) {
changes.add(new Tuple<String, File>("removed", file));
}
@Override
public void added(File file) {
changes.add(new Tuple<String, File>("added", file));
}
});
EventsStackerRunnable stack = new EventsStackerRunnable(key, Paths.get(FileUtils.getFileAbsolutePath(baseDir)),
list, baseDir, acceptAllFilter, acceptAllFilter);
stack.run();
assertEquals(0, changes.size());
stack.added(new File(baseDir, "f1.txt"));
stack.removed(new File(baseDir, "f1.txt"));
stack.run();
assertEquals(1, changes.size());
assertEquals("removed", changes.get(0).o1);
changes.clear();
stack.added(new File(baseDir, "f1.txt"));
stack.run();
assertEquals(1, changes.size());
assertEquals("added", changes.get(0).o1);
changes.clear();
stack.added(new File(baseDir, "f1.txt"));
stack.removed(new File(baseDir, "f1.txt"));
stack.overflow(baseDir);
stack.added(new File(baseDir, "f1.txt"));
stack.removed(new File(baseDir, "f1.txt"));
stack.run();
assertEquals(1, changes.size());
assertEquals("removed", changes.get(0).o1);
changes.clear();
stack.run();
assertEquals(0, changes.size());
stack.overflow(baseDir);
baseDir.mkdir();
stack.run();
assertEquals(2, changes.size());
assertEquals("removed", changes.get(0).o1);
assertEquals("added", changes.get(1).o1);
changes.clear();
}
private FileFilter pyFilesFilter = new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().endsWith(".py");
}
};
private FileFilter acceptAllFilter = new FileFilter() {
@Override
public boolean accept(File pathname) {
return true;
}
};
public void testPathWatchDirs() throws Exception {
baseDir.mkdir();
pathWatch.track(baseDir, new TrackChangesListener());
File dir = new File(baseDir, "dir");
dir.mkdir();
synchronized (lockToSynchWait) {
lockToSynchWait.wait(300);
}
if (getChangeHappened()) {
fail("Not expecting any addition when a directory is added inside a directory unless that directory "
+ "changed its time with interesting content.");
}
dir.delete();
synchronized (lockToSynchWait) {
lockToSynchWait.wait(300);
}
if (getChangeHappened()) {
fail("Should not report removal when nothing interesting changed.");
}
}
public void testPathWatchDirs2() throws Exception {
baseDir.mkdir();
pathWatch.setDirectoryFileFilter(pyFilesFilter, acceptAllFilter);
pathWatch.track(baseDir, new TrackChangesListener());
File dir = new File(baseDir, "dir");
dir.mkdir();
File f = new File(dir, "t.txt");
FileUtils.writeStrToFile("test", f);
synchronized (lockToSynchWait) {
lockToSynchWait.wait(300);
}
if (getChangeHappened()) {
fail("Not expecting any addition when a directory is added inside a directory unless that directory "
+ "changed its time with interesting content.");
}
dir.delete();
synchronized (lockToSynchWait) {
lockToSynchWait.wait(300);
}
if (getChangeHappened()) {
fail("Should not report removal when nothing interesting changed.");
}
}
public void testPathWatchDirs3() throws Exception {
baseDir.mkdir();
pathWatch.setDirectoryFileFilter(pyFilesFilter, acceptAllFilter);
pathWatch.track(baseDir, new TrackChangesListener());
File f = new File(baseDir, "t.txt");
FileUtils.writeStrToFile("test", f);
synchronized (lockToSynchWait) {
lockToSynchWait.wait(300);
}
if (getChangeHappened()) {
fail("Not expecting any addition when a directory is added inside a directory unless that directory "
+ "changed its time with interesting content.");
}
f = new File(baseDir, "t.py");
FileUtils.writeStrToFile("test", f);
synchronized (lockToSynchWait) {
lockToSynchWait.wait(300);
}
assertTrue(getChangeHappened());
}
public void testPathWatchDirs4() throws Exception {
baseDir.mkdir();
pathWatch.setDirectoryFileFilter(pyFilesFilter, acceptAllFilter);
pathWatch.track(baseDir, new TrackChangesListener());
File dir = new File(baseDir, "dir");
pathWatch.log.append("Creating :").appendObject(dir).append('\n');
dir.mkdir();
File f = new File(dir, "t.txt");
pathWatch.log.append("Creating :").appendObject(f).append('\n');
FileUtils.writeStrToFile("test", f);
synchronized (lockToSynchWait) {
lockToSynchWait.wait(300);
}
if (getChangeHappened()) {
fail("Not expecting any addition when a directory is added inside a directory unless that directory "
+ "changed its time with interesting content.");
}
f = new File(dir, "t.py");
pathWatch.log.append("Creating :").appendObject(f).append('\n');
FileUtils.writeStrToFile("test", f);
waitUntilCondition(new ICallback<String, Object>() {
@Override
public String call(Object arg) {
if (getChangeHappened()) {
return null;
}
return "No change detected. \nLog:\n" + pathWatch.log;
}
});
}
public void testPathWatch() throws Exception {
// This test passes if run on its own (not even with the other test in the
// same file)
pathWatch.log.append("\n\n");
pathWatch.log.appendN('-', 50);
pathWatch.log.append("testPathWatch\n");
baseDir.mkdir();
final List<Tuple<String, File>> changes = Collections.synchronizedList(new ArrayList<Tuple<String, File>>());
IFilesystemChangesListener listener = new IFilesystemChangesListener() {
@Override
public void removed(File file) {
changes.add(new Tuple<String, File>("removed", file));
}
@Override
public void added(File file) {
changes.add(new Tuple<String, File>("added", file));
}
};
IFilesystemChangesListener listener2 = new IFilesystemChangesListener() {
@Override
public void removed(File file) {
changes.add(new Tuple<String, File>("removed", file));
}
@Override
public void added(File file) {
changes.add(new Tuple<String, File>("added", file));
}
};
pathWatch.track(baseDir, listener);
for (int i = 0; i < 5; i++) {
FileUtils.writeStrToFile("FILE1", new File(baseDir, "f" + i + ".txt"));
}
waitUntilCondition(new ICallback<String, Object>() {
@Override
public String call(Object arg) {
Tuple<String, File>[] array = createChangesArray(changes);
HashSet<Tuple<String, File>> set = new HashSet<>(Arrays.asList(array));
Set<String> filesChanged = new HashSet<>();
for (Tuple<String, File> tuple : array) {
if (tuple.o1.equals("added")) {
filesChanged.add(FileUtils.getFileAbsolutePath(tuple.o2));
}
}
if (set.size() == 5) {
return null;
}
return changes.toString();
}
});
changes.clear();
pathWatch.log.append("--- Will delete base dir files ---\n");
File[] files = baseDir.listFiles();
if (files != null) {
for (int i = 0; i < files.length; ++i) {
File f = files[i];
f.delete();
}
}
waitUntilCondition(new ICallback<String, Object>() {
@Override
public String call(Object arg) {
int foundRemovals = 0;
Tuple<String, File>[] array = createChangesArray(changes);
for (Tuple<String, File> tuple : array) {
if (tuple.o1.equals("removed")) {
foundRemovals += 1;
}
}
if (foundRemovals == 5) {
return null;
}
return changes.toString();
}
});
changes.clear();
pathWatch.log.append("--- Will delete base dir ---\n");
assertTrue(baseDir.delete());
waitUntilCondition(new ICallback<String, Object>() {
@Override
public String call(Object arg) {
Tuple<String, File>[] array = createChangesArray(changes);
if (array.length == 1) {
for (Tuple<String, File> tuple : array) {
assertEquals("removed", tuple.o1);
assertEquals(baseDir, tuple.o2);
}
return null;
}
return changes.toString();
}
});
changes.clear();
pathWatch.log.append("--- Will create base dir ---");
baseDir.mkdir();
pathWatch.track(baseDir, listener);
pathWatch.track(baseDir, listener2);
pathWatch.log.append("--- Will delete base dir--- \n");
assertTrue(baseDir.delete());
//JPathWatch did notify us (through an extension) that a tracked directory was removed (i.e.: ExtendedWatchEventKind.KEY_INVALID).
// Java 1.7 doesn't, so the test below no longer works.
//
//waitUntilCondition(new ICallback<String, Object>() {
//
// public String call(Object arg) {
// Tuple<String, File>[] array = createChangesArray(changes);
// if (array.length == 2) { //2 listeners
// for (Tuple<String, File> tuple : array) {
// assertEquals("removed", tuple.o1);
// assertEquals(baseDir, tuple.o2);
// }
// return null;
// }
// return changes.toString();
// }
//});
//
//changes.clear();
//
//pathWatch.stopTrack(baseDir, listener); //Shouldn't be listening anymore, but just to check if there's some error
//listener2 not removed (but not there anymore)
baseDir.mkdir();
synchronized (lockToSynchWait) {
lockToSynchWait.wait(300);
}
assertEquals(0, changes.size());
}
private void waitUntilCondition(ICallback<String, Object> call) {
try {
TestUtils.waitUntilCondition(call);
} catch (AssertionError e1) {
fail("\nLog:" + pathWatch.log.toString() + "\n----------\n" + e1.getMessage());
}
}
@SuppressWarnings("unchecked")
private Tuple<String, File>[] createChangesArray(final List<Tuple<String, File>> changes) {
Tuple<String, File>[] array = changes.toArray(new Tuple[changes.size()]);
return array;
}
}