mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Nicolas Capponi
28.02.2014 3c028d4d56ba0a2ebd9bc86e39bcdee2e666fec7
Checkpoint commit for OPENDJ-1206 : Create a new ReplicationBackend/ChangelogBackend 
to support cn=changelog

Add more tests for changelog backend. Tests are still disabled (until changelog backend is
activated in code).

ChangelogBackedTestCase.java:
- add new tests for cookie search : one and two suffixes cases
- add TODOs for other test cases, mostly on psearches
- renaming and cleanup
1 files modified
453 ■■■■ changed files
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/ChangelogBackendTestCase.java 453 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/ChangelogBackendTestCase.java
@@ -32,6 +32,7 @@
import static org.opends.server.replication.protocol.OperationContext.*;
import static org.opends.server.types.ResultCode.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
import static org.testng.Assert.*;
import java.io.ByteArrayOutputStream;
@@ -43,9 +44,12 @@
import java.util.Set;
import java.util.SortedSet;
import org.assertj.core.api.Assertions;
import org.opends.server.TestCaseUtils;
import org.opends.server.api.Backend;
import org.opends.server.backends.ChangelogBackend.SearchParams;
import org.opends.server.controls.ExternalChangelogRequestControl;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.ModifyDNOperationBasis;
import org.opends.server.loggers.debug.DebugTracer;
@@ -56,6 +60,7 @@
import org.opends.server.replication.common.CSN;
import org.opends.server.replication.common.CSNGenerator;
import org.opends.server.replication.common.MultiDomainServerState;
import org.opends.server.replication.common.ServerState;
import org.opends.server.replication.plugin.DomainFakeCfg;
import org.opends.server.replication.plugin.ExternalChangelogDomainFakeCfg;
import org.opends.server.replication.plugin.LDAPReplicationDomain;
@@ -107,9 +112,12 @@
  private static final String USER1_ENTRY_UUID = "11111111-1111-1111-1111-111111111111";
  private static final long CHANGENUMBER_ZERO = 0L;
  private static final int SERVER_ID_1 = 1201;
  private static final int SERVER_ID_2 = 1202;
  private static final String TEST_ROOT_DN_STRING2 = "o=test2";
  private static final String TEST_BACKEND_ID2 = "test2";
  private static final String TEST_ROOT_DN_STRING2 = "o=" + TEST_BACKEND_ID2;
  private static DN ROOT_DN_OTEST;
  private static DN ROOT_DN_OTEST2;
@@ -181,7 +189,7 @@
      @Override
      public boolean isECLEnabledDomain(DN baseDN)
      {
        return baseDN.equals(ROOT_DN_OTEST);
        return baseDN.equals(ROOT_DN_OTEST) || baseDN.equals(ROOT_DN_OTEST2);
      }
    });
    debugInfo("configure", "ReplicationServer created:" + replicationServer);
