/*
* JBoss, Home of Professional Open Source
* Copyright 2014, JBoss Inc., and individual contributors as indicated
* by the @authors tag.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.as.patching.management;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* Tests "safe" delete operation {@link DeleteOp}
*
* @author Bartosz Spyrko-Smietanko
*/
public class DeleteOpTest {
public static final boolean NOT_PROTECTED = false;
public static final boolean PROTECTED = true;
public static final FilenameFilter INCLUDE_ALL = (d,f) -> true;
private static String TEST_DIR = "test";
public static final String BACKUP_ROOT_FILE_NAME = TEST_DIR + "/" + ".bkp";
@Before
public void setUp() {
final File testDir = new File(TEST_DIR);
if (testDir.exists()) {
recursiveCleanUp(testDir);
}
testDir.mkdir();
}
@After
public void tearDown() {
final File testDir = new File(TEST_DIR);
if (testDir.exists()) {
recursiveCleanUp(testDir);
}
}
private void recursiveCleanUp(File file) {
if (file.isDirectory()) {
for (File child : file.listFiles()) {
recursiveCleanUp(child);
}
}
file.delete();
}
@Test
public void removeNonProtectedFileWithoutErrors() throws Exception {
final File origin = createFile("test.txt", NOT_PROTECTED);
delete(origin, INCLUDE_ALL);
assertOriginalFileRemoved("test.txt");
}
@Test
public void failToDeleteProtectedFile() throws Exception {
final File origin = createFile("test.txt", PROTECTED);
delete(origin, INCLUDE_ALL);
assertOriginalFileExists("test.txt");
}
@Test
public void removeFolderWithUnprotectedFiles() throws Exception {
File dir = createDir("parent", createFile("parent/test.txt", NOT_PROTECTED));
delete(dir, INCLUDE_ALL);
assertOriginalFileRemoved("parent");
assertBackupFolderWasRemoved();
}
@Test
public void failToRemoveParentIfChildIsProtected() throws Exception {
File dir = createDir("parent", createFile("parent/test.txt", PROTECTED));
delete(dir, INCLUDE_ALL);
assertOriginalFileExists("parent");
assertOriginalFileExists("parent/test.txt");
assertBackupFolderWasRemoved();
}
@Test
public void doNotDeleteAnyFilesIfOneFileIsProtected() throws Exception {
File dir = createDir("parent", createFile("parent/test2.txt", NOT_PROTECTED), createFile("parent/test.txt", PROTECTED));
delete(dir, INCLUDE_ALL);
assertOriginalFileExists("parent");
assertOriginalFileExists("parent/test.txt");
assertOriginalFileExists("parent/test2.txt");
assertBackupFolderWasRemoved();
}
@Test
public void executeTwoDeleteOpsWithSameBackupDir() throws Exception {
final File dir1 = createDir("dir1", createFile("dir1/test.txt", NOT_PROTECTED));
final File dir2 = createDir("dir2", createFile("dir2/test.txt", PROTECTED));
final DeleteOp op1 = new DeleteOp(dir1, INCLUDE_ALL);
final DeleteOp op2 = new DeleteOp(dir2, INCLUDE_ALL);
try {
DeleteOp.execute(Arrays.asList(op1, op2));
fail("DeleteOp should throw an exception");
} catch(IOException e) {
assertOriginalFileExists("dir1/test.txt");
assertOriginalFileExists("dir2/test.txt");
}
}
@Test
public void dontRemoveIgnoredFiles() throws Exception {
final File dir = createDir("dir1", createFile("dir1/test1.txt", NOT_PROTECTED), createFile("dir1/test2.txt", NOT_PROTECTED));
boolean operationResult = delete(dir, exclude("test1.txt"));
assertTrue("The delete with ignored files should be succesful", operationResult);
assertOriginalFileExists("dir1/test1.txt");
assertOriginalFileRemoved("dir1/test2.txt");
}
@Test
public void dontRemoveIgnoredDirectories() throws Exception {
final File dir = createDir("dir1",
createDir("dir2",
createFile("dir1/dir2/test1.txt", NOT_PROTECTED)),
createFile("dir1/test2.txt", NOT_PROTECTED));
delete(dir, exclude("dir2"));
assertOriginalFileExists("dir1/dir2/test1.txt");
assertOriginalFileRemoved("dir1/test2.txt");
}
@Test
public void completeSuccesfullyIfAllFilesWereIgnored() throws Exception {
final File dir = createDir("dir1");
final DeleteOp op1 = new DeleteOp(dir, (d,f)->false); // exclude all files
DeleteOp.execute(Arrays.asList(op1));
assertOriginalFileExists("dir1");
assertBackupFolderWasRemoved();
}
@Test
public void removeBackupFolderAfterCommit() throws Exception {
file("dir1").mkdir();
file("dir1", "test1.txt").createNewFile();
boolean operationResult = delete(file("dir1"), INCLUDE_ALL);
assertTrue("The delete should have succeeded", operationResult);
assertBackupFolderWasRemoved();
}
@Test
public void removeBackupFolderAfterRollback() throws Exception {
final File dir1 = createDir("dir1", createFile("dir1/test.txt", PROTECTED));
boolean operationResult = delete(dir1, INCLUDE_ALL);
assertFalse("The delete should have failed", operationResult);
assertBackupFolderWasRemoved();
}
@Test
public void failDeleteIfDeletedFileExistsInBackupFolder() throws Exception {
createDir(".bkp", createDir("dir1", createFile(".bkp/dir1/test.txt", NOT_PROTECTED)));
final File dir1 = createDir("dir1", createFile("dir1/test.txt", NOT_PROTECTED));
boolean operationResult = delete(dir1, INCLUDE_ALL);
assertFalse("The delete should have failed", operationResult);
}
private static void assertOriginalFileExists(String path) {
final String fullPath = TEST_DIR + "/" + path;
final File file = new File(fullPath);
assertTrue("Original file [" + fullPath + "] should not be removed", file.exists());
}
private static void assertOriginalFileRemoved(String path) {
final String fullPath = TEST_DIR + "/" + path;
final File file = new File(fullPath);
assertFalse("Original file [" + fullPath + "] should be removed", file.exists());
}
private static void assertBackupFolderWasRemoved() {
final File file = new File(BACKUP_ROOT_FILE_NAME);
assertFalse("Backup folder [" + BACKUP_ROOT_FILE_NAME + "] should be removed", file.exists());
}
private boolean delete(File origin, FilenameFilter filter) throws IOException {
final DeleteOp op = new DeleteOp(origin, filter);
try {
DeleteOp.execute(Arrays.asList(op));
return true;
} catch (IOException e) {
return false;
}
}
private static File file(String... parts) {
StringBuilder sb = new StringBuilder(TEST_DIR);
for (String part : parts) {
sb.append("/").append(part);
}
return new File(sb.toString());
}
private static FilenameFilter exclude(String fileName) {
return (d,f) -> !f.equals(fileName);
}
private static File createDir(String name, File... files) {
String fileName = TEST_DIR + "/" + name;
File dir = new MockFile(fileName, false, files);
if (!dir.exists()) {
dir.mkdir();
}
return dir;
}
private static File createFile(String name, boolean deleteProtected) throws IOException {
createParents(name);
String fileName = TEST_DIR + "/" + name;
File origin = new MockFile(fileName, deleteProtected);
origin.createNewFile();
return origin;
}
private static void createParents(String name) {
File parent = new File(TEST_DIR);
final String[] split = name.split("/");
for (int i = 0; i < split.length - 1; i++) {
String p = split[i];
parent = new File(parent, p);
parent.mkdir();
}
}
/*
Since exact behaviour of File depends on the underlying platform, File.setReadonly / File.setWritable does not
guarantee move protection. MockFile is to help simulate those issues.
*/
private static class MockFile extends File {
private File[] children;
private boolean protect;
MockFile(String name, boolean protect) {
super(name);
this.protect = protect;
}
MockFile(String name, boolean protect, File... children) {
this(name, protect);
this.children = children;
}
@Override
public boolean delete() {
if (protect) {
return false;
} else {
return super.delete();
}
}
@Override
public boolean renameTo(File dest) {
if (protect) {
return false;
} else {
return super.renameTo(dest);
}
}
@Override
public File[] listFiles(FileFilter filter) {
final File[] files = super.listFiles(filter);
// need to return the mock ones, but skip ones that were removed
ArrayList<File> filtered = new ArrayList<>();
for (File file : files) {
for (File child : children) {
if (file.getAbsolutePath().equals(child.getAbsolutePath())) {
filtered.add(child);
}
}
}
return filtered.toArray(new File[]{});
}
@Override
public File[] listFiles(FilenameFilter filter) {
final File[] files = super.listFiles(filter);
// need to return the mock ones, but skip ones that were removed
ArrayList<File> filtered = new ArrayList<>();
for (File file : files) {
for (File child : children) {
if (file.getAbsolutePath().equals(child.getAbsolutePath())) {
filtered.add(child);
}
}
}
return filtered.toArray(new File[]{});
}
}
}