package org.peerbox.filerecovery; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; import java.io.FileNotFoundException; 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.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomUtils; import org.hive2hive.core.H2HJUnitTest; import org.hive2hive.core.api.interfaces.IH2HNode; import org.hive2hive.core.exceptions.NoPeerConnectionException; import org.hive2hive.core.exceptions.NoSessionException; import org.hive2hive.core.model.IFileVersion; import org.hive2hive.core.security.UserCredentials; import org.hive2hive.core.utils.FileTestUtil; import org.hive2hive.core.utils.NetworkTestUtil; import org.hive2hive.core.utils.TestFileConfiguration; import org.hive2hive.core.utils.TestProcessComponentListener; import org.hive2hive.core.utils.helper.TestFileAgent; import org.hive2hive.processframework.exceptions.InvalidProcessStateException; import org.hive2hive.processframework.exceptions.ProcessExecutionException; import org.hive2hive.processframework.interfaces.IProcessComponent; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.peerbox.BaseJUnitTest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class FileVersionSelectorTest extends BaseJUnitTest { private static Logger logger = LoggerFactory.getLogger(FileVersionSelectorTest.class); private static final int NETWORK_SIZE = 6; private static List<IH2HNode> network; private static IH2HNode client; private static UserCredentials userCredentials; private static File root; private static File file; private static int FILE_SIZE = 128*1024; private static int NUM_VERSIONS = 4; private static List<String> content; private static final String fileName = "test-file.txt"; @BeforeClass public static void beforeClass() throws Exception { initNetwork(); uploadVersions(); // keep at least (num versions + initial version) many versions assertTrue(NUM_VERSIONS <= new TestFileConfiguration().getMaxNumOfVersions()-1); } private static void initNetwork() throws InvalidProcessStateException, NoPeerConnectionException, ProcessExecutionException { network = NetworkTestUtil.createH2HNetwork(NETWORK_SIZE); client = network.get(RandomUtils.nextInt(0, network.size())); // register a user userCredentials = H2HJUnitTest.generateRandomCredentials(); root = FileTestUtil.getTempDirectory(); client.getUserManager().createRegisterProcess(userCredentials).execute(); client.getUserManager().createLoginProcess(userCredentials, new TestFileAgent(root)).execute(); } private static void uploadVersions() throws Exception { content = new ArrayList<String>(); // add an intial file to the network file = new File(root, fileName); String fileContent = RandomStringUtils.randomAscii(FILE_SIZE); content.add(fileContent); logger.info("Initial content: {}...", fileContent.substring(0, 10)); FileUtils.write(file, fileContent); client.getFileManager().createAddProcess(file).execute(); // update and upload for(int i = 0; i < NUM_VERSIONS; ++i) { Thread.sleep(2000); // sleep such that each file has different timestamp fileContent = RandomStringUtils.randomAscii(FILE_SIZE); content.add(fileContent); logger.info("File version {} content: {}...", i, fileContent.substring(0, 10)); FileUtils.write(file, fileContent); client.getFileManager().createUpdateProcess(file).execute(); } } @AfterClass public static void afterClass() { NetworkTestUtil.shutdownH2HNetwork(network); } @Test public void testRecoverAllVersions() throws Exception { // recover all versions for(int i = 0; i < NUM_VERSIONS; ++ i) { recoverVersion(i); } } private FileVersionSelectorListener recoverVersion(int version) throws Exception { // recover version FileVersionSelectorListener versionSelectorListener = new FileVersionSelectorListener(file.toPath(), version); client.getFileManager().createRecoverProcess(file, versionSelectorListener.getFileVersionSelector()).execute(); // assert content equality String recoveredFileName = versionSelectorListener.getRecoveredFileName(); assertNotNull(recoveredFileName); assertFalse(recoveredFileName.isEmpty()); Path recoveredFile = Paths.get(root.toString(), recoveredFileName); assertTrue(Files.exists(recoveredFile)); String expected = content.get(version); String recovered = new String(Files.readAllBytes(recoveredFile)); logger.info("Version {}:\n\tExpected content: {}... \n\tRecovered content: {}...", version, expected.substring(0, 10), recovered.substring(0, 10)); assertTrue(expected.equals(recovered)); return versionSelectorListener; } @Test public void testCancel() throws NoSessionException, NoPeerConnectionException, InvalidProcessStateException, ProcessExecutionException { // count number of files to make sure no file recovered int numElementsBefore = root.list().length; // recover version and cancel FileVersionSelectorListener versionSelectorListener = new FileVersionSelectorListener(file.toPath(), -1); try { client.getFileManager().createRecoverProcess(file, versionSelectorListener.getFileVersionSelector()).execute(); fail("Expected exception was not thrown."); } catch(ProcessExecutionException pex) { // expected exception since no version selected when cancelled. logger.info("Exception: {}", pex.getMessage()); } int numElementsAfter = root.list().length; assertEquals(numElementsBefore, numElementsAfter); } @Test public void testCancelBeforeSelect() throws NoSessionException, NoPeerConnectionException, InvalidProcessStateException, ProcessExecutionException { // recover version and cancel FileVersionSelectorListener versionSelectorListener = new FileVersionSelectorListener(file.toPath(), 0); versionSelectorListener.getFileVersionSelector().cancel(); // cancel before onAvailableVersionsReceived IProcessComponent<Void> p = client.getFileManager().createRecoverProcess(file, versionSelectorListener.getFileVersionSelector()); TestProcessComponentListener plistener = new TestProcessComponentListener(); p.attachListener(plistener); try { p.execute(); fail("Expected exception was not thrown."); } catch (ProcessExecutionException pex) { // expected exception since no version selected when cancelled. logger.info("Exception: {}", pex.getMessage()); } assertTrue(plistener.hasExecutionFailed()); assertFalse(plistener.hasExecutionSucceeded()); } @Test(expected=IllegalStateException.class) public void testSelectBeforeOnAvailableVersions() { FileVersionSelectorListener versionSelectorListener = new FileVersionSelectorListener(file.toPath(), 0); versionSelectorListener.getFileVersionSelector().selectVersion((IFileVersion)null, file.toPath()); } @Test(expected=IllegalStateException.class) public void testSelectTwice() throws FileNotFoundException, NoSessionException, NoPeerConnectionException, InterruptedException, InvalidProcessStateException { FileVersionSelectorListener versionSelectorListener = new FileVersionSelectorListener(file.toPath(), 0); List<IFileVersion> versions = new ArrayList<IFileVersion>(); for(int i = 0; i < NUM_VERSIONS; ++i) { versions.add(null); } versionSelectorListener.getFileVersionSelector().selectVersion(versions); versionSelectorListener.getFileVersionSelector().selectVersion((IFileVersion)null, file.toPath()); } @Test public void testRecoverMultipleTimes() throws Exception { // if same version is recovered multiple times, the file name should have a counter FileVersionSelectorListener listener_1 = recoverVersion(0); FileVersionSelectorListener listener_2 = recoverVersion(0); String recoveredName_1 = listener_1.getRecoveredFileName(); String recoveredName_2 = listener_2.getRecoveredFileName(); assertFalse(recoveredName_1.equals(recoveredName_2)); } private class FileVersionSelectorListener implements IFileVersionSelectorListener { private Path fileToRecover; private int versionToRecover; private FileVersionSelector versionSelector; public FileVersionSelectorListener(Path fileToRecover, int versionToRecover) { this.fileToRecover = fileToRecover; this.versionToRecover = versionToRecover; this.versionSelector = new FileVersionSelector(this); } public String getRecoveredFileName() { return versionSelector.getRecoveredFileName(); } public FileVersionSelector getFileVersionSelector() { return versionSelector; } @Override public void onAvailableVersionsReceived(List<IFileVersion> availableVersions) { Assert.assertTrue(availableVersions.size() == NUM_VERSIONS); Assert.assertTrue(versionToRecover < availableVersions.size()); if(versionToRecover != -1) { versionSelector.selectVersion(availableVersions.get(versionToRecover), fileToRecover); } else { versionSelector.cancel(); } } } }