package org.peerbox.watchservice;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyObject;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.peerbox.testutils.WatchServiceTestHelpers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FolderWatchServiceTest {
private static final Logger logger = LoggerFactory.getLogger(FolderWatchServiceTest.class);
private static Path basePath;
private FolderWatchService watchService;
private static final int SLEEP_TIME = 1000;
private static final int FILE_SIZE = 1*1024*1024;
@Mock
private ILocalFileEventListener fileEventListener;
/**
* !! NOTE: need to call start() watchservice in each test !!
*/
@BeforeClass
public static void setup() {
basePath = Paths.get(FileUtils.getTempDirectoryPath(), "PeerWasp_FolderWatchServiceTest");
basePath.toFile().mkdir();
logger.info("Path: {}", basePath);
}
@Before
public void initialization() throws Exception {
FileUtils.cleanDirectory(basePath.toFile());
MockitoAnnotations.initMocks(this);
watchService = new FolderWatchService();
watchService.addFileEventListener(fileEventListener);
}
@After
public void cleanup() throws Exception {
watchService.stop();
watchService = null;
FileUtils.cleanDirectory(basePath.toFile());
}
@Test
public void testServiceStart() throws Exception {
Path file = addModifyDelete("file_1.txt");
// service stopped -> no events should be processed
Mockito.verify(fileEventListener, Mockito.never()).onLocalFileCreated(file);
Mockito.verify(fileEventListener, Mockito.never()).onLocalFileModified(file);
Mockito.verify(fileEventListener, Mockito.never()).onLocalFileDeleted(file);
watchService.start(basePath);
file = addModifyDelete("file_2.txt");
// service started -> events should be processed
Mockito.verify(fileEventListener, Mockito.times(1)).onLocalFileCreated(file);
Mockito.verify(fileEventListener, Mockito.atLeastOnce()).onLocalFileModified(file);
Mockito.verify(fileEventListener, Mockito.times(1)).onLocalFileDeleted(file);
}
@Test
public void testServiceStop() throws Exception {
watchService.start(basePath);
sleep();
watchService.stop();
Path file = addModifyDelete("file_1.txt");
// service stopped -> no events should be processed
Mockito.verify(fileEventListener, Mockito.never()).onLocalFileCreated(file);
Mockito.verify(fileEventListener, Mockito.never()).onLocalFileModified(file);
Mockito.verify(fileEventListener, Mockito.never()).onLocalFileDeleted(file);
}
@Test
public void testServiceRestart() throws Exception {
watchService.start(basePath);
Path file = addModifyDelete("file_1.txt");
// service started -> events should be processed
Mockito.verify(fileEventListener, Mockito.times(1)).onLocalFileCreated(file);
Mockito.verify(fileEventListener, Mockito.atLeastOnce()).onLocalFileModified(file);
Mockito.verify(fileEventListener, Mockito.times(1)).onLocalFileDeleted(file);
watchService.stop();
file = addModifyDelete("file_2.txt");
// service stopped -> no events should be processed
Mockito.verify(fileEventListener, Mockito.never()).onLocalFileCreated(file);
Mockito.verify(fileEventListener, Mockito.never()).onLocalFileModified(file);
Mockito.verify(fileEventListener, Mockito.never()).onLocalFileDeleted(file);
watchService.start(basePath);
// service stopped -> no events should be processed
Mockito.verify(fileEventListener, Mockito.never()).onLocalFileCreated(file);
Mockito.verify(fileEventListener, Mockito.never()).onLocalFileModified(file);
Mockito.verify(fileEventListener, Mockito.never()).onLocalFileDeleted(file);
}
private Path addModifyDelete(String filename) throws IOException, InterruptedException {
// file operations
Path file = Paths.get(basePath.toString(), filename);
assertTrue(file.toFile().createNewFile());
sleep();
FileWriter out = new FileWriter(file.toFile());
WatchServiceTestHelpers.writeRandomData(out, FILE_SIZE);
out.close();
sleep();
Files.delete(file);
sleep();
return file;
}
@Test
public void testFileAddEvent() throws Exception {
watchService.start(basePath);
// new file
Path add = Paths.get(basePath.toString(), "add.txt");
assertTrue(add.toFile().createNewFile());
sleep();
// expect 1 event
Mockito.verify(fileEventListener, Mockito.times(1)).onLocalFileCreated(add);
}
@Test
public void testFileModifyEvent() throws Exception {
// new file
Path modify = Paths.get(basePath.toString(), "modify.txt");
assertTrue(modify.toFile().createNewFile());
watchService.start(basePath);
// write some content
FileWriter out = new FileWriter(modify.toFile());
WatchServiceTestHelpers.writeRandomData(out, FILE_SIZE);
out.close();
sleep();
// expect multiple modify events
Mockito.verify(fileEventListener, Mockito.never()).onLocalFileCreated(modify);
Mockito.verify(fileEventListener, Mockito.atLeastOnce()).onLocalFileModified(modify);
}
@Test
public void testFileDeleteEvent() throws Exception {
// new file
Path delete = Paths.get(basePath.toString(), "delete.txt");
assertTrue(delete.toFile().createNewFile());
// write some content
FileWriter out = new FileWriter(delete.toFile());
WatchServiceTestHelpers.writeRandomData(out, FILE_SIZE);
out.close();
watchService.start(basePath);
// delete
assertTrue(delete.toFile().delete());
sleep();
// expect 1 event
Mockito.verify(fileEventListener, Mockito.times(1)).onLocalFileDeleted(delete);
}
@Test
public void testFileMoveEvent() throws Exception {
// new file and directory
Path move = Paths.get(basePath.toString(), "move.txt");
assertTrue(move.toFile().createNewFile());
Path newDir = Paths.get(basePath.toString(), "newlocation");
assertTrue(newDir.toFile().mkdir());
// write some content
FileWriter out = new FileWriter(move.toFile());
WatchServiceTestHelpers.writeRandomData(out, FILE_SIZE);
out.close();
watchService.start(basePath);
// move the file into directory
Path dstFile = Paths.get(newDir.toString(), move.getFileName().toString());
FileUtils.moveFile(move.toFile(), dstFile.toFile());
sleep();
// expect 1 delete, 1 create event
Mockito.verify(fileEventListener, Mockito.times(1)).onLocalFileDeleted(move);
Mockito.verify(fileEventListener, Mockito.times(1)).onLocalFileCreated(dstFile);
}
@Test
public void testFileRenameEvent() throws Exception {
// new file
Path rename = Paths.get(basePath.toString(), "rename.txt");
assertTrue(rename.toFile().createNewFile());
// write some content
FileWriter out = new FileWriter(rename.toFile());
WatchServiceTestHelpers.writeRandomData(out, FILE_SIZE);
out.close();
watchService.start(basePath);
// rename the file
Path newName = Paths.get(basePath.toString(), "rename_new.txt");
assertTrue(rename.toFile().renameTo(newName.toFile()));
sleep();
// expect 1 delete, 1 create event
Mockito.verify(fileEventListener, Mockito.times(1)).onLocalFileDeleted(rename);
Mockito.verify(fileEventListener, Mockito.times(1)).onLocalFileCreated(newName);
}
@Test
public void testFileCopyEvent() throws Exception {
// new file
Path original = Paths.get(basePath.toString(), "copy.txt");
assertTrue(original.toFile().createNewFile());
// write some content
FileWriter out = new FileWriter(original.toFile());
WatchServiceTestHelpers.writeRandomData(out, FILE_SIZE);
out.close();
watchService.start(basePath);
// rename the file
Path copy = Paths.get(basePath.toString(), "copy_of_file.txt");
FileUtils.copyFile(original.toFile(), copy.toFile());
sleep();
Mockito.verify(fileEventListener, Mockito.times(1)).onLocalFileCreated(copy);
}
@Test
public void testFolderAddEvent() throws Exception {
Path newFolder = Paths.get(basePath.toString(), "newfolder");
watchService.start(basePath);
assertTrue(newFolder.toFile().mkdir());
sleep();
Mockito.verify(fileEventListener, Mockito.times(1)).onLocalFileCreated(newFolder);
}
@Test
public void testAddFileInNewFolderEvent() throws Exception {
Path newFolder = Paths.get(basePath.toString(), "newfolder");
Path newFile = Paths.get(newFolder.toString(), "file.txt");
watchService.start(basePath);
assertTrue(newFolder.toFile().mkdir());
// sleep(); //-> this sleep shows that create event is fired if we wait a bit (until folder is registered)
Thread.sleep(100);
assertTrue(newFile.toFile().createNewFile());
sleep();
Mockito.verify(fileEventListener, Mockito.times(1)).onLocalFileCreated(newFolder);
Mockito.verify(fileEventListener, Mockito.times(1)).onLocalFileCreated(newFile);
}
@Test
public void testEmptyFolderDelete() throws Exception {
// create folder and delete it
Path folder = Paths.get(basePath.toString(), "todelete");
Files.createDirectory(folder);
watchService.start(basePath);
Files.delete(folder);
sleep();
Mockito.verify(fileEventListener, Mockito.times(1)).onLocalFileDeleted(folder);
}
private List<Path> createFolderWithFiles(Path folder, int numFiles) throws IOException {
Files.createDirectory(folder);
List<Path> files = new ArrayList<Path>();
for(int i = 0; i < numFiles; ++i) {
// create file with some content
Path file = Paths.get(folder.toString(), String.format("%s.txt", i));
Files.createFile(file);
FileWriter out = new FileWriter(file.toFile());
WatchServiceTestHelpers.writeRandomData(out, FILE_SIZE);
out.close();
files.add(file);
}
return files;
}
@Test
public void testFolderDelete() throws Exception {
// create folder and some files in it.
Path folder = Paths.get(basePath.toString(), "todelete");
List<Path> files = createFolderWithFiles(folder, 200);
watchService.start(basePath);
FileUtils.deleteDirectory(folder.toFile());
sleep();
Mockito.verify(fileEventListener, Mockito.times(1)).onLocalFileDeleted(folder);
Mockito.verify(fileEventListener, Mockito.times(1+files.size())).onLocalFileDeleted(anyObject());
}
@Test
public void testFolderMoveEvent() throws Exception {
Path folder = Paths.get(basePath.toString(), "tomove");
List<Path> files = createFolderWithFiles(folder, 200);
Path newLocation = Paths.get(basePath.toString(), "newlocation");
Files.createDirectory(newLocation);
newLocation = Paths.get(newLocation.toString(), "tomove");
watchService.start(basePath);
Files.move(folder, newLocation);
sleep();
Mockito.verify(fileEventListener, Mockito.times(1)).onLocalFileDeleted(folder);
Mockito.verify(fileEventListener, Mockito.times(1)).onLocalFileCreated(newLocation);
}
@Test
public void testFolderRenameEvent() throws Exception {
Path folder = Paths.get(basePath.toString(), "torename");
List<Path> files = createFolderWithFiles(folder, 200);
Path rename = Paths.get(basePath.toString(), "torename_rename");
watchService.start(basePath);
Files.move(folder, rename);
sleep();
Mockito.verify(fileEventListener, Mockito.times(1)).onLocalFileDeleted(folder);
Mockito.verify(fileEventListener, Mockito.times(1)).onLocalFileCreated(rename);
}
@Test
public void testFolderCopyEvent() throws Exception {
Path folder = Paths.get(basePath.toString(), "tomove");
List<Path> files = createFolderWithFiles(folder, 200);
Path copy = Paths.get(basePath.toString(), "copy");
watchService.start(basePath);
Files.copy(folder, copy);
sleep();
// old folder untouched
Mockito.verify(fileEventListener, Mockito.never()).onLocalFileDeleted(folder);
Mockito.verify(fileEventListener, Mockito.never()).onLocalFileCreated(folder);
Mockito.verify(fileEventListener, Mockito.never()).onLocalFileModified(folder);
// new folder
Mockito.verify(fileEventListener, Mockito.times(1)).onLocalFileCreated(anyObject());
}
@Test
public void testHighLoad() throws Exception {
watchService.start(basePath);
List<Path> files = new ArrayList<Path>();
for(int i = 0; i < 10000; ++i) {
Path p = Paths.get(basePath.toString(), String.format("%s.txt", i));
assertTrue(p.toFile().createNewFile());
files.add(p);
}
sleep();
Mockito.verify(fileEventListener, Mockito.times(files.size())).onLocalFileCreated(anyObject());
}
@Test
public void testManyFoldersAndEmptyFiles() throws Exception {
watchService.start(basePath);
Path base = basePath;
int numFolders = 10;
int numFilesPerFolder = 100;
List<Path> files = new ArrayList<Path>(numFolders*numFilesPerFolder);
for (int k = 0; k < numFolders; ++k) {
Path sub = Paths.get(String.format("%s", k));
Files.createDirectory(base.resolve(sub));
Thread.sleep(100);
for (int i = 0; i < numFilesPerFolder; ++i) {
Path f = sub.resolve(String.format("%s.txt", i));
Path fullPath = base.resolve(f);
Files.createFile(fullPath);
files.add(f);
}
}
sleep();
Mockito.verify(fileEventListener, Mockito.times(numFolders + numFolders*numFilesPerFolder)).onLocalFileCreated(anyObject());
}
@Test
public void testManyEmptyFiles() throws Exception {
watchService.start(basePath);
Path base = basePath;
int numFolders = 10;
int numFilesPerFolder = 100;
List<Path> files = new ArrayList<Path>(numFolders*numFilesPerFolder);
for (int k = 0; k < numFolders; ++k) {
Path sub = Paths.get(String.format("%s", k));
Files.createDirectory(base.resolve(sub));
Thread.sleep(100);
for (int i = 0; i < numFilesPerFolder; ++i) {
Path f = sub.resolve(String.format("%s.txt", i));
Path fullPath = base.resolve(f);
Files.createFile(fullPath);
files.add(f);
}
}
sleep();
Mockito.verify(fileEventListener, Mockito.times(numFolders + numFolders*numFilesPerFolder)).onLocalFileCreated(anyObject());
}
@Test
public void testManyFoldersAndSmallFiles() throws Exception {
watchService.start(basePath);
Path base = basePath;
int numFolders = 10;
int numFilesPerFolder = 100;
List<Path> files = new ArrayList<Path>(numFolders*numFilesPerFolder);
for (int k = 0; k < numFolders; ++k) {
Path sub = Paths.get(String.format("%s", k));
Files.createDirectory(base.resolve(sub));
Thread.sleep(50);
for (int i = 0; i < numFilesPerFolder; ++i) {
Path f = sub.resolve(String.format("%s.txt", i));
Path fullPath = base.resolve(f);
byte[] bytesToWrite = fullPath.toString().getBytes(Charset.forName("UTF-8"));
Files.write(fullPath, bytesToWrite);
files.add(f);
}
}
sleep();
Mockito.verify(fileEventListener, Mockito.times(numFolders + numFolders*numFilesPerFolder)).onLocalFileCreated(anyObject());
}
/**
* Without the short sleep of 10 milliseconds, some events are lost. This
* happens due to the need to register new subfolders manually. While this is
* done, events triggered by the file system are ignored.
* @throws Exception
*/
@Test
public void testManySmallFiles() throws Exception {
watchService.start(basePath);
Path base = basePath;
int numFolders = 10;
int numFilesPerFolder = 100;
List<Path> files = new ArrayList<Path>(numFolders*numFilesPerFolder);
for (int k = 0; k < numFolders; ++k) {
Path sub = Paths.get(String.format("%s", k));
Files.createDirectory(base.resolve(sub));
Thread.sleep(100);
for (int i = 0; i < numFilesPerFolder; ++i) {
Path f = sub.resolve(String.format("%s.txt", i));
Path fullPath = base.resolve(f);
byte[] bytesToWrite = fullPath.toString().getBytes(Charset.forName("UTF-8"));
Files.write(fullPath, bytesToWrite);
files.add(f);
}
}
sleep();
Mockito.verify(fileEventListener, Mockito.times(numFolders + numFolders*numFilesPerFolder)).onLocalFileCreated(anyObject());
}
private void sleep() throws InterruptedException {
Thread.sleep(SLEEP_TIME);
}
}