From d79ffa0d51c8de927346b74bb413d1f1455d4028 Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Tue, 13 Feb 2007 01:37:52 +0000
Subject: [PATCH] Fix a bug in the LDIFDiff tool that could cause it to miss added or deleted entries under certain conditions.  Also, add a number of test cases to cover the LDIFDiff tool.

---
 opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries-reverse-singlevalue.ldif  |   76 +++
 opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-emptytosingle.ldif                        |    7 
 opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/target-multipleentries.ldif                    |   32 +
 opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/target-singleentry.ldif                        |    6 
 opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-nochanges.ldif                            |    1 
 opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries-reverse.ldif              |   58 ++
 opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/source-empty.ldif                              |    0 
 opendj-sdk/opends/src/server/org/opends/server/tools/LDIFModify.java                                        |  109 ++--
 opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java                                    |   16 
 opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries.ldif                      |   58 ++
 opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singletomultiple-reverse.ldif             |   29 +
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDIFDiffTestCase.java          |  612 ++++++++++++++++++++++++
 opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/target-empty.ldif                              |    0 
 opendj-sdk/opends/src/server/org/opends/server/tools/LDIFDiff.java                                          |  130 +++--
 opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipletosingle-reverse.ldif             |   42 +
 opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singletoempty.ldif                        |    6 
 opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singleentry.ldif                          |    5 
 opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipletosingle.ldif                     |   29 +
 opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singletomultiple-singlevalue.ldif         |   60 ++
 opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/source-singleentry.ldif                        |    5 
 opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries-singlevalue.ldif          |   76 +++
 opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/source-multipleentries.ldif                    |   26 +
 opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singletomultiple.ldif                     |   42 +
 opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singleentry-reverse.ldif                  |    5 
 opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipletosingle-reverse-singlevalue.ldif |   60 ++
 25 files changed, 1,389 insertions(+), 101 deletions(-)

diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java b/opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java
index 831cad5..1791dba 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java
@@ -869,6 +869,22 @@
 
 
   /**
+   * Retrieves the path to the configuration file used to initialize the
+   * Directory Server.
+   *
+   * @return  The path to the configuration file used to initialize the
+   *          Directory Server.
+   */
+  public static String getConfigFile()
+  {
+    assert debugEnter(CLASS_NAME, "getConfigFile");
+
+    return directoryServer.configFile;
+  }
+
+
+
+  /**
    * Starts up the Directory Server.  It must have already been bootstrapped
    * and cannot be running.
    *
diff --git a/opendj-sdk/opends/src/server/org/opends/server/tools/LDIFDiff.java b/opendj-sdk/opends/src/server/org/opends/server/tools/LDIFDiff.java
index bac6ef9..3ace5ef 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/tools/LDIFDiff.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/tools/LDIFDiff.java
@@ -103,7 +103,7 @@
    */
   public static void main(String[] args)
   {
-    int exitCode = mainDiff(args);
+    int exitCode = mainDiff(args, false);
     if (exitCode != 0)
     {
       System.exit(exitCode);
@@ -116,13 +116,17 @@
    * Parses the provided command line arguments and performs the appropriate
    * LDIF diff operation.
    *
-   * @param  args  The command line arguments provided to this program.
+   * @param  args               The command line arguments provided to this
+   *                            program.
+   * @param  serverInitialized  Indicates whether the Directory Server has
+   *                            already been initialized (and therefore should
+   *                            not be initialized a second time).
    *
    * @return  The return code for this operation.  A value of zero indicates
    *          that all processing completed successfully.  A nonzero value
    *          indicates that some problem occurred during processing.
    */
-  public static int mainDiff(String[] args)
+  public static int mainDiff(String[] args, boolean serverInitialized)
   {
     BooleanArgument overwriteExisting;
     BooleanArgument showUsage;
@@ -215,57 +219,60 @@
     }
 
 
-    // Bootstrap the Directory Server configuration for use as a client.
-    DirectoryServer directoryServer = DirectoryServer.getInstance();
-    directoryServer.bootstrapClient();
-
-
-    // If we're to use the configuration then initialize it, along with the
-    // schema.
     boolean checkSchema = configFile.isPresent();
-    if (checkSchema)
+    if (! serverInitialized)
     {
-      try
-      {
-        directoryServer.initializeJMX();
-      }
-      catch (Exception e)
-      {
-        int    msgID   = MSGID_LDIFDIFF_CANNOT_INITIALIZE_JMX;
-        String message = getMessage(msgID,
-                                    String.valueOf(configFile.getValue()),
-                                    e.getMessage());
-        System.err.println(message);
-        return 1;
-      }
+      // Bootstrap the Directory Server configuration for use as a client.
+      DirectoryServer directoryServer = DirectoryServer.getInstance();
+      directoryServer.bootstrapClient();
 
-      try
-      {
-        directoryServer.initializeConfiguration(configClass.getValue(),
-                                                configFile.getValue());
-      }
-      catch (Exception e)
-      {
-        int    msgID   = MSGID_LDIFDIFF_CANNOT_INITIALIZE_CONFIG;
-        String message = getMessage(msgID,
-                                    String.valueOf(configFile.getValue()),
-                                    e.getMessage());
-        System.err.println(message);
-        return 1;
-      }
 
-      try
+      // If we're to use the configuration then initialize it, along with the
+      // schema.
+      if (checkSchema)
       {
-        directoryServer.initializeSchema();
-      }
-      catch (Exception e)
-      {
-        int    msgID   = MSGID_LDIFDIFF_CANNOT_INITIALIZE_SCHEMA;
-        String message = getMessage(msgID,
-                                    String.valueOf(configFile.getValue()),
-                                    e.getMessage());
-        System.err.println(message);
-        return 1;
+        try
+        {
+          directoryServer.initializeJMX();
+        }
+        catch (Exception e)
+        {
+          int    msgID   = MSGID_LDIFDIFF_CANNOT_INITIALIZE_JMX;
+          String message = getMessage(msgID,
+                                      String.valueOf(configFile.getValue()),
+                                      e.getMessage());
+          System.err.println(message);
+          return 1;
+        }
+
+        try
+        {
+          directoryServer.initializeConfiguration(configClass.getValue(),
+                                                  configFile.getValue());
+        }
+        catch (Exception e)
+        {
+          int    msgID   = MSGID_LDIFDIFF_CANNOT_INITIALIZE_CONFIG;
+          String message = getMessage(msgID,
+                                      String.valueOf(configFile.getValue()),
+                                      e.getMessage());
+          System.err.println(message);
+          return 1;
+        }
+
+        try
+        {
+          directoryServer.initializeSchema();
+        }
+        catch (Exception e)
+        {
+          int    msgID   = MSGID_LDIFDIFF_CANNOT_INITIALIZE_SCHEMA;
+          String message = getMessage(msgID,
+                                      String.valueOf(configFile.getValue()),
+                                      e.getMessage());
+          System.err.println(message);
+          return 1;
+        }
       }
     }
 
@@ -427,7 +434,7 @@
         Iterator<DN> sourceIterator = sourceMap.keySet().iterator();
         while (sourceIterator.hasNext())
         {
-          writeDelete(writer, targetMap.get(sourceIterator.next()));
+          writeDelete(writer, sourceMap.get(sourceIterator.next()));
         }
         return 0;
       }
@@ -461,6 +468,18 @@
             }
             else
             {
+              // There are no more source entries, so if there are more target
+              // entries then they're all adds.
+              writeAdd(writer, targetEntry);
+
+              while (targetIterator.hasNext())
+              {
+                targetDN    = targetIterator.next();
+                targetEntry = targetMap.get(targetDN);
+                writeAdd(writer, targetEntry);
+                differenceFound = true;
+              }
+
               break;
             }
           }