@@ -227,28 +235,230 @@
  }
  @Test(enabled=false)
  public void searchChangesOnOneSuffixUsingEmptyCookie() throws Exception
  public void searchInCookieModeOnOneSuffixUsingEmptyCookie() throws Exception
  {
    String testName = "FourChangesCookie";
    debugInfo(testName, "Starting test\n\n");
    String test = "EmptyCookie";
    debugInfo(test, "Starting test\n\n");
    CSN[] csns = generateAndPublishChangesForEachOperationType(testName);
    final CSN[] csns = generateAndPublishUpdateMsgForEachOperationType(test, true);
    final String[] cookies = buildCookiesFromCsns(csns);
    searchChangesForEachOperationTypeUsingEmptyCookie(csns, testName);
    int nbEntries = 4;
    String cookie = "";
    InternalSearchOperation searchOp =
        searchChangelogUsingCookie("(targetdn=*" + test + "*,o=test)", cookie, nbEntries, SUCCESS, test);
    final List<SearchResultEntry> searchEntries = searchOp.getSearchEntries();
    assertDelEntry(searchEntries.get(0), test + 1, test + "uuid1", CHANGENUMBER_ZERO, csns[0], cookies[0]);
    assertAddEntry(searchEntries.get(1), test + 2, USER1_ENTRY_UUID, CHANGENUMBER_ZERO, csns[1], cookies[1]);
    assertModEntry(searchEntries.get(2), test + 3, test + "uuid3", CHANGENUMBER_ZERO, csns[2], cookies[2]);
    assertModDNEntry(searchEntries.get(3), test + 4, test + "new4", test+"uuid4", CHANGENUMBER_ZERO,
        csns[3], cookies[3]);
    assertResultsContainCookieControl(searchOp, cookies);
    assertChangelogAttributesInRootDSE(true, 1, 4);
    debugInfo(testName, "Ending search with success");
    debugInfo(test, "Ending search with success");
  }
  @Test(enabled=false)
  public void searchChangesOnOneSuffixUsingDraftMode() throws Exception
  public void searchInCookieModeOnOneSuffix() throws Exception
  {
    String test = "CookieOneSuffix";
    debugInfo(test, "Starting test\n\n");
    InternalSearchOperation searchOp = null;
    final CSN[] csns = generateAndPublishUpdateMsgForEachOperationType(test, true);
    final String[] cookies = buildCookiesFromCsns(csns);
    // check querying with cookie of delete entry : should return  3 entries
    int nbEntries = 3;
    searchOp = searchChangelogUsingCookie("(targetdn=*" + test + "*,o=test)", cookies[0], nbEntries, SUCCESS, test);
    List<SearchResultEntry> searchEntries = searchOp.getSearchEntries();
    assertAddEntry(searchEntries.get(0), test + 2, USER1_ENTRY_UUID, CHANGENUMBER_ZERO, csns[1], cookies[1]);
    assertModEntry(searchEntries.get(1), test + 3, test + "uuid3", CHANGENUMBER_ZERO, csns[2], cookies[2]);
    assertModDNEntry(searchEntries.get(2), test + 4, test + "new4", test+"uuid4", CHANGENUMBER_ZERO,
        csns[3], cookies[3]);
    // check querying with cookie of add entry : should return 2 entries
    nbEntries = 2;
    searchOp = searchChangelogUsingCookie("(targetdn=*" + test + "*,o=test)", cookies[1], nbEntries, SUCCESS, test);
    // check querying with cookie of mod entry : should return 1 entry
    nbEntries = 1;
    searchOp = searchChangelogUsingCookie("(targetdn=*" + test + "*,o=test)", cookies[2], nbEntries, SUCCESS, test);
    searchEntries = searchOp.getSearchEntries();
    assertModDNEntry(searchEntries.get(0), test + 4, test + "new4", test+"uuid4", CHANGENUMBER_ZERO,
        csns[3], cookies[3]);
    // check querying with cookie of mod dn entry : should return 0 entry
    nbEntries = 0;
    searchOp = searchChangelogUsingCookie("(targetdn=*" + test + "*,o=test)", cookies[3], nbEntries, SUCCESS, test);
    debugInfo(test, "Ending search with success");
  }
  @Test(enabled=false)
  public void searchInCookieModeAfterDomainIsRemoved() throws Exception
  {
    // TODO
    // see testECLAfterDomainIsRemoved
  }
  @Test(enabled=false)
  public void searchInCookieModeWithPrivateBackend() throws Exception
  {
    // TODO
    // see ExternalChangeLogTest#ECLOnPrivateBackend
  }
  /**
   * This test enables a second suffix. It will break all tests using search on
   * one suffix if run before them, so it is necessary to add them as
   * dependencies.
   */
  @Test(enabled=false, dependsOnMethods = {
    "searchInCookieModeOnOneSuffixUsingEmptyCookie",
    "searchInCookieModeOnOneSuffix",
    "searchInCookieModeWithPrivateBackend",
    "searchInCookieModeAfterDomainIsRemoved",
    "searchInDraftModeOnOneSuffixMultipleTimes",
    "searchInDraftModeOnOneSuffix",
    "searchInDraftModeWithInvalidChangeNumber" })
  public void searchInCookieModeOnTwoSuffixes() throws Exception
  {
    String test = "CookieTwoSuffixes";
    debugInfo(test, "Starting test\n\n");
    Backend<?> backendForSecondSuffix = null;
    try
    {
      backendForSecondSuffix = initializeMemoryBackend(true, TEST_BACKEND_ID2);
      // publish 4 changes (2 on each suffix)
      long time = TimeThread.getTime();
      int seqNum = 1;
      CSN csn1 = new CSN(time, seqNum++, SERVER_ID_1);
      CSN csn2 = new CSN(time, seqNum++, SERVER_ID_2);
      CSN csn3 = new CSN(time, seqNum++, SERVER_ID_2);
      CSN csn4 = new CSN(time, seqNum++, SERVER_ID_1);
      publishUpdateMessagesInOTest(test, false, new UpdateMsg[] {
        generateDeleteMsg(TEST_ROOT_DN_STRING,  csn1, test, 1) });
      publishUpdateMessagesInOTest2(test, false, new UpdateMsg[] {
        generateDeleteMsg(TEST_ROOT_DN_STRING2,  csn2, test, 2),
        generateDeleteMsg(TEST_ROOT_DN_STRING2,  csn3, test, 3) });
      publishUpdateMessagesInOTest(test, false, new UpdateMsg[] {
        generateDeleteMsg(TEST_ROOT_DN_STRING,  csn4, test, 4) });
      // search on all suffixes using empty cookie
      String cookie = "";
      InternalSearchOperation searchOp =
          searchChangelogUsingCookie("(targetDN=*" + test + "*)", cookie, 4, SUCCESS, test);
      cookie = readCookieFromNthEntry(searchOp.getSearchEntries(), 2);
      // search using previous cookie and expect to get ONLY the 4th change
      LDIFWriter ldifWriter = getLDIFWriter();
      searchOp = searchChangelogUsingCookie("(targetDN=*" + test + "*)", cookie, 1, SUCCESS, test);
      cookie = assertEntriesContainsCSNsAndReadLastCookie(test, searchOp.getSearchEntries(), ldifWriter, csn4);
      // publish a new change on first suffix
      CSN csn5 = new CSN(time, seqNum++, SERVER_ID_1);
      publishUpdateMessagesInOTest(test, false, new UpdateMsg[] {
        generateDeleteMsg(TEST_ROOT_DN_STRING,  csn5, test, 5) });
      // search using last cookie and expect to get the last change
      searchOp = searchChangelogUsingCookie("(targetDN=*" + test + "*)", cookie, 1, SUCCESS, test);
      assertEntriesContainsCSNsAndReadLastCookie(test, searchOp.getSearchEntries(), ldifWriter, csn5);
      // search on first suffix only, with empty cookie
      cookie = "";
      searchOp = searchChangelogUsingCookie("(targetDN=*" + test + "*,o=test)", cookie, 3, SUCCESS, test);
      cookie = assertEntriesContainsCSNsAndReadLastCookie(test, searchOp.getSearchEntries(), ldifWriter,
          csn1, csn4, csn5);
      // publish 4 more changes (2 on each suffix, on differents server id)
      time = TimeThread.getTime();
      seqNum = 6;
      int serverId11 = 1203;
      int serverId22 = 1204;
      CSN csn6 = new CSN(time, seqNum++, serverId11);
      CSN csn7 = new CSN(time, seqNum++, serverId22);
      CSN csn8 = new CSN(time, seqNum++, serverId11);
      CSN csn9 = new CSN(time, seqNum++, serverId22);
      publishUpdateMessages(test, ROOT_DN_OTEST2, serverId11, false,  new UpdateMsg[] {
        generateDeleteMsg(TEST_ROOT_DN_STRING2,  csn6, test, 6) });
      publishUpdateMessages(test, ROOT_DN_OTEST, serverId22, false,  new UpdateMsg[] {
        generateDeleteMsg(TEST_ROOT_DN_STRING,  csn7, test, 7) });
      publishUpdateMessages(test, ROOT_DN_OTEST2, serverId11, false,  new UpdateMsg[] {
        generateDeleteMsg(TEST_ROOT_DN_STRING2,  csn8, test, 8) });
      publishUpdateMessages(test, ROOT_DN_OTEST, serverId22, false,  new UpdateMsg[] {
        generateDeleteMsg(TEST_ROOT_DN_STRING,  csn9, test, 9) });
      // ensure oldest state is correct for each suffix and for each server id
      final ServerState oldestState = getDomainOldestState(ROOT_DN_OTEST);
      assertEquals(oldestState.getCSN(SERVER_ID_1), csn1);
      assertEquals(oldestState.getCSN(serverId22), csn7);
      final ServerState oldestState2 = getDomainOldestState(ROOT_DN_OTEST2);
      assertEquals(oldestState2.getCSN(SERVER_ID_2), csn2);
      assertEquals(oldestState2.getCSN(serverId11), csn6);
      // test last cookie on root DSE
      MultiDomainServerState expectedLastCookie =
          new MultiDomainServerState("o=test:" + csn5 + " " + csn9 + ";o=test2:" + csn3 + " " + csn8 + ";");
      final String lastCookie = readLastCookieFromRootDSE();
      assertThat(lastCookie).isEqualTo(expectedLastCookie.toString());
      // test unknown domain in provided cookie
      // This case seems to be very hard to obtain in the real life
      // (how to remove a domain from a RS topology ?)
      final String cookie2 = lastCookie + "o=test6:";
      debugInfo(test, "Search with bad domain in cookie=" + cookie);
      searchOp = searchChangelogUsingCookie("(targetDN=*" + test + "*,o=test)", cookie2, 0, UNWILLING_TO_PERFORM, test);
      // test missing domain in provided cookie
      final String cookie3 = lastCookie.substring(lastCookie.indexOf(';')+1);
      debugInfo(test, "Search with bad domain in cookie=" + cookie);
      searchOp = searchChangelogUsingCookie("(targetDN=*" + test + "*,o=test)", cookie3, 0, UNWILLING_TO_PERFORM, test);
      String expectedError = ERR_RESYNC_REQUIRED_MISSING_DOMAIN_IN_PROVIDED_COOKIE
          .get("o=test:;","<"+ cookie3 + "o=test:;>").toString();
      assertThat(searchOp.getErrorMessage().toString()).isEqualToIgnoringCase(expectedError);
    }
    finally
    {
      removeBackend(backendForSecondSuffix);
      replicationServer.getChangelogDB().getReplicationDomainDB().removeDomain(ROOT_DN_OTEST2);
    }
  }
  @Test(enabled=false)
  public void searchInDraftModeWithInvalidChangeNumber() throws Exception
  {
    String testName = "UnknownChangeNumber";
    debugInfo(testName, "Starting test\n\n");
    searchChangelog("(changenumber=1000)", 0, SUCCESS, testName);
    debugInfo(testName, "Ending test with success");
  }
  @Test(enabled=false)
  public void searchInDraftModeOnOneSuffix() throws Exception
  {
    long firstChangeNumber = 1;
    String testName = "FourChanges/" + firstChangeNumber;
    debugInfo(testName, "Starting test\n\n");
    CSN[] csns = generateAndPublishChangesForEachOperationType(testName);
    CSN[] csns = generateAndPublishUpdateMsgForEachOperationType(testName, false);
    searchChangesForEachOperationTypeUsingDraftMode(firstChangeNumber, csns, testName);
@@ -258,18 +468,18 @@
  }
  @Test(enabled=false)
  public void searchChangesOnOneSuffixMultipleTimesUsingDraftMode() throws Exception
  public void searchInDraftModeOnOneSuffixMultipleTimes() throws Exception
  {
    replicationServer.getChangelogDB().setPurgeDelay(0);
    // write 4 changes starting from changenumber 1, and search them
    String testName = "Multiple/1";
    CSN[] csns = generateAndPublishChangesForEachOperationType(testName);
    CSN[] csns = generateAndPublishUpdateMsgForEachOperationType(testName, false);
    searchChangesForEachOperationTypeUsingDraftMode(1, csns, testName);
    // write 4 more changes starting from changenumber 5, and search them
    testName = "Multiple/5";
    csns = generateAndPublishChangesForEachOperationType(testName);
    csns = generateAndPublishUpdateMsgForEachOperationType(testName, false);
    searchChangesForEachOperationTypeUsingDraftMode(5, csns, testName);
    // search from the provided change number: 6 (should be the add msg)
@@ -284,7 +494,7 @@
    // add a new change, then check again first and last change number without previous search
    CSN csn = new CSN(TimeThread.getTime(), 10, SERVER_ID_1);
    publishChanges(testName, generateDeleteMsg(TEST_ROOT_DN_STRING, csn, testName, 1));
    publishUpdateMessagesInOTest(testName, false, generateDeleteMsg(TEST_ROOT_DN_STRING, csn, testName, 1));
    assertChangelogAttributesInRootDSE(true, 1, 9);
  }
