com.cloudant.sync.datastore.DatastoreImplForceInsertTest.java Source code

Java tutorial

Introduction

Here is the source code for com.cloudant.sync.datastore.DatastoreImplForceInsertTest.java

Source

/**
 * Copyright (c) 2013 Cloudant, Inc. All rights reserved.
 *
 * 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.
 */

package com.cloudant.sync.datastore;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;

import com.cloudant.sync.sqlite.SQLDatabase;
import com.cloudant.sync.util.CouchUtils;
import com.cloudant.sync.util.JSONUtils;
import com.cloudant.sync.util.TestUtils;

import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;

public class DatastoreImplForceInsertTest {

    public static final String OBJECT_ID = "object_id";
    String database_dir;
    String documentOneFile = "fixture/document_1.json";
    String documentTwoFile = "fixture/document_2.json";

    SQLDatabase database = null;
    DatastoreImpl datastore = null;
    byte[] jsonData = null;
    DocumentBody bodyOne = null;
    DocumentBody bodyTwo = null;

    @Before
    public void setUp() throws Exception {
        database_dir = TestUtils.createTempTestingDir(DatastoreImplForceInsertTest.class.getName());
        datastore = new DatastoreImpl(database_dir, "test");

        jsonData = FileUtils.readFileToByteArray(TestUtils.loadFixture(documentOneFile));
        bodyOne = new DocumentBodyImpl(jsonData);

        jsonData = FileUtils.readFileToByteArray(TestUtils.loadFixture(documentTwoFile));
        bodyTwo = new DocumentBodyImpl(jsonData);
    }

    @After
    public void tearDown() throws Exception {
        TestUtils.deleteDatabaseQuietly(database);
        TestUtils.deleteTempTestingDir(database_dir);
    }

    @Test(expected = IllegalArgumentException.class)
    public void forceInsert_revHistoryNotInRightOrder_exception() throws Exception {
        DocumentRevision rev = createDbObject();
        datastore.forceInsert(rev, "1-rev", "3-rev", "2-rev", "4-rev");
    }

    @Test(expected = IllegalArgumentException.class)
    public void forceInsert_currentRevisionNotInTheHistory_exception() throws Exception {
        DocumentRevision rev = createDbObject();
        datastore.forceInsert(rev, "1-rev", "2-rev", "3-rev");
    }

    @Test
    public void forceInsert_documentNotInLocalDB_documentShouldBeInserted() throws Exception {
        DocumentRevision rev = createDbObject();
        datastore.forceInsert(rev, "1-rev", "2-rev", "4-rev");
        assertDBObjectIsCorrect(OBJECT_ID, 4, bodyOne);
    }

    private DocumentRevision createDbObject() {
        return createDbObject("4-rev", bodyOne);
    }

    private DocumentRevision createDbObject(String rev, DocumentBody body) {
        DocumentRevisionBuilder builder = new DocumentRevisionBuilder();
        builder.setDocId(OBJECT_ID);
        builder.setRevId(rev);
        builder.setDeleted(false);
        builder.setBody(body);
        return builder.build();
    }

    private DocumentRevision createDbObjectDeleted(String rev) {
        DocumentRevisionBuilder builder = new DocumentRevisionBuilder();
        builder.setDocId(OBJECT_ID);
        builder.setRevId(rev);
        builder.setDeleted(true);
        builder.setBody(new DocumentBodyImpl(JSONUtils.emptyJSONObjectAsBytes()));
        return builder.build();
    }

    @Test
    public void forceInsert_newRevisionsFromRemoteDB_newRevisionShouldBeInserted() throws Exception {
        {
            DocumentRevision rev = createDbObject();
            datastore.forceInsert(rev, "1-rev", "2-rev", "4-rev");
        }

        assertDBObjectIsCorrect(OBJECT_ID, 4, bodyOne);

        {
            DocumentRevisionBuilder builder = new DocumentRevisionBuilder();
            builder.setDocId(OBJECT_ID);
            builder.setRevId("5-rev");
            builder.setDeleted(false);
            builder.setBody(bodyOne);
            DocumentRevision newRev = builder.build();

            datastore.forceInsert(newRev, "1-rev", "2-rev", "4-rev", "5-rev");
        }

        assertDBObjectIsCorrect(OBJECT_ID, 5, bodyOne);
    }

