/* * This file is part of Fim - File Integrity Manager * * Copyright (C) 2017 Etienne Vrignaud * * Fim is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Fim is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Fim. If not, see <http://www.gnu.org/licenses/>. */ package org.fim; import org.fim.command.CommitCommand; import org.fim.command.DisplayIgnoredFilesCommand; import org.fim.command.FindDuplicatesCommand; import org.fim.command.InitCommand; import org.fim.command.LogCommand; import org.fim.command.RemoveDuplicatesCommand; import org.fim.command.RollbackCommand; import org.fim.command.StatusCommand; import org.fim.command.exception.BadFimUsageException; import org.fim.model.CompareResult; import org.fim.model.Context; import org.fim.model.CorruptedStateException; import org.fim.model.DuplicateResult; import org.fim.model.HashMode; import org.fim.model.LogResult; import org.fim.model.Modification; import org.fim.model.ModificationCounts; import org.fim.model.State; import org.fim.tooling.RepositoryTool; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.Collection; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; import static org.fim.internal.StateManager.STATE_EXTENSION; import static org.fim.model.HashMode.dontHash; import static org.fim.model.HashMode.hashAll; import static org.fim.model.HashMode.hashMediumBlock; import static org.fim.model.HashMode.hashSmallBlock; import static org.fim.model.Modification.added; import static org.fim.model.Modification.attributesModified; import static org.fim.model.Modification.contentModified; import static org.fim.model.Modification.copied; import static org.fim.model.Modification.dateModified; import static org.fim.model.Modification.deleted; import static org.fim.model.Modification.duplicated; import static org.fim.model.Modification.renamed; @RunWith(Parameterized.class) public class FullScenarioTest { private HashMode hashMode; private Path dir01; private InitCommand initCommand; private StatusCommand statusCommand; private CommitCommand commitCommand; private FindDuplicatesCommand findDuplicatesCommand; private RemoveDuplicatesCommand removeDuplicatesCommand; private LogCommand logCommand; private DisplayIgnoredFilesCommand displayIgnoredFilesCommand; private RollbackCommand rollbackCommand; private RepositoryTool tool; private Path rootDir; public FullScenarioTest(final HashMode hashMode) { this.hashMode = hashMode; } @Parameterized.Parameters(name = "Hash mode: {0}") public static Collection<Object[]> parameters() { return Arrays.asList(new Object[][]{ {dontHash}, {hashSmallBlock}, {hashMediumBlock}, {hashAll} }); } @Before public void setUp() throws IOException { tool = new RepositoryTool(this.getClass(), hashMode); rootDir = tool.getRootDir(); dir01 = rootDir.resolve("dir01"); initCommand = new InitCommand(); statusCommand = new StatusCommand(); commitCommand = new CommitCommand(); findDuplicatesCommand = new FindDuplicatesCommand(); removeDuplicatesCommand = new RemoveDuplicatesCommand(); logCommand = new LogCommand(); displayIgnoredFilesCommand = new DisplayIgnoredFilesCommand(); rollbackCommand = new RollbackCommand(); } @Test public void fullScenario() throws Exception { Context context = tool.getContext(); tool.createASetOfFiles(10); Thread.sleep(2); // In order to detect modified dates State state = (State) initCommand.execute(context); assertThat(state.getModificationCounts().getAdded()).isEqualTo(10); assertThat(state.getFileCount()).isEqualTo(10); Path dotFim = rootDir.resolve(".fim"); assertThat(Files.exists(dotFim)).isTrue(); assertThat(Files.exists(dotFim.resolve("settings.json"))).isTrue(); assertThat(Files.exists(dotFim.resolve("states/state_1.json.gz"))).isTrue(); doSomeModifications(); ModificationCounts modificationCounts; CompareResult compareResult = (CompareResult) statusCommand.execute(context); modificationCounts = compareResult.getModificationCounts(); if (hashMode == dontHash) { assertThat(compareResult.modifiedCount()).isEqualTo(11); assertThat(modificationCounts.getRenamed()).isEqualTo(0); assertThat(modificationCounts.getDeleted()).isEqualTo(2); } else { assertThat(compareResult.modifiedCount()).isEqualTo(10); assertThat(modificationCounts.getRenamed()).isEqualTo(1); assertThat(modificationCounts.getDeleted()).isEqualTo(1); } assertDuplicatedFilesCountEqualsTo(context, 2); addIgnoredFiles(context); runCommandFromDirectory(context, dir01); compareResult = (CompareResult) commitCommand.execute(context); assertThat(compareResult.modifiedCount()).isEqualTo(13); modificationCounts = compareResult.getModificationCounts(); assertThat(modificationCounts.getRenamed()).isEqualTo(0); assertThat(modificationCounts.getDeleted()).isEqualTo(2); assertLastStateContainSameModifications(context, modificationCounts); // Committing once again does nothing commit_AndAssertFilesModifiedCountEqualsTo(context, 0); assertFilesModifiedCountEqualsTo(context, 0); LogResult logResult = (LogResult) logCommand.execute(context); assertThat(logResult.getLogEntries().size()).isEqualTo(3); Set<String> ignoredFiles = (Set<String>) displayIgnoredFilesCommand.execute(context); assertThat(ignoredFiles.size()).isEqualTo(6); assertCanRollbackLastCommit(context, 2, 3); assertFilesModifiedCountEqualsTo(context, 13); // We can rollback again assertCanRollbackLastCommit(context, 1, 0); // Nothing more to rollback assertCanRollbackLastCommit(context, 1, 0); if (hashMode == hashAll || hashMode == hashMediumBlock) { Context superFastModeContext = tool.createContext(hashSmallBlock, true); superFastModeContext.setComment("Using hash mode " + hashSmallBlock); // Commit using super-fast mode (hashSmallBlock) compareResult = (CompareResult) commitCommand.execute(superFastModeContext); assertThat(compareResult.modifiedCount()).isEqualTo(15); modificationCounts = compareResult.getModificationCounts(); assertThat(modificationCounts.getAdded()).isEqualTo(6); assertThat(modificationCounts.getCopied()).isEqualTo(1); assertThat(modificationCounts.getDuplicated()).isEqualTo(3); assertThat(modificationCounts.getDateModified()).isEqualTo(1); assertThat(modificationCounts.getContentModified()).isEqualTo(2); assertThat(modificationCounts.getRenamed()).isEqualTo(1); assertThat(modificationCounts.getDeleted()).isEqualTo(1); assertLastStateContainSameModifications(context, modificationCounts); // Check that the last commit command did not modify the hashMode assertThat(superFastModeContext.getHashMode()).isEqualTo(hashSmallBlock); assertThatUsingNormalHashModeNoModificationIsDetected(context); // Commit a new file from the dir01 sub directory tool.createFile(dir01.resolve("file15")); Context fromSubDirectoryContext = tool.createInvokedFromSubDirContext(hashSmallBlock, "dir01", hashMode == hashAll); superFastModeContext.setComment("From from sub directory dir01"); compareResult = (CompareResult) commitCommand.execute(fromSubDirectoryContext); assertThat(compareResult.modifiedCount()).isEqualTo(1); modificationCounts = compareResult.getModificationCounts(); assertThat(modificationCounts.getAdded()).isEqualTo(1); assertLastStateContainSameModifications(context, modificationCounts); // Add two files tool.setFileContent("file13", "New file 13"); tool.setFileContent("file14", "New file 14"); // Commit again using super-fast mode (hashSmallBlock) compareResult = (CompareResult) commitCommand.execute(superFastModeContext); assertThat(compareResult.modifiedCount()).isEqualTo(2); modificationCounts = compareResult.getModificationCounts(); assertThat(modificationCounts.getAdded()).isEqualTo(2); assertLastStateContainSameModifications(context, modificationCounts); assertThatUsingNormalHashModeNoModificationIsDetected(context); logResult = (LogResult) logCommand.execute(context); assertThat(logResult.getLogEntries().size()).isEqualTo(4); modificationCounts = logResult.getLogEntries().get(2).getModificationCounts(); assertThat(modificationCounts.getAdded()).isEqualTo(1); assertThat(modificationCounts.getDeleted()).isEqualTo(0); assertThat(modificationCounts.getCopied()).isEqualTo(0); assertFileExists(rootDir, "file03.dup1"); assertFileExists(rootDir, "file03.dup2"); assertFileExists(rootDir, "file07.dup1"); context.setCalledFromTest(true); long totalFilesRemoved = (long) removeDuplicatesCommand.execute(context); assertThat(totalFilesRemoved).isEqualTo(3); assertFileDoesNotExist(rootDir, "file03.dup1"); assertFileDoesNotExist(rootDir, "file03.dup2"); assertFileDoesNotExist(rootDir, "file07.dup1"); } } private void assertThatUsingNormalHashModeNoModificationIsDetected(Context context) throws Exception { commit_AndAssertFilesModifiedCountEqualsTo(context, 0); } private void doSomeModifications() throws IOException { Files.createDirectories(dir01); Files.move(rootDir.resolve("file01"), dir01.resolve("file01")); tool.touchLastModified("file02"); Files.copy(rootDir.resolve("file03"), rootDir.resolve("file03.dup1")); Files.copy(rootDir.resolve("file03"), rootDir.resolve("file03.dup2")); tool.setFileContent("file04", "foo"); Files.copy(rootDir.resolve("file05"), rootDir.resolve("file11")); tool.setFileContent("file05", "bar"); Files.delete(rootDir.resolve("file06")); Files.copy(rootDir.resolve("file07"), rootDir.resolve("file07.dup1")); tool.setFileContent("file12", "New file 12"); } private void addIgnoredFiles(Context context) throws Exception { tool.createFile("ignored_type1"); tool.createFile("ignored_type2"); tool.createFile(dir01.resolve("ignored_type1")); tool.createFile(dir01.resolve("ignored_type2")); tool.createFile("media.mp3"); tool.createFile("media.mp4"); tool.createFile(dir01.resolve("media.mp3")); tool.createFile(dir01.resolve("media.mp4")); assertFilesModifiedCountEqualsTo(context, hashMode == dontHash ? 19 : 18); tool.createFimIgnore(rootDir, "**/*.mp3\n" + "ignored_type1"); assertFilesModifiedCountEqualsTo(context, hashMode == dontHash ? 17 : 16); } private void runCommandFromDirectory(Context context, Path subDirectory) throws Exception { Context subDirectoryContext = context.clone(); subDirectoryContext.setCurrentDirectory(subDirectory); subDirectoryContext.setInvokedFromSubDirectory(true); assertFilesModifiedCountEqualsTo(subDirectoryContext, 4); tool.createFimIgnore(subDirectory, "*.mp4\n" + "ignored_type2"); assertFilesModifiedCountEqualsTo(subDirectoryContext, 3); assertDuplicatedFilesCountEqualsTo(subDirectoryContext, 0); commit_AndAssertFilesModifiedCountEqualsTo(subDirectoryContext, 3); assertFilesModifiedCountEqualsTo(subDirectoryContext, 0); } private void assertFilesModifiedCountEqualsTo(Context context, int expectedModifiedFileCount) throws Exception { CompareResult compareResult = (CompareResult) statusCommand.execute(context); assertThat(compareResult.modifiedCount()).isEqualTo(expectedModifiedFileCount); } private void commit_AndAssertFilesModifiedCountEqualsTo(Context context, int expectedModifiedFileCount) throws Exception { CompareResult compareResult = (CompareResult) commitCommand.execute(context); assertThat(compareResult.modifiedCount()).isEqualTo(expectedModifiedFileCount); assertLastStateHashModeEqualsTo(context, context.getHashMode()); } private void assertLastStateHashModeEqualsTo(Context context, HashMode expectedHashMode) throws IOException, CorruptedStateException { State lastState = loadLastState(context); assertThat(lastState.getHashMode()).isEqualTo(expectedHashMode); } private void assertLastStateContainSameModifications(Context context, ModificationCounts modificationCounts) throws IOException, CorruptedStateException { State lastState = loadLastState(context); int count = countModification(lastState, added); assertThat(modificationCounts.getAdded()).isEqualTo(count); count = countModification(lastState, copied); assertThat(modificationCounts.getCopied()).isEqualTo(count); count = countModification(lastState, duplicated); assertThat(modificationCounts.getDuplicated()).isEqualTo(count); count = countModification(lastState, dateModified); assertThat(modificationCounts.getDateModified()).isEqualTo(count); count = countModification(lastState, contentModified); assertThat(modificationCounts.getContentModified()).isEqualTo(count); count = countModification(lastState, attributesModified); assertThat(modificationCounts.getAttributesModified()).isEqualTo(count); count = countModification(lastState, renamed); assertThat(modificationCounts.getRenamed()).isEqualTo(count); count = countModification(lastState, deleted); assertThat(modificationCounts.getDeleted()).isEqualTo(count); } private State loadLastState(Context context) throws IOException, CorruptedStateException { Path lastStateFile = getStateFile(context, getLastStateNumber(context)); return State.loadFromGZipFile(lastStateFile, false); } private int countModification(State lastState, Modification modification) { long count = lastState.getFileStates().stream().filter(fileState -> fileState.getModification() == modification).count(); return (int) count; } private void assertDuplicatedFilesCountEqualsTo(Context context, int expectedDuplicatedSetCount) throws Exception { try { DuplicateResult duplicateResult = (DuplicateResult) findDuplicatesCommand.execute(context); assertThat(duplicateResult.getDuplicateSets().size()).isEqualTo(expectedDuplicatedSetCount); } catch (BadFimUsageException ex) { if (context.getHashMode() != dontHash) { throw ex; } } } private void assertCanRollbackLastCommit(Context context, int expectedLogEntriesCount, int expectedIgnoredFilesCount) throws Exception { rollbackCommand.execute(context); LogResult logResult = (LogResult) logCommand.execute(context); assertThat(logResult.getLogEntries().size()).isEqualTo(expectedLogEntriesCount); Set<String> ignoredFiles = (Set<String>) displayIgnoredFilesCommand.execute(context); assertThat(ignoredFiles.size()).isEqualTo(expectedIgnoredFilesCount); } private void assertFileExists(Path directory, String fileName) { assertThat(Files.exists(directory.resolve(fileName))).isTrue(); } private void assertFileDoesNotExist(Path directory, String fileName) { assertThat(Files.exists(directory.resolve(fileName))).isFalse(); } private Path getStateFile(Context context, int stateNumber) { return context.getRepositoryStatesDir().resolve("state_" + stateNumber + STATE_EXTENSION); } private int getLastStateNumber(Context context) { for (int index = 1; ; index++) { if (!Files.exists(getStateFile(context, index))) { return index - 1; } } } }