/******************************************************************************* * Mission Control Technologies, Copyright (c) 2009-2012, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * * The MCT platform is 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. * * MCT includes source code licensed under additional open source licenses. See * the MCT Open Source Licenses file included with this distribution or the About * MCT Licenses dialog available at runtime from the MCT Help menu for additional * information. *******************************************************************************/ package gov.nasa.arc.mct.gui.actions; import gov.nasa.arc.mct.components.AbstractComponent; import gov.nasa.arc.mct.gui.ContextAwareAction; import gov.nasa.arc.mct.gui.MCTMutableTreeNode; import gov.nasa.arc.mct.gui.SelectionProvider; import gov.nasa.arc.mct.gui.View; import gov.nasa.arc.mct.gui.housing.MCTDirectoryArea; import gov.nasa.arc.mct.gui.housing.MCTHousing; import gov.nasa.arc.mct.gui.impl.ActionContextImpl; import gov.nasa.arc.mct.platform.spi.PersistenceProvider; import gov.nasa.arc.mct.platform.spi.Platform; import gov.nasa.arc.mct.platform.spi.PlatformAccess; import gov.nasa.arc.mct.platform.spi.WindowManager; import gov.nasa.arc.mct.policy.ExecutionResult; import gov.nasa.arc.mct.policy.PolicyContext; import gov.nasa.arc.mct.policy.PolicyInfo; import gov.nasa.arc.mct.services.component.PolicyManager; import gov.nasa.arc.mct.services.component.ViewInfo; import gov.nasa.arc.mct.services.component.ViewType; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; import javax.swing.JTree; import javax.swing.tree.TreePath; import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class TestDeleteAll { private static final ResourceBundle BUNDLE = ResourceBundle.getBundle("gov/nasa/arc/mct/gui/actions/Bundle"); private static final String CONFIRM = BUNDLE.getString("DeleteAllCoreText"); private static final String ABORT = BUNDLE.getString("DeleteAllAbortText"); private static final String OK = BUNDLE.getString("DeleteAllErrorConfirm"); @Mock ActionContextImpl mockContext; @Mock MCTHousing mockHousing; @Mock SelectionProvider mockSelection; @Mock MCTDirectoryArea mockDirectory; @Mock Platform mockPlatform; @Mock PersistenceProvider mockPersistence; @Mock WindowManager mockWindowing; @Mock MCTMutableTreeNode mockNode; @Mock PolicyManager mockPolicy; private TreePath[] selectedTreePaths; // Since JTree can't be mocked private Platform oldPlatform; // For policy support private ExecutionResult yes = new ExecutionResult(null, true, ""); private ExecutionResult no = new ExecutionResult(null, false, ""); private Set<AbstractComponent> canCompose = new HashSet<AbstractComponent>(); private Set<AbstractComponent> canDelete = new HashSet<AbstractComponent>(); private ContextAwareAction deleteAll; @BeforeClass public void cachePlatform() { oldPlatform = PlatformAccess.getPlatform(); } @AfterClass public void restorePlatform() { new PlatformAccess().setPlatform(oldPlatform); } @BeforeMethod public void setup() { MockitoAnnotations.initMocks(this); new PlatformAccess().setPlatform(mockPlatform); // Common setup Mockito.when(mockNode.getParentTree()) .thenReturn(new PseudoJTree()); Mockito.when(mockHousing.getSelectionProvider()) .thenReturn(mockSelection); Mockito.when(mockPlatform.getPolicyManager()) .thenReturn(mockPolicy); Mockito.when(mockPolicy.execute( Mockito.anyString(),Mockito.<PolicyContext>any())) .thenAnswer(new Answer<ExecutionResult>() { @Override public ExecutionResult answer(InvocationOnMock invocation) throws Throwable { Set<AbstractComponent> set = invocation.getArguments()[0].equals( PolicyInfo.CategoryType.COMPOSITION_POLICY_CATEGORY.getKey()) ? canCompose : canDelete; PolicyContext context = (PolicyContext) invocation.getArguments()[1]; return set.contains( context.getProperty( PolicyContext.PropertyName.TARGET_COMPONENT.getName(), AbstractComponent.class)) ? yes : no; } }); deleteAll = new DeleteAllAction(); } @Test (dataProvider="canHandleTestCases") public void testCanHandle( boolean hasHousing, Collection<View> selection, boolean hasDirectory, boolean hasFirstNode, TreePath[] treePaths, boolean expected ) { // Setup inputs Mockito.when(mockContext.getTargetHousing()) .thenReturn(hasHousing ? mockHousing : null); Mockito.when(mockHousing.getDirectoryArea()) .thenReturn(hasDirectory ? mockDirectory : null); Mockito.when(mockDirectory.getSelectedDirectoryNode()) .thenReturn(hasFirstNode ? mockNode : null); Mockito.when(mockSelection.getSelectedManifestations()) .thenReturn(selection); selectedTreePaths = treePaths; // Test can handle Assert.assertEquals( deleteAll.canHandle(mockContext), expected); } @Test (dataProvider = "isEnabledTestCases") public void testIsEnabled( boolean canComposeParent, boolean canDeleteParent, boolean[] canCompose, boolean[] canDelete, boolean expected ) { // Setup inputs Collection<View> selection = Arrays.asList(mockView(ViewType.NODE)); Mockito.when(mockContext.getTargetHousing()) .thenReturn(mockHousing); Mockito.when(mockHousing.getDirectoryArea()) .thenReturn(mockDirectory); Mockito.when(mockDirectory.getSelectedDirectoryNode()) .thenReturn(mockNode); Mockito.when(mockSelection.getSelectedManifestations()) .thenReturn(selection); // Setup selected nodes, including a parent int nodes = Math.max(canCompose.length, canDelete.length); selectedTreePaths = new TreePath[nodes]; AbstractComponent mockParent = mockComponent(canDeleteParent, canComposeParent); MCTMutableTreeNode mockParentNode = Mockito.mock(MCTMutableTreeNode.class); View mockParentView = mockView(mockParent, ViewType.NODE); Mockito.when(mockParentNode.getUserObject()).thenReturn(mockParentView); for (int i = 0; i < nodes; i++) { AbstractComponent mockChild = mockComponent(canDelete [i % canDelete.length ], canCompose[i % canCompose.length]); TreePath mockPath = Mockito.mock(TreePath.class); MCTMutableTreeNode mockTreeNode = Mockito.mock(MCTMutableTreeNode.class); Mockito.when(mockPath.getLastPathComponent()).thenReturn(mockTreeNode); View mockView = mockView(mockChild, ViewType.NODE); Mockito.when(mockTreeNode.getUserObject()).thenReturn(mockView); Mockito.when(mockTreeNode.getParent()).thenReturn(mockParentNode); selectedTreePaths[i] = mockPath; } // Verify precondition; obey ContextAwareAction life cycle Assert.assertTrue(deleteAll.canHandle(mockContext)); // Test isEnabled Assert.assertEquals(deleteAll.isEnabled(), expected); } @Test (dataProvider = "actionPerformedTestCases") public void testActionPerformed( Collection<AbstractComponent> selected, String choice, // User choice in dialog final Collection<AbstractComponent> toDelete // null implies no persistence call ) { // Setup inputs Collection<View> selection = Arrays.asList(mockView(ViewType.NODE)); Mockito.when(mockContext.getTargetHousing()) .thenReturn(mockHousing); Mockito.when(mockHousing.getDirectoryArea()) .thenReturn(mockDirectory); Mockito.when(mockDirectory.getSelectedDirectoryNode()) .thenReturn(mockNode); Mockito.when(mockSelection.getSelectedManifestations()) .thenReturn(selection); Mockito.when(mockPlatform.getPersistenceProvider()) .thenReturn(mockPersistence); Mockito.when(mockPlatform.getWindowManager()) .thenReturn(mockWindowing); // Setup selected nodes, including a parent selectedTreePaths = new TreePath[selected.size()]; AbstractComponent mockParent = mockComponent(false, true); MCTMutableTreeNode mockParentNode = Mockito.mock(MCTMutableTreeNode.class); View mockParentView = mockView(mockParent, ViewType.NODE); Mockito.when(mockParentNode.getUserObject()).thenReturn(mockParentView); int i = 0; for (AbstractComponent component : selected) { TreePath mockPath = Mockito.mock(TreePath.class); MCTMutableTreeNode mockTreeNode = Mockito.mock(MCTMutableTreeNode.class); Mockito.when(mockPath.getLastPathComponent()).thenReturn(mockTreeNode); View mockView = mockView(component, ViewType.NODE); Mockito.when(mockTreeNode.getUserObject()).thenReturn(mockView); Mockito.when(mockTreeNode.getParent()).thenReturn(mockParentNode); selectedTreePaths[i++] = mockPath; } // Ensure dialog choice Mockito.when(mockWindowing.<Object>showInputDialog( Mockito.anyString(), Mockito.anyString(), Mockito.<Object[]>any(), Mockito.any(), Mockito.<Map<String,Object>>any())) .thenReturn(choice); // Verify precondition; obey ContextAwareAction life cycle Assert.assertTrue(deleteAll.canHandle(mockContext)); // Verify precondition, obey life cycle Assert.assertEquals(deleteAll.isEnabled(), true); // Fire the action deleteAll.actionPerformed(Mockito.mock(ActionEvent.class)); // Verify expected consequential interactions Mockito.verify(mockWindowing, Mockito.times(toDelete != null ? toDelete.size() : 0)) .closeWindows(Mockito.anyString()); Mockito.verify(mockPersistence, Mockito.times(toDelete != null ? 1 : 0)) .delete(Mockito.argThat( new ArgumentMatcher<Collection<AbstractComponent>>() { @Override public boolean matches(Object argument) { if (argument instanceof Collection) { Collection c = (Collection) argument; return c.containsAll(toDelete) && toDelete.containsAll(c); } return false; } } )); } @DataProvider public Object[][] canHandleTestCases() { Collection<Object[]> testCases = new ArrayList<Object[]>(); boolean truths[] = {false, true}; Collection<View> validSelection = Arrays.asList(mockView(ViewType.NODE)); Collection<?>[] selections = { Collections.emptyList(), Arrays.asList(mockView(ViewType.OBJECT)), validSelection }; TreePath[][] treePaths = { null, {}, { Mockito.mock(TreePath.class) } }; // Permute possible for (boolean hasHousing : truths) { for (Collection<?> selection : selections) { for (boolean hasDirectory : truths) { for (boolean hasNode : truths) { for (TreePath[] treePath : treePaths) { // Based expectation on conditions boolean expected = hasHousing && (selection == validSelection) && hasDirectory && hasNode && (treePath != null) && (treePath.length != 0); testCases.add(new Object[]{ hasHousing, selection, hasDirectory, hasNode, treePath, expected }); } } } } } Object[][] result = new Object[testCases.size()][]; int i = 0; Iterator<Object[]> iter = testCases.iterator(); while (iter.hasNext()) { result[i++] = iter.next(); } return result; } @DataProvider public Object[][] isEnabledTestCases() { Collection<Object[]> testCases = new ArrayList<Object[]>(); boolean truths[] = { false, true }; // A variety of true/false arrangements to permute // Note: Arrays are treated as circular by test, // so {true} covers {true,true,true} boolean groups[][] = { {true}, {false}, {false, false, false, false, true}, {true, true, false} }; for (boolean canComposeParent : truths) { for (boolean canDeleteParent : truths) { for (boolean[] canCompose : groups) { for (boolean[] canDelete : groups) { // Should only care that parent can be composed, // and that all selected children can be deleted boolean expected = canComposeParent; for (boolean canDeleteChild : canDelete) { expected &= canDeleteChild; } testCases.add(new Object[] { canComposeParent, canDeleteParent, canCompose, canDelete, expected }); } } } } Object[][] result = new Object[testCases.size()][]; int i = 0; Iterator<Object[]> iter = testCases.iterator(); while (iter.hasNext()) { result[i++] = iter.next(); } return result; } @DataProvider public Object[][] actionPerformedTestCases() { Collection<Object[]> testCases = new ArrayList<Object[]>(); boolean[][] groups = { {true, true}, {true, false}, {false, true}, {false, false} }; boolean[] truths = { true, false }; // Some other parent, somewhere AbstractComponent mockParent = mockComponent(false, true); for (boolean last : truths) { // Whether or not comps are the last manifestations for (boolean[] group : groups) { // Assemble object graphs Collection<AbstractComponent> selected = new ArrayList<AbstractComponent>(); Collection<AbstractComponent> toDelete = new HashSet<AbstractComponent>(); for (int i = 0; i < group.length; i++) { AbstractComponent mockComponent = mockComponent(true, true); Mockito.when(mockComponent.getReferencingComponents()) .thenReturn(Arrays.asList(mockParent)); toDelete.add(mockComponent); selected.add(mockComponent); List<AbstractComponent> children = new ArrayList<AbstractComponent>(); for (int j = 0; j < group.length ; j++){ AbstractComponent mockChild = mockComponent(group[j], true); children.add(mockChild); if (group[j]) { toDelete.add(mockChild); } List<AbstractComponent> grandchildren = new ArrayList<AbstractComponent>(); for (int k = 0; k < group.length; k++) { AbstractComponent mockGrandchild = mockComponent(group[k], true); grandchildren.add(mockGrandchild); if (group[j] && group[k]) { toDelete.add(mockGrandchild); } Mockito.when(mockGrandchild.getReferencingComponents()) .thenReturn(last ? Arrays.asList(mockChild) : Arrays.asList(mockParent, mockChild)); } Mockito.when(mockChild.getComponents()) .thenReturn(grandchildren); Mockito.when(mockChild.getReferencingComponents()) .thenReturn(last ? Arrays.asList(mockComponent) : Arrays.asList(mockParent, mockComponent)); } Mockito.when(mockComponent.getComponents()) .thenReturn(children); } // Can only delete if everything is deletable, // or there are no last manifestations. boolean canDelete = true; for (boolean delete : group) { if (!delete) { canDelete = !last; } } // Exercise different choices for input dialog for (String choice : new String[] {CONFIRM, ABORT, OK}) { if (choice.equals(ABORT) || !canDelete) { toDelete = null; } testCases.add(new Object[]{selected,choice,toDelete}); } } } Object[][] result = new Object[testCases.size()][]; int i = 0; Iterator<Object[]> iter = testCases.iterator(); while (iter.hasNext()) { result[i++] = iter.next(); } return result; } private static int counter = 0; private AbstractComponent mockComponent(boolean canBeDeleted, boolean canBeComposed) { final AbstractComponent mockComponent = Mockito.mock(AbstractComponent.class); Mockito.when(mockComponent.getComponents()) .thenReturn(Collections.<AbstractComponent>emptyList()); Mockito.when(mockComponent.getComponentId()) .thenReturn("mock" + counter++); if (canBeDeleted) { canDelete.add(mockComponent); } if (canBeComposed) { canCompose.add(mockComponent); } return mockComponent; } private View mockView(ViewType vt) { return mockView( Mockito.mock(AbstractComponent.class), vt ); } private View mockView(AbstractComponent ac, ViewType vt) { View view = Mockito.mock(View.class); ViewInfo viewInfo = Mockito.mock(ViewInfo.class); Mockito.when(viewInfo.getViewType()).thenReturn(vt); Mockito.when(view.getInfo()).thenReturn(viewInfo); Mockito.when(view.getManifestedComponent()).thenReturn(ac); return view; } private class PseudoJTree extends JTree { private static final long serialVersionUID = 1L; @Override public TreePath[] getSelectionPaths() { return selectedTreePaths; } } }