@@ -477,6 +496,17 @@
             }
             else
             {
+              // There are no more target entries so all of the remaining source
+              // entries are deletes.
+              writeDelete(writer, sourceEntry);
+              differenceFound = true;
+              while (sourceIterator.hasNext())
+              {
+                sourceDN = sourceIterator.next();
+                sourceEntry = sourceMap.get(sourceDN);
+                writeDelete(writer, sourceEntry);
+              }
+
               break;
             }
           }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/tools/LDIFModify.java b/opendj-sdk/opends/src/server/org/opends/server/tools/LDIFModify.java
index 979adca..e00f2b1 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/tools/LDIFModify.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/tools/LDIFModify.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.tools;
 
@@ -394,7 +394,7 @@
    */
   public static void main(String[] args)
   {
-    int returnCode = ldifModifyMain(args);
+    int returnCode = ldifModifyMain(args, false);
     if (returnCode != 0)
     {
       System.exit(returnCode);
@@ -407,12 +407,16 @@
    * Processes the command-line arguments and makes the appropriate updates to
    * the LDIF file.
    *
-   * @param  args  The command-line arguments provided to the client.
+   * @param  args               The command line arguments provided to this
+   *                            program.
+   * @param  serverInitialized  Indicates whether the Directory Server has
+   *                            already been initialized (and therefore should
+   *                            not be initialized a second time).
    *
    * @return  A value of zero if everything completed properly, or nonzero if
    *          any problem(s) occurred.
    */
-  public static int ldifModifyMain(String[] args)
+  public static int ldifModifyMain(String[] args, boolean serverInitialized)
   {
     // Prepare the argument parser.
     BooleanArgument showUsage;
@@ -498,57 +502,60 @@
     }
 
 
-    // Bootstrap the Directory Server configuration for use as a client.
-    DirectoryServer directoryServer = DirectoryServer.getInstance();
-    directoryServer.bootstrapClient();
-
-
-    // If we're to use the configuration then initialize it, along with the
-    // schema.
-    boolean checkSchema = configFile.isPresent();
-    if (checkSchema)
+    if (! serverInitialized)
     {
-      try
-      {
-        directoryServer.initializeJMX();
-      }
-      catch (Exception e)
-      {
-        int    msgID   = MSGID_LDIFMODIFY_CANNOT_INITIALIZE_JMX;
-        String message = getMessage(msgID,
-                                    String.valueOf(configFile.getValue()),
-                                    e.getMessage());
-        System.err.println(message);
-        return 1;
-      }
+      // Bootstrap the Directory Server configuration for use as a client.
+      DirectoryServer directoryServer = DirectoryServer.getInstance();
+      directoryServer.bootstrapClient();
 
-      try
-      {
-        directoryServer.initializeConfiguration(configClass.getValue(),
-                                                configFile.getValue());
-      }
-      catch (Exception e)
-      {
-        int    msgID   = MSGID_LDIFMODIFY_CANNOT_INITIALIZE_CONFIG;
-        String message = getMessage(msgID,
-                                    String.valueOf(configFile.getValue()),
-                                    e.getMessage());
-        System.err.println(message);
-        return 1;
-      }
 
-      try
+      // If we're to use the configuration then initialize it, along with the
+      // schema.
+      boolean checkSchema = configFile.isPresent();
+      if (checkSchema)
       {
-        directoryServer.initializeSchema();
-      }
-      catch (Exception e)
-      {
-        int    msgID   = MSGID_LDIFMODIFY_CANNOT_INITIALIZE_SCHEMA;
-        String message = getMessage(msgID,
-                                    String.valueOf(configFile.getValue()),
-                                    e.getMessage());
-        System.err.println(message);
-        return 1;
+        try
+        {
+          directoryServer.initializeJMX();
+        }
+        catch (Exception e)
+        {
+          int    msgID   = MSGID_LDIFMODIFY_CANNOT_INITIALIZE_JMX;
+          String message = getMessage(msgID,
+                                      String.valueOf(configFile.getValue()),
+                                      e.getMessage());
+          System.err.println(message);
+          return 1;
+        }
+
+        try
+        {
+          directoryServer.initializeConfiguration(configClass.getValue(),
+                                                  configFile.getValue());
+        }
+        catch (Exception e)
+        {
+          int    msgID   = MSGID_LDIFMODIFY_CANNOT_INITIALIZE_CONFIG;
+          String message = getMessage(msgID,
+                                      String.valueOf(configFile.getValue()),
+                                      e.getMessage());
+          System.err.println(message);
+          return 1;
+        }
+
+        try
+        {
+          directoryServer.initializeSchema();
+        }
+        catch (Exception e)
+        {
+          int    msgID   = MSGID_LDIFMODIFY_CANNOT_INITIALIZE_SCHEMA;
+          String message = getMessage(msgID,
+                                      String.valueOf(configFile.getValue()),
+                                      e.getMessage());
+          System.err.println(message);
+          return 1;
+        }
       }
     }
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-emptytosingle.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-emptytosingle.ldif
new file mode 100644
index 0000000..73edf02
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-emptytosingle.ldif
@@ -0,0 +1,7 @@
+dn: dc=example,dc=com
+changetype: add
+objectClass: top
+objectClass: domain
+dc: example
+description: test
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries-reverse-singlevalue.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries-reverse-singlevalue.ldif
new file mode 100644
index 0000000..702620a
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries-reverse-singlevalue.ldif
@@ -0,0 +1,76 @@
+dn: dc=example,dc=com
+changetype: modify
+delete: objectClass
+objectClass: organization
+
+dn: dc=example,dc=com
+changetype: modify
+delete: objectClass
+objectClass: dcObject
+
+dn: dc=example,dc=com
+changetype: modify
+add: objectClass
+objectClass: domain
+
+dn: dc=example,dc=com
+changetype: modify
+delete: o
+o: Example Corp.
+
+dn: dc=example,dc=com
+changetype: modify
+delete: description
+description: description 1
+
+dn: dc=example,dc=com
+changetype: modify
+delete: description
+description: description 2
+
+dn: dc=example,dc=com
+changetype: modify
+delete: description
+description: description 3
+
+dn: ou=Applications,dc=example,dc=com
+changetype: delete
+# objectClass: top
+# objectClass: organizationalUnit
+# ou: Applications
+
+dn: ou=Groups,dc=example,dc=com
+changetype: add
+objectClass: top
+objectClass: organizationalUnit
+ou: Groups
+
+dn: ou=People,dc=example,dc=com
+changetype: modify
+delete: description
+description: This is where you put the people
+
+dn: cn=Test User,ou=People,dc=example,dc=com
+changetype: delete
+# objectClass: inetOrgPerson
+# objectClass: person
+# objectClass: top
+# objectClass: organizationalPerson
+# cn: Test User
+# givenName: Test
+# sn: User
+# uid: test.user
+# userPassword: password
+
+dn: uid=test.user,ou=People,dc=example,dc=com
+changetype: add
+objectClass: inetOrgPerson
+objectClass: person
+objectClass: top
+objectClass: organizationalPerson
+cn: Test User
+givenName: Test
+sn: User
+uid: test.user
+userPassword: password
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries-reverse.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries-reverse.ldif
new file mode 100644
index 0000000..0d85be3
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries-reverse.ldif
@@ -0,0 +1,58 @@
+dn: dc=example,dc=com
+changetype: modify
+delete: objectClass
+objectClass: organization
+objectClass: dcObject
+-
+add: objectClass
+objectClass: domain
+-
+delete: o
+o: Example Corp.
+-
+delete: description
+description: description 1
+description: description 2
+description: description 3
+
+dn: ou=Applications,dc=example,dc=com
+changetype: delete
+# objectClass: top
+# objectClass: organizationalUnit
+# ou: Applications
+
+dn: ou=Groups,dc=example,dc=com
+changetype: add
+objectClass: top
+objectClass: organizationalUnit
+ou: Groups
+
+dn: ou=People,dc=example,dc=com
+changetype: modify
+delete: description
+description: This is where you put the people
+
+dn: cn=Test User,ou=People,dc=example,dc=com
+changetype: delete
+# objectClass: inetOrgPerson
+# objectClass: person
+# objectClass: top
+# objectClass: organizationalPerson
+# cn: Test User
+# givenName: Test
+# sn: User
+# uid: test.user
+# userPassword: password
+
+dn: uid=test.user,ou=People,dc=example,dc=com
+changetype: add
+objectClass: inetOrgPerson
+objectClass: person
+objectClass: top
+objectClass: organizationalPerson
+cn: Test User
+givenName: Test
+sn: User
+uid: test.user
+userPassword: password
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries-singlevalue.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries-singlevalue.ldif
new file mode 100644
index 0000000..5d651f5
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries-singlevalue.ldif
@@ -0,0 +1,76 @@
+dn: dc=example,dc=com
+changetype: modify
+delete: objectClass
+objectClass: domain
+
+dn: dc=example,dc=com
+changetype: modify
+add: objectClass
+objectClass: organization
+
+dn: dc=example,dc=com
+changetype: modify
+add: objectClass
+objectClass: dcObject
+
+dn: dc=example,dc=com
+changetype: modify
+add: o
+o: Example Corp.
+
+dn: dc=example,dc=com
+changetype: modify
+add: description
+description: description 1
+
+dn: dc=example,dc=com
+changetype: modify
+add: description
+description: description 2
+
+dn: dc=example,dc=com
+changetype: modify
+add: description
+description: description 3
+
+dn: ou=Applications,dc=example,dc=com
+changetype: add
+objectClass: top
+objectClass: organizationalUnit
+ou: Applications
+
+dn: ou=Groups,dc=example,dc=com
+changetype: delete
+# objectClass: top
+# objectClass: organizationalUnit
+# ou: Groups
+
+dn: ou=People,dc=example,dc=com
+changetype: modify
+add: description
+description: This is where you put the people
+
+dn: cn=Test User,ou=People,dc=example,dc=com
+changetype: add
+objectClass: inetOrgPerson
+objectClass: person
+objectClass: top
+objectClass: organizationalPerson
+cn: Test User
+givenName: Test
+sn: User
+uid: test.user
+userPassword: password
+
+dn: uid=test.user,ou=People,dc=example,dc=com
+changetype: delete
+# objectClass: inetOrgPerson
+# objectClass: person
+# objectClass: top
+# objectClass: organizationalPerson
+# cn: Test User
+# givenName: Test
+# sn: User
+# uid: test.user
+# userPassword: password
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries.ldif
new file mode 100644
index 0000000..1b6b331
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries.ldif
@@ -0,0 +1,58 @@
+dn: dc=example,dc=com
+changetype: modify
+delete: objectClass
+objectClass: domain
+-
+add: objectClass
+objectClass: organization
+objectClass: dcObject
+-
+add: o
+o: Example Corp.
+-
+add: description
+description: description 1
+description: description 2
+description: description 3
+
+dn: ou=Applications,dc=example,dc=com
+changetype: add
+objectClass: top
+objectClass: organizationalUnit
+ou: Applications
+
+dn: ou=Groups,dc=example,dc=com
+changetype: delete
+# objectClass: top
+# objectClass: organizationalUnit
+# ou: Groups
+
+dn: ou=People,dc=example,dc=com
+changetype: modify
+add: description
+description: This is where you put the people
+
+dn: cn=Test User,ou=People,dc=example,dc=com
+changetype: add
+objectClass: inetOrgPerson
+objectClass: person
+objectClass: top
+objectClass: organizationalPerson
+cn: Test User
+givenName: Test
+sn: User
+uid: test.user
+userPassword: password
+
+dn: uid=test.user,ou=People,dc=example,dc=com
+changetype: delete
+# objectClass: inetOrgPerson
+# objectClass: person
+# objectClass: top
+# objectClass: organizationalPerson
+# cn: Test User
+# givenName: Test
+# sn: User
+# uid: test.user
+# userPassword: password
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipletosingle-reverse-singlevalue.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipletosingle-reverse-singlevalue.ldif
new file mode 100644
index 0000000..bf64dab
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipletosingle-reverse-singlevalue.ldif
@@ -0,0 +1,60 @@
+dn: dc=example,dc=com
+changetype: modify
+delete: objectClass
+objectClass: organization
+
+dn: dc=example,dc=com
+changetype: modify
+delete: objectClass
+objectClass: dcObject
+
+dn: dc=example,dc=com
+changetype: modify
+add: objectClass
+objectClass: domain
+
+dn: dc=example,dc=com
+changetype: modify
+delete: o
+o: Example Corp.
+
+dn: dc=example,dc=com
+changetype: modify
+delete: description
+description: description 1
+
+dn: dc=example,dc=com
+changetype: modify
+delete: description
+description: description 2
+
+dn: dc=example,dc=com
+changetype: modify
+delete: description
+description: description 3
+
+dn: ou=Applications,dc=example,dc=com
+changetype: delete
+# objectClass: top
+# objectClass: organizationalUnit
+# ou: Applications
+
+dn: ou=People,dc=example,dc=com
+changetype: delete
+# objectClass: top
+# objectClass: organizationalUnit
+# ou: People
+# description: This is where you put the people
+
+dn: cn=Test User,ou=People,dc=example,dc=com
+changetype: delete
+# objectClass: inetOrgPerson
+# objectClass: person
+# objectClass: top
+# objectClass: organizationalPerson
+# cn: Test User
+# givenName: Test
+# sn: User
+# uid: test.user
+# userPassword: password
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipletosingle-reverse.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipletosingle-reverse.ldif
new file mode 100644
index 0000000..1ed6adf
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipletosingle-reverse.ldif
@@ -0,0 +1,42 @@
+dn: dc=example,dc=com
+changetype: modify
+delete: objectClass
+objectClass: organization
+objectClass: dcObject
+-
+add: objectClass
+objectClass: domain
+-
+delete: o
+o: Example Corp.
+-
+delete: description
+description: description 1
+description: description 2
+description: description 3
+
+dn: ou=Applications,dc=example,dc=com
+changetype: delete
+# objectClass: top
+# objectClass: organizationalUnit
+# ou: Applications
+
+dn: ou=People,dc=example,dc=com
+changetype: delete
+# objectClass: top
+# objectClass: organizationalUnit
+# ou: People
+# description: This is where you put the people
+
+dn: cn=Test User,ou=People,dc=example,dc=com
+changetype: delete
+# objectClass: inetOrgPerson
+# objectClass: person
+# objectClass: top
+# objectClass: organizationalPerson
+# cn: Test User
+# givenName: Test
+# sn: User
+# uid: test.user
+# userPassword: password
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipletosingle.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipletosingle.ldif
new file mode 100644
index 0000000..d2ec40b
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipletosingle.ldif
@@ -0,0 +1,29 @@
+dn: dc=example,dc=com
+changetype: modify
+add: description
+description: test
+
+dn: ou=Groups,dc=example,dc=com
+changetype: delete
+# objectClass: top
+# objectClass: organizationalUnit
+# ou: Groups
+
+dn: ou=People,dc=example,dc=com
+changetype: delete
+# objectClass: top
+# objectClass: organizationalUnit
+# ou: People
+
+dn: uid=test.user,ou=People,dc=example,dc=com
+changetype: delete
+# objectClass: inetOrgPerson
+# objectClass: person
+# objectClass: top
+# objectClass: organizationalPerson
+# cn: Test User
+# givenName: Test
+# sn: User
+# uid: test.user
+# userPassword: password
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-nochanges.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-nochanges.ldif
new file mode 100644
index 0000000..f0d759c
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-nochanges.ldif
@@ -0,0 +1 @@
+# No differences were detected between the source and target LDIF files.
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singleentry-reverse.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singleentry-reverse.ldif
new file mode 100644
index 0000000..4959671
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singleentry-reverse.ldif
@@ -0,0 +1,5 @@
+dn: dc=example,dc=com
+changetype: modify
+delete: description
+description: test
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singleentry.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singleentry.ldif
new file mode 100644
index 0000000..3cb38ff
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singleentry.ldif
@@ -0,0 +1,5 @@
+dn: dc=example,dc=com
+changetype: modify
+add: description
+description: test
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singletoempty.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singletoempty.ldif
new file mode 100644
index 0000000..a01b3f9
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singletoempty.ldif
@@ -0,0 +1,6 @@
+dn: dc=example,dc=com
+changetype: delete
+# objectClass: top
+# objectClass: domain
+# dc: example
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singletomultiple-reverse.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singletomultiple-reverse.ldif
new file mode 100644
index 0000000..571916a
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singletomultiple-reverse.ldif
@@ -0,0 +1,29 @@
+dn: dc=example,dc=com
+changetype: modify
+delete: description
+description: test
+
+dn: ou=Groups,dc=example,dc=com
+changetype: add
+objectClass: top
+objectClass: organizationalUnit
+ou: Groups
+
+dn: ou=People,dc=example,dc=com
+changetype: add
+objectClass: top
+objectClass: organizationalUnit
+ou: People
+
+dn: uid=test.user,ou=People,dc=example,dc=com
+changetype: add
+objectClass: inetOrgPerson
+objectClass: person
+objectClass: top
+objectClass: organizationalPerson
+cn: Test User
+givenName: Test
+sn: User
+uid: test.user
+userPassword: password
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singletomultiple-singlevalue.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singletomultiple-singlevalue.ldif
new file mode 100644
index 0000000..d37d9b1
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singletomultiple-singlevalue.ldif
@@ -0,0 +1,60 @@
+dn: dc=example,dc=com
+changetype: modify
+delete: objectClass
+objectClass: domain
+
+dn: dc=example,dc=com
+changetype: modify
+add: objectClass
+objectClass: organization
+
+dn: dc=example,dc=com
+changetype: modify
+add: objectClass
+objectClass: dcObject
+
+dn: dc=example,dc=com
+changetype: modify
+add: o
+o: Example Corp.
+
+dn: dc=example,dc=com
+changetype: modify
+add: description
+description: description 1
+
+dn: dc=example,dc=com
+changetype: modify
+add: description
+description: description 2
+
+dn: dc=example,dc=com
+changetype: modify
+add: description
+description: description 3
+
+dn: ou=Applications,dc=example,dc=com
+changetype: add
+objectClass: top
+objectClass: organizationalUnit
+ou: Applications
+
+dn: ou=People,dc=example,dc=com
+changetype: add
+objectClass: top
+objectClass: organizationalUnit
+ou: People
+description: This is where you put the people
+
+dn: cn=Test User,ou=People,dc=example,dc=com
+changetype: add
+objectClass: inetOrgPerson
+objectClass: person
+objectClass: top
+objectClass: organizationalPerson
+cn: Test User
+givenName: Test
+sn: User
+uid: test.user
+userPassword: password
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singletomultiple.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singletomultiple.ldif
new file mode 100644
index 0000000..8b48b71
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-singletomultiple.ldif
@@ -0,0 +1,42 @@
+dn: dc=example,dc=com
+changetype: modify
+delete: objectClass
+objectClass: domain
+-
+add: objectClass
+objectClass: organization
+objectClass: dcObject
+-
+add: o
+o: Example Corp.
+-
+add: description
+description: description 1
+description: description 2
+description: description 3
+
+dn: ou=Applications,dc=example,dc=com
+changetype: add
+objectClass: top
+objectClass: organizationalUnit
+ou: Applications
+
+dn: ou=People,dc=example,dc=com
+changetype: add
+objectClass: top
+objectClass: organizationalUnit
+ou: People
+description: This is where you put the people
+
+dn: cn=Test User,ou=People,dc=example,dc=com
+changetype: add
+objectClass: inetOrgPerson
+objectClass: person
+objectClass: top
+objectClass: organizationalPerson
+cn: Test User
+givenName: Test
+sn: User
+uid: test.user
+userPassword: password
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/source-empty.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/source-empty.ldif
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/source-empty.ldif
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/source-multipleentries.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/source-multipleentries.ldif
new file mode 100644
index 0000000..7696b99
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/source-multipleentries.ldif
@@ -0,0 +1,26 @@
+dn: dc=example,dc=com
+objectClass: top
+objectClass: domain
+dc: example
+
+dn: ou=Groups,dc=example,dc=com
+objectClass: top
+objectClass: organizationalUnit
+ou: Groups
+
+dn: ou=People,dc=example,dc=com
+objectClass: top
+objectClass: organizationalUnit
+ou: People
+
+dn: uid=test.user,ou=People,dc=example,dc=com
+objectClass: top
+objectClass: person
+objectClass: organizationalPerson
+objectClass: inetOrgPerson
+uid: test.user
+givenName: Test
+sn: User
+cn: Test User
+userPassword: password
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/source-singleentry.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/source-singleentry.ldif
new file mode 100644
index 0000000..b2163ea
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/source-singleentry.ldif
@@ -0,0 +1,5 @@
+dn: dc=example,dc=com
+objectClass: top
+objectClass: domain
+dc: example
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/target-empty.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/target-empty.ldif
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/target-empty.ldif
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/target-multipleentries.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/target-multipleentries.ldif
new file mode 100644
index 0000000..56a5d9b
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/target-multipleentries.ldif
@@ -0,0 +1,32 @@
+dn: dc=example,dc=com
+objectClass: top
+objectClass: organization
+objectClass: dcObject
+dc: example
+o: Example Corp.
+description: description 1
+description: description 2
+description: description 3
+
+dn: ou=Applications,dc=example,dc=com
+objectClass: top
+objectClass: organizationalUnit
+ou: Applications
+
+dn: ou=People,dc=example,dc=com
+objectClass: top
+objectClass: organizationalUnit
+ou: People
+description: This is where you put the people
+
+dn: cn=Test User,ou=People,dc=example,dc=com
+objectClass: top
+objectClass: person
+objectClass: organizationalPerson
+objectClass: inetOrgPerson
+uid: test.user
+givenName: Test
+sn: User
+cn: Test User
+userPassword: password
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/target-singleentry.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/target-singleentry.ldif
new file mode 100644
index 0000000..d2c5328
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/target-singleentry.ldif
@@ -0,0 +1,6 @@
+dn: dc=example,dc=com
+objectClass: top
+objectClass: domain
+dc: example
+description: test
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDIFDiffTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDIFDiffTestCase.java
new file mode 100644
index 0000000..8c0f7d3
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDIFDiffTestCase.java
@@ -0,0 +1,612 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.tools;
+
+
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import org.opends.server.TestCaseUtils;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.util.Base64;
+
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.*;
+
+import static org.testng.Assert.*;
+
+
+
+/**
+ * A set of test cases for the LDIFDiff tool.
+ */
+public class LDIFDiffTestCase
+       extends ToolsTestCase
+{
+  // The path to the file that will be used if there are no differences between
+  // the source and target LDIF data sets.
+  private String noDiffsFile =
+       System.getProperty(TestCaseUtils.PROPERTY_BUILD_ROOT) + File.separator +
+       "tests" + File.separator + "unit-tests-testng" + File.separator +
+       "resource" + File.separator + "ldif-diff" + File.separator +
+       "diff-nochanges.ldif";
+
+
+
+  /**
+   * Make sure that the server is running, since we need it for schema
+   * handling.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @BeforeClass()
+  public void startServer()
+         throws Exception
+  {
+    TestCaseUtils.startServer();
+  }
+
+
+
+  /**
+   * Tests the LDIFDiff tool with an argument that will simply cause it to
+   * display usage information.
+   */
+  @Test()
+  public void testUsage()
+  {
+    String[] args =
+    {
+      "--help"
+    };
+
+    assertEquals(LDIFDiff.mainDiff(args, true), 0);
+  }
+
+
+
+  /**
+   * Tests the LDIFDiff tool with an invalid set of arguments.
+   */
+  @Test()
+  public void testInvalidArguments()
+  {
+    String[] args =
+    {
+      "--invalid"
+    };
+
+    assertFalse(LDIFDiff.mainDiff(args, true) == 0);
+  }
+
+
+
+  /**
+   * Retrieves the names of the files that should be used when testing the
+   * ldif-diff tool.  Each element of the outer array should be an array
+   * containing the following elements:
+   * <OL>
+   *   <LI>The path to the source LDIF file</LI>
+   *   <LI>The path to the target LDIF file</LI>
+   *   <LI>The path to the diff file, or {@code null} if the diff is supposed
+   *       to fail</LI>
+   * </OL>
+   */
+  @DataProvider(name = "testdata")
+  public Object[][] getTestData()
+  {
+    String buildRoot = System.getProperty(TestCaseUtils.PROPERTY_BUILD_ROOT);
+    String ldifRoot  = buildRoot + File.separator + "tests" + File.separator +
+                       "unit-tests-testng" + File.separator + "resource" +
+                       File.separator + "ldif-diff" + File.separator;
+
+    return new Object[][]
+    {
+      // Both files are empty.
+      new Object[] { ldifRoot + "source-empty.ldif",
+                     ldifRoot + "target-empty.ldif",
+                     noDiffsFile, noDiffsFile },
+
+      // Both files are the single-entry source.
+      new Object[] { ldifRoot + "source-singleentry.ldif",
+                     ldifRoot + "source-singleentry.ldif",
+                     noDiffsFile, noDiffsFile },
+
+      // Both files are the single-entry target.
+      new Object[] { ldifRoot + "target-singleentry.ldif",
+                     ldifRoot + "target-singleentry.ldif",
+                     noDiffsFile, noDiffsFile },
+
+      // Both files are the multiple-entry source.
+      new Object[] { ldifRoot + "source-multipleentries.ldif",
+                     ldifRoot + "source-multipleentries.ldif",
+                     noDiffsFile, noDiffsFile },
+
+      // Both files are the multiple-entry target.
+      new Object[] { ldifRoot + "target-multipleentries.ldif",
+                     ldifRoot + "target-multipleentries.ldif",
+                     noDiffsFile, noDiffsFile },
+
+      // The source is empty but the target has a single entry.
+      new Object[] { ldifRoot + "source-empty.ldif",
+                     ldifRoot + "target-singleentry.ldif",
+                     ldifRoot + "diff-emptytosingle.ldif",
+                     ldifRoot + "diff-emptytosingle.ldif" },
+
+      // The source has a single entry but the target is empty.
+      new Object[] { ldifRoot + "source-singleentry.ldif",
+                     ldifRoot + "target-empty.ldif",
+                     ldifRoot + "diff-singletoempty.ldif",
+                     ldifRoot + "diff-singletoempty.ldif" },
+
+      // Make a change to only a single entry in the source->target direction.
+      new Object[] { ldifRoot + "source-singleentry.ldif",
+                     ldifRoot + "target-singleentry.ldif",
+                     ldifRoot + "diff-singleentry.ldif",
+                     ldifRoot + "diff-singleentry.ldif" },
+
+      // Make a change to only a single entry in the target->source direction.
+      new Object[] { ldifRoot + "target-singleentry.ldif",
+                     ldifRoot + "source-singleentry.ldif",
+                     ldifRoot + "diff-singleentry-reverse.ldif",
+                     ldifRoot + "diff-singleentry-reverse.ldif" },
+
+      // Make changes to multiple entries in the source->target direction.
+      new Object[] { ldifRoot + "source-multipleentries.ldif",
+                     ldifRoot + "target-multipleentries.ldif",
+                     ldifRoot + "diff-multipleentries.ldif",
+                     ldifRoot + "diff-multipleentries-singlevalue.ldif" },
+
+      // Make changes to multiple entries in the target->source direction.
+      new Object[] { ldifRoot + "target-multipleentries.ldif",
+                     ldifRoot + "source-multipleentries.ldif",
+                     ldifRoot + "diff-multipleentries-reverse.ldif",
+                     ldifRoot +
+                          "diff-multipleentries-reverse-singlevalue.ldif" },
+
+      // Go from one entry to multiple in the source->target direction.
+      new Object[] { ldifRoot + "source-singleentry.ldif",
+                     ldifRoot + "target-multipleentries.ldif",
+                     ldifRoot + "diff-singletomultiple.ldif",
+                     ldifRoot + "diff-singletomultiple-singlevalue.ldif" },
+
+      // Go from one entry to multiple in the target->source direction.
+      new Object[] { ldifRoot + "target-singleentry.ldif",
+                     ldifRoot + "source-multipleentries.ldif",
+                     ldifRoot + "diff-singletomultiple-reverse.ldif",
+                     ldifRoot + "diff-singletomultiple-reverse.ldif" },
+
+      // Go from multiple entries to one in the source->target direction.
+      new Object[] { ldifRoot + "source-multipleentries.ldif",
+                     ldifRoot + "target-singleentry.ldif",
+                     ldifRoot + "diff-multipletosingle.ldif",
+                     ldifRoot + "diff-multipletosingle.ldif" },
+
+      // Go from multiple entries to one in the target->source direction.
+      new Object[] { ldifRoot + "target-multipleentries.ldif",
+                     ldifRoot + "source-singleentry.ldif",
+                     ldifRoot + "diff-multipletosingle-reverse.ldif",
+                     ldifRoot +
+                          "diff-multipletosingle-reverse-singlevalue.ldif" },
+
+      // The source file doesn't exist.
+      new Object[] { ldifRoot + "source-notfound.ldif",
+                     ldifRoot + "target-singleentry.ldif",
+                     null, null },
+
+      // The target file doesn't exist.
+      new Object[] { ldifRoot + "source-singleentry.ldif",
+                     ldifRoot + "target-notfound.ldif",
+                     null, null }
+    };
+  }
+
+
+
+
+  /**
+   * Tests the LDIFDiff tool with the provided information to ensure that the
+   * normal mode of operation works as expected.  This is a bit tricky because
+   * the attributes and values will be written in an indeterminite order, so we
+   * can't just use string equality.  We'll have to use a crude checksum
+   * mechanism to test whether they are equal.  Combined with other methods in
+   * this class, this should be good enough.
+   *
+   * @param  sourceFile           The path to the file containing the source
+   *                              data set.
+   * @param  targetFile           The path to the file containing the target
+   *                              data set.
+   * @param  normalDiffFile       The path to the file containing the expected
+   *                              diff in "normal" form (at most one record per
+   *                              entry), or {@code null} if the diff is
+   *                              supposed to fail.
+   * @param  singleValueDiffFile  The path to the file containing the expected
+   *                              diff in "single-value" form, where each
+   *                              attribute-level change results in a separate
+   *                              entry per attribute value.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata")
+  public void testVerifyNormal(String sourceFile, String targetFile,
+                               String normalDiffFile,
+                               String singleValueDiffFile)
+         throws Exception
+  {
+    File outputFile = File.createTempFile("difftest", "ldif");
+    outputFile.deleteOnExit();
+
+    String[] args =
+    {
+      "-s", sourceFile,
+      "-t", targetFile,
+      "-o", outputFile.getAbsolutePath(),
+      "-O"
+    };
+
+    if (normalDiffFile == null)
+    {
+      // We expect this to fail, so just make sure that it does.
+      assertFalse(LDIFDiff.mainDiff(args, true) == 0);
+      outputFile.delete();
+      return;
+    }
+
+    assertEquals(LDIFDiff.mainDiff(args, true), 0);
+
+    long outputChecksum = 0L;
+    BufferedReader reader = new BufferedReader(new FileReader(outputFile));
+    String line = reader.readLine();
+    while (line != null)
+    {
+      outputChecksum += line.hashCode();
+      line = reader.readLine();
+    }
+    reader.close();
+
+    long expectedChecksum = 0L;
+    reader = new BufferedReader(new FileReader(normalDiffFile));
+    line = reader.readLine();
+    while (line != null)
+    {
+      expectedChecksum += line.hashCode();
+      line = reader.readLine();
+    }
+    reader.close();
+
+    assertEquals(outputChecksum, expectedChecksum);
+
+    outputFile.delete();
+  }
+
+
+
+
+  /**
+   * Tests the LDIFDiff tool with the provided information to ensure that the
+   * single value changes mode of operation works as expected.  This is a bit
+   * tricky because the attributes and values will be written in an
+   * indeterminite order, so we can't just use string equality.  We'll have to
+   * use a crude checksum mechanism to test whether they are equal.  Combined
+   * with other methods in this class, this should be good enough.
+   *
+   * @param  sourceFile           The path to the file containing the source
+   *                              data set.
+   * @param  targetFile           The path to the file containing the target
+   *                              data set.
+   * @param  normalDiffFile       The path to the file containing the expected
+   *                              diff in "normal" form (at most one record per
+   *                              entry), or {@code null} if the diff is
+   *                              supposed to fail.
+   * @param  singleValueDiffFile  The path to the file containing the expected
+   *                              diff in "single-value" form, where each
+   *                              attribute-level change results in a separate
+   *                              entry per attribute value.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata")
+  public void testVerifySingleValue(String sourceFile, String targetFile,
+                                    String normalDiffFile,
+                                    String singleValueDiffFile)
+         throws Exception
+  {
+    File outputFile = File.createTempFile("difftest", "ldif");
+    outputFile.deleteOnExit();
+
+    String[] args =
+    {
+      "-s", sourceFile,
+      "-t", targetFile,
+      "-o", outputFile.getAbsolutePath(),
+      "-O",
+      "-S"
+    };
+
+    if (singleValueDiffFile == null)
+    {
+      // We expect this to fail, so just make sure that it does.
+      assertFalse(LDIFDiff.mainDiff(args, true) == 0);
+      outputFile.delete();
+      return;
+    }
+
+    assertEquals(LDIFDiff.mainDiff(args, true), 0);
+
+    long outputChecksum = 0L;
+    BufferedReader reader = new BufferedReader(new FileReader(outputFile));
+    String line = reader.readLine();
+    while (line != null)
+    {
+      outputChecksum += line.hashCode();
+      line = reader.readLine();
+    }
+    reader.close();
+
+    long expectedChecksum = 0L;
+    reader = new BufferedReader(new FileReader(singleValueDiffFile));
+    line = reader.readLine();
+    while (line != null)
+    {
+      expectedChecksum += line.hashCode();
+      line = reader.readLine();
+    }
+    reader.close();
+
+    assertEquals(outputChecksum, expectedChecksum);
+
+    outputFile.delete();
+  }
+
+
+
+  /**
+   * Tests the LDIFDiff tool by first identifying the differences between the
+   * source and the target and then using the LDIFModify tool to apply the
+   * identified changes to the source LDIF and verify that it matches the
+   * target.
+   *
+   * @param  sourceFile           The path to the file containing the source
+   *                              data set.
+   * @param  targetFile           The path to the file containing the target
+   *                              data set.
+   * @param  normalDiffFile       The path to the file containing the expected
+   *                              diff in "normal" form (at most one record per
+   *                              entry), or {@code null} if the diff is
+   *                              supposed to fail.
+   * @param  singleValueDiffFile  The path to the file containing the expected
+   *                              diff in "single-value" form, where each
+   *                              attribute-level change results in a separate
+   *                              entry per attribute value.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata")
+  public void testReconstructNormal(String sourceFile, String targetFile,
+                                    String normalDiffFile,
+                                    String singleValueDiffFile)
+         throws Exception
+  {
+    // If the command is expected to fail, or if there aren't any differences,
+    // then bail out now.
+    if ((normalDiffFile == null) || normalDiffFile.equals(noDiffsFile))
+    {
+      return;
+    }
+
+
+    // Generate the diff file.
+    File diffOutputFile = File.createTempFile("difftest", "ldif");
+    diffOutputFile.deleteOnExit();
+
+    String[] args =
+    {
+      "-s", sourceFile,
+      "-t", targetFile,
+      "-o", diffOutputFile.getAbsolutePath()
+    };
+
+    assertEquals(LDIFDiff.mainDiff(args, true), 0);
+
+
+    // Use LDIFModify to generate a new target file.
+    File newTargetFile = File.createTempFile("difftest", "newtarget.ldif");
+    newTargetFile.deleteOnExit();
+
+    args = new String[]
+    {
+      "-c", DirectoryServer.getInstance().getConfigFile(),
+      "-s", sourceFile,
+      "-m", diffOutputFile.getAbsolutePath(),
+      "-t", newTargetFile.getAbsolutePath()
+    };
+
+    assertEquals(LDIFModify.ldifModifyMain(args, true), 0);
+
+
+    // Use LDIFDiff again to verify that there are effectively no differences
+    // between the original target and the new target.
+    File newDiffFile = File.createTempFile("difftest", "newdiff.ldif");
+    newDiffFile.deleteOnExit();
+
+    args = new String[]
+    {
+      "-s", targetFile,
+      "-t", newTargetFile.getAbsolutePath(),
+      "-o", newDiffFile.getAbsolutePath()
+    };
+
+    assertEquals(LDIFDiff.mainDiff(args, true), 0);
+
+
+    // Read the contents of the new diff file and make sure it matches the
+    // contents of the "no changes" diff file.
+    long newDiffChecksum = 0L;
+    BufferedReader reader = new BufferedReader(new FileReader(newDiffFile));
+    String line = reader.readLine();
+    while (line != null)
+    {
+      newDiffChecksum += line.hashCode();
+      line = reader.readLine();
+    }
+    reader.close();
+
+    long expectedChecksum = 0L;
+    reader = new BufferedReader(new FileReader(noDiffsFile));
+    line = reader.readLine();
+    while (line != null)
+    {
+      expectedChecksum += line.hashCode();
+      line = reader.readLine();
+    }
+    reader.close();
+
+    assertEquals(newDiffChecksum, expectedChecksum);
+
+    diffOutputFile.delete();
+    newTargetFile.delete();
+    newDiffFile.delete();
+  }
+
+
+
+  /**
+   * Tests the LDIFDiff tool by first identifying the differences between the
+   * source and the target (using the single-value format) and then using the
+   * LDIFModify tool to apply the identified changes to the source LDIF and
+   * verify that it matches the target.
+   *
+   * @param  sourceFile           The path to the file containing the source
+   *                              data set.
+   * @param  targetFile           The path to the file containing the target
+   *                              data set.
+   * @param  normalDiffFile       The path to the file containing the expected
+   *                              diff in "normal" form (at most one record per
+   *                              entry), or {@code null} if the diff is
+   *                              supposed to fail.
+   * @param  singleValueDiffFile  The path to the file containing the expected
+   *                              diff in "single-value" form, where each
+   *                              attribute-level change results in a separate
+   *                              entry per attribute value.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testdata")
+  public void testReconstructSingleValue(String sourceFile, String targetFile,
+                                         String normalDiffFile,
+                                         String singleValueDiffFile)
+         throws Exception
+  {
+    // If the command is expected to fail, or if there aren't any differences,
+    // then bail out now.
+    if ((normalDiffFile == null) || singleValueDiffFile.equals(noDiffsFile))
+    {
+      return;
+    }
+
+
+    // Generate the diff file.
+    File diffOutputFile = File.createTempFile("difftest", "ldif");
+    diffOutputFile.deleteOnExit();
+
+    String[] args =
+    {
+      "-s", sourceFile,
+      "-t", targetFile,
+      "-o", diffOutputFile.getAbsolutePath(),
+      "-S"
+    };
+
+    assertEquals(LDIFDiff.mainDiff(args, true), 0);
+
+
+    // Use LDIFModify to generate a new target file.
+    File newTargetFile = File.createTempFile("difftest", "newtarget.ldif");
+    newTargetFile.deleteOnExit();
+
+    args = new String[]
+    {
+      "-c", DirectoryServer.getInstance().getConfigFile(),
+      "-s", sourceFile,
+      "-m", diffOutputFile.getAbsolutePath(),
+      "-t", newTargetFile.getAbsolutePath()
+    };
+
+    assertEquals(LDIFModify.ldifModifyMain(args, true), 0);
+
+
+    // Use LDIFDiff again to verify that there are effectively no differences
+    // between the original target and the new target.
+    File newDiffFile = File.createTempFile("difftest", "newdiff.ldif");
+    newDiffFile.deleteOnExit();
+
+    args = new String[]
+    {
+      "-s", targetFile,
+      "-t", newTargetFile.getAbsolutePath(),
+      "-o", newDiffFile.getAbsolutePath()
+    };
+
+    assertEquals(LDIFDiff.mainDiff(args, true), 0);
+
+
+    // Read the contents of the new diff file and make sure it matches the
+    // contents of the "no changes" diff file.
+    long newDiffChecksum = 0L;
+    BufferedReader reader = new BufferedReader(new FileReader(newDiffFile));
+    String line = reader.readLine();
+    while (line != null)
+    {
+      newDiffChecksum += line.hashCode();
+      line = reader.readLine();
+    }
+    reader.close();
+
+    long expectedChecksum = 0L;
+    reader = new BufferedReader(new FileReader(noDiffsFile));
+    line = reader.readLine();
+    while (line != null)
+    {
+      expectedChecksum += line.hashCode();
+      line = reader.readLine();
+    }
+    reader.close();
+
+    assertEquals(newDiffChecksum, expectedChecksum);
+
+    diffOutputFile.delete();
+    newTargetFile.delete();
+    newDiffFile.delete();
+  }
+}
+

--
Gitblit v1.10.0