    @Test
    public void forceInsert_longerPathFromRemoteDB_remoteDBWins() throws Exception {

        {
            DocumentRevision rev = createDbObject();
            datastore.forceInsert(rev, "1-rev", "2-rev", "4-rev");

            DocumentRevision insertedObj = datastore.getDocument(OBJECT_ID);
            insertedObj.setBody(bodyTwo);
            DocumentRevision updatedObj = datastore.updateDocumentFromRevision(insertedObj);

            Assert.assertNotNull(updatedObj);

            assertDBObjectIsCorrect(OBJECT_ID, 5, bodyTwo);
        }

        {
            DocumentRevisionBuilder builder = new DocumentRevisionBuilder();
            builder.setDocId(OBJECT_ID);
            builder.setRevId("6-rev");
            builder.setDeleted(false);
            builder.setBody(bodyOne);
            DocumentRevision newRev = builder.build();

            datastore.forceInsert(newRev, "1-rev", "2-rev", "4-rev", "5-rev", "6-rev");
        }

        assertDBObjectIsCorrect(OBJECT_ID, 6, bodyOne);

    }

    @Test
    public void forceInsert_longerPathFromLocalDB_localDBWins() throws Exception {
        {
            DocumentRevision rev = createDbObject();
            datastore.forceInsert(rev, "1-rev", "2-rev", "4-rev");

            DocumentRevision insertedObj = datastore.getDocument(OBJECT_ID);
            insertedObj.setBody(bodyTwo);
            DocumentRevision updateObj = datastore.updateDocumentFromRevision(insertedObj);
            insertedObj.setBody(bodyTwo);
            DocumentRevision updateObj2 = datastore.updateDocumentFromRevision(updateObj);

            Assert.assertNotNull(updateObj2);

            assertDBObjectIsCorrect(OBJECT_ID, 6, bodyTwo);
        }

        {
            DocumentRevisionBuilder builder = new DocumentRevisionBuilder();
            builder.setDocId(OBJECT_ID);
            builder.setRevId("5-rev");
            builder.setDeleted(false);
            builder.setBody(bodyOne);
            DocumentRevision newRev = builder.build();

            datastore.forceInsert(newRev, "1-rev", "2-rev", "4-rev", "5-rev");
        }

        assertDBObjectIsCorrect(OBJECT_ID, 6, bodyTwo);

        DocumentRevision p = datastore.getDocument(OBJECT_ID, "5-rev");
        Assert.assertNotNull(p);
        Assert.assertEquals("5-rev", p.getRevision());
        Assert.assertTrue(Arrays.equals(bodyOne.asBytes(), p.getBody().asBytes()));
    }

    @Test
    public void forceInsert_sameLengthOfPath_remoteRevisionWins() throws Exception {
        {
            DocumentRevision rev = createDbObject();
            datastore.forceInsert(rev, "1-rev", "2-rev", "3-rev", "4-rev");

            DocumentRevision insertedObj = datastore.getDocument(OBJECT_ID);
            insertedObj.setBody(bodyTwo);
            DocumentRevision updateObj = datastore.updateDocumentFromRevision(insertedObj);
            insertedObj.setBody(bodyTwo);
            DocumentRevision updateObj2 = datastore.updateDocumentFromRevision(updateObj);

            Assert.assertNotNull(updateObj2);

            assertDBObjectIsCorrect(OBJECT_ID, 6, bodyTwo);
        }

        String localRevisionId6 = null;
        String remoteRevisionId6 = null;
        {
            DocumentRevisionTree tree = datastore.getAllRevisionsOfDocument(OBJECT_ID);
            DocumentRevision current = tree.getCurrentRevision();
            List<DocumentRevision> all = tree.getPathForNode(current.getSequence());
            // Make sure the latest revision from remote db has bigger String (in terms of String comparison)
            for (DocumentRevision a : all) {
                int g = CouchUtils.generationFromRevId(a.getRevision());
                if (g == 6) {
                    localRevisionId6 = a.getRevision();
                    StringBuilder sb = new StringBuilder(localRevisionId6);
                    sb.setCharAt(2, (char) (sb.charAt(2) + 1));
                    remoteRevisionId6 = sb.toString();
                }
            }

            Assert.assertNotNull(localRevisionId6);
            Assert.assertNotNull(remoteRevisionId6);

            DocumentRevisionBuilder builder = new DocumentRevisionBuilder();
            builder.setDocId(OBJECT_ID);
            builder.setRevId(remoteRevisionId6);
            builder.setDeleted(false);
            builder.setBody(bodyOne);
            DocumentRevision newRev = builder.build();

            datastore.forceInsert(newRev, "1-rev", "2-rev", "3-rev", "4-rev", "5-rev", remoteRevisionId6);
        }

        DocumentRevision obj = datastore.getDocument(OBJECT_ID);
        Assert.assertEquals(remoteRevisionId6, obj.getRevision());
        Assert.assertTrue(Arrays.equals(bodyOne.asBytes(), obj.getBody().asBytes()));
    }

