/******************************************************************************* * Copyright (c) 2012 Arapiki Solutions Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * psmith - initial API and * implementation and/or initial documentation *******************************************************************************/ package com.buildml.refactor; import static org.junit.Assert.*; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.buildml.model.CommonTestUtils; import com.buildml.model.IActionMgr; import com.buildml.model.IBuildStore; import com.buildml.model.IFileGroupMgr; import com.buildml.model.IFileMgr; import com.buildml.model.IPackageMemberMgr; import com.buildml.model.IPackageMgr; import com.buildml.model.IActionMgr.OperationType; import com.buildml.model.impl.FileGroupMgr; import com.buildml.model.undo.MultiUndoOp; import com.buildml.refactor.CanNotRefactorException.Cause; import com.buildml.refactor.imports.ImportRefactorer; import com.buildml.utils.errors.ErrorCode; /** * Test cases to verify the IImportRefactorer implementation. These tests focus on * deleting paths. * * @author Peter Smith <psmith@arapiki.com> */ public class TestImportRefactorDeleteFile { /*=====================================================================================* * FIELDS/TYPES *=====================================================================================*/ private IBuildStore buildStore; private IFileMgr fileMgr; private IActionMgr actionMgr; /* action and file IDs used in the tests */ private int action, actionA, actionA1, actionA2, actionA3, actionA4, actionA5, actionA6; private int dirWork, fileWorkAIn, fileWorkBOut, fileWorkCOut, fileWorkDOut, fileWorkEIn; private int fileWorkFOut, fileWorkGOut, fileWorkHIn, fileWorkIOut, fileWorkJOut, fileWorkKOut; private int fileWorkMakefile, fileUnused, dirUnused, fileWorkLog; private IImportRefactorer importRefactorer; /*=====================================================================================* * SETUP/TEARDOWN *=====================================================================================*/ /** * Method called before each test case - sets up default configuration. * @throws Exception */ @Before public void setUp() throws Exception { /* get a new empty BuildStore */ buildStore = CommonTestUtils.getEmptyBuildStore(); /* fetch the associated managers */ fileMgr = buildStore.getFileMgr(); actionMgr = buildStore.getActionMgr(); /* this is the object under test */ importRefactorer = new ImportRefactorer(buildStore); /* * Set up a realistic import scenario. * action : <action root> * actionA : make all > log * actionA1 : cp fileA.in fileB.out * actionA2 : cp fileA.in fileC.out * actionA3 : cp fileB.out fileD.out * actionA4 : cp fileE.in fileF.out >fileG.out * actionA5 : cp fileH.in fileI.out >fileJ.out * actionA6 : cp fileI.out fileK.out * * dirWork /home/work * fileWorkAIn /home/work/fileA.in * fileWorkBOut /home/work/fileB.out * fileWorkCOut /home/work/fileC.out * fileWorkDOut /home/work/fileD.out * fileWorkEIn /home/work/fileE.in * fileWorkFOut /home/work/fileF.out * fileWorkGOut /home/work/fileG.out * fileWorkHIn /home/work/fileH.in * fileWorkIOut /home/work/fileI.out * fileWorkJOut /home/work/fileJ.out * fileWorkKOut /home/work/fileK.out * fileWorkMakefile /home/work/Makefile * fileWorkLog /home/work/log * fileUnused /home/work/README * dirUnused /home/work/unused * /home/work/unused/1/2 * /home/work/unused/1/2/3 * /home/work/unused/1/4 * /home/work/unused/1/4/5 */ dirWork = fileMgr.addDirectory("/home/work"); fileWorkAIn = fileMgr.addFile("/home/work/fileA.in"); fileWorkBOut = fileMgr.addFile("/home/work/fileB.out"); fileWorkCOut = fileMgr.addFile("/home/work/fileC.out"); fileWorkDOut = fileMgr.addFile("/home/work/fileD.out"); fileWorkEIn = fileMgr.addFile("/home/work/fileE.in"); fileWorkFOut = fileMgr.addFile("/home/work/fileF.out"); fileWorkGOut = fileMgr.addFile("/home/work/fileG.out"); fileWorkHIn = fileMgr.addFile("/home/work/fileH.in"); fileWorkIOut = fileMgr.addFile("/home/work/fileI.out"); fileWorkJOut = fileMgr.addFile("/home/work/fileJ.out"); fileWorkKOut = fileMgr.addFile("/home/work/fileK.out"); fileWorkMakefile = fileMgr.addFile("/home/work/Makefile"); fileWorkLog = fileMgr.addFile("/home/work/log"); fileUnused = fileMgr.addFile("/home/work/README"); dirUnused = fileMgr.addDirectory("/home/work/unused"); fileMgr.addFile("/home/work/unused/1/2/3"); fileMgr.addFile("/home/work/unused/1/4/5"); action = actionMgr.getRootAction("root"); actionA = actionMgr.addShellCommandAction(action, dirWork, "make all"); actionMgr.addFileAccess(actionA, fileWorkMakefile, OperationType.OP_READ); actionMgr.addFileAccess(actionA, fileWorkLog, OperationType.OP_WRITE); actionA1 = actionMgr.addShellCommandAction(actionA, dirWork, "cp fileA.in fileB.out"); actionMgr.addFileAccess(actionA1, fileWorkAIn, OperationType.OP_READ); actionMgr.addFileAccess(actionA1, fileWorkBOut, OperationType.OP_WRITE); actionA2 = actionMgr.addShellCommandAction(actionA, dirWork, "cp fileA.in fileC.out"); actionMgr.addFileAccess(actionA2, fileWorkAIn, OperationType.OP_READ); actionMgr.addFileAccess(actionA2, fileWorkCOut, OperationType.OP_WRITE); actionA3 = actionMgr.addShellCommandAction(actionA, dirWork, "cp fileB.out fileD.out"); actionMgr.addFileAccess(actionA3, fileWorkBOut, OperationType.OP_READ); actionMgr.addFileAccess(actionA3, fileWorkDOut, OperationType.OP_WRITE); actionA4 = actionMgr.addShellCommandAction(actionA, dirWork, "cp fileE.in fileF.out > fileG.out"); actionMgr.addFileAccess(actionA4, fileWorkEIn, OperationType.OP_READ); actionMgr.addFileAccess(actionA4, fileWorkFOut, OperationType.OP_WRITE); actionMgr.addFileAccess(actionA4, fileWorkGOut, OperationType.OP_WRITE); actionA5 = actionMgr.addShellCommandAction(actionA, dirWork, "cp fileH.in fileI.out > fileJ.out"); actionMgr.addFileAccess(actionA5, fileWorkHIn, OperationType.OP_READ); actionMgr.addFileAccess(actionA5, fileWorkIOut, OperationType.OP_WRITE); actionMgr.addFileAccess(actionA5, fileWorkJOut, OperationType.OP_WRITE); actionA6 = actionMgr.addShellCommandAction(actionA, dirWork, "cp fileI.out fileK.out"); actionMgr.addFileAccess(actionA6, fileWorkIOut, OperationType.OP_READ); actionMgr.addFileAccess(actionA6, fileWorkKOut, OperationType.OP_WRITE); } /*-------------------------------------------------------------------------------------*/ /** * Method called after each test cases - closes the BuildStore. * @throws Exception */ @After public void tearDown() throws Exception { buildStore.close(); } /*=====================================================================================* * TEST METHODS *=====================================================================================*/ /** * Test deletion of non-existent path - should fail. * @throws Exception */ @Test public void testDeleteBadPath() throws Exception { /* * Delete a non-existent path - should give an error. */ int badPathId = 10000; try { MultiUndoOp multiOp = new MultiUndoOp(); importRefactorer.deletePath(multiOp, badPathId, false, false); fail("Failed when trying to delete non-existent file."); } catch (CanNotRefactorException ex) { assertEquals(Cause.INVALID_PATH, ex.getCauseCode()); assertArrayEquals(new Integer[] { badPathId } , ex.getCauseIDs()); } } /*-------------------------------------------------------------------------------------*/ /** * Test deletion of an unused path - should work. * @throws Exception */ @Test public void testDeleteUnusedPath() throws Exception { /* * Delete an unused path - there should be no problems doing this. */ assertFalse(fileMgr.isPathTrashed(fileUnused)); MultiUndoOp multiOp = new MultiUndoOp(); try { importRefactorer.deletePath(multiOp, fileUnused, false, false); multiOp.redo(); } catch (CanNotRefactorException ex) { fail("Failed to delete: " + fileUnused); } assertTrue(fileMgr.isPathTrashed(fileUnused)); /* Undo, then redo the operation - should succeed */ multiOp.undo(); assertFalse(fileMgr.isPathTrashed(fileUnused)); multiOp.redo(); assertTrue(fileMgr.isPathTrashed(fileUnused)); } /*-------------------------------------------------------------------------------------*/ /** * Attempt to delete a path twice - should fail the second time. */ @Test public void testDeleteUnusedPathTwice() { /* delete the file for the first time */ assertFalse(fileMgr.isPathTrashed(fileUnused)); try { MultiUndoOp multiOp = new MultiUndoOp(); importRefactorer.deletePath(multiOp, fileUnused, false, false); multiOp.redo(); } catch (CanNotRefactorException ex) { fail("Failed to delete: " + fileUnused); } assertTrue(fileMgr.isPathTrashed(fileUnused)); /* delete the file a second time - should fail */ try { MultiUndoOp multiOp = new MultiUndoOp(); importRefactorer.deletePath(multiOp, fileUnused, false, false); fail("Failed when attempting to delete a file the second time."); } catch (CanNotRefactorException ex) { assertEquals(Cause.INVALID_PATH, ex.getCauseCode()); assertArrayEquals(new Integer[] { fileUnused } , ex.getCauseIDs()); } assertTrue(fileMgr.isPathTrashed(fileUnused)); } /*-------------------------------------------------------------------------------------*/ /** * Attempt to delete a file (fileE.In) that's read by an action - should fail to delete. * This is essentially a source file. */ @Test public void testDeleteSourceFileInUse() { try { MultiUndoOp multiOp = new MultiUndoOp(); importRefactorer.deletePath(multiOp, fileWorkEIn, false, false); fail("Failed when trying to delete in-use file."); } catch (CanNotRefactorException ex) { assertEquals(Cause.PATH_IN_USE, ex.getCauseCode()); assertArrayEquals(new Integer[] { actionA4 } , ex.getCauseIDs()); } } /*-------------------------------------------------------------------------------------*/ /** * Attempt to delete a file (fileE.in) that's read by an action. Use the "removeFromAction" * flag so that it'll be removed from the action that reads it. Also, undo and redo the * operation. */ @Test public void testForceDeleteSourceFileInUse() { /* ensure that actionA4 reads fileE.in */ Integer filesRead[] = actionMgr.getFilesAccessed(actionA4, OperationType.OP_READ); assertEquals(1, filesRead.length); assertEquals(fileWorkEIn, filesRead[0].intValue()); /* attempt the deletion */ MultiUndoOp multiOp = new MultiUndoOp(); try { importRefactorer.deletePath(multiOp, fileWorkEIn, false, true); } catch (CanNotRefactorException ex) { fail("Failed to remove file-access from action."); } multiOp.redo(); /* Test that actionA4 no longer reads fileE.in. */ filesRead = actionMgr.getFilesAccessed(actionA4, OperationType.OP_READ); assertEquals(0, filesRead.length); /* undo the delete and re-test actionA4 */ multiOp.undo(); filesRead = actionMgr.getFilesAccessed(actionA4, OperationType.OP_READ); assertEquals(1, filesRead.length); assertEquals(fileWorkEIn, filesRead[0].intValue()); /* redo the delete and re-test actionA4 */ multiOp.redo(); filesRead = actionMgr.getFilesAccessed(actionA4, OperationType.OP_READ); assertEquals(0, filesRead.length); } /*-------------------------------------------------------------------------------------*/ /** * Attempt to delete a file (fileB.out) that's read by an action - should fail to delete. * This file is also generated by an action. */ @Test public void testDeleteGeneratedFileInUse() { try { MultiUndoOp multiOp = new MultiUndoOp(); importRefactorer.deletePath(multiOp, fileWorkBOut, false, false); fail("Failed when trying to delete in-use file."); } catch (CanNotRefactorException ex) { assertEquals(Cause.PATH_IN_USE, ex.getCauseCode()); assertArrayEquals(new Integer[] { actionA3 } , ex.getCauseIDs()); } } /*-------------------------------------------------------------------------------------*/ /** * Attempt to delete a file (fileK.out) that's generated, but not used by any actions. Set * alsoDeleteActions to false. */ @Test public void testDeleteGeneratedFile() { try { MultiUndoOp multiOp = new MultiUndoOp(); importRefactorer.deletePath(multiOp, fileWorkKOut, false, false); fail("Failed when trying to delete in-use file."); } catch (CanNotRefactorException ex) { assertEquals(Cause.PATH_IS_GENERATED, ex.getCauseCode()); assertArrayEquals(new Integer[] { actionA6 } , ex.getCauseIDs()); } } /*-------------------------------------------------------------------------------------*/ /** * Attempt to delete a file (fileG.out) that's generated, but not used by any actions. Set * alsoDeleteActions to true so that the generating action will also be deleted. */ @Test public void testDeleteGeneratedFileAndAction() { MultiUndoOp multiOp = new MultiUndoOp(); try { importRefactorer.deletePath(multiOp, fileWorkGOut, true, false); multiOp.redo(); } catch (CanNotRefactorException ex) { fail("Failed to delete file and action."); } /* * Check that the deletion and file and action, as well as all the action-file * links has truly taken place. */ assertTrue(fileMgr.isPathTrashed(fileWorkGOut)); assertTrue(actionMgr.isActionTrashed(actionA4)); assertEquals(0, actionMgr.getFilesAccessed(actionA4, OperationType.OP_UNSPECIFIED).length); /* * Undo the operation and check again. */ multiOp.undo(); assertFalse(fileMgr.isPathTrashed(fileWorkGOut)); assertFalse(actionMgr.isActionTrashed(actionA4)); assertTrue(CommonTestUtils.sortedArraysEqual( new Integer[] { fileWorkEIn }, actionMgr.getFilesAccessed(actionA4, OperationType.OP_READ))); assertTrue(CommonTestUtils.sortedArraysEqual( new Integer[] { fileWorkFOut, fileWorkGOut }, actionMgr.getFilesAccessed(actionA4, OperationType.OP_WRITE))); /* * Finally, redo the operation and check again. */ multiOp.redo(); assertTrue(fileMgr.isPathTrashed(fileWorkGOut)); assertTrue(actionMgr.isActionTrashed(actionA4)); assertEquals(0, actionMgr.getFilesAccessed(actionA4, OperationType.OP_UNSPECIFIED).length); } /*-------------------------------------------------------------------------------------*/ /** * Attempt to delete a file (fileJ.out) that's generated, but not used by any actions. Set * alsoDeleteActions to true so that the generating action will also be deleted. However, * this action also generates a second file (fileI.out) that's still in use. This is an error. */ @Test public void testDeleteGeneratedFileAndActionInUse() { try { MultiUndoOp multiOp = new MultiUndoOp(); importRefactorer.deletePath(multiOp, fileWorkJOut, true, false); fail("Failed when deleting an file/action that's in use."); } catch (CanNotRefactorException ex) { assertEquals(Cause.ACTION_IN_USE, ex.getCauseCode()); assertArrayEquals(new Integer[] { fileWorkIOut }, ex.getCauseIDs()); } } /*-------------------------------------------------------------------------------------*/ /** * Attempt to delete a directory that's not empty - should fail to delete. */ @Test public void testDeleteDirectoryNotEmpty() { try { MultiUndoOp multiOp = new MultiUndoOp(); importRefactorer.deletePath(multiOp, dirWork, false, false); fail("Failed when trying to delete non-empty directory."); } catch (CanNotRefactorException ex) { assertEquals(Cause.DIRECTORY_NOT_EMPTY, ex.getCauseCode()); assertArrayEquals(new Integer[] { dirWork } , ex.getCauseIDs()); } } /*-------------------------------------------------------------------------------------*/ /** * Attempt to delete a file that's in use by a non-atomic action - should fail. */ @Test public void testDeleteFileWithNonAtomicAction() { try { MultiUndoOp multiOp = new MultiUndoOp(); importRefactorer.deletePath(multiOp, fileWorkLog, true, false); fail("Failed when deleting file used by non-atomic action."); } catch (CanNotRefactorException ex) { assertEquals(Cause.ACTION_NOT_ATOMIC, ex.getCauseCode()); assertArrayEquals(new Integer[] { actionA } , ex.getCauseIDs()); } } /*-------------------------------------------------------------------------------------*/ /** * Attempt to delete a sub-tree where one or more files are in use. */ @Test public void testDeleteInUseSubTree() { /* we can't delete the whole /home/work directory - it's in use */ try { MultiUndoOp multiOp = new MultiUndoOp(); importRefactorer.deletePathTree(multiOp, dirWork, true, false); fail("Incorrectly deleted in-use files"); } catch (CanNotRefactorException e) { assertEquals(Cause.PATH_IN_USE, e.getCauseCode()); } /* delete fileWorkLog - will have NOT_ATOMIC error */ try { MultiUndoOp multiOp = new MultiUndoOp(); importRefactorer.deletePathTree(multiOp, fileWorkLog, true, false); fail("Incorrectly deleted in-use files"); } catch (CanNotRefactorException e) { assertEquals(Cause.ACTION_NOT_ATOMIC, e.getCauseCode()); } /* delete fileWorkKOut - will fail unless we set alsoDeleteActions */ assertFalse(fileMgr.isPathTrashed(fileWorkKOut)); assertFalse(actionMgr.isActionTrashed(actionA6)); try { MultiUndoOp multiOp = new MultiUndoOp(); importRefactorer.deletePathTree(multiOp, fileWorkKOut, false, false); fail("Incorrectly deleted in-use files"); } catch (CanNotRefactorException e) { assertEquals(Cause.PATH_IS_GENERATED, e.getCauseCode()); } try { MultiUndoOp multiOp = new MultiUndoOp(); importRefactorer.deletePathTree(multiOp, fileWorkKOut, true, false); multiOp.redo(); assertTrue(fileMgr.isPathTrashed(fileWorkKOut)); assertTrue(actionMgr.isActionTrashed(actionA6)); } catch (CanNotRefactorException e) { fail("Couldn't delete file and generating action"); } } /*-------------------------------------------------------------------------------------*/ /** * Delete a sub-tree that's not in use. */ @Test public void testDeleteSubTree() { assertEquals(dirUnused, fileMgr.getPath("/home/work/unused")); MultiUndoOp multiOp = new MultiUndoOp(); try { importRefactorer.deletePathTree(multiOp, dirUnused, false, false); multiOp.redo(); } catch (CanNotRefactorException e) { fail("Failed to delete unused file sub-tree: " + e.getCauseCode()); } assertEquals(ErrorCode.BAD_PATH, fileMgr.getPath("/home/work/unused")); /* undo the delete */ multiOp.undo(); assertNotSame(ErrorCode.BAD_PATH, fileMgr.getPath("/home/work/unused")); assertNotSame(ErrorCode.BAD_PATH, fileMgr.getPath("/home/work/unused/1/2/3")); assertNotSame(ErrorCode.BAD_PATH, fileMgr.getPath("/home/work/unused/1/4/5")); /* redo the delete */ multiOp.redo(); assertEquals(ErrorCode.BAD_PATH, fileMgr.getPath("/home/work/unused")); } /*-------------------------------------------------------------------------------------*/ /** * Try to delete files that are members of filegroups (this should be disallowed). */ @Test public void testDeleteFileFromFileGroup() { IFileGroupMgr fileGroupMgr = buildStore.getFileGroupMgr(); IPackageMgr pkgMgr = buildStore.getPackageMgr(); int pkgId = pkgMgr.addPackage("PkgA"); /* create a file group, and add a source file and a generated file into it */ int fgId1 = fileGroupMgr.newSourceGroup(pkgId); int fgId2 = fileGroupMgr.newSourceGroup(pkgId); assertEquals(0, fileGroupMgr.addPathId(fgId1, fileWorkAIn)); assertEquals(0, fileGroupMgr.addPathId(fgId2, fileWorkAIn)); assertEquals(1, fileGroupMgr.addPathId(fgId1, fileWorkBOut)); /* try to delete the source file - should fail */ MultiUndoOp multiOp = new MultiUndoOp(); try { importRefactorer.deletePath(multiOp, fileWorkAIn, false, true); fail("Failed to reject deletion."); } catch (CanNotRefactorException ex) { assertEquals(Cause.FILE_STILL_IN_GROUP, ex.getCauseCode()); assertArrayEquals(new Integer[] { fgId1, fgId2 }, ex.getCauseIDs()); } /* remove the source file from the file groups */ assertEquals(ErrorCode.OK, fileGroupMgr.removeEntry(fgId1, 0)); assertEquals(ErrorCode.OK, fileGroupMgr.removeEntry(fgId2, 0)); /* try to delete the source file again - should succeed now it's no longer in the file groups */ multiOp = new MultiUndoOp(); try { importRefactorer.deletePath(multiOp, fileWorkAIn, false, true); } catch (CanNotRefactorException ex) { fail("Failed to delete source file"); } multiOp.redo(); /* try to delete the generated file (and associated generator action) - should fail */ multiOp = new MultiUndoOp(); try { importRefactorer.deletePath(multiOp, fileWorkBOut, true, true); fail("Failed to reject deletion."); } catch (CanNotRefactorException ex) { assertEquals(Cause.FILE_STILL_IN_GROUP, ex.getCauseCode()); assertArrayEquals(new Integer[] { fgId1 }, ex.getCauseIDs()); } } /*-------------------------------------------------------------------------------------*/ }