@@ -294,7 +504,7 @@
   */
  // TODO : enable when code is checking the privileges correctly
  @Test(enabled=false)
  public void searchingChangelogWithoutPrivilegeShouldFail() throws Exception
  public void searchingWithoutPrivilegeShouldFail() throws Exception
  {
    AuthenticationInfo nonPrivilegedUser = new AuthenticationInfo();
@@ -305,6 +515,33 @@
    assertEquals(op.getErrorMessage().toMessage(), NOTE_SEARCH_CHANGELOG_INSUFFICIENT_PRIVILEGES.get());
  }
  @Test(enabled=false)
  public void persistentSearch() throws Exception
  {
   // TODO
   // ExternalChangeLogTest#FullTestPersistentSearchWithChangesOnlyRequest
   // ExternalChangeLogTest#FullTestPersistentSearchWithInitValuesRequest
   // ExternalChangeLogTest#ECLReplicationServerFullTest16
  }
  @Test(enabled=false)
  public void simultaneousPersistentSearches() throws Exception
  {
    // TODO
    // see testECLAfterDomainIsRemoved
  }
  // TODO : other tests methods in ECLTest
  // test search after a purge ? cf testECLAfterChangelogTrim
  // testChangeTimeHeartbeat
  // TestECLWithIncludeAttributes
  // TODO: list of todos extracted from ExternalChangeLogTest
  // ECL Test SEARCH abandon and check everything shutdown and cleaned
  // ECL Test PSEARCH abandon and check everything shutdown and cleaned
  // ECL Test the attributes list and values returned in ECL entries
  // ECL Test search -s base, -s one
  /**
   * With an empty RS, a search should return only root entry.
   */