    @Test
    public void forceInsert_sameLengthOfPath_localRevisionWins() throws Exception {
        {
            DocumentRevision rev = createDbObject();
            datastore.forceInsert(rev, "1-rev", "2-rev", "3-rev", "4-rev");

            DocumentRevision insertedObj = datastore.getDocument(OBJECT_ID);
            insertedObj.setBody(bodyTwo);
            DocumentRevision updateObj = datastore.updateDocumentFromRevision(insertedObj);
            insertedObj.setBody(bodyTwo);
            DocumentRevision updateObj2 = datastore.updateDocumentFromRevision(updateObj);

            Assert.assertNotNull(updateObj2);

            assertDBObjectIsCorrect(OBJECT_ID, 6, bodyTwo);
        }

        String localRevisionId6 = null;
        String remoteRevisionId6 = null;
        {
            // Make sure the latest revision from remote db has smaller String (in terms of String comparison)
            DocumentRevisionTree tree = datastore.getAllRevisionsOfDocument(OBJECT_ID);
            DocumentRevision current = tree.getCurrentRevision();
            List<DocumentRevision> all = tree.getPathForNode(current.getSequence());
            for (DocumentRevision a : all) {
                int g = CouchUtils.generationFromRevId(a.getRevision());
                if (g == 6) {
                    localRevisionId6 = a.getRevision();
                    StringBuilder sb = new StringBuilder(localRevisionId6);
                    sb.setCharAt(2, (char) (sb.charAt(2) - 1));
                    remoteRevisionId6 = sb.toString();
                }
            }

            Assert.assertNotNull(localRevisionId6);
            Assert.assertNotNull(remoteRevisionId6);

            DocumentRevisionBuilder builder = new DocumentRevisionBuilder();
            builder.setDocId(OBJECT_ID);
            builder.setRevId(remoteRevisionId6);
            builder.setDeleted(false);
            builder.setBody(bodyOne);
            DocumentRevision newRev = builder.build();

            datastore.forceInsert(newRev, "1-rev", "2-rev", "3-rev", "4-rev", "5-rev", remoteRevisionId6);
        }

        DocumentRevision obj = datastore.getDocument(OBJECT_ID);
        Assert.assertEquals(localRevisionId6, obj.getRevision());
        Assert.assertTrue(Arrays.equals(bodyTwo.asBytes(), obj.getBody().asBytes()));
    }

    @Test
    public void forceInsert_conflictsWithDocDeletedInLocalDB_nonDeletionWins() throws Exception {
        List<String> revs = new ArrayList<String>();
        revs.add("1-rev");
        revs.add("2-rev");
        revs.add("4-rev");

        {
            DocumentRevision rev = createDbObject();
            datastore.forceInsert(rev, "1-rev", "2-rev", "4-rev");

            DocumentRevision insertedObj = datastore.getDocument(OBJECT_ID);
            insertedObj.setBody(bodyTwo);
            DocumentRevision updateObj = datastore.updateDocumentFromRevision(insertedObj);
            updateObj.setBody(bodyTwo);
            DocumentRevision updateObj2 = datastore.updateDocumentFromRevision(updateObj);
            Assert.assertNotNull(updateObj2);

            // Delete the document from the local database
            datastore.deleteDocumentFromRevision(updateObj2);
            DocumentRevision deletedObj = datastore.getDocument(OBJECT_ID);
            Assert.assertTrue(deletedObj.isDeleted());
        }

        {
            DocumentRevisionBuilder builder = new DocumentRevisionBuilder();
            builder.setDocId(OBJECT_ID);
            builder.setRevId("5-rev");
            builder.setDeleted(false);
            builder.setBody(bodyOne);
            DocumentRevision newRev = builder.build();

            datastore.forceInsert(newRev, "1-rev", "2-rev", "4-rev", "5-rev");
        }

        assertDBObjectIsCorrect(OBJECT_ID, 5, bodyOne);
    }

