blob: 6c1eb8dc26d4c4a0911a9f09ba4224b67265f60c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.jackrabbit.oak.plugins.document;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
/**
* Tests for {@code NodeStore#branch}
*/
public class DocumentMKBranchMergeTest extends BaseDocumentMKTest {
@Test
public void oneBranchAddedChildren1() {
addNodes(null, "/trunk", "/trunk/child1");
assertNodesExist(null, "/trunk", "/trunk/child1");
String branchRev = mk.branch(null);
branchRev = addNodes(branchRev, "/branch1", "/branch1/child1");
assertNodesExist(branchRev, "/trunk", "/trunk/child1");
assertNodesExist(branchRev, "/branch1", "/branch1/child1");
assertNodesNotExist(null, "/branch1", "/branch1/child1");
addNodes(null, "/trunk/child2");
assertNodesExist(null, "/trunk/child2");
assertNodesNotExist(branchRev, "/trunk/child2");
mk.merge(branchRev, "");
assertNodesExist(null, "/trunk", "/trunk/child1", "/trunk/child2", "/branch1", "/branch1/child1");
}
@Test
public void oneBranchAddedChildren2() {
addNodes(null, "/trunk", "/trunk/child1");
assertNodesExist(null, "/trunk", "/trunk/child1");
String branchRev = mk.branch(null);
branchRev = addNodes(branchRev, "/trunk/child1/child2");
assertNodesExist(branchRev, "/trunk", "/trunk/child1");
assertNodesExist(branchRev, "/trunk/child1/child2");
assertNodesNotExist(null, "/trunk/child1/child2");
addNodes(null, "/trunk/child3");
assertNodesExist(null, "/trunk/child3");
assertNodesNotExist(branchRev, "/trunk/child3");
mk.merge(branchRev, "");
assertNodesExist(null, "/trunk", "/trunk/child1", "/trunk/child1/child2", "/trunk/child3");
}
@Test
public void oneBranchAddedChildren3() {
addNodes(null, "/root", "/root/child1");
assertNodesExist(null, "/root", "/root/child1");
String branchRev = mk.branch(null);
// System.out.println("branchRev: " + branchRev);
addNodes(null, "/root/child2");
assertNodesExist(null, "/root", "/root/child1", "/root/child2");
assertNodesExist(branchRev, "/root", "/root/child1");
assertNodesNotExist(branchRev, "/root/child2");
branchRev = addNodes(branchRev, "/root/child1/child3", "/root/child4");
assertNodesExist(branchRev, "/root", "/root/child1", "/root/child1/child3", "/root/child4");
assertNodesNotExist(branchRev, "/root/child2");
assertNodesExist(null, "/root", "/root/child1", "/root/child2");
assertNodesNotExist(null, "/root/child1/child3", "/root/child4");
mk.merge(branchRev, "");
assertNodesExist(null, "/root", "/root/child1", "/root/child2",
"/root/child1/child3", "/root/child4");
}
@Test
public void oneBranchRemovedChildren() {
addNodes(null, "/trunk", "/trunk/child1");
assertNodesExist(null, "/trunk", "/trunk/child1");
String branchRev = mk.branch(null);
branchRev = removeNodes(branchRev, "/trunk/child1");
assertNodesExist(branchRev, "/trunk");
assertNodesNotExist(branchRev, "/trunk/child1");
assertNodesExist(null, "/trunk", "/trunk/child1");
mk.merge(branchRev, "");
assertNodesExist(null, "/trunk");
assertNodesNotExist(null, "/trunk/child1");
}
@Test
public void oneBranchRemovedRoot() {
addNodes(null, "/trunk", "/trunk/child1");
assertNodesExist(null, "/trunk", "/trunk/child1");
String branchRev = mk.branch(null);
branchRev = removeNodes(branchRev, "/trunk");
assertNodesNotExist(branchRev, "/trunk", "/trunk/child1");
assertNodesExist(null, "/trunk", "/trunk/child1");
mk.merge(branchRev, "");
assertNodesNotExist(null, "/trunk", "/trunk/child1");
}
/**
* This is a test to make sure properties are properly escaped in merge.
*/
@Test
public void oneBranchAddPropertyRoot() {
String branchRev = mk.branch(null);
branchRev = setProp(branchRev, "/jcr:primaryType", "nam:rep:root");
assertPropExists(branchRev, "/", "jcr:primaryType");
branchRev = mk.merge(branchRev, "");
assertPropExists(branchRev, "/", "jcr:primaryType");
String mergedNode = mk.getNodes("/", branchRev, 0, 0, -1, null);
String expectedNode = "{\"jcr:primaryType\":\"nam:rep:root\",\":childNodeCount\":0}";
assertEquals("Wrong property value after merge", expectedNode, mergedNode);
}
@Test
public void oneBranchChangedProperties() {
addNodes(null, "/trunk", "/trunk/child1");
setProp(null, "/trunk/child1/prop1", "value1");
setProp(null, "/trunk/child1/prop2", "value2");
assertNodesExist(null, "/trunk", "/trunk/child1");
assertPropExists(null, "/trunk/child1", "prop1");
assertPropExists(null, "/trunk/child1", "prop2");
String branchRev = mk.branch(null);
branchRev = setProp(branchRev, "/trunk/child1/prop1", "value1a");
branchRev = setProp(branchRev, "/trunk/child1/prop2", null);
branchRev = setProp(branchRev, "/trunk/child1/prop3", "value3");
assertPropValue(branchRev, "/trunk/child1", "prop1", "value1a");
assertPropNotExists(branchRev, "/trunk/child1", "prop2");
assertPropValue(branchRev, "/trunk/child1", "prop3", "value3");
assertPropValue(null, "/trunk/child1", "prop1", "value1");
assertPropExists(null, "/trunk/child1", "prop2");
assertPropNotExists(null, "/trunk/child1", "prop3");
mk.merge(branchRev, "");
assertPropValue(null, "/trunk/child1", "prop1", "value1a");
assertPropNotExists(null, "/trunk/child1", "prop2");
assertPropValue(null, "/trunk/child1", "prop3", "value3");
}
@Test
public void oneBranchAddedSubChildren() {
addNodes(null, "/trunk", "/trunk/child1", "/trunk/child1/child2", "/trunk/child1/child2/child3");
assertNodesExist(null, "/trunk", "/trunk/child1", "/trunk/child1/child2", "/trunk/child1/child2/child3");
String branchRev = mk.branch(null);
branchRev = addNodes(branchRev, "/branch1", "/branch1/child1", "/branch1/child1/child2", "/branch1/child1/child2/child3");
assertNodesExist(branchRev, "/trunk", "/trunk/child1", "/trunk/child1/child2", "/trunk/child1/child2/child3");
assertNodesExist(branchRev, "/branch1", "/branch1/child1", "/branch1/child1/child2", "/branch1/child1/child2/child3");
assertNodesNotExist(null, "/branch1", "/branch1/child1", "/branch1/child1/child2", "/branch1/child1/child2/child3");
addNodes(null, "/trunk/child1/child2/child3/child4", "/trunk/child5");
assertNodesExist(null, "/trunk/child1/child2/child3/child4", "/trunk/child5");
assertNodesNotExist(branchRev, "/trunk/child1/child2/child3/child4", "/trunk/child5");
mk.merge(branchRev, "");
assertNodesExist(null, "/trunk", "/trunk/child1", "/trunk/child1/child2", "/trunk/child1/child2/child3", "/trunk/child1/child2/child3/child4");
assertNodesExist(null, "/branch1", "/branch1/child1", "/branch1/child1/child2", "/branch1/child1/child2/child3");
}
@Test
public void oneBranchAddedChildrenAndAddedProperties() {
addNodes(null, "/trunk", "/trunk/child1");
setProp(null, "/trunk/child1/prop1", "value1");
setProp(null, "/trunk/child1/prop2", "value2");
assertNodesExist(null, "/trunk", "/trunk/child1");
assertPropExists(null, "/trunk/child1", "prop1");
assertPropExists(null, "/trunk/child1", "prop2");
String branchRev = mk.branch(null);
branchRev = addNodes(branchRev, "/branch1", "/branch1/child1");
branchRev = setProp(branchRev, "/branch1/child1/prop1", "value1");
branchRev = setProp(branchRev, "/branch1/child1/prop2", "value2");
assertNodesExist(branchRev, "/trunk", "/trunk/child1");
assertPropExists(branchRev, "/trunk/child1", "prop1");
assertPropExists(branchRev, "/trunk/child1", "prop2");
assertNodesExist(branchRev, "/branch1", "/branch1/child1");
assertPropExists(branchRev, "/branch1/child1", "prop1");
assertPropExists(branchRev, "/branch1/child1", "prop2");
assertNodesNotExist(null, "/branch1", "/branch1/child1");
assertPropNotExists(null, "/branch1/child1", "prop1");
assertPropNotExists(null, "/branch1/child1", "prop2");
mk.merge(branchRev, "");
assertNodesExist(null, "/trunk", "/trunk/child1");
assertPropExists(null, "/trunk/child1", "prop1");
assertPropExists(null, "/trunk/child1", "prop2");
assertNodesExist(null, "/branch1", "/branch1/child1");
assertPropExists(null, "/branch1/child1", "prop1");
assertPropExists(null, "/branch1/child1", "prop2");
}
@Test
public void twoBranchesAddedChildren1() {
addNodes(null, "/trunk", "/trunk/child1");
assertNodesExist(null, "/trunk", "/trunk/child1");
String branchRev1 = mk.branch(null);
String branchRev2 = mk.branch(null);
branchRev1 = addNodes(branchRev1, "/branch1", "/branch1/child1");
branchRev2 = addNodes(branchRev2, "/branch2", "/branch2/child2");
assertNodesExist(branchRev1, "/trunk", "/trunk/child1");
assertNodesExist(branchRev2, "/trunk", "/trunk/child1");
assertNodesExist(branchRev1, "/branch1/child1");
assertNodesNotExist(branchRev1, "/branch2/child2");
assertNodesExist(branchRev2, "/branch2/child2");
assertNodesNotExist(branchRev2, "/branch1/child1");
assertNodesNotExist(null, "/branch1/child1", "/branch2/child2");
addNodes(null, "/trunk/child2");
assertNodesExist(null, "/trunk/child2");
assertNodesNotExist(branchRev1, "/trunk/child2");
assertNodesNotExist(branchRev2, "/trunk/child2");
mk.merge(branchRev1, "");
assertNodesExist(null, "/trunk", "/branch1", "/branch1/child1");
assertNodesNotExist(null, "/branch2", "/branch2/child2");
mk.merge(branchRev2, "");
assertNodesExist(null, "/trunk", "/branch1", "/branch1/child1", "/branch2", "/branch2/child2");
}
@Test
@Ignore
public void oneBranchAddedChildrenWithConflict() {
addNodes(null, "/trunk", "/trunk/child1");
assertNodesExist(null, "/trunk", "/trunk/child1");
String branchRev = mk.branch(null);
branchRev = removeNodes(branchRev, "/trunk/child1");
assertNodesExist(branchRev, "/trunk");
assertNodesNotExist(branchRev, "/trunk/child1");
addNodes(null, "/trunk/child1/child2");
assertNodesExist(null, "/trunk", "/trunk/child1", "/trunk/child1/child2");
mk.merge(branchRev, "");
assertNodesExist(null, "/trunk");
assertNodesNotExist(null, "/trunk/child1", "/trunk/child1/child2");
}
@Test
public void propertyConflictWithMergedBranch() {
addNodes(null, "/trunk");
String rev = setProp(null, "/trunk/prop1", "value1");
assertPropExists(null, "/trunk", "prop1");
String branchRev = mk.branch(null);
branchRev = setProp(branchRev, "/trunk/prop1", "value1a");
assertPropValue(branchRev, "/trunk", "prop1", "value1a");
mk.merge(branchRev, "");
assertPropValue(null, "/trunk", "prop1", "value1a");
try {
setProp(rev, "/trunk/prop1", "value1b");
fail("Expected: Concurrent modification exception");
} catch (Exception expected) {
// expected
}
}
@Test
public void oneBranchChangedPropertiesWithConflict() {
addNodes(null, "/trunk");
setProp(null, "/trunk/prop1", "value1");
assertPropExists(null, "/trunk", "prop1");
String branchRev = mk.branch(null);
branchRev = setProp(branchRev, "/trunk/prop1", "value1a");
assertPropValue(branchRev, "/trunk", "prop1", "value1a");
setProp(null, "/trunk/prop1", "value1b");
try {
mk.merge(branchRev, "");
fail("Expected: Concurrent modification exception");
} catch (Exception expected) {
// expected
}
}
@Test
public void twoBranchChangedPropertiesWithConflict() {
addNodes(null, "/trunk");
setProp(null, "/trunk/prop1", "value1");
setProp(null, "/trunk/prop2", "value1");
assertPropExists(null, "/trunk", "prop1");
assertPropExists(null, "/trunk", "prop2");
String branchRev1 = mk.branch(null);
branchRev1 = setProp(branchRev1, "/trunk/prop1", "value1-b1");
assertPropValue(branchRev1, "/trunk", "prop1", "value1-b1");
String branchRev2 = mk.branch(null);
branchRev2 = setProp(branchRev2, "/trunk/prop2", "value1-b2");
assertPropValue(branchRev2, "/trunk", "prop2", "value1-b2");
// creates a conflict for both branches
mk.commit("/", "^\"trunk/prop1\":\"value1-modified\"" +
"^\"trunk/prop2\":\"value1-modified\"", null, null);
try {
mk.merge(branchRev1, "");
fail("Expected: Concurrent modification exception");
} catch (Exception expected) {
// expected
}
try {
mk.merge(branchRev2, "");
fail("Expected: Concurrent modification exception");
} catch (Exception expected) {
// expected
}
}
@Test
public void addExistingRootInBranch() {
addNodes(null, "/root");
assertNodesExist(null, "/root");
String branchRev = mk.branch(null);
try {
addNodes(branchRev, "/root");
fail("Should not be able to add the same root node twice");
} catch (Exception expected) {
// expected
}
}
@Test
public void addExistingChildInBranch() {
addNodes(null, "/root", "/root/child1");
assertNodesExist(null, "/root", "/root/child1");
String branchRev = mk.branch(null);
branchRev = addNodes(branchRev, "/root/child2");
assertNodesExist(branchRev, "/root/child1", "/root/child2");
try {
addNodes(branchRev, "/root/child1");
fail("Should not be able to add the same root node twice");
} catch (Exception expected) {
// expected
}
}
@Test
public void trunkMergeNotAllowed() {
String rev = mk.commit("", "+\"/child1\":{}", null, "");
try {
mk.merge(rev, "");
fail("Exception expected");
} catch (Exception expected) {
// expected
}
}
@Test
@Ignore
public void movesInBranch() {
String rev = mk.commit("/", "+\"a\":{\"b\":{}}", null, null);
String branchRev = mk.branch(rev);
branchRev = mk.commit("/", ">\"a\":\"x\"^\"x/b/p\":1>\"x\":\"a\"", branchRev, null);
rev = mk.merge(branchRev, null);
assertNodesExist(rev, "/a", "/a/b");
assertPropExists(rev, "/a/b", "p");
}
@Test
public void concurrentNonConflictingMerges() throws Exception {
int numThreads = 10;
mk.commit("/", "+\"test\":{}", null, null);
List<Thread> workers = new ArrayList<Thread>();
final List<Exception> exceptions = Collections.synchronizedList(new ArrayList<Exception>());
for (int i = 0; i < numThreads; i++) {
final String path = "/test/t" + i;
mk.commit("", "+\"" + path + "\":{}", null, null);
workers.add(new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < 50; i++) {
String branchRev = mk.branch(null);
branchRev = mk.commit(path, "+\"node" + i + "\":{}", branchRev, null);
mk.merge(branchRev, null);
}
} catch (DocumentStoreException e) {
exceptions.add(e);
}
}
}));
}
for (Thread t : workers) {
t.start();
}
for (Thread t : workers) {
t.join();
}
if (!exceptions.isEmpty()) {
throw exceptions.get(0);
}
}
//--------------------------< internal >------------------------------------
private String setProp(String rev, String prop, Object value) {
value = value == null? null : "\"" + value + "\"";
return mk.commit("", "^\"" + prop + "\" : " + value, rev, "");
}
}