@@ -320,17 +557,6 @@
  }
  @Test(enabled=false)
  public void searchWithUnknownChangeNumberShouldReturnNoResult() throws Exception
  {
    String testName = "UnknownChangeNumber";
    debugInfo(testName, "Starting test\n\n");
    searchChangelog("(changenumber=1000)", 0, SUCCESS, testName);
    debugInfo(testName, "Ending test with success");
  }
  @Test(enabled=false)
  public void operationalAndVirtualAttributesShouldNotBeVisibleOutsideRootDSE() throws Exception
  {
    String testName = "attributesVisibleOutsideRootDSE";
@@ -455,6 +681,70 @@
    throw error;
  }
  private String readLastCookieFromRootDSE() throws Exception
  {
    String cookie = "";
    LDIFWriter ldifWriter = getLDIFWriter();
    InternalSearchOperation searchOp = searchDNWithBaseScope("", newSet("lastExternalChangelogCookie"));
    List<SearchResultEntry> entries = searchOp.getSearchEntries();
    if (entries != null)
    {
      for (SearchResultEntry resultEntry : entries)
      {
        ldifWriter.writeEntry(resultEntry);
        cookie = getAttributeValue(resultEntry, "lastexternalchangelogcookie");
      }
    }
    return cookie;
  }
  private String assertLastCookieDifferentThanLastValue(final String lastCookie) throws Exception
  {
    int count = 0;
    while (count < 100)
    {
      final String newCookie = readLastCookieFromRootDSE();
      if (!newCookie.equals(lastCookie))
      {
        return newCookie;
      }
      count++;
      Thread.sleep(10);
    }
    Assertions.fail("Expected last cookie should have been updated, but it always stayed at value '" + lastCookie + "'");
    return null;// dead code
  }
  private String readCookieFromNthEntry(List<SearchResultEntry> entries, int i)
  {
    SearchResultEntry entry = entries.get(i);
    return entry.getAttribute("changelogcookie").get(0).iterator().next().toString();
  }
  private String assertEntriesContainsCSNsAndReadLastCookie(String test, List<SearchResultEntry> entries,
      LDIFWriter ldifWriter, CSN... csns) throws Exception
  {
    assertThat(getCSNsFromEntries(entries)).containsExactly(csns);
    debugAndWriteEntries(ldifWriter, entries, test);
    return readCookieFromNthEntry(entries, csns.length - 1);
  }
  private List<CSN> getCSNsFromEntries(List<SearchResultEntry> entries)
  {
    List<CSN> results = new ArrayList<CSN>(entries.size());
    for (SearchResultEntry entry : entries)
    {
      results.add(new CSN(getAttributeValue(entry, "replicationCSN")));
    }
    return results;
  }
  private ServerState getDomainOldestState(DN baseDN)
  {
    return replicationServer.getReplicationServerDomain(baseDN).getOldestState();
  }
  private void assertSearchParameters(SearchParams searchParams, long firstChangeNumber,
      long lastChangeNumber, CSN csn) throws Exception
  {
@@ -463,32 +753,58 @@
    assertEquals(searchParams.getCSN(), csn == null ? new CSN(0, 0, 0) : csn);
  }
  private CSN[] generateAndPublishChangesForEachOperationType(String testName) throws Exception
  private CSN[] generateAndPublishUpdateMsgForEachOperationType(String testName, boolean checkLastCookie)
      throws Exception
  {
    CSN[] csns = generateCSNs(4, SERVER_ID_1);
    List<UpdateMsg> messages = new ArrayList<UpdateMsg>();
    messages.add(generateDeleteMsg(TEST_ROOT_DN_STRING, csns[0], testName, 1));
    messages.add(generateAddMsg(TEST_ROOT_DN_STRING, csns[1], USER1_ENTRY_UUID, testName));
    messages.add(generateModMsg(TEST_ROOT_DN_STRING, csns[2], testName));
    messages.add(generateModDNMsg(TEST_ROOT_DN_STRING, csns[3], testName));
    List<UpdateMsg> updateMsgs = new ArrayList<UpdateMsg>();
    updateMsgs.add(generateDeleteMsg(TEST_ROOT_DN_STRING, csns[0], testName, 1));
    updateMsgs.add(generateAddMsg(TEST_ROOT_DN_STRING, csns[1], USER1_ENTRY_UUID, testName));
    updateMsgs.add(generateModMsg(TEST_ROOT_DN_STRING, csns[2], testName));
    updateMsgs.add(generateModDNMsg(TEST_ROOT_DN_STRING, csns[3], testName));
    publishChanges(testName, messages.toArray(new UpdateMsg[4]));
    publishUpdateMessagesInOTest(testName, checkLastCookie, updateMsgs.toArray(new UpdateMsg[4]));
    return csns;
  }
  /** Publish a list changes to the default replication broker used by tests. */
  private void publishChanges(String testName, UpdateMsg...messages) throws Exception
  // shortcut method for default base DN and server id used in tests
  private void publishUpdateMessagesInOTest(String testName, boolean checkLastCookie, UpdateMsg...messages)
      throws Exception
  {
    publishUpdateMessages(testName, ROOT_DN_OTEST, SERVER_ID_1, checkLastCookie, messages);
  }
  private void publishUpdateMessagesInOTest2(String testName, boolean checkLastCookie, UpdateMsg...messages)
      throws Exception
  {
    publishUpdateMessages(testName, ROOT_DN_OTEST2, SERVER_ID_2, checkLastCookie, messages);
  }
  /**
   * Publish a list of update messages to the replication broker corresponding to given baseDN and server id.
   *
   * @param checkLastCookie if true, checks that last cookie is update after each message publication
   */
  private void publishUpdateMessages(String testName, DN baseDN, int serverId, boolean checkLastCookie,
      UpdateMsg...messages) throws Exception
  {
    Pair<ReplicationBroker, LDAPReplicationDomain> replicationObjects = null;
    try
    {
      replicationObjects = enableReplication(ROOT_DN_OTEST, SERVER_ID_1, replicationServerPort, brokerSessionTimeout);
      replicationObjects = enableReplication(baseDN, serverId, replicationServerPort, brokerSessionTimeout);
      ReplicationBroker broker = replicationObjects.getFirst();
      String cookie = "";
      for (UpdateMsg msg : messages)
      {
        debugInfo(testName, " publishes " + msg.getCSN());
        broker.publish(msg);
        if (checkLastCookie)
        {
          cookie = assertLastCookieDifferentThanLastValue(cookie);
        }
      }
    }
    finally
@@ -501,26 +817,14 @@
    }
  }
  private void searchChangesForEachOperationTypeUsingEmptyCookie(CSN[] csns, String testName) throws Exception
  private String[] buildCookiesFromCsns(CSN[] csns)
  {
    int nbEntries = 4;
    String cookie= "";
    InternalSearchOperation searchOp =
        searchChangelogUsingCookie("(targetdn=*" + testName + "*,o=test)", cookie, nbEntries, SUCCESS, testName);
    final String[] cookies = new String[nbEntries];
    final String[] cookies = new String[csns.length];
    for (int j = 0; j < cookies.length; j++)
    {
      cookies[j] = "o=test:" + csns[j] + ";";
    }
    final List<SearchResultEntry> searchEntries = searchOp.getSearchEntries();
    assertDelEntry(searchEntries.get(0), testName + 1, testName + "uuid1", CHANGENUMBER_ZERO, csns[0], cookies[0]);
    assertAddEntry(searchEntries.get(1), testName + 2, USER1_ENTRY_UUID, CHANGENUMBER_ZERO, csns[1], cookies[1]);
    assertModEntry(searchEntries.get(2), testName + 3, testName + "uuid3", CHANGENUMBER_ZERO, csns[2], cookies[2]);
    assertModDNEntry(searchEntries.get(3), testName + 4, testName + "new4", testName+"uuid4", CHANGENUMBER_ZERO,
        csns[3], cookies[3]);
    assertResultsContainCookieControl(searchOp, cookies);
    return cookies;
  }
  private void searchChangesForEachOperationTypeUsingDraftMode(long firstChangeNumber, CSN[] csns, String testName)