    @Test
    public void forceInsert_conflictsWithDocDeletedInRemoteDB_nonDeletionWins() throws Exception {
        {
            DocumentRevision rev = createDbObject();
            datastore.forceInsert(rev, "1-rev", "2-rev", "4-rev");

            DocumentRevision insertedObj = datastore.getDocument(OBJECT_ID);
            insertedObj.setBody(bodyTwo);
            DocumentRevision updateObj = datastore.updateDocumentFromRevision(insertedObj);
            updateObj.setBody(bodyTwo);
            DocumentRevision updateObj2 = datastore.updateDocumentFromRevision(updateObj);
            Assert.assertNotNull(updateObj2);
        }

        {
            DocumentRevisionBuilder builder = new DocumentRevisionBuilder();
            builder.setDocId(OBJECT_ID);
            builder.setRevId("8-rev");
            builder.setDeleted(true);
            builder.setBody(DocumentBodyFactory.EMPTY);
            DocumentRevision newRev = builder.build();

            datastore.forceInsert(newRev, "1-rev", "2-rev", "4-rev", "5-rev", "6-rev", "7-rev", "8-rev");
        }

        assertDBObjectIsCorrect(OBJECT_ID, 6, bodyTwo);
    }

    private void assertDBObjectIsCorrect(String docId, int revGeneration, DocumentBody body) throws Exception {
        DocumentRevision obj = datastore.getDocument(docId);
        Assert.assertNotNull(obj);
        Assert.assertEquals(revGeneration, CouchUtils.generationFromRevId(obj.getRevision()));
        Assert.assertTrue(Arrays.equals(body.asBytes(), obj.getBody().asBytes()));
    }

    @Test
    public void forceInsert_newTreeLengthOfOneFromRemoteDb_newTreeShouldBeInsertedAndNewTreeWins()
            throws Exception {
        {
            DocumentRevision rev = createDbObject("1-a", bodyOne);
            datastore.forceInsert(rev, "1-a");
            DocumentRevision insertedObj = datastore.getDocument(OBJECT_ID);
            Assert.assertEquals("1-a", insertedObj.getRevision());
        }

        {
            DocumentRevision rev = createDbObject("1-b", bodyTwo);
            datastore.forceInsert(rev, "1-b");
        }

        DocumentRevisionTree tree = datastore.getAllRevisionsOfDocument(OBJECT_ID);
        Assert.assertThat(tree.leafs(), hasSize(2));
        Assert.assertThat(tree.leafRevisionIds(), hasItems("1-a", "1-b"));
        assertDocumentHasRevAndBody(OBJECT_ID, "1-b", bodyTwo);
    }

    @Test
    public void forceInsert_newTreeLengthOfOneFromRemoteDb_newTreeShouldBeInsertedButOldTreeWins()
            throws Exception {
        {
            DocumentRevision rev = createDbObject("1-x", bodyOne);
            datastore.forceInsert(rev, "1-x");
            DocumentRevision insertedObj = datastore.getDocument(OBJECT_ID);
            Assert.assertEquals("1-x", insertedObj.getRevision());
        }

        {
            DocumentRevision rev = createDbObject("1-a", bodyTwo);
            datastore.forceInsert(rev, "1-a");
        }

        DocumentRevisionTree tree = datastore.getAllRevisionsOfDocument(OBJECT_ID);
        Assert.assertThat(tree.leafs(), hasSize(2));
        Assert.assertThat(tree.leafRevisionIds(), hasItems("1-a", "1-x"));
        assertDocumentHasRevAndBody(OBJECT_ID, "1-x", bodyOne);
    }

    @Test
    public void forceInsert_newTreeLengthOfTwoFromRemoteDb_newTreeShouldBeInserted() throws Exception {
        {
            DocumentRevision rev = createDbObject("1-x", bodyOne);
            datastore.forceInsert(rev, "1-x");
            DocumentRevision insertedObj = datastore.getDocument(OBJECT_ID);
            Assert.assertEquals("1-x", insertedObj.getRevision());
        }

        {
            DocumentRevision rev = createDbObject("2-c", bodyTwo);
            datastore.forceInsert(rev, "1-a", "2-c");
        }

        DocumentRevisionTree tree = datastore.getAllRevisionsOfDocument(OBJECT_ID);
        Assert.assertThat(tree.leafs(), hasSize(2));
        Assert.assertThat(tree.leafRevisionIds(), hasItems("1-x", "2-c"));

        DocumentRevision leaf = datastore.getDocument(OBJECT_ID, "2-c");
        Assert.assertThat(tree.getPath(leaf.getSequence()), equalTo(Arrays.asList("2-c", "1-a")));

        assertDocumentHasRevAndBody(OBJECT_ID, "2-c", bodyTwo);
    }

