/******************************************************************************* * Copyright (c) 2008-2011 Chair for Applied Software Engineering, * Technische Universitaet Muenchen. * 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: * chodnick ******************************************************************************/ package org.eclipse.emf.emfstore.client.changetracking.test.canonization; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.IOException; import java.util.List; import org.eclipse.emf.emfstore.client.test.common.cases.ComparingESTest; import org.eclipse.emf.emfstore.client.test.common.dsl.Create; import org.eclipse.emf.emfstore.internal.client.model.CompositeOperationHandle; import org.eclipse.emf.emfstore.internal.client.model.exceptions.InvalidHandleException; import org.eclipse.emf.emfstore.internal.client.model.util.EMFStoreCommand; import org.eclipse.emf.emfstore.internal.common.model.Project; import org.eclipse.emf.emfstore.internal.common.model.util.ModelUtil; import org.eclipse.emf.emfstore.internal.server.model.versioning.operations.AbstractOperation; import org.eclipse.emf.emfstore.internal.server.model.versioning.operations.AttributeOperation; import org.eclipse.emf.emfstore.internal.server.model.versioning.operations.CompositeOperation; import org.eclipse.emf.emfstore.internal.server.model.versioning.operations.CreateDeleteOperation; import org.eclipse.emf.emfstore.internal.server.model.versioning.operations.MultiReferenceOperation; import org.eclipse.emf.emfstore.internal.server.model.versioning.operations.util.OperationsCanonizer; import org.eclipse.emf.emfstore.test.model.TestElement; import org.junit.Test; /** * Tests canonization of attribute operations. * * @author chodnick */ public class AttributeTest extends ComparingESTest { private static final String SOME_NAME = "someName"; //$NON-NLS-1$ private static final String SOME_NEW_NAME = "some new Name"; //$NON-NLS-1$ private static final String ORIGINAL_DESCRIPTION2 = "originalDescription2"; //$NON-NLS-1$ private static final String ORIGINAL_NAME2 = "originalName2"; //$NON-NLS-1$ private static final String ORIGINAL_DESCRIPTION1 = "originalDescription1"; //$NON-NLS-1$ private static final String ORIGINAL_NAME1 = "originalName1"; //$NON-NLS-1$ private static final String ORIGINAL_DESCRIPTION = "originalDescription"; //$NON-NLS-1$ private static final String ORIGINAL_NAME = "originalName"; //$NON-NLS-1$ private static final String DESCRIPTION_OF_TEST_ELEMENT2 = "DescriptionOfTestElement2"; //$NON-NLS-1$ private static final String NAME_OF_TEST_ELEMENT2 = "NameOfTestElement2"; //$NON-NLS-1$ private static final String DESCRIPTION_OF_TEST_ELEMENT = "DescriptionOfTestElement"; //$NON-NLS-1$ private static final String NAME_OF_TEST_ELEMENT = "NameOfTestElement"; //$NON-NLS-1$ private static final String DESC_2 = "desc 2"; //$NON-NLS-1$ private static final String DESCRIPTION2 = "description"; //$NON-NLS-1$ private static final String FINAL_DESC = "final desc"; //$NON-NLS-1$ private static final String SOME_OTHER_DESC = "some other desc"; //$NON-NLS-1$ private static final String SOME_DESC = "some desc";//$NON-NLS-1$ private static final String OLD_SECTION = "oldSection";//$NON-NLS-1$ private static final String HOME = "home";//$NON-NLS-1$ private static final String MAGGIE = "maggie";//$NON-NLS-1$ private static final String HOMER = "homer";//$NON-NLS-1$ private static final String SOME_SECTION = "some section";//$NON-NLS-1$ private static final String Y_NAME = "Y";//$NON-NLS-1$ private static final String X_NAME = "X";//$NON-NLS-1$ private static final String SECTION_CREATION = "sectionCreation";//$NON-NLS-1$ private static final String DESC_1 = "desc 1";//$NON-NLS-1$ private static final String NAME = "Name";//$NON-NLS-1$ private static final String NEW_DESCRIPTION = "newDescription";//$NON-NLS-1$ private static final String OLD_DESCRIPTION = "oldDescription";//$NON-NLS-1$ private static final String A_NAME = "A"; //$NON-NLS-1$ private static final String B_NAME = "B"; //$NON-NLS-1$ private static final String C_NAME = "C"; //$NON-NLS-1$ private static final String NEW_NAME = "newName"; //$NON-NLS-1$ private static final String OLD_NAME = "oldName"; //$NON-NLS-1$ /** * Tests canonization for consecutive attribute changes on a single feature. * * @throws IOException */ @Test public void consecutiveAttributeChangeSingleFeature() throws IOException { final TestElement useCase = Create.testElement(); new EMFStoreCommand() { @Override protected void doRun() { getProject().addModelElement(useCase); useCase.setName(OLD_NAME); } }.run(false); final Project expectedProject = ModelUtil.clone(getProject()); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); new EMFStoreCommand() { @Override protected void doRun() { clearOperations(); useCase.setName(A_NAME); useCase.setName(B_NAME); useCase.setName(C_NAME); useCase.setName(NEW_NAME); } }.run(false); assertEquals(NEW_NAME, useCase.getName()); assertEquals(4, forceGetOperations().size()); final List<AbstractOperation> operations = forceGetOperations(); new EMFStoreCommand() { @Override protected void doRun() { OperationsCanonizer.canonize(operations); } }.run(false); assertEquals(operations.size(), 1); final AttributeOperation reverse = (AttributeOperation) operations.get(0).reverse(); new EMFStoreCommand() { @Override protected void doRun() { reverse.apply(getProject()); } }.run(false); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); } /** * Tests canonization for consecutive attribute changes on a single feature. * * @throws IOException */ @Test public void consecutiveAttributeChangeSingleFeatureToNull() throws IOException { final TestElement useCase = Create.testElement(); new EMFStoreCommand() { @Override protected void doRun() { getProject().addModelElement(useCase); useCase.setName(OLD_NAME); } }.run(false); final Project expectedProject = ModelUtil.clone(getProject()); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); new EMFStoreCommand() { @Override protected void doRun() { clearOperations(); useCase.setName(A_NAME); useCase.setName(B_NAME); useCase.setName(C_NAME); useCase.setName(null); } }.run(false); assertEquals(null, useCase.getName()); final List<AbstractOperation> operations = forceGetOperations(); new EMFStoreCommand() { @Override protected void doRun() { OperationsCanonizer.canonize(operations); } }.run(false); assertEquals(operations.size(), 1); final AttributeOperation reverse = (AttributeOperation) operations.get(0).reverse(); new EMFStoreCommand() { @Override protected void doRun() { reverse.apply(getProject()); } }.run(false); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); } /** * Tests canonization for consecutive attribute changes on a single feature. * * @throws IOException */ @Test public void consecutiveAttributeChangeSingleFeatureNullToValue() throws IOException { final TestElement useCase = Create.testElement(); new EMFStoreCommand() { @Override protected void doRun() { getProject().addModelElement(useCase); useCase.setName(null); } }.run(false); final Project expectedProject = ModelUtil.clone(getProject()); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); new EMFStoreCommand() { @Override protected void doRun() { clearOperations(); useCase.setName(A_NAME); useCase.setName(B_NAME); useCase.setName(C_NAME); } }.run(false); assertEquals(C_NAME, useCase.getName()); final List<AbstractOperation> operations = forceGetOperations(); assertEquals(3, operations.size()); new EMFStoreCommand() { @Override protected void doRun() { OperationsCanonizer.canonize(operations); } }.run(false); assertEquals(operations.size(), 1); final AttributeOperation reverse = (AttributeOperation) operations.get(0).reverse(); new EMFStoreCommand() { @Override protected void doRun() { reverse.apply(getProject()); } }.run(false); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); } /** * Tests canonization for consecutive attribute changes, resulting in a noop. * * @throws IOException */ @Test public void attributeChangeNoOp() throws IOException { final TestElement useCase = Create.testElement(); new EMFStoreCommand() { @Override protected void doRun() { getProject().addModelElement(useCase); useCase.setName(OLD_NAME); } }.run(false); final Project expectedProject = ModelUtil.clone(getProject()); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); new EMFStoreCommand() { @Override protected void doRun() { clearOperations(); useCase.setName(A_NAME); useCase.setName(B_NAME); useCase.setName(C_NAME); useCase.setName(OLD_NAME); } }.run(false); assertEquals(OLD_NAME, useCase.getName()); final List<AbstractOperation> operations = forceGetOperations(); assertEquals(4, operations.size()); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); canonize(operations); // should not have created any operations, we were just resetting the name to its original value assertEquals(operations.size(), 0); } /** * Tests canonization for consecutive attribute changes, resulting in a noop. * * @throws IOException */ @Test public void attributeChangeNoOpNull() throws IOException { final TestElement useCase = Create.testElement(); new EMFStoreCommand() { @Override protected void doRun() { getProject().addModelElement(useCase); useCase.setName(null); } }.run(false); final Project expectedProject = ModelUtil.clone(getProject()); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); new EMFStoreCommand() { @Override protected void doRun() { clearOperations(); useCase.setName(A_NAME); useCase.setName(B_NAME); useCase.setName(C_NAME); useCase.setName(null); } }.run(false); assertEquals(null, useCase.getName()); final List<AbstractOperation> operations = forceGetOperations(); assertEquals(4, operations.size()); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); new EMFStoreCommand() { @Override protected void doRun() { OperationsCanonizer.canonize(operations); } }.run(false); // should not have created any operations, we were just resetting the name to its original value assertEquals(operations.size(), 0); } /** * Tests canonization for consecutive attribute changes, resulting in a noop. * * @throws IOException */ @Test public void attributeChangeMultiFeatureNoOp() throws IOException { final TestElement useCase = Create.testElement(); new EMFStoreCommand() { @Override protected void doRun() { getProject().addModelElement(useCase); useCase.setName(OLD_NAME); useCase.setDescription(OLD_DESCRIPTION); } }.run(false); final Project expectedProject = ModelUtil.clone(getProject()); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); new EMFStoreCommand() { @Override protected void doRun() { clearOperations(); useCase.setName(A_NAME); useCase.setDescription(X_NAME); useCase.setName(B_NAME); useCase.setDescription(Y_NAME); useCase.setName(C_NAME); useCase.setDescription(OLD_DESCRIPTION); useCase.setName(OLD_NAME); } }.run(false); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); final List<AbstractOperation> operations = forceGetOperations(); assertEquals(7, operations.size()); canonize(operations); // should not have created any operations, we were just resetting everything to its original value assertEquals(operations.size(), 0); } /** * Tests canonization for consecutive attribute changes on multiple features. * * @throws IOException */ @Test public void consecutiveAttributeChangeMultiFeature() throws IOException { final TestElement useCase = Create.testElement(); new EMFStoreCommand() { @Override protected void doRun() { getProject().addModelElement(useCase); useCase.setName(OLD_NAME); } }.run(false); final Project expectedProject = ModelUtil.clone(getProject()); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); new EMFStoreCommand() { @Override protected void doRun() { clearOperations(); useCase.setName(A_NAME); useCase.setDescription(OLD_DESCRIPTION); useCase.setName(B_NAME); useCase.setName(C_NAME); useCase.setDescription(NEW_DESCRIPTION); useCase.setName(NEW_NAME); } }.run(false); assertEquals(NEW_NAME, useCase.getName()); final List<AbstractOperation> operations = forceGetOperations(); assertEquals(6, operations.size()); new EMFStoreCommand() { @Override protected void doRun() { OperationsCanonizer.canonize(operations); } }.run(false); assertEquals(operations.size(), 2); new EMFStoreCommand() { @Override protected void doRun() { for (int i = operations.size() - 1; i >= 0; i--) { final AbstractOperation reverse = operations.get(i).reverse(); reverse.apply(getProject()); } } }.run(false); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); } /** * Tests canonization for mixed attribute changes on a single feature. * * @throws IOException */ @Test public void mixedAttributeChangeSingleFeature() throws IOException { final TestElement useCase = Create.testElement(); final TestElement actor = Create.testElement(); final TestElement section = Create.testElement(); new EMFStoreCommand() { @Override protected void doRun() { getProject().addModelElement(useCase); getProject().addModelElement(actor); getProject().addModelElement(section); useCase.setName(OLD_NAME); section.setName(SOME_SECTION); actor.setName(HOMER); } }.run(false); final Project expectedProject = ModelUtil.clone(getProject()); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); new EMFStoreCommand() { @Override protected void doRun() { clearOperations(); useCase.setName(A_NAME); actor.setName(MAGGIE); useCase.setName(B_NAME); useCase.setNonContained_NTo1(actor); useCase.setName(C_NAME); section.setName(HOME); useCase.setName(NEW_NAME); } }.run(false); final List<AbstractOperation> operations = forceGetOperations(); assertEquals(7, operations.size()); new EMFStoreCommand() { @Override protected void doRun() { OperationsCanonizer.canonize(operations); } }.run(false); new EMFStoreCommand() { @Override protected void doRun() { for (int i = operations.size() - 1; i >= 0; i--) { final AbstractOperation reverse = operations.get(i).reverse(); reverse.apply(getProject()); } } }.run(false); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); } /** * Tests canonization for mixed attribute changes on a single feature. * * @throws IOException */ @Test public void mixedAttributeChangeMultiFeature() throws IOException { final TestElement useCase = Create.testElement(); final TestElement actor = Create.testElement(); final TestElement section = Create.testElement(); final TestElement oldSection = Create.testElement(); new EMFStoreCommand() { @Override protected void doRun() { getProject().addModelElement(useCase); getProject().addModelElement(actor); getProject().addModelElement(section); getProject().addModelElement(oldSection); useCase.setContainer(oldSection); actor.setContainer(oldSection); useCase.setName(OLD_NAME); oldSection.setName(OLD_SECTION); section.setName(SOME_SECTION); actor.setName(HOMER); } }.run(false); final Project expectedProject = ModelUtil.clone(getProject()); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); new EMFStoreCommand() { @Override protected void doRun() { clearOperations(); useCase.setName(A_NAME); actor.setName(MAGGIE); useCase.setName(B_NAME); useCase.setDescription(SOME_DESC); useCase.setNonContained_NTo1(actor); useCase.setName(C_NAME); section.setName(HOME); useCase.setDescription(SOME_OTHER_DESC); useCase.setName(NEW_NAME); useCase.setDescription(FINAL_DESC); } }.run(false); final List<AbstractOperation> operations = forceGetOperations(); assertEquals(NEW_NAME, useCase.getName()); assertEquals(FINAL_DESC, useCase.getDescription()); assertEquals(HOME, section.getName()); assertEquals(MAGGIE, actor.getName()); new EMFStoreCommand() { @Override protected void doRun() { OperationsCanonizer.canonize(operations); } }.run(false); new EMFStoreCommand() { @Override protected void doRun() { for (int i = operations.size() - 1; i >= 0; i--) { final AbstractOperation reverse = operations.get(i).reverse(); reverse.apply(getProject()); } } }.run(false); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); } /** * Test the creation and completion of a composite operation, that contains attribute changes. * * @throws InvalidHandleException if the test fails * @throws IOException */ @Test public void compositeAttributeChangesACA() throws InvalidHandleException, IOException { final TestElement section = Create.testElement(); new EMFStoreCommand() { @Override protected void doRun() { getProject().addModelElement(section); section.setName(NAME); section.setDescription(OLD_DESCRIPTION); } }.run(false); final Project expectedProject = ModelUtil.clone(getProject()); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); new EMFStoreCommand() { @Override protected void doRun() { clearOperations(); section.setDescription(DESC_1); final CompositeOperationHandle handle = getProjectSpace().beginCompositeOperation(); section.setDescription(NEW_DESCRIPTION); final TestElement useCase = Create.testElement(); section.getContainedElements().add(useCase); try { handle.end(SECTION_CREATION, DESCRIPTION2, ModelUtil.getProject(section).getModelElementId(section)); } catch (final InvalidHandleException e) { fail(); } section.setDescription(DESC_2); } }.run(false); final List<AbstractOperation> operations = forceGetOperations(); assertEquals(DESC_2, section.getDescription()); assertEquals(3, operations.size()); new EMFStoreCommand() { @Override protected void doRun() { OperationsCanonizer.canonize(operations); } }.run(false); new EMFStoreCommand() { @Override protected void doRun() { for (int i = operations.size() - 1; i >= 0; i--) { final AbstractOperation reverse = operations.get(i).reverse(); reverse.apply(getProject()); } } }.run(false); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); } /** * Test the creation and completion of a composite operation, that contains attribute changes. * * @throws InvalidHandleException if the test fails * @throws IOException */ @Test public void compositeAttributeChangesAC() throws InvalidHandleException, IOException { final TestElement section = Create.testElement(); new EMFStoreCommand() { @Override protected void doRun() { getProject().addModelElement(section); section.setName(NAME); section.setDescription(OLD_DESCRIPTION); } }.run(false); final Project expectedProject = ModelUtil.clone(getProject()); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); new EMFStoreCommand() { @Override protected void doRun() { clearOperations(); section.setDescription(DESC_1); final CompositeOperationHandle handle = getProjectSpace().beginCompositeOperation(); section.setDescription(NEW_DESCRIPTION); final TestElement useCase = Create.testElement(); section.getContainedElements().add(useCase); try { handle.end(SECTION_CREATION, DESCRIPTION2, ModelUtil.getProject(section).getModelElementId(section)); } catch (final InvalidHandleException e) { fail(); } } }.run(false); final List<AbstractOperation> operations = forceGetOperations(); assertEquals(2, operations.size()); assertEquals(NEW_DESCRIPTION, section.getDescription()); new EMFStoreCommand() { @Override protected void doRun() { OperationsCanonizer.canonize(operations); } }.run(false); new EMFStoreCommand() { @Override protected void doRun() { for (int i = operations.size() - 1; i >= 0; i--) { final AbstractOperation reverse = operations.get(i).reverse(); reverse.apply(getProject()); } } }.run(false); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); } /** * Test the creation and completion of a composite operation, that contains attribute changes. * * @throws InvalidHandleException if the test fails * @throws IOException */ @Test public void compositeAttributeChangesCA() throws InvalidHandleException, IOException { final TestElement section = Create.testElement(); new EMFStoreCommand() { @Override protected void doRun() { getProject().addModelElement(section); section.setName(NAME); section.setDescription(OLD_DESCRIPTION); } }.run(false); final Project expectedProject = ModelUtil.clone(getProject()); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); new EMFStoreCommand() { @Override protected void doRun() { clearOperations(); final CompositeOperationHandle handle = getProjectSpace().beginCompositeOperation(); section.setDescription(NEW_DESCRIPTION); final TestElement useCase = Create.testElement(); section.getContainedElements().add(useCase); try { handle.end(SECTION_CREATION, DESCRIPTION2, ModelUtil.getProject(section).getModelElementId(section)); } catch (final InvalidHandleException e) { fail(); } section.setDescription(DESC_2); } }.run(false); final List<AbstractOperation> operations = forceGetOperations(); assertEquals(2, operations.size()); new EMFStoreCommand() { @Override protected void doRun() { OperationsCanonizer.canonize(operations); } }.run(false); new EMFStoreCommand() { @Override protected void doRun() { for (int i = operations.size() - 1; i >= 0; i--) { final AbstractOperation reverse = operations.get(i).reverse(); reverse.apply(getProject()); } } }.run(false); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); } /** * Tests canonization for create and consecutive attribute changes. * * @throws IOException */ @Test public void createAndChangeAttributesSimple() throws IOException { final Project originalProject = ModelUtil.clone(getProject()); final TestElement useCase = Create.testElement(); new EMFStoreCommand() { @Override protected void doRun() { getProject().addModelElement(useCase); useCase.setName(NAME_OF_TEST_ELEMENT); useCase.setDescription(DESCRIPTION_OF_TEST_ELEMENT); } }.run(false); assertEquals(NAME_OF_TEST_ELEMENT, useCase.getName()); final List<AbstractOperation> operations = forceGetOperations(); // expecting a create and two attribute operations assertEquals(3, operations.size()); new EMFStoreCommand() { @Override protected void doRun() { OperationsCanonizer.canonize(operations); } }.run(false); // now expecting only the create with folded in attributes assertEquals(operations.size(), 1); assertTrue(operations.get(0) instanceof CreateDeleteOperation); final CreateDeleteOperation op = (CreateDeleteOperation) operations.get(0); assertEquals(((TestElement) op.getModelElement()).getName(), NAME_OF_TEST_ELEMENT); assertEquals(((TestElement) op.getModelElement()).getDescription(), DESCRIPTION_OF_TEST_ELEMENT); // test if the create is reversible and re-reversible final Project expectedProject = ModelUtil.clone(getProject()); new EMFStoreCommand() { @Override protected void doRun() { op.reverse().apply(getProject()); } }.run(false); assertTrue(ModelUtil.areEqual(getProject(), originalProject)); new EMFStoreCommand() { @Override protected void doRun() { op.reverse().reverse().apply(getProject()); } }.run(false); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); } /** * Tests canonization for create and consecutive attribute changes. * * @throws IOException */ @Test public void createAndChangeAttributesComplex() throws IOException { final Project originalProject = ModelUtil.clone(getProject()); final TestElement useCase = Create.testElement(); final TestElement useCase2 = Create.testElement(); new EMFStoreCommand() { @Override protected void doRun() { getProject().addModelElement(useCase); getProject().addModelElement(useCase2); useCase.setName(NAME_OF_TEST_ELEMENT); useCase.setDescription(DESCRIPTION_OF_TEST_ELEMENT); useCase2.setName(NAME_OF_TEST_ELEMENT2); useCase2.setDescription(DESCRIPTION_OF_TEST_ELEMENT2); } }.run(false); assertEquals(NAME_OF_TEST_ELEMENT, useCase.getName()); assertEquals(NAME_OF_TEST_ELEMENT2, useCase2.getName()); final List<AbstractOperation> operations = forceGetOperations(); // expecting a create and two attribute operations per usecase assertEquals(6, operations.size()); new EMFStoreCommand() { @Override protected void doRun() { OperationsCanonizer.canonize(operations); } }.run(false); // now expecting only the creates with folded in attributes assertEquals(2, operations.size()); assertTrue(operations.get(0) instanceof CreateDeleteOperation); final CreateDeleteOperation op = (CreateDeleteOperation) operations.get(0); assertEquals(((TestElement) op.getModelElement()).getName(), NAME_OF_TEST_ELEMENT); assertEquals(((TestElement) op.getModelElement()).getDescription(), DESCRIPTION_OF_TEST_ELEMENT); assertTrue(operations.get(1) instanceof CreateDeleteOperation); final CreateDeleteOperation op2 = (CreateDeleteOperation) operations.get(1); assertEquals(((TestElement) op2.getModelElement()).getName(), NAME_OF_TEST_ELEMENT2); assertEquals(((TestElement) op2.getModelElement()).getDescription(), DESCRIPTION_OF_TEST_ELEMENT2); // test reversibility, too new EMFStoreCommand() { @Override protected void doRun() { op2.reverse().apply(getProject()); op.reverse().apply(getProject()); } }.run(false); assertTrue(ModelUtil.areEqual(getProject(), originalProject)); } /** * Test the creation and completion of a composite operation, that contains attribute changes. * * @throws InvalidHandleException if the test fails * @throws IOException */ @Test public void createAndAttributeChangesACA() throws InvalidHandleException, IOException { final Project originalProject = ModelUtil.clone(getProject()); final TestElement section = Create.testElement(); new EMFStoreCommand() { @Override protected void doRun() { getProject().addModelElement(section); section.setName(NAME); section.setDescription(OLD_DESCRIPTION); final CompositeOperationHandle handle = getProjectSpace().beginCompositeOperation(); section.setDescription(NEW_DESCRIPTION); final TestElement useCase = Create.testElement(); section.getContainedElements().add(useCase); try { handle.end(SECTION_CREATION, DESCRIPTION2, ModelUtil.getProject(section).getModelElementId(section)); } catch (final InvalidHandleException e) { fail(); } section.setDescription(DESC_2); } }.run(false); final List<AbstractOperation> operations = forceGetOperations(); // expect create, 2 attribute ops, the composite, 1 attribute op assertEquals(5, operations.size()); assertTrue(operations.get(0) instanceof CreateDeleteOperation); assertTrue(operations.get(1) instanceof AttributeOperation); assertTrue(operations.get(2) instanceof AttributeOperation); assertTrue(operations.get(3) instanceof CompositeOperation); assertTrue(operations.get(4) instanceof AttributeOperation); new EMFStoreCommand() { @Override protected void doRun() { OperationsCanonizer.canonize(operations); } }.run(false); // expect create, the composite and 1 attribute op assertEquals(3, operations.size()); assertTrue(operations.get(0) instanceof CreateDeleteOperation); assertTrue(operations.get(1) instanceof CompositeOperation); assertTrue(operations.get(2) instanceof AttributeOperation); final Project expectedProject = ModelUtil.clone(getProject()); // test reversibility new EMFStoreCommand() { @Override protected void doRun() { for (int i = operations.size() - 1; i >= 0; i--) { final AbstractOperation reverse = operations.get(i).reverse(); reverse.apply(getProject()); } } }.run(false); assertTrue(ModelUtil.areEqual(getProject(), originalProject)); // test redo new EMFStoreCommand() { @Override protected void doRun() { operations.get(0).apply(getProject()); operations.get(1).apply(getProject()); operations.get(2).apply(getProject()); } }.run(false); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); } /** * Tests canonization for consecutive attribute changes followed by a delete. * * @throws IOException */ @Test public void changeAttributesAndDeleteSimple() throws IOException { final TestElement useCase = Create.testElement(); new EMFStoreCommand() { @Override protected void doRun() { getProject().addModelElement(useCase); useCase.setName(ORIGINAL_NAME); useCase.setDescription(ORIGINAL_DESCRIPTION); } }.run(false); clearOperations(); assertTrue(getProjectSpace().getLocalChangePackage().isEmpty()); assertTrue(forceGetOperations().isEmpty()); final Project originalProject = ModelUtil.clone(getProject()); new EMFStoreCommand() { @Override protected void doRun() { useCase.setName(NAME_OF_TEST_ELEMENT); useCase.setDescription(DESCRIPTION_OF_TEST_ELEMENT); getProject().deleteModelElement(useCase); } }.run(false); final List<AbstractOperation> operations = forceGetOperations(); // expecting two attribute operations and a delete assertEquals(3, operations.size()); new EMFStoreCommand() { @Override protected void doRun() { OperationsCanonizer.canonize(operations); } }.run(false); // now expecting only the delete with folded in attributes assertEquals(1, operations.size()); assertTrue(operations.get(0) instanceof CreateDeleteOperation); final CreateDeleteOperation op = (CreateDeleteOperation) operations.get(0); assertTrue(op.isDelete()); assertEquals(((TestElement) op.getModelElement()).getName(), ORIGINAL_NAME); assertEquals(((TestElement) op.getModelElement()).getDescription(), ORIGINAL_DESCRIPTION); // test if the delete is reversible and re-reversible final Project expectedProject = ModelUtil.clone(getProject()); new EMFStoreCommand() { @Override protected void doRun() { op.reverse().apply(getProject()); } }.run(false); assertTrue(ModelUtil.areEqual(getProject(), originalProject)); new EMFStoreCommand() { @Override protected void doRun() { op.reverse().reverse().apply(getProject()); } }.run(false); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); } /** * Tests canonization for consecutive attribute changes and delete. * * @throws IOException */ @Test public void changeAttributesAndDeleteComplex() throws IOException { final TestElement useCase = Create.testElement(); final TestElement useCase2 = Create.testElement(); final TestElement section = Create.testElement(); new EMFStoreCommand() { @Override protected void doRun() { getProject().addModelElement(section); section.getContainedElements().add(useCase); section.getContainedElements().add(useCase2); useCase.setName(ORIGINAL_NAME1); useCase.setDescription(ORIGINAL_DESCRIPTION1); useCase2.setName(ORIGINAL_NAME2); useCase2.setDescription(ORIGINAL_DESCRIPTION2); } }.run(false); clearOperations(); final Project originalProject = ModelUtil.clone(getProject()); new EMFStoreCommand() { @Override protected void doRun() { useCase.setName(NAME_OF_TEST_ELEMENT); useCase.setDescription(DESCRIPTION_OF_TEST_ELEMENT); useCase2.setName(NAME_OF_TEST_ELEMENT2); useCase2.setDescription(DESCRIPTION_OF_TEST_ELEMENT2); assertEquals(NAME_OF_TEST_ELEMENT, useCase.getName()); assertEquals(NAME_OF_TEST_ELEMENT2, useCase2.getName()); getProject().deleteModelElement(useCase); } }.run(false); new EMFStoreCommand() { @Override protected void doRun() { getProject().deleteModelElement(useCase2); } }.run(false); final List<AbstractOperation> operations = forceGetOperations(); // expecting two attribute operations and a delete per usecase assertEquals(6, operations.size()); new EMFStoreCommand() { @Override protected void doRun() { OperationsCanonizer.canonize(operations); } }.run(false); // now expecting only the deletes with folded in attributes assertEquals(2, operations.size()); assertTrue(operations.get(0) instanceof CreateDeleteOperation); final CreateDeleteOperation op = (CreateDeleteOperation) operations.get(0); assertEquals(ORIGINAL_NAME1, ((TestElement) op.getModelElement()).getName()); assertEquals(ORIGINAL_DESCRIPTION1, ((TestElement) op.getModelElement()).getDescription()); assertTrue(operations.get(1) instanceof CreateDeleteOperation); final CreateDeleteOperation op2 = (CreateDeleteOperation) operations.get(1); assertEquals(((TestElement) op2.getModelElement()).getName(), ORIGINAL_NAME2); assertEquals(((TestElement) op2.getModelElement()).getDescription(), ORIGINAL_DESCRIPTION2); // test reversibility, too new EMFStoreCommand() { @Override protected void doRun() { op2.reverse().apply(getProject()); op.reverse().apply(getProject()); } }.run(false); assertTrue(ModelUtil.areEqual(getProject(), originalProject)); } /** * Tests canonization for consecutive attribute changes and delete on orphans. */ // commented out, orphan behaviour is irrelevant at present. This reversibility test currently fails. // @Test // public void changeAttributesAndDeleteOrphansComplex() { // // TestElement useCase = Create.testElement(); // TestElement useCase2 = Create.testElement(); // // getProject().getModelElements().add(useCase); // getProject().getModelElements().add(useCase2); // // useCase.setName("originalName1"); // useCase.setDescription("originalDescription1"); // // useCase2.setName("originalName2"); // useCase2.setDescription("originalDescription2"); // // Project originalProject = ModelUtil.clone(getProject()); // // clearOperations(); // // useCase.setName("NameOfTestElement"); // useCase.setDescription("DescriptionOfTestElement"); // // useCase2.setName("NameOfTestElement2"); // useCase2.setDescription("DescriptionOfTestElement2"); // // assertEquals("NameOfTestElement", useCase.getName()); // assertEquals("NameOfTestElement2", useCase2.getName()); // // getProject().deleteModelElement(useCase); // getProject().deleteModelElement(useCase2); // // List<AbstractOperation> operations = forceGetOperations(); // // // expecting two attribute operations and a delete per usecase // assertEquals(operations.size(), 6); // OperationsCanonizer.canonize(operations); // // // now expecting only the deletes with folded in attributes // assertEquals(operations.size(), 2); // assertTrue(operations.get(0) instanceof CreateDeleteOperation); // // CreateDeleteOperation op = (CreateDeleteOperation) operations.get(0); // // assertEquals(op.getModelElement().getName(), "originalName1"); // assertEquals(op.getModelElement().getDescription(), "originalDescription1"); // // assertTrue(operations.get(1) instanceof CreateDeleteOperation); // // CreateDeleteOperation op2 = (CreateDeleteOperation) operations.get(1); // // assertEquals(op2.getModelElement().getName(), "originalName2"); // assertEquals(op2.getModelElement().getDescription(), "originalDescription2"); // // // test reversibility, too // // op2.reverse().apply(getProject()); // op.reverse().apply(getProject()); // // assertTrue(ModelUtil.areEqual(getProject(), originalProject)); // // } /** * Test the creation and completion of a composite operation, that contains attribute changes. * * @throws InvalidHandleException if the test fails * @throws IOException */ // BEGIN COMPLEX CODE @Test public void attributeChangesACAAndDelete() throws InvalidHandleException, IOException { final TestElement section = Create.testElement(); new EMFStoreCommand() { @Override protected void doRun() { getProject().addModelElement(section); section.setName(ORIGINAL_NAME); section.setDescription(ORIGINAL_DESCRIPTION); } }.run(false); final Project originalProject = ModelUtil.clone(getProject()); new EMFStoreCommand() { @Override protected void doRun() { clearOperations(); section.setName(SOME_NEW_NAME); final CompositeOperationHandle handle = getProjectSpace().beginCompositeOperation(); section.setDescription(NEW_DESCRIPTION); final TestElement useCase = Create.testElement(); section.getContainedElements().add(useCase); try { handle.end(SECTION_CREATION, DESCRIPTION2, ModelUtil.getProject(section).getModelElementId(section)); } catch (final InvalidHandleException e) { fail(); } section.setDescription(DESC_2); getProject().deleteModelElement(section); } }.run(false); final List<AbstractOperation> operations = forceGetOperations(); // expect 1 attribute op, the composite, 1 attribute op, the delete assertEquals(4, operations.size()); assertTrue(operations.get(0) instanceof AttributeOperation); assertTrue(operations.get(1) instanceof CompositeOperation); assertTrue(operations.get(2) instanceof AttributeOperation); assertTrue(operations.get(3) instanceof CreateDeleteOperation); new EMFStoreCommand() { @Override protected void doRun() { OperationsCanonizer.canonize(operations); } }.run(false); // expect 1 attribute op, the composite and the delete with folded in attribute assertEquals(3, operations.size()); assertTrue(operations.get(0) instanceof AttributeOperation); assertTrue(operations.get(1) instanceof CompositeOperation); assertTrue(operations.get(2) instanceof CreateDeleteOperation); final CreateDeleteOperation delOp = (CreateDeleteOperation) operations.get(2); assertTrue(delOp.isDelete()); // not folded, interfering composite was inbeetween assertEquals(SOME_NEW_NAME, ((TestElement) delOp.getModelElement()).getName()); // folded, value is oldValue from "newDescription"-> "desc 2" assertEquals(NEW_DESCRIPTION, ((TestElement) delOp.getModelElement()).getDescription()); final Project expectedProject = ModelUtil.clone(getProject()); // test reversibility new EMFStoreCommand() { @Override protected void doRun() { for (int i = operations.size() - 1; i >= 0; i--) { final AbstractOperation reverse = operations.get(i).reverse(); reverse.apply(getProject()); } } }.run(false); assertTrue(ModelUtil.areEqual(getProject(), originalProject)); // test redo new EMFStoreCommand() { @Override protected void doRun() { operations.get(0).apply(getProject()); operations.get(1).apply(getProject()); operations.get(2).apply(getProject()); } }.run(false); assertTrue(ModelUtil.areEqual(getProject(), expectedProject)); } // END COMPLEX CODE /** * Tests canonization for create, attribute changes and delete. * * @throws IOException */ @Test public void createChangeAttributeAndDelete() throws IOException { final Project originalProject = ModelUtil.clone(getProject()); final TestElement useCase = Create.testElement(); new EMFStoreCommand() { @Override protected void doRun() { getProject().addModelElement(useCase); useCase.setName(SOME_NAME); useCase.setName(NEW_NAME); getProject().deleteModelElement(useCase); } }.run(false); final List<AbstractOperation> operations = forceGetOperations(); // expect create, 2 attribute ops, delete assertEquals(4, operations.size()); new EMFStoreCommand() { @Override protected void doRun() { OperationsCanonizer.canonize(operations); } }.run(false); // expect attributes folding into create, and create and delete removed, // as they would be directly adjacent to each other assertEquals(operations.size(), 0); assertTrue(ModelUtil.areEqual(getProject(), originalProject)); } /** * Tests canonization for create, attribute changes and delete. * * @throws IOException */ @Test public void createChangeReferencesAndDelete() throws IOException { final TestElement useCase2 = Create.testElement(); new EMFStoreCommand() { @Override protected void doRun() { getProject().addModelElement(useCase2); } }.run(false); final Project originalProject = ModelUtil.clone(getProject()); new EMFStoreCommand() { @Override protected void doRun() { clearOperations(); final TestElement useCase = Create.testElement(); getProject().addModelElement(useCase); useCase.setName(SOME_NAME); useCase.getReferences().add(useCase2); getProject().deleteModelElement(useCase); } }.run(false); final List<AbstractOperation> operations = forceGetOperations(); // expect create, 1 attribute ops, 1 multiref op, the delete assertEquals(4, operations.size()); canonize(operations); // expect attributes folding into create, the multiref and delete remain assertEquals(operations.size(), 3); assertTrue(operations.get(0) instanceof CreateDeleteOperation); assertTrue(operations.get(1) instanceof MultiReferenceOperation); assertTrue(operations.get(2) instanceof CreateDeleteOperation); // check the folding of the attribute final CreateDeleteOperation createOp = (CreateDeleteOperation) operations.get(0); assertEquals(SOME_NAME, ((TestElement) createOp.getModelElement()).getName()); // check reversibility new EMFStoreCommand() { @Override protected void doRun() { operations.get(2).reverse().apply(getProject()); operations.get(1).reverse().apply(getProject()); operations.get(0).reverse().apply(getProject()); } }.run(false); assertTrue(ModelUtil.areEqual(getProject(), originalProject)); } }