@@ -954,6 +1258,53 @@
  }
  /**
   * Creates a memory backend, to be used as additional backend in tests.
   */
  private static Backend<?> initializeMemoryBackend(boolean createBaseEntry, String backendId) throws Exception
  {
    DN baseDN = DN.decode("o=" + backendId);
    //  Retrieve backend. Warning: it is important to perform this each time,
    //  because a test may have disabled then enabled the backend (i.e a test
    //  performing an import task). As it is a memory backend, when the backend
    //  is re-enabled, a new backend object is in fact created and old reference
    //  to memory backend must be invalidated. So to prevent this problem, we
    //  retrieve the memory backend reference each time before cleaning it.
    MemoryBackend memoryBackend = (MemoryBackend) DirectoryServer.getBackend(backendId);
    if (memoryBackend == null)
    {
      memoryBackend = new MemoryBackend();
      memoryBackend.setBackendID(backendId);
      memoryBackend.setBaseDNs(new DN[] {baseDN});
      memoryBackend.initializeBackend();
      DirectoryServer.registerBackend(memoryBackend);
    }
    memoryBackend.clearMemoryBackend();
    if (createBaseEntry)
    {
      memoryBackend.addEntry(createEntry(baseDN), null);
    }
    return memoryBackend;
  }
  private static void removeBackend(Backend<?>... backends)
  {
    for (Backend<?> backend : backends)
    {
      if (backend != null)
      {
        MemoryBackend memoryBackend = (MemoryBackend) backend;
        memoryBackend.clearMemoryBackend();
        memoryBackend.finalizeBackend();
        DirectoryServer.deregisterBackend(memoryBackend);
      }
    }
  }
  /**
   * Utility - log debug message - highlight it is from the test and not
   * from the server code. Makes easier to observe the test steps.
   */