    @Test
    public void forceInsert_pickWinnerOfConflicts_Simple_7x_last() throws Exception {

        // regression test for pickWinnerOfConflicts:
        // under previous behaviour this would fail to mark 2-y as current even though 7-x is
        // deleted, if 7-x is inserted last

        // create a chain of revs 1-x -> 6-x
        // then add 2-y and 7-x

        DocumentRevision rev1 = createDbObject("1-x", bodyOne);
        DocumentRevision rev2 = createDbObject("2-x", bodyOne);
        DocumentRevision rev3 = createDbObject("3-x", bodyOne);
        DocumentRevision rev4 = createDbObject("4-x", bodyOne);
        DocumentRevision rev5 = createDbObject("5-x", bodyOne);
        DocumentRevision rev6 = createDbObject("6-x", bodyOne);

        DocumentRevision rev7 = createDbObjectDeleted("7-x");
        DocumentRevision rev2_alt = createDbObject("2-y", bodyOne);

        datastore.forceInsert(rev1, "1-x");
        datastore.forceInsert(rev2, "1-x", "2-x");
        datastore.forceInsert(rev3, "1-x", "2-x", "3-x");
        datastore.forceInsert(rev4, "1-x", "2-x", "3-x", "4-x");
        datastore.forceInsert(rev5, "1-x", "2-x", "3-x", "4-x", "5-x");
        datastore.forceInsert(rev6, "1-x", "2-x", "3-x", "4-x", "5-x", "6-x");

        datastore.forceInsert(rev2_alt, "1-x", "2-y");
        datastore.forceInsert(rev7, "6-x", "7-x");

        Assert.assertEquals(datastore.getDocument(OBJECT_ID).getRevision(), "2-y");
    }

    @Test
    public void forceInsert_pickWinnerOfConflicts_Simple_2y_last() throws Exception {

        // this test is the same as the one above but we switch round the last two forceInserts
        // - this ensure we get the same result regardless of insertion order

        DocumentRevision rev1 = createDbObject("1-x", bodyOne);
        DocumentRevision rev2 = createDbObject("2-x", bodyOne);
        DocumentRevision rev3 = createDbObject("3-x", bodyOne);
        DocumentRevision rev4 = createDbObject("4-x", bodyOne);
        DocumentRevision rev5 = createDbObject("5-x", bodyOne);
        DocumentRevision rev6 = createDbObject("6-x", bodyOne);

        DocumentRevision rev7 = createDbObjectDeleted("7-x");
        DocumentRevision rev2_alt = createDbObject("2-y", bodyOne);

        datastore.forceInsert(rev1, "1-x");
        datastore.forceInsert(rev2, "1-x", "2-x");
        datastore.forceInsert(rev3, "1-x", "2-x", "3-x");
        datastore.forceInsert(rev4, "1-x", "2-x", "3-x", "4-x");
        datastore.forceInsert(rev5, "1-x", "2-x", "3-x", "4-x", "5-x");
        datastore.forceInsert(rev6, "1-x", "2-x", "3-x", "4-x", "5-x", "6-x");

        datastore.forceInsert(rev7, "6-x", "7-x");
        datastore.forceInsert(rev2_alt, "1-x", "2-y");

        Assert.assertEquals(datastore.getDocument(OBJECT_ID).getRevision(), "2-y");
    }

