blob: a2402a8d443186671b100a5888a63aa66616bb6f [file] [log] [blame]
/*
* Copyright (c) 2010-2013, 2015 Eike Stepper (Berlin, Germany) and others.
* 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:
* Eike Stepper - initial API and implementation
*/
package org.eclipse.emf.cdo.tests.bugzilla;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.common.revision.delta.CDOFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDOListFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta;
import org.eclipse.emf.cdo.eresource.CDOResource;
import org.eclipse.emf.cdo.server.IRepository.WriteAccessHandler;
import org.eclipse.emf.cdo.server.IStoreAccessor.CommitContext;
import org.eclipse.emf.cdo.server.ITransaction;
import org.eclipse.emf.cdo.session.CDOSession;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionDelta;
import org.eclipse.emf.cdo.tests.AbstractCDOTest;
import org.eclipse.emf.cdo.tests.model3.NodeA;
import org.eclipse.emf.cdo.transaction.CDOTransaction;
import org.eclipse.emf.cdo.util.CDOUtil;
import org.eclipse.emf.cdo.view.CDOView;
import org.eclipse.net4j.util.om.monitor.OMMonitor;
import org.eclipse.emf.edit.command.DragAndDropCommand;
import java.text.MessageFormat;
/**
* Moving nodes in a tree structure (simulate a {@link DragAndDropCommand}) may result in an inconsistent tree.
* <p>
* See bug 319836
*
* @author Cyril Jaquier
*/
public class Bugzilla_319836_Test extends AbstractCDOTest
{
private static final boolean SHOW_BUG = true;
public void testNodeMovesInTreeCreatesCycle() throws Exception
{
{
// Setup an initial session and a transaction.
CDOSession session = openSession();
CDOTransaction tr1 = session.openTransaction();
CDOResource resource = tr1.createResource(getResourcePath("/test1"));
NodeA n1 = getModel3Factory().createNodeA();
NodeA n2 = getModel3Factory().createNodeA();
NodeA n3 = getModel3Factory().createNodeA();
resource.getContents().add(n1);
// Create a deep tree.
//
// n1
// `- n2
// ___`- n3
n1.getChildren().add(n2);
n2.getChildren().add(n3);
tr1.commit();
setNames(n1, n2, n3);
tr1.commit();
addCommitContextTracer();
// First move.
//
// n1
// |- n2
// `- n3
if (SHOW_BUG)
{
n2.getChildren().remove(n3);
}
n1.getChildren().add(n3);
// Second move.
//
// n1
// `- n3
// ___`- n2
// Something bad will happen during the execution of the next line. Set a breakpoint in
// org.eclipse.emf.internal.cdo.transaction.CDOSavepointImpl.detachedObjects.new Map<CDOID, CDOObject>()
// {...}.put(CDOID, CDOObject) and see how the previous REMOVE is eaten.
if (SHOW_BUG)
{
n1.getChildren().remove(n2);
}
n3.getChildren().add(n2);
// Problem is with n2; the removal of its child has been lost
CDOID n2ID = CDOUtil.getCDOObject(n2).cdoID();
CDORevisionDelta revDelta = tr1.getRevisionDeltas().get(n2ID);
assertEquals(2, revDelta.getFeatureDeltas().size());
tr1.commit();
// Checks the tree.
assertEquals(1, n1.getChildren().size());
assertEquals(0, n2.getChildren().size());
assertEquals(1, n3.getChildren().size());
}
{
// Setup a new session. The bug only does not seem to occur if we use the same session.
CDOSession session = openSession();
CDOView view = session.openView();
CDOResource resource = view.getResource(getResourcePath("/test1"));
NodeA n1 = (NodeA)resource.getContents().get(0);
// Checks the tree.
assertEquals(1, n1.getChildren().size());
NodeA n3 = n1.getChildren().get(0);
assertEquals(1, n3.getChildren().size());
NodeA n2 = n3.getChildren().get(0);
// Oups... n2 has a child!? And even worst... it's n3 :'( Houston, we have a problem...
assertEquals(0, n2.getChildren().size());
}
}
public void testNodeMovesInTreeDuplicatesNode() throws Exception
{
{
// Setup an initial session and a transaction.
CDOSession session = openSession();
CDOTransaction tr1 = session.openTransaction();
CDOResource resource = tr1.createResource(getResourcePath("/test1"));
NodeA n1 = getModel3Factory().createNodeA();
NodeA n2 = getModel3Factory().createNodeA();
NodeA n3 = getModel3Factory().createNodeA();
NodeA n4 = getModel3Factory().createNodeA();
resource.getContents().add(n1);
// Create a deep tree.
//
// n1
// `- n2
// ___`- n3
// ______`- n4
n1.getChildren().add(n2);
n2.getChildren().add(n3);
n3.getChildren().add(n4);
tr1.commit();
setNames(n1, n2, n3);
tr1.commit();
addCommitContextTracer();
// First move.
//
// n1
// `- n2
// ___|- n3
// ___`- n4
if (SHOW_BUG)
{
n3.getChildren().remove(n4);
}
n2.getChildren().add(n4);
// Second move.
//
// n1
// |- n2
// |__`- n4
// `- n3
// Something bad will happen during the execution of the next line. Set a breakpoint in
// org.eclipse.emf.internal.cdo.transaction.CDOSavepointImpl.detachedObjects.new Map<CDOID, CDOObject>()
// {...}.put(CDOID, CDOObject) and see how the previous REMOVE is eaten.
if (SHOW_BUG)
{
n2.getChildren().remove(n3);
}
n1.getChildren().add(n3);
tr1.commit();
// Checks the tree.
assertEquals(2, n1.getChildren().size());
assertEquals(1, n2.getChildren().size());
assertEquals(0, n3.getChildren().size());
assertEquals(0, n4.getChildren().size());
}
{
// Setup a new session. The bug only does not seem to occur if we use the same session.
CDOSession session = openSession();
CDOView view = session.openView();
CDOResource resource = view.getResource(getResourcePath("/test1"));
NodeA n1 = (NodeA)resource.getContents().get(0);
// Checks the tree.
assertEquals(2, n1.getChildren().size());
NodeA n2 = n1.getChildren().get(0);
assertEquals(1, n2.getChildren().size());
NodeA n4 = n2.getChildren().get(0);
assertEquals(0, n4.getChildren().size());
NodeA n3 = n1.getChildren().get(1);
// Oups... n3 has a child!?
assertEquals(0, n3.getChildren().size());
}
}
public void testNodeMovesInTreeEatsNode() throws Exception
{
{
// Setup an initial session and a transaction.
CDOSession session = openSession();
CDOTransaction tr1 = session.openTransaction();
CDOResource resource = tr1.createResource(getResourcePath("/test1"));
NodeA n1 = getModel3Factory().createNodeA();
NodeA n2 = getModel3Factory().createNodeA();
NodeA n3 = getModel3Factory().createNodeA();
NodeA n4 = getModel3Factory().createNodeA();
resource.getContents().add(n1);
// Create a flat tree.
//
// n1
// |- n2
// |- n3
// `- n4
n1.getChildren().add(n2);
n1.getChildren().add(n3);
n1.getChildren().add(n4);
tr1.commit();
setNames(n1, n2, n3);
tr1.commit();
addCommitContextTracer();
// First move.
//
// n1
// |- n2
// `- n3
// ___`- n4
if (SHOW_BUG)
{
n1.getChildren().remove(n4);
}
n3.getChildren().add(n4);
// Second move.
//
// n1
// `- n2
// ___`- n3
// ______`- n4
// Something bad will happen during the execution of the next line. Set a breakpoint in
// org.eclipse.emf.internal.cdo.transaction.CDOSavepointImpl.detachedObjects.new Map<CDOID, CDOObject>()
// {...}.put(CDOID, CDOObject) and see how the previous ADD is eaten.
if (SHOW_BUG)
{
n1.getChildren().remove(n3);
}
n2.getChildren().add(n3);
tr1.commit();
// Checks the tree.
assertEquals(1, n1.getChildren().size());
assertEquals(n2, n1.getChildren().get(0));
assertEquals(1, n2.getChildren().size());
assertEquals(n3, n2.getChildren().get(0));
assertEquals(1, n3.getChildren().size());
assertEquals(n4, n3.getChildren().get(0));
assertEquals(0, n4.getChildren().size());
}
{
// Setup a new session. The bug only does not seem to occur if we use the same session.
CDOSession session = openSession();
CDOView view = session.openView();
CDOResource resource = view.getResource(getResourcePath("/test1"));
NodeA n1 = (NodeA)resource.getContents().get(0);
// Checks the tree.
assertEquals(1, n1.getChildren().size());
NodeA n2 = n1.getChildren().get(0);
assertEquals(1, n2.getChildren().size());
NodeA n3 = n2.getChildren().get(0);
// Oups... Where is n4!?
assertEquals(1, n3.getChildren().size());
NodeA n4 = n3.getChildren().get(0);
assertEquals(0, n4.getChildren().size());
}
}
private void setNames(NodeA... nodes)
{
for (NodeA node : nodes)
{
String id = CDOUtil.getCDOObject(node).cdoID().toString();
msg("Node: " + id);
node.setName(id);
}
}
private void addCommitContextTracer()
{
getRepository().addHandler(new WriteAccessHandler()
{
public void handleTransactionBeforeCommitting(ITransaction transaction, CommitContext commitContext,
OMMonitor monitor) throws RuntimeException
{
msg(toPrettyString("\t", commitContext.getDirtyObjectDeltas()));
}
public void handleTransactionAfterCommitted(ITransaction transaction, CommitContext commitContext,
OMMonitor monitor)
{
}
/**
* Prints the {@link InternalCDORevisionDelta}s in a more friendly way than {@link #toString()}.
*
* @param spacer
* the spacer used to increment the output
* @return {@link String}
*/
private String toPrettyString(String spacer, InternalCDORevisionDelta[] dirtyObjects)
{
StringBuilder sb = new StringBuilder();
// Delta.
sb.append(spacer).append("Delta revision(s):\n");
for (InternalCDORevisionDelta item : dirtyObjects)
{
String m = MessageFormat.format("{0}@{1}:{2}v{3}", item.getEClass().getName(), item.getID(),
item.getBranch().getID(), item.getVersion());
sb.append(spacer).append(spacer).append(m).append("\n");
// Feature deltas.
for (CDOFeatureDelta delta : item.getFeatureDeltas())
{
printFeatureDelta(sb, delta, spacer, 3);
}
}
return sb.toString();
}
/**
* Pretty prints {@link CDOFeatureDelta}, recursing into {@link CDOListFeatureDelta}.
*
* @param sb
* {@link StringBuilder} where the output is written
* @param delta
* {@link CDOFeatureDelta}
* @param spacer
* the spacer used to increment the output
* @param numberOfSpacer
* the minimal number of spacer in front of a line
*/
private void printFeatureDelta(StringBuilder sb, CDOFeatureDelta delta, String spacer, int numberOfSpacer)
{
if (delta instanceof CDOListFeatureDelta)
{
CDOListFeatureDelta list = (CDOListFeatureDelta)delta;
for (int i = 0; i < numberOfSpacer; i++)
{
sb.append(spacer);
}
sb.append("CDOListFeatureDelta").append("\n");
for (CDOFeatureDelta c : list.getListChanges())
{
sb.append(spacer);
printFeatureDelta(sb, c, spacer, numberOfSpacer);
}
}
else
{
for (int i = 0; i < numberOfSpacer; i++)
{
sb.append(spacer);
}
sb.append(delta).append("\n");
}
}
});
}
}