package org.peerbox.watchservice;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.BlockingQueue;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Mockito;
import org.peerbox.app.manager.file.IFileManager;
import org.peerbox.testutils.FileTestUtils;
import org.peerbox.watchservice.filetree.FileTree;
import org.peerbox.watchservice.filetree.composite.FileComponent;
import org.peerbox.watchservice.integration.TestPeerWaspConfig;
import org.peerbox.watchservice.states.EstablishedState;
import org.peerbox.watchservice.states.InitialState;
import org.peerbox.watchservice.states.LocalCreateState;
import org.peerbox.watchservice.states.LocalHardDeleteState;
import org.peerbox.watchservice.states.LocalMoveState;
import org.peerbox.watchservice.states.LocalUpdateState;
import com.google.common.collect.SetMultimap;
import com.google.common.io.Files;
/**
*
* @author Claudio
* This test creates a new directory in the user's home directory, where some files are created for
* test purposes. The directory and the files are deleted after the execution of the testcases. This class
* tests if the events triggered by the FolderWatchService are correctly aggregated and delivered to the H2H
* framework, whereas the FileManager is mocked to decouple the test from the filesharing library
*/
public class FileEventManagerTest {
private static int nrFiles = 9;
private static FileTree fileTree;
private static FileEventManager manager;
private static ActionExecutor actionExecutor;
private static IFileManager fileManager;
private static String parentPath = System.getProperty("user.home") + File.separator + "PeerWasp_FileEventManagerTest" + File.separator;
private static File testDirectory;
private static ArrayList<String> filePaths = new ArrayList<String>();
private static ArrayList<File> files = new ArrayList<File>();
private TestPeerWaspConfig config = new TestPeerWaspConfig();
/**
* Create the test directory and the files.
*/
@BeforeClass
public static void staticSetup(){
fileTree = new FileTree(Paths.get(parentPath), true);
manager = new FileEventManager(fileTree, null);
fileManager = Mockito.mock(IFileManager.class);
actionExecutor = new ActionExecutor(manager, fileManager, new TestPeerWaspConfig());
actionExecutor.setWaitForActionCompletion(false);
actionExecutor.start();
}
/**
* Delete the test directory and the files.
*/
@AfterClass
public static void rollback(){
for(int i = 0; i < nrFiles; i++){
files.get(i).delete();
}
assertTrue(testDirectory.delete());
}
@Before
public void setup(){
testDirectory = new File(parentPath);
testDirectory.mkdir();
try {
for(int i = 0; i < nrFiles; i++){
filePaths.add(parentPath + "file" + i + ".txt");
files.add(new File(filePaths.get(i)));
files.get(i).createNewFile();
System.out.println("Created file " + files.get(i));
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Triggers a create event and a modify event on the same file. Ensures
* that only one element is stored in the action queue. This element
* has to be still in the create state after this two events are processed.
*/
@Test
public void onFileCreatedTest(){
BlockingQueue<FileComponent> fileComponentsToCheck = manager.getFileComponentQueue().getQueue();
long start = System.currentTimeMillis();
System.out.println("Start onFileCreatedTest");
manager.onLocalFileCreated(Paths.get(filePaths.get(0)));
assertTrue(fileComponentsToCheck.size() == 1);
assertTrue(fileComponentsToCheck.peek().getAction().getCurrentState() instanceof LocalCreateState);
manager.onLocalFileModified(Paths.get(filePaths.get(0)));
assertTrue(fileComponentsToCheck.size() == 1);
assertTrue(fileComponentsToCheck.peek().getAction().getCurrentState() instanceof LocalCreateState);
FileComponent file = fileComponentsToCheck.peek();
//check if the testcase was run in time
long end = System.currentTimeMillis();
assertTrue(end - start <= config.getAggregationIntervalInMillis());
sleepMillis(config.getAggregationIntervalInMillis() * 2);
assertTrue(file.getAction().getCurrentState() instanceof EstablishedState);
assertTrue(fileComponentsToCheck.size() == 0);
//cleanup
manager.onLocalFileHardDelete(Paths.get(filePaths.get(0)));
sleepMillis(200); //wait for the state machine before delete is simulated
manager.onLocalFileDeleted(Paths.get(filePaths.get(0)));
sleepMillis(config.getAggregationIntervalInMillis() * 2);
System.out.println("Current state: " + file.getAction().getCurrentState().getClass());
assertTrue(manager.getFileTree().getFile(files.get(0).toPath()) == null);
assertTrue(file.getAction().getCurrentState() instanceof InitialState);
assertTrue(fileComponentsToCheck.size() == 0);
}
/**
* This test simulates a create event and waits ActionExecutor.ACTION_WAIT_TIME_MS amount of
* time for the event to be handled. After that, a move is simulated using a delete event on
* the same file and a create event on a new file with the same content (but different name).
*/
@Test
public void fromDeleteToMoveTest(){
//handle artificial create event, wait for handling
manager.onLocalFileCreated(Paths.get(filePaths.get(7)));
BlockingQueue<FileComponent> actionsToCheck = manager.getFileComponentQueue().getQueue();;
FileComponent file1 = actionsToCheck.peek();
sleepMillis(config.getAggregationIntervalInMillis() * 2);
//check if exactly one element exists in the queue
assertTrue(actionsToCheck.size() == 0);
//initiate delete event
long start = System.currentTimeMillis();
manager.onLocalFileDeleted(Paths.get(filePaths.get(7)));
assertTrue(actionsToCheck.size() == 1);
//initiate re-creation, ensure that all happens in time
manager.onLocalFileCreated(Paths.get(filePaths.get(8)));
FileComponent file2 = actionsToCheck.peek();
assertTrue(actionsToCheck.size() == 1);
System.out.println(actionsToCheck.peek().getAction().getCurrentState().getClass());
assertTrue(actionsToCheck.peek().getAction().getCurrentState() instanceof LocalMoveState);
long end = System.currentTimeMillis();
assertTrue(end - start <= config.getAggregationIntervalInMillis());
sleepMillis(config.getAggregationIntervalInMillis() * 2);
//cleanup
deleteFile(Paths.get(filePaths.get(8)));
sleepMillis(config.getAggregationIntervalInMillis() * 2);
assertTrue(manager.getFileTree().getFile(files.get(8).toPath()) == null);
assertTrue(actionsToCheck.size() == 0);
assertTrue(file1.getAction().getCurrentState() instanceof InitialState);
assertTrue(file1.getAction().getCurrentState() instanceof InitialState);
}
/**
* Simulate a file delete and an additional modify event, check if the file
* remains in the delete state and only one action is stored in the queue.
*/
@Test
public void onFileDeletedTest(){
BlockingQueue<FileComponent> actionsToCheck = manager.getFileComponentQueue().getQueue();;
SetMultimap<String, FileComponent> deletedFiles = manager.getFileTree().getDeletedByContentHash();
System.out.println("Start onFileDeletedTest");
manager.onLocalFileCreated(Paths.get(filePaths.get(0)));
FileComponent createdFile = actionsToCheck.peek();
//HERE
assertTrue(actionsToCheck.size() == 1);
assertTrue(createdFile.getAction().getCurrentState() instanceof LocalCreateState);
sleepMillis(config.getAggregationIntervalInMillis() * 2);
assertTrue(createdFile.getAction().getCurrentState() instanceof EstablishedState);
assertTrue(actionsToCheck.size() == 0);
long start = System.currentTimeMillis();
manager.onLocalFileHardDelete(Paths.get(filePaths.get(0)));
sleepMillis(200);
manager.onLocalFileDeleted(Paths.get(filePaths.get(0)));
System.out.println(actionsToCheck.size());
assertTrue(actionsToCheck.size() == 1);
assertTrue(actionsToCheck.peek().getAction().getCurrentState() instanceof LocalHardDeleteState);
manager.onLocalFileModified(Paths.get(filePaths.get(0)));
assertTrue(actionsToCheck.size() == 1);
assertTrue(actionsToCheck.peek().getAction().getCurrentState() instanceof LocalHardDeleteState);
System.out.println("deletedFiles.size(): " + deletedFiles.size());
//assertTrue(deletedFiles.size() == 1);
//Set<FileComponent> equalHashes = deletedFiles.get(createdFile.getContentHash());
//assertTrue(equalHashes.size() == 1);
//assertTrue(equalHashes.contains(createdFile));
//check if the testcase was run in time
long end = System.currentTimeMillis();
assertTrue(end - start <= config.getAggregationIntervalInMillis());
sleepMillis(config.getAggregationIntervalInMillis() * 5);
assertTrue(actionsToCheck.size() == 0);
assertTrue(manager.getFileTree().getFile(files.get(0).toPath()) == null);
System.out.println(createdFile.getAction().getCurrentState().getClass());
assertTrue(createdFile.getAction().getCurrentState() instanceof InitialState);
System.out.println(actionsToCheck.size());
assertTrue(deletedFiles.size() == 0);
}
/**
* This test issues several modify events for the same file over a long
* period to check if the events are aggregated accordingly.
* @throws IOException
*/
@Test
public void onFileModifiedTest() throws IOException{
BlockingQueue<FileComponent> actionsToCheck = manager.getFileComponentQueue().getQueue();;
long start = System.currentTimeMillis();
System.out.println("Start onFileModifiedTest");
manager.onLocalFileCreated(Paths.get(filePaths.get(0)));
manager.onLocalFileModified(Paths.get(filePaths.get(0)));
assertTrue(actionsToCheck.size() == 1);
assertNotNull(actionsToCheck);
assertNotNull(actionsToCheck.peek());
assertNotNull(actionsToCheck.peek().getAction().getCurrentState());
assertTrue(actionsToCheck.peek().getAction().getCurrentState() instanceof LocalCreateState); //no null pointers should occur anymore here
long end = System.currentTimeMillis();
assertTrue(end - start <= config.getAggregationIntervalInMillis());
//issue continuous modifies over a period longer than the wait time
sleepMillis(config.getAggregationIntervalInMillis() * 2);
FileTestUtils.writeRandomData(files.get(0).toPath(), 50);
manager.onLocalFileModified(Paths.get(filePaths.get(0)));
sleepMillis(config.getAggregationIntervalInMillis() / 2);
FileTestUtils.writeRandomData(files.get(0).toPath(), 50);
manager.onLocalFileModified(Paths.get(filePaths.get(0)));
sleepMillis(config.getAggregationIntervalInMillis() / 2);
FileComponent comp = actionsToCheck.peek();
assertTrue(actionsToCheck.peek().getAction().getCurrentState() instanceof LocalUpdateState);
assertTrue(actionsToCheck.size() == 1);
sleepMillis(config.getAggregationIntervalInMillis() * 2);
printBlockingQueue(actionsToCheck);
assertTrue(actionsToCheck.size() == 0);
// System.out.println(comp.getAction().getCurrentState().getClass());
//cleanup
manager.onLocalFileHardDelete(Paths.get(filePaths.get(0)));
sleepMillis(200);
manager.onLocalFileDeleted(Paths.get(filePaths.get(0)));
sleepMillis(config.getAggregationIntervalInMillis() * 5);
assertTrue(manager.getFileTree().getFile(files.get(0).toPath()) == null);
assertTrue(comp.getAction().getCurrentState() instanceof InitialState);
assertTrue(actionsToCheck.size() == 0);
}
private void printBlockingQueue(BlockingQueue<FileComponent> queue){
System.out.println("Queue:");
ArrayList<FileComponent> components = new ArrayList<FileComponent>(queue);
int i = 0;
for(FileComponent comp : components){
System.out.println(i + ": " + comp.getAction().getCurrentState().getClass() + ": " + comp.getPath());
}
}
/**
* Advanced test with four files and different events on them.
*
* The different events:
* - modify file0
* - create file1
* - delete file0
* - modify file2
* - delete file2
* - create file3 > move from file2 to file3
*
* Expected action queue content
*
* (tail) [move file2 to file3], [delete file0] [create file1] (head)
* @throws IOException
*/
@Test
public void multipleFilesTest() throws IOException{
//measure start time to ensure the testcase runs before the queue is processed
System.out.println("Start multipleFilesTest");
BlockingQueue<FileComponent> actionsToCheck = manager.getFileComponentQueue().getQueue();;
//issue all the events, check state of head and if the action corresponds to the correct file
manager.onLocalFileCreated(Paths.get(filePaths.get(0)));
//manager.onFileCreated(Paths.get(filePaths.get(1)), false);
manager.onLocalFileCreated(Paths.get(filePaths.get(2)));
//manager.onFileCreated(Paths.get(filePaths.get(3)), false);
sleepMillis(config.getAggregationIntervalInMillis() * 2);
long start = System.currentTimeMillis();
FileTestUtils.writeRandomData(files.get(0).toPath(), 50);
manager.onLocalFileModified(Paths.get(filePaths.get(0)));
sleepMillis(50);
printQueue(actionsToCheck);
assertTrue(actionsToCheck.size() == 1);
assertTrue(actionsToCheck.peek().getAction().getCurrentState() instanceof LocalUpdateState);
assertTrue(actionsToCheck.peek().getPath().toString().equals(filePaths.get(0)));
manager.onLocalFileCreated(Paths.get(filePaths.get(1)));
sleepMillis(10);
assertTrue(actionsToCheck.size() == 2);
assertTrue(actionsToCheck.peek().getAction().getCurrentState() instanceof LocalUpdateState);
assertTrue(actionsToCheck.peek().getPath().toString().equals(filePaths.get(0)));
manager.onLocalFileHardDelete(Paths.get(filePaths.get(0)));
sleepMillis(200);
manager.onLocalFileDeleted(Paths.get(filePaths.get(0)));
sleepMillis(10);
System.out.println("actionsToCheck.size() " + actionsToCheck.size());
ArrayList<FileComponent> array = new ArrayList<FileComponent>(actionsToCheck);
for(FileComponent comp : array){
System.out.println(comp.getPath() + ": " + comp.getAction().getCurrentState().getClass().toString());
}
assertTrue(actionsToCheck.size() == 2);
assertTrue(actionsToCheck.peek().getAction().getCurrentState() instanceof LocalCreateState);
assertTrue(actionsToCheck.peek().getPath().toString().equals(filePaths.get(1)));
FileTestUtils.writeRandomData(files.get(2).toPath(), 50);
manager.onLocalFileModified(Paths.get(filePaths.get(2)));
sleepMillis(10);
System.out.println("actionsToCheck.size() " + actionsToCheck.size());
assertTrue(actionsToCheck.size() == 3);
assertTrue(actionsToCheck.peek().getAction().getCurrentState() instanceof LocalCreateState);
assertTrue(actionsToCheck.peek().getPath().toString().equals(filePaths.get(1)));
Files.move(files.get(2), files.get(3));
manager.onLocalFileDeleted(Paths.get(filePaths.get(2)));
sleepMillis(10);
System.out.println("size: " + actionsToCheck.size());
Vector<FileComponent> actions = new Vector<FileComponent>(actionsToCheck);
for(int i = 0; i < actions.size(); i++){
System.out.println(i + ": " + actions.get(i).getPath() + " - " + actions.get(i).getAction().getCurrentState().getClass());
}
assertTrue(actionsToCheck.size() == 3);
assertTrue(actionsToCheck.peek().getAction().getCurrentState() instanceof LocalCreateState);
assertTrue(actionsToCheck.peek().getPath().toString().equals(filePaths.get(1)));
manager.onLocalFileCreated(Paths.get(filePaths.get(3)));
sleepMillis(10);
System.out.println("actionsToCheck.size() " + actionsToCheck.size());
assertTrue(actionsToCheck.size() == 3);
assertTrue(actionsToCheck.peek().getAction().getCurrentState() instanceof LocalCreateState);
assertTrue(actionsToCheck.peek().getPath().toString().equals(filePaths.get(1)));
List<FileComponent> actionsList = new ArrayList<FileComponent>(actionsToCheck);
//poll elements from the queue, check state and file path for each of them
FileComponent head = actionsList.get(0);
assertTrue(actionsToCheck.size() == 3);
assertTrue(head.getAction().getCurrentState() instanceof LocalCreateState);
assertTrue(head.getPath().toString().equals(filePaths.get(1)));
head = actionsList.get(1);
assertTrue(head.getAction().getCurrentState() instanceof LocalHardDeleteState);
assertTrue(head.getPath().toString().equals(filePaths.get(0)));
head = actionsList.get(2);
assertTrue(head.getAction().getCurrentState() instanceof LocalMoveState);
System.out.println("head.getAction().getFilePath().toString(): " + head.getPath().toString());
System.out.println("filePaths.get(3): " + filePaths.get(3));
assertTrue(head.getPath().toString().equals(filePaths.get(3)));
long end = System.currentTimeMillis();
assertTrue(end - start <= config.getAggregationIntervalInMillis());
//cleanup:
// deleteFile(Paths.get(filePaths.get(0)));
deleteFile(Paths.get(filePaths.get(1)));
// deleteFile(Paths.get(filePaths.get(2)));
deleteFile(Paths.get(filePaths.get(3)));
assertTrue(manager.getFileTree().getFile(files.get(0).toPath()) == null);
assertTrue(manager.getFileTree().getFile(files.get(1).toPath()) == null);
assertTrue(manager.getFileTree().getFile(files.get(2).toPath()) == null);
assertTrue(manager.getFileTree().getFile(files.get(3).toPath()) == null);
sleepMillis(config.getAggregationIntervalInMillis() * 5);
}
private void printQueue(BlockingQueue<FileComponent> queue) {
Vector<FileComponent> files = new Vector<FileComponent>(queue);
for(int i = 0; i < files.size(); i++){
System.out.println(i + ". File :" + files.get(i).getPath() + " - " + files.get(i).getAction().getCurrentState());
}
}
private void deleteFile(Path filePath){
System.out.println("Hard delete file " + filePath);
manager.onLocalFileHardDelete(filePath);
sleepMillis(200);
manager.onLocalFileDeleted(filePath);
sleepMillis(10);
}
/**
* This test simulates the the process of creating AND moving/renaming a file
* before the upload to the network was triggered. Therefore, the old file should
* be ignored (initial state, where execute does nothing) and the new file should
* be pushed as a create.
*/
@Test
public void createOnLocalMove(){
//sleepMillis(ActionExecutor.ACTION_WAIT_TIME_MS*3);
long start = System.currentTimeMillis();
BlockingQueue<FileComponent> actionsToCheck = manager.getFileComponentQueue().getQueue();;
assertTrue(actionsToCheck.size() == 0);
manager.onLocalFileCreated(Paths.get(filePaths.get(4)));
sleepMillis(10);
//move the file LOCALLY
Paths.get(filePaths.get(4)).toFile().delete();
manager.onLocalFileDeleted(Paths.get(filePaths.get(4)));
sleepMillis(10);
manager.onLocalFileCreated(Paths.get(filePaths.get(5)));
//sleepMillis(10);
FileComponent head = actionsToCheck.peek();
System.out.println("actionsToCheck.size(): " + actionsToCheck.size());
ArrayList<FileComponent> array = new ArrayList<FileComponent>(actionsToCheck);
for(FileComponent comp : array){
System.out.println(comp.getPath() + ": " + comp.getAction().getCurrentState().getClass().toString());
}
assertTrue(actionsToCheck.size() == 2);
assertTrue(array.get(0).getAction().getCurrentState() instanceof InitialState);
assertTrue(array.get(0).getPath().toString().equals(filePaths.get(4)));
assertTrue(array.get(1).getAction().getCurrentState() instanceof LocalCreateState);
assertTrue(array.get(1).getPath().toString().equals(filePaths.get(5)));
long end = System.currentTimeMillis();
assertTrue(end - start <= config.getAggregationIntervalInMillis());
sleepMillis(config.getAggregationIntervalInMillis() * 5);
}
/**
* Wait the defined time interval. Useful to guarantee different timestamps in
* milliseconds if events are programatically created. Furthermore allows to wait
* for a cleaned action queue if ActionExecutor.ACTION_TIME_TO_WAIT * 2 is passed
* as millisToSleep
*/
public static void sleepMillis(long millisToSleep){
try {
Thread.sleep(millisToSleep);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}