    @Test
    public void forceInsert_pickWinnerOfConflicts_Complex() throws Exception {

        // build a complex tree and delete random leaf nodes
        // check that after each delete the correct rev is marked as current
        // ensures that deleted revs are never marked current and that the next eligible leaf in
        // the tree is always correctly nominated to be current by pickWinnerOfConflicts

        // first build a tree such that there will be leaf nodes in the range of generations between
        // 2 and maxtree+2

        int maxTree = 10; // deepest tree will have revs from 1..maxTree+1 followed by the conflicted leaf nodes
        int nConflicts = 3; // number of conflicted leaf nodes
        char startChar = 'a'; // revids will be x-a, x-b, x-c etc where x is the generation
        String startRev = String.format("1-%c", startChar);
        DocumentRevision root = createDbObject(startRev, bodyOne);
        // make subtree for 1-a, 2-a etc
        makeSubTree(startRev, String.format("%c", startChar), 1, maxTree + 2, nConflicts);
        // now make subtrees starting at 2-b, 3-c etc, rooted at 1-a, 2-a etc
        // each subtree is of a constant height ensuring leaf nodes at a variety of generations
        for (int i = 0; i < maxTree - 1; i++) {
            String rootId = String.format("%d-%c", i + 1, startChar);
            makeSubTree(rootId, String.format("%c", startChar + i + 1), i + 1, i + 3, nConflicts);
        }

        // now go through continually deleting random leafs until they have all been deleted

        Random a = new Random();

        // fetch non-deleted leafs until there are none left
        List<DocumentRevision> leafs;
        while ((leafs = (datastore.getAllRevisionsOfDocument(OBJECT_ID).leafRevisions(true))).size() != 0) {
            // getDocument() should never return a deleted document:
            // under previous behaviour of pickWinnerOfConflicts, this would fail
            DocumentRevision currentLeaf = datastore.getDocument(OBJECT_ID);
            Assert.assertFalse("Current revision should not have been marked as deleted. Current leaf: "
                    + currentLeaf + ". Current state of all leaf nodes: " + leafs, currentLeaf.isDeleted());

            // root the new deleted doc at the randomly selected leaf node
            int random = a.nextInt(leafs.size());
            DocumentRevision randomLeaf = leafs.get(random);
            String newRevId = String.format("%d-%s-deleted", randomLeaf.getGeneration() + 1,
                    randomLeaf.getRevision());
            DocumentRevision deleted = createDbObjectDeleted(newRevId);
            datastore.forceInsert(deleted, randomLeaf.getRevision(), newRevId);

            // we use the same comparator as pickWinnerOfConflicts
            // re-fetch leafs after insert
            leafs = (datastore.getAllRevisionsOfDocument(OBJECT_ID).leafRevisions(true));
            Collections.sort(leafs, new Comparator<DocumentRevision>() {
                @Override
                public int compare(DocumentRevision r1, DocumentRevision r2) {
                    int generationCompare = r1.getGeneration() - r2.getGeneration();
                    if (generationCompare != 0) {
                        return -generationCompare;
                    } else {
                        return -r1.getRevision().compareTo(r2.getRevision());
                    }
                }
            });
            currentLeaf = datastore.getDocument(OBJECT_ID);

            if (leafs.size() == 0) {
                break;
            }

            // check that our view of 'current' agrees with what pickWinnerOfConflicts did
            Assert.assertEquals(currentLeaf, leafs.get(0));

            // also check that none of the other leafs are marked current
            for (DocumentRevision leaf : leafs.subList(1, leafs.size())) {
                Assert.assertFalse(
                        "Leaf " + leaf + " should not be marked current. Current state of all leaf nodes: " + leafs,
                        leaf.isCurrent());
            }
        }
        // check that after all of them are deleted, the winning rev is the deepest leaf node with
        // the highest sorted revid
        String expectedRevId = String.format("%d-%d-%c%d-deleted", maxTree + 3, maxTree + 2, startChar,
                nConflicts - 1);
        Assert.assertEquals(
                "RevId should have been highest expected value. Current state of all sorted leaf nodes: " + leafs,
                expectedRevId, datastore.getDocument(OBJECT_ID).getRevision());
    }

    private void assertDocumentHasRevAndBody(String id, String rev, DocumentBody body) throws Exception {
        DocumentRevision obj = datastore.getDocument(id);
        Assert.assertEquals(rev, obj.getRevision());
        Assert.assertTrue(Arrays.equals(obj.getBody().asBytes(), body.asBytes()));
    }

    // make a chain of revisions from `start` to `depth` inclusive and then add `conflicts` number
    // of leaf nodes. the interior nodes are named "1-x" etc (using `id`) and the leaf nodes are
    // named "12-11-x0" "12-11-x1" etc
    private void makeSubTree(String root, String id, int start, int depth, int conflicts) throws DocumentException {
        int i;
        String lastRevId = root;
        for (i = start; i < depth - 1; i++) {
            String revId = String.format("%d-%s", i + 1, id);
            DocumentRevision rev = createDbObject(revId, bodyOne);
            datastore.forceInsert(rev, lastRevId, revId);
            lastRevId = revId;
        }
        // now some leaf nodes of the format "12-11-x0", "12-11-x1" etc
        for (int j = 0; j < conflicts; j++) {
            String revId = String.format("%d-%s%d", i + 1, id, j);
            DocumentRevision rev = createDbObject(revId, bodyOne);
            datastore.forceInsert(rev, lastRevId, revId);
        }
    }

}