/******************************************************************************* * 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.IFileMgr; import com.buildml.model.IActionMgr.OperationType; import com.buildml.model.types.ActionSet; import com.buildml.model.undo.MultiUndoOp; import com.buildml.refactor.CanNotRefactorException.Cause; import com.buildml.refactor.imports.ImportRefactorer; /** * Test cases to verify the IImportRefactorer implementation. These tests focus * on merging actions. * * @author Peter Smith <psmith@arapiki.com> */ public class TestImportRefactorMergeActions { /*=====================================================================================* * FIELDS/TYPES *=====================================================================================*/ private IBuildStore buildStore; private IFileMgr fileMgr; private IActionMgr actionMgr; /* action IDs */ private int action, actionA, actionA1, actionA1a, actionA1b, actionA2, actionA2a, actionA2b; private int actionA3, actionA4, actionA4a, actionA4b, actionA4c; private int dirWork, fileWorkFooC, fileWorkFooO, fileWorkBarC, fileWorkBarO, fileWorkMakefile; private int fileWorkMainC, fileWorkMainO, fileWorkMain, fileWorkLibA; private int fileTmpAaaS, fileTmpBbbS, fileTmpCccS, fileUsrCrt0O, fileUsrCrtendO, fileUnused; 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 * actionA1 : gcc -c foo.o foo.c * actionA1a: cc1 -o /tmp/aaa.s foo.c * actionA1b: as -o foo.o /tmp/aaa.s * actionA2 : gcc -c bar.o bar.c * actionA2a: cc1 -o /tmp/bbb.s bar.c * actionA2b: as -o bar.o /tmp/bbb.s * actionA3 : ar c lib.a foo.o bar.o * actionA4 : gcc -o main main.c lib.a * actionA4a: cc1 -o /tmp/ccc.s main.c * actionA4b: as -o main.o /tmp/ccc.s * actionA4c: ld -o main /usr/crt0.o main.o lib.a /usr/crtend.o * * * fileWork /home/work * fileWorkFooC /home/work/foo.c * fileWorkFooO /home/work/foo.o * fileWorkBarC /home/work/bar.c * fileWorkBarO /home/work/bar.o * fileWorkMainC /home/work/main.c * fileWorkMainO /home/work/main.o * fileWorkMain /home/work/main * fileWorkLibA /home/work/lib.a * fileWorkMakefile /home/work/Makefile * fileTmpAaaS /tmp/aaa.s * fileTmpBbbS /tmp/bbb.s * fileTmpCccS /tmp/ccc.s * fileUsrCrt0O /usr/crt0.o * fileUsrCrtendO /usr/crtend.o * fileUnused /home/work/README */ dirWork = fileMgr.addDirectory("/home/work"); fileWorkFooC = fileMgr.addFile("/home/work/foo.c"); fileWorkFooO = fileMgr.addFile("/home/work/foo.o"); fileWorkBarC = fileMgr.addFile("/home/work/bar.c"); fileWorkBarO = fileMgr.addFile("/home/work/bar.o"); fileWorkMainC = fileMgr.addFile("/home/work/main.c"); fileWorkMainO = fileMgr.addFile("/home/work/main.o"); fileWorkMain = fileMgr.addFile("/home/work/main"); fileWorkLibA = fileMgr.addFile("/home/work/lib.a"); fileWorkMakefile = fileMgr.addFile("/home/work/Makefile"); fileTmpAaaS = fileMgr.addFile("/tmp/aaa.s"); fileTmpBbbS = fileMgr.addFile("/tmp/bbb.s"); fileTmpCccS = fileMgr.addFile("/tmp/ccc.s"); fileUsrCrt0O = fileMgr.addFile("/usr/crt0.o"); fileUsrCrtendO = fileMgr.addFile("/usr/crtend.o"); fileUnused = fileMgr.addFile("/home/work/README"); action = actionMgr.getRootAction("root"); actionA = actionMgr.addShellCommandAction(action, dirWork, "make all"); actionMgr.addFileAccess(actionA, fileWorkMakefile, OperationType.OP_READ); actionA1 = actionMgr.addShellCommandAction(actionA, dirWork, "gcc -c foo.o foo.c"); actionA1a = actionMgr.addShellCommandAction(actionA1, dirWork, "cc1 -o /tmp/aaa.s foo.c"); actionMgr.addFileAccess(actionA1a, fileWorkFooC, OperationType.OP_READ); actionMgr.addFileAccess(actionA1a, fileTmpAaaS, OperationType.OP_WRITE); actionA1b = actionMgr.addShellCommandAction(actionA1, dirWork, "as -o foo.o /tmp/aaa.s"); actionMgr.addFileAccess(actionA1b, fileTmpAaaS, OperationType.OP_READ); actionMgr.addFileAccess(actionA1b, fileWorkFooO, OperationType.OP_WRITE); actionMgr.addFileAccess(actionA1, fileTmpAaaS, OperationType.OP_DELETE); actionA2 = actionMgr.addShellCommandAction(actionA, dirWork, "gcc -c bar.o bar.c"); actionA2a = actionMgr.addShellCommandAction(actionA2, dirWork, "cc1 -o /tmp/bbb.s bar.c"); actionMgr.addFileAccess(actionA2a, fileWorkBarC, OperationType.OP_READ); actionMgr.addFileAccess(actionA2a, fileTmpBbbS, OperationType.OP_WRITE); actionA2b = actionMgr.addShellCommandAction(actionA2, dirWork, "as -o bar.o /tmp/bbb.s"); actionMgr.addFileAccess(actionA2b, fileTmpBbbS, OperationType.OP_READ); actionMgr.addFileAccess(actionA2b, fileWorkBarO, OperationType.OP_WRITE); actionA3 = actionMgr.addShellCommandAction(actionA, dirWork, "ar c lib.a foo.o bar.o"); actionMgr.addFileAccess(actionA3, fileWorkFooO, OperationType.OP_READ); actionMgr.addFileAccess(actionA3, fileWorkBarO, OperationType.OP_READ); actionMgr.addFileAccess(actionA3, fileWorkLibA, OperationType.OP_WRITE); actionMgr.addFileAccess(actionA3, fileTmpBbbS, OperationType.OP_DELETE); actionA4 = actionMgr.addShellCommandAction(actionA, dirWork, "gcc -o main main.c lib.a"); actionA4a = actionMgr.addShellCommandAction(actionA4, dirWork, "cc1 -o /tmp/ccc.s main.c"); actionMgr.addFileAccess(actionA4a, fileWorkMainC, OperationType.OP_READ); actionMgr.addFileAccess(actionA4a, fileTmpCccS, OperationType.OP_WRITE); actionA4b = actionMgr.addShellCommandAction(actionA4, dirWork, "as -o main.o /tmp/ccc.s"); actionMgr.addFileAccess(actionA4b, fileTmpCccS, OperationType.OP_READ); actionMgr.addFileAccess(actionA4b, fileWorkMainO, OperationType.OP_WRITE); actionA4c = actionMgr.addShellCommandAction(actionA4, dirWork, "ld -o main /usr/crt0.o main.o lib.a /usr/crtend.o"); actionMgr.addFileAccess(actionA4c, fileUsrCrt0O, OperationType.OP_READ); actionMgr.addFileAccess(actionA4c, fileWorkMainO, OperationType.OP_READ); actionMgr.addFileAccess(actionA4c, fileWorkLibA, OperationType.OP_READ); actionMgr.addFileAccess(actionA4c, fileUsrCrtendO, OperationType.OP_READ); actionMgr.addFileAccess(actionA4c, fileWorkMain, OperationType.OP_WRITE); actionMgr.addFileAccess(actionA4, fileTmpCccS, OperationType.OP_DELETE); } /*-------------------------------------------------------------------------------------*/ /** * Method called after each test cases - closes the BuildStore. * @throws Exception */ @After public void tearDown() throws Exception { buildStore.close(); } /*=====================================================================================* * TEST METHODS *=====================================================================================*/ /** * Test the "make atomic" operation. */ @Test public void testMakeAtomic() { /* actionA4c is already atomic - no work required */ try { MultiUndoOp multiOp = new MultiUndoOp(); importRefactorer.makeActionAtomic(multiOp, actionA4c); multiOp.redo(); } catch (CanNotRefactorException e) { fail("Action is already atomic, but got an error."); } assertEquals(0, actionMgr.getChildren(actionA4c).length); CommonTestUtils.sortedArraysEqual( new Integer[] { fileUsrCrt0O, fileWorkMainO, fileWorkLibA, fileUsrCrtendO, fileWorkMain }, actionMgr.getFilesAccessed(actionA4c, OperationType.OP_UNSPECIFIED)); /* actionA4 has three children */ assertEquals(3, actionMgr.getChildren(actionA4).length); MultiUndoOp multiOp = new MultiUndoOp(); try { importRefactorer.makeActionAtomic(multiOp, actionA4); multiOp.redo(); } catch (CanNotRefactorException e) { fail("Failed to make action4 atomic."); } /* Check that the former children have been deleted */ assertEquals(0, actionMgr.getChildren(actionA4).length); assertTrue(actionMgr.isActionTrashed(actionA4a)); assertTrue(actionMgr.isActionTrashed(actionA4b)); assertTrue(actionMgr.isActionTrashed(actionA4c)); /* Check the new set of file accesses */ CommonTestUtils.sortedArraysEqual( new Integer[] { fileWorkMainC, fileUsrCrt0O, fileWorkLibA, fileUsrCrtendO }, actionMgr.getFilesAccessed(actionA4, OperationType.OP_READ)); CommonTestUtils.sortedArraysEqual( new Integer[] { fileWorkMainO, fileWorkMain }, actionMgr.getFilesAccessed(actionA4, OperationType.OP_WRITE)); assertEquals(0, actionMgr.getFilesAccessed(actionA4, OperationType.OP_MODIFIED).length); assertEquals(0, actionMgr.getFilesAccessed(actionA4, OperationType.OP_DELETE).length); /* Try to make a trashed file atomic - should fail */ try { MultiUndoOp tmpMultiOp = new MultiUndoOp(); importRefactorer.makeActionAtomic(tmpMultiOp, actionA4a); fail("Was able to make a trashed file atomic."); } catch (CanNotRefactorException e1) { assertEquals(Cause.ACTION_IS_TRASHED, e1.getCauseCode()); } /* undo the operation, and check that things are back to normal */ multiOp.undo(); assertEquals(3, actionMgr.getChildren(actionA4).length); assertFalse(actionMgr.isActionTrashed(actionA4a)); assertFalse(actionMgr.isActionTrashed(actionA4b)); assertFalse(actionMgr.isActionTrashed(actionA4c)); } /*-------------------------------------------------------------------------------------*/ /** * Test the "make atomic" operation, with an "in use" action. */ @Test public void testDeleteActionInUse() { /* * Delete actionA2b (left action) */ assertFalse(actionMgr.isActionTrashed(actionA2b)); assertTrue(CommonTestUtils.sortedArraysEqual(new Integer[] { actionA2a, actionA2b }, actionMgr.getChildren(actionA2))); try { MultiUndoOp multiOp = new MultiUndoOp(); importRefactorer.deleteAction(multiOp, actionA2b); fail("Failed to detect in-use action"); } catch (CanNotRefactorException e) { assertEquals(Cause.ACTION_IN_USE, e.getCauseCode()); assertTrue(CommonTestUtils.sortedArraysEqual( new Integer[] { fileWorkBarO }, e.getCauseIDs())); } } /*-------------------------------------------------------------------------------------*/ /** * Test the "make atomic" operation on actionA4c (a leaf) */ @Test public void testDeleteActionLeaf() { /* * Delete actionA4c (leaf action) */ assertFalse(actionMgr.isActionTrashed(actionA4c)); assertTrue(CommonTestUtils.sortedArraysEqual( new Integer[] { actionA4a, actionA4b, actionA4c }, actionMgr.getChildren(actionA4))); MultiUndoOp multiOp = new MultiUndoOp(); try { importRefactorer.deleteAction(multiOp, actionA4c); multiOp.redo(); /* success */ } catch (CanNotRefactorException e) { fail("Failed to delete actionA4c"); } /* check the after state */ assertTrue(actionMgr.isActionTrashed(actionA4c)); assertTrue(CommonTestUtils.sortedArraysEqual(new Integer[] { actionA4a, actionA4b }, actionMgr.getChildren(actionA4))); /* undo the operation */ multiOp.undo(); assertFalse(actionMgr.isActionTrashed(actionA4c)); assertTrue(CommonTestUtils.sortedArraysEqual( new Integer[] { actionA4a, actionA4b, actionA4c }, actionMgr.getChildren(actionA4))); /* redo the operation */ multiOp.redo(); assertTrue(actionMgr.isActionTrashed(actionA4c)); assertTrue(CommonTestUtils.sortedArraysEqual(new Integer[] { actionA4a, actionA4b }, actionMgr.getChildren(actionA4))); } /*-------------------------------------------------------------------------------------*/ /** * Test the "make atomic" operation the root action, or an invalid action - should fail. */ @Test public void testDeleteActionInvalid() { /* deleting the root action is illegal */ int rootAction = actionMgr.getRootAction("root"); try { MultiUndoOp multiOp = new MultiUndoOp(); importRefactorer.deleteAction(multiOp, rootAction); fail("Incorrectly deleted root action"); } catch (CanNotRefactorException e) { assertEquals(Cause.INVALID_ACTION, e.getCauseCode()); assertTrue(CommonTestUtils.sortedArraysEqual( new Integer[] { rootAction }, e.getCauseIDs())); } /* deleting an invalid actionID is illegal */ try { MultiUndoOp multiOp = new MultiUndoOp(); importRefactorer.deleteAction(multiOp, 1234); fail("Incorrectly deleted root action"); } catch (CanNotRefactorException e) { assertEquals(Cause.INVALID_ACTION, e.getCauseCode()); assertTrue(CommonTestUtils.sortedArraysEqual( new Integer[] { 1234 }, e.getCauseIDs())); } } /*-------------------------------------------------------------------------------------*/ /** * Test the "make atomic" operation on an unused parent action. */ @Test public void testDeleteActionValidParent() { /* * Delete actionA (parent action) */ int rootAction = actionMgr.getRootAction("root"); assertFalse(actionMgr.isActionTrashed(actionA)); assertTrue(CommonTestUtils.sortedArraysEqual( new Integer[] { actionA }, actionMgr.getChildren(rootAction))); MultiUndoOp multiOp = new MultiUndoOp(); try { importRefactorer.deleteAction(multiOp, actionA); multiOp.redo(); /* success */ } catch (CanNotRefactorException e) { fail("Failed to delete actionA"); } /* check the after state */ assertTrue(actionMgr.isActionTrashed(actionA)); assertTrue(CommonTestUtils.sortedArraysEqual( new Integer[] { actionA1, actionA2, actionA3, actionA4 }, actionMgr.getChildren(rootAction))); /* undo the operation */ multiOp.undo(); assertFalse(actionMgr.isActionTrashed(actionA)); assertTrue(CommonTestUtils.sortedArraysEqual( new Integer[] { actionA }, actionMgr.getChildren(rootAction))); /* redo the operation */ multiOp.redo(); assertTrue(actionMgr.isActionTrashed(actionA)); assertTrue(CommonTestUtils.sortedArraysEqual( new Integer[] { actionA1, actionA2, actionA3, actionA4 }, actionMgr.getChildren(rootAction))); } /*-------------------------------------------------------------------------------------*/ /** * Test merging of multiple actions - these test cases should fail. */ @Test public void testMergeActionsFail() { /* test merging of non-atomic action (actionA1) - should fail */ try { MultiUndoOp multiOp = new MultiUndoOp(); ActionSet set = new ActionSet(actionMgr, new Integer[] { actionA1a, actionA1 }); importRefactorer.mergeActions(multiOp, set); fail("Incorrectly merged an atomic action."); } catch (CanNotRefactorException e) { assertEquals(Cause.ACTION_NOT_ATOMIC, e.getCauseCode()); assertTrue(CommonTestUtils.sortedArraysEqual( new Integer[] { actionA1 }, e.getCauseIDs())); } /* test merging of invalid action ID */ try { MultiUndoOp multiOp = new MultiUndoOp(); ActionSet set = new ActionSet(actionMgr, new Integer[] { actionA1a, 1234 }); importRefactorer.mergeActions(multiOp, set); fail("Incorrectly merged an atomic action."); } catch (CanNotRefactorException e) { assertEquals(Cause.INVALID_ACTION, e.getCauseCode()); assertTrue(CommonTestUtils.sortedArraysEqual( new Integer[] { 1234 }, e.getCauseIDs())); } /* test merging of trashed action - actionA4c */ try { MultiUndoOp multiOp = new MultiUndoOp(); importRefactorer.deleteAction(multiOp, actionA4c); multiOp.redo(); } catch (CanNotRefactorException e1) { fail("Failed to remove actionA4c"); } try { MultiUndoOp multiOp = new MultiUndoOp(); ActionSet set = new ActionSet(actionMgr, new Integer[] { actionA4b, actionA4c }); importRefactorer.mergeActions(multiOp, set); fail("Incorrectly merged a trashed action."); } catch (CanNotRefactorException e) { assertEquals(Cause.ACTION_IS_TRASHED, e.getCauseCode()); assertTrue(CommonTestUtils.sortedArraysEqual( new Integer[] { actionA4c }, e.getCauseIDs())); } } /*-------------------------------------------------------------------------------------*/ /** * Test merging of multiple actions - these test cases should pass. */ @Test public void testMergeActions() { /* test merging of a single atomic action (actionA4b) - will succeed */ try { MultiUndoOp multiOp = new MultiUndoOp(); ActionSet set = new ActionSet(actionMgr, new Integer[] { actionA4b }); importRefactorer.mergeActions(multiOp, set); } catch (CanNotRefactorException e1) { fail("Failed to merge single action actionA4b"); } /* test merging of two actions - will succeed */ MultiUndoOp multiOp = new MultiUndoOp(); try { ActionSet set = new ActionSet(actionMgr, new Integer[] { actionA1b, actionA1a }); importRefactorer.mergeActions(multiOp, set); multiOp.redo(); assertFalse(actionMgr.isActionTrashed(actionA1a)); assertTrue(actionMgr.isActionTrashed(actionA1b)); assertEquals("cc1 -o /tmp/aaa.s foo.c\nas -o foo.o /tmp/aaa.s", actionMgr.getSlotValue(actionA1a, IActionMgr.COMMAND_SLOT_ID)); assertArrayEquals(new Integer[] { fileWorkFooC }, actionMgr.getFilesAccessed(actionA1a, OperationType.OP_READ)); assertTrue(CommonTestUtils.sortedArraysEqual( new Integer[] { fileWorkFooO, fileTmpAaaS }, actionMgr.getFilesAccessed(actionA1a, OperationType.OP_WRITE))); } catch (CanNotRefactorException e1) { fail("Failed to merge two actions."); } /* undo, and check for sanity */ multiOp.undo(); assertFalse(actionMgr.isActionTrashed(actionA1a)); assertFalse(actionMgr.isActionTrashed(actionA1b)); assertEquals("cc1 -o /tmp/aaa.s foo.c", actionMgr.getSlotValue(actionA1a, IActionMgr.COMMAND_SLOT_ID)); assertEquals("as -o foo.o /tmp/aaa.s", actionMgr.getSlotValue(actionA1b, IActionMgr.COMMAND_SLOT_ID)); assertArrayEquals(new Integer[] { fileWorkFooC }, actionMgr.getFilesAccessed(actionA1a, OperationType.OP_READ)); assertArrayEquals(new Integer[] { fileTmpAaaS }, actionMgr.getFilesAccessed(actionA1a, OperationType.OP_WRITE)); assertArrayEquals(new Integer[] { fileTmpAaaS }, actionMgr.getFilesAccessed(actionA1b, OperationType.OP_READ)); assertArrayEquals(new Integer[] { fileWorkFooO }, actionMgr.getFilesAccessed(actionA1b, OperationType.OP_WRITE)); /* redo, and check for sanity */ multiOp.redo(); assertFalse(actionMgr.isActionTrashed(actionA1a)); assertTrue(actionMgr.isActionTrashed(actionA1b)); assertEquals("cc1 -o /tmp/aaa.s foo.c\nas -o foo.o /tmp/aaa.s", actionMgr.getSlotValue(actionA1a, IActionMgr.COMMAND_SLOT_ID)); assertArrayEquals(new Integer[] { fileWorkFooC }, actionMgr.getFilesAccessed(actionA1a, OperationType.OP_READ)); assertTrue(CommonTestUtils.sortedArraysEqual( new Integer[] { fileWorkFooO, fileTmpAaaS }, actionMgr.getFilesAccessed(actionA1a, OperationType.OP_WRITE))); } /*-------------------------------------------------------------------------------------*/ }