/*
 * Incomplete regression tests for the diff/diff3 library.
 *
 * ====================================================================
 *    Licensed to the Apache Software Foundation (ASF) under one
 *    or more contributor license agreements.  See the NOTICE file
 *    distributed with this work for additional information
 *    regarding copyright ownership.  The ASF licenses this file
 *    to you under the Apache License, Version 2.0 (the
 *    "License"); you may not use this file except in compliance
 *    with the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing,
 *    software distributed under the License is distributed on an
 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *    KIND, either express or implied.  See the License for the
 *    specific language governing permissions and limitations
 *    under the License.
 * ====================================================================
 */


#include "../svn_test.h"

#include "svn_diff.h"
#include "svn_pools.h"
#include "svn_utf.h"

/* Used to terminate lines in large multi-line string literals. */
#define NL APR_EOL_STR

/* Random number seed.  Yes, it's global, just pretend you can't see it. */
static apr_uint32_t diff_diff3_seed;

/* Return the value of the current random number seed, initializing it if
   necessary */
static apr_uint32_t
seed_val(void)
{
  static svn_boolean_t first = TRUE;

  if (first)
    {
      diff_diff3_seed = (apr_uint32_t) apr_time_now();
      first = FALSE;
    }

  return diff_diff3_seed;
}

/* Return a random number N such that MIN_VAL <= N <= MAX_VAL */
static apr_uint32_t
range_rand(apr_uint32_t min_val,
           apr_uint32_t max_val)
{
  apr_uint64_t diff = max_val - min_val;
  apr_uint64_t val = diff * svn_test_rand(&diff_diff3_seed);
  val /= 0xffffffff;
  return min_val + (apr_uint32_t) val;
}

/* Make a file that is between MIN_LINES and MAX_LINES lines long, with at
   most VAR_LINES distinct lines.  If BLOCK_LINES is non-zero then every
   other block of BLOCK_LINES lines will be identical, if BLOCK_LINES is
   zero all lines will have contents chosen at random.  If TRAILING_NEWLINE
   is TRUE then the file will have a trailing newline, if not then it wont. */
static svn_error_t *
make_random_file(const char *filename,
                 int min_lines,
                 int max_lines,
                 int var_lines,
                 int block_lines,
                 svn_boolean_t trailing_newline,
                 apr_pool_t *pool)
{
  apr_file_t *file;
  int num_lines;

  num_lines = range_rand(min_lines, max_lines);

  SVN_ERR(svn_io_file_open(&file, filename,
                           APR_WRITE | APR_CREATE | APR_TRUNCATE,
                           APR_OS_DEFAULT,
                           pool));

  while (num_lines--)
    {
      int x;
      if (! (block_lines && (num_lines / block_lines % 2)))
        x = range_rand(1, var_lines);
      else
        x = 0;
      if (num_lines || trailing_newline)
        apr_file_printf(file, "line %d line %d line %d\n", x, x, x);
      else
        apr_file_printf(file, "line %d line %d line %d", x, x, x);
    }

  SVN_ERR(svn_io_file_close(file, pool));

  return SVN_NO_ERROR;
}


/* Create a file called FILENAME containing CONTENTS */
static svn_error_t *
make_file(const char *filename,
          const char *contents,
          apr_pool_t *pool)
{
  apr_file_t *file;
  apr_status_t status;

  SVN_ERR(svn_io_file_open(&file, filename,
                           APR_WRITE | APR_CREATE | APR_TRUNCATE,
                           APR_OS_DEFAULT, pool));

  status = apr_file_write_full(file, contents, strlen(contents), NULL);
  if (status)
    return svn_error_createf(status, NULL, "failed to write '%s'", filename);

  SVN_ERR(svn_io_file_close(file, pool));

  return SVN_NO_ERROR;
}


/* Create three files called FILENAME1, FILENAME2 and FILENAME3
   containing CONTENTS1, CONTENTS2 and CONTENTS3 respectively.  Run a
   three way merge to merge the difference between CONTENTS1 and
   CONTENTS2 into CONTENTS3, using OPTIONS, and verify that it results
   in EXPECTED.  The files FILENAME1, FILENAME2 and FILENAME3 will be
   deleted if the merge is successful, and preserved otherwise.  If
   the merge fails the merge output will be in a file called
   "merge-FILENAME1-FILENAME2-FILENAME3".  The conflict style STYLE is
   used. */
static svn_error_t *
three_way_merge(const char *base_filename1,
                const char *base_filename2,
                const char *base_filename3,
                const char *contents1,
                const char *contents2,
                const char *contents3,
                const char *expected,
                const svn_diff_file_options_t *options,
                svn_diff_conflict_display_style_t style,
                apr_pool_t *pool)
{
  svn_diff_t *diff;
  apr_file_t *output;
  svn_stream_t *ostream;
  svn_stringbuf_t *actual;
  char *merge_name = apr_psprintf(
      pool, "merge-%s-%s-%s", base_filename1, base_filename2, base_filename3);

  const char *filename1 = svn_test_data_path(base_filename1, pool);
  const char *filename2 = svn_test_data_path(base_filename2, pool);
  const char *filename3 = svn_test_data_path(base_filename3, pool);

  /* We have an EXPECTED string we can match, because we don't support
     any other combinations (yet) than the ones above. */
  svn_string_t *original = svn_string_create(contents1, pool);
  svn_string_t *modified = svn_string_create(contents2, pool);
  svn_string_t *latest = svn_string_create(contents3, pool);

  options = options ? options : svn_diff_file_options_create(pool);

  SVN_ERR(svn_diff_mem_string_diff3(&diff,
                                    original, modified, latest, options, pool));

  actual = svn_stringbuf_create_empty(pool);
  ostream = svn_stream_from_stringbuf(actual, pool);

  SVN_ERR(svn_diff_mem_string_output_merge3
          (ostream, diff, original, modified, latest,
           apr_psprintf(pool, "||||||| %s", base_filename1),
           apr_psprintf(pool, "<<<<<<< %s", base_filename2),
           apr_psprintf(pool, ">>>>>>> %s", base_filename3),
           NULL, /* separator */
           style,
           NULL, NULL, /* cancel */
           pool));

  SVN_ERR(svn_stream_close(ostream));
  if (strcmp(actual->data, expected) != 0)
    return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
                             "Failed mem-diff, expected and actual "
                             "outputs differ.\nEXPECTED:\n%s\n"
                             "ACTUAL:\n%s\n", expected, actual->data);

  SVN_ERR(make_file(filename1, contents1, pool));
  SVN_ERR(make_file(filename2, contents2, pool));
  SVN_ERR(make_file(filename3, contents3, pool));

  SVN_ERR(svn_diff_file_diff3_2(&diff, filename1, filename2, filename3,
                                options, pool));
  SVN_ERR(svn_io_file_open(&output, merge_name,
                           APR_WRITE | APR_CREATE | APR_TRUNCATE,
                           APR_OS_DEFAULT, pool));

  ostream = svn_stream_from_aprfile2(output, FALSE, pool);
  SVN_ERR(svn_diff_file_output_merge3(
              ostream, diff,
              filename1, filename2, filename3,
              apr_psprintf(pool, "||||||| %s", base_filename1),
              apr_psprintf(pool, "<<<<<<< %s", base_filename2),
              apr_psprintf(pool, ">>>>>>> %s", base_filename3),
              NULL, /* separator */
              style,
              NULL, NULL, /* cancel */
              pool));
  SVN_ERR(svn_stream_close(ostream));
  SVN_ERR(svn_stringbuf_from_file2(&actual, merge_name, pool));
  if (strcmp(actual->data, expected))
    return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
                             "failed merging diff '%s' to '%s' into '%s'",
                             base_filename1, base_filename2, base_filename3);

  SVN_ERR(svn_io_remove_file2(filename1, TRUE, pool));
  if (strcmp(filename1, filename2))
    SVN_ERR(svn_io_remove_file2(filename2, TRUE, pool));
  if (strcmp(filename1, filename3) && strcmp(filename2, filename3))
    SVN_ERR(svn_io_remove_file2(filename3, TRUE, pool));
  SVN_ERR(svn_io_remove_file2(merge_name, TRUE, pool));

  return SVN_NO_ERROR;
}


/* Create two files called FILENAME1 and FILENAME2 containing
   CONTENTS1 and CONTENTS2 respectively.  Run a two way diff between
   CONTENTS1 and CONTENTS2, using OPTIONS, and verify that it results
   in EXPECTED.  Then run the trivial merges to update CONTENTS1 to
   CONTENTS2 and CONTENTS2 to CONTENTS1. The files FILENAME1,
   FILENAME2 and be deleted if the diff and merges are successful, and
   preserved otherwise.  If the diff fails the diff output will be in
   a file called "diff-FILENAME1-FILENAME2".  */
static svn_error_t *
two_way_diff(const char *base_filename1,
             const char *base_filename2,
             const char *contents1,
             const char *contents2,
             const char *expected,
             const svn_diff_file_options_t *options,
             apr_pool_t *pool)
{
  svn_diff_t *diff;
  apr_file_t *output;
  svn_stream_t *ostream;
  svn_stringbuf_t *actual;
  char *diff_name = (char *)apr_pstrdup(
      pool, svn_test_data_path(
          apr_psprintf(pool, "diff-%s-%s", base_filename1, base_filename2),
          pool));

  const char *filename1 = svn_test_data_path(base_filename1, pool);
  const char *filename2 = svn_test_data_path(base_filename2, pool);

  /* Some of the tests have lots of lines, although not much data as
     the lines are short, and the in-memory diffs allocate a lot of
     memory.  Since we are doing multiple diff in a single test we use
     a subpool to reuse that memory. */
  apr_pool_t *subpool = svn_pool_create(pool);

  /* We have an EXPECTED string we can match, because we don't support
     any other combinations (yet) than the ones above. */
  svn_string_t *original = svn_string_create(contents1, pool);
  svn_string_t *modified = svn_string_create(contents2, pool);

  options = options ? options : svn_diff_file_options_create(pool);

  SVN_ERR(svn_diff_mem_string_diff(&diff, original, modified, options,
                                   subpool));

  actual = svn_stringbuf_create_empty(pool);
  ostream = svn_stream_from_stringbuf(actual, pool);

  SVN_ERR(svn_diff_mem_string_output_unified(ostream, diff,
                                             base_filename1, base_filename2,
                                             SVN_APR_LOCALE_CHARSET,
                                             original, modified, subpool));
  svn_pool_clear(subpool);
  SVN_ERR(svn_stream_close(ostream));
  if (strcmp(actual->data, expected) != 0)
    return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
                             "Failed mem-diff, expected and actual "
                             "outputs differ.\nEXPECTED:\n%s\n"
                             "ACTUAL:\n%s\n", expected, actual->data);

  SVN_ERR(make_file(filename1, contents1, pool));
  SVN_ERR(make_file(filename2, contents2, pool));

  /* Check that two-way diff between contents1 and contents2 produces
     expected output. */
  SVN_ERR(svn_diff_file_diff_2(&diff, filename1, filename2, options, pool));

  SVN_ERR(svn_io_file_open(&output, diff_name,
                           APR_WRITE | APR_CREATE | APR_TRUNCATE,
                           APR_OS_DEFAULT, pool));

  ostream = svn_stream_from_aprfile2(output, FALSE, pool);
  SVN_ERR(svn_diff_file_output_unified2(ostream, diff,
                                        filename1, filename2,
                                        base_filename1, base_filename2,
                                        SVN_APR_LOCALE_CHARSET, pool));
  SVN_ERR(svn_stream_close(ostream));

  SVN_ERR(svn_stringbuf_from_file2(&actual, diff_name, pool));
  if (strcmp(actual->data, expected))
    {
      /*svn_stringbuf_t *dump_actual;
      svn_stream_t *dump_ostream;
      dump_actual = svn_stringbuf_create_empty(pool);
      dump_ostream = svn_stream_from_stringbuf(dump_actual, pool);

      SVN_ERR(svn_diff_mem_string_output_unified(dump_ostream, diff,
                                                 "expected", "actual",
                                                 SVN_APR_LOCALE_CHARSET,
                                                 svn_string_create(expected, pool),
                                                 svn_string_create(actual->data, pool),
                                                 pool));
      SVN_ERR(svn_stream_close(ostream));

      SVN_DBG(("%s\n", dump_actual->data));

      SVN_ERR(make_file("memory", expected, pool));*/
      return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
                               "failed comparing '%s' and '%s'"
                               " (memory and file results are different)",
                               base_filename1, base_filename2);
    }

  /* May as well do the trivial merges while we are here */
  SVN_ERR(three_way_merge(base_filename1, base_filename2, base_filename1,
                          contents1, contents2, contents1, contents2, NULL,
                          svn_diff_conflict_display_modified_latest,
                          subpool));
  svn_pool_clear(subpool);
  SVN_ERR(three_way_merge(base_filename2, base_filename1, base_filename2,
                          contents2, contents1, contents2, contents1, NULL,
                          svn_diff_conflict_display_modified_latest,
                          subpool));
  svn_pool_destroy(subpool);

  SVN_ERR(svn_io_remove_file2(diff_name, TRUE, pool));

  return SVN_NO_ERROR;
}

struct random_mod
{
  int index;  /* Zero based line number */
  int mod;    /* Type of mod: 0, 1, 2 (can be interpreted as you like just
                 do it consistently) */
};

/* Fill the SELECTED array of length NUM to select with randomly chosen
   values, ensuring that none of SELECTED.INDEX are duplicates and that all
   the SELECTED.INDEX values are less than NUM_LINES.  Also ensure that for
   each SELECTED.INDEX the three elements of LINES from SELECTED.INDEX-1 to
   SELECTED.INDEX+1 are unset.  Set all LINES[SELECTED.INDEX]. */
static void
select_lines(struct random_mod *selected,
             int num_to_select,
             svn_boolean_t *lines,
             int num_lines)
{
  int i;
  for (i = 0; i < num_to_select; ++i)
    {
      int j;
      for (;;)
        {
          j= range_rand(0, num_lines - 1);
          if (lines[j] /* already selected */
              ||
              (j > 0 && lines[j - 1]) /* previous selected */
              ||
              (j < num_lines - 1 && lines[j + 1])) /* next selected */
            continue; /* try again */
          break; /* got one */
        }
      selected[i].index = j;
      selected[i].mod = range_rand(0, 2);
      lines[j] = TRUE;
    }
}


/* Create a file called FILENAME where the contents are obtained by
   applying the modifications in MOD_LINES, of which there are NUM_MODS, to
   a theoretical pristine file of length NUM_LINES lines. */
static svn_error_t *
make_random_merge_file(const char *filename,
                       int num_lines,
                       struct random_mod *mod_lines,
                       int num_mods,
                       apr_pool_t *pool)
{
  apr_file_t *file;
  int i;

  SVN_ERR(svn_io_file_open(&file, filename,
                           APR_WRITE | APR_CREATE | APR_TRUNCATE,
                           APR_OS_DEFAULT, pool));

  for (i = 0; i < num_lines; ++i)
    {
      int j;
      for (j = 0; j < num_mods; ++j)
        if (mod_lines[j].index == i)
          break;

      if (j < num_mods)
        {
          switch (mod_lines[j].mod)
            {
            case 0:
              apr_file_printf(file, "replace line %d\n", i);
              break;
            case 1:
              apr_file_printf(file,
                              "added line %d\n"
                              "unmodified line %d\n"
                              "added line %d\n",
                              i, i, i);
              break;
            default:
              ; /* Delete the line */
            }
        }
      else
        {
          apr_file_printf(file, "unmodified line %d\n", i);
        }
    }

  SVN_ERR(svn_io_file_close(file, pool));

  return SVN_NO_ERROR;
}


/* ========================================================================== */

static svn_error_t *
dump_core(apr_pool_t *pool)
{
  SVN_ERR(two_way_diff("foo1", "bar1",
                       "",
                       "",
                       "",
                       NULL, pool));

  SVN_ERR(two_way_diff("foo2", "bar2",
                       "Aa\n"
                       "Bb\n"
                       "Cc\n",

                       "",

                       "--- foo2"         NL
                       "+++ bar2"         NL
                       "@@ -1,3 +0,0 @@"  NL
                       "-Aa\n"
                       "-Bb\n"
                       "-Cc\n",
                       NULL, pool));

  SVN_ERR(two_way_diff("foo3", "bar3",
                       "",

                       "Aa\n"
                       "Bb\n"
                       "Cc\n",

                       "--- foo3"         NL
                       "+++ bar3"         NL
                       "@@ -0,0 +1,3 @@"  NL
                       "+Aa\n"
                       "+Bb\n"
                       "+Cc\n",
                       NULL, pool));

  return SVN_NO_ERROR;
}


static svn_error_t *
test_two_way_unified(apr_pool_t *pool)
{
  svn_diff_file_options_t *diff_opts = svn_diff_file_options_create(pool);

  SVN_ERR(two_way_diff("foo4", "bar4",
                       "Aa\n",

                       "Aa\n"
                       "Bb\n"
                       "Cc\n",

                       "--- foo4"       NL
                       "+++ bar4"       NL
                       "@@ -1 +1,3 @@"  NL
                       " Aa\n"
                       "+Bb\n"
                       "+Cc\n",
                       NULL, pool));

  SVN_ERR(two_way_diff("foo4b", "bar4b",
                       "Cc\n",

                       "Aa\n"
                       "Bb\n"
                       "Cc\n",

                       "--- foo4b"      NL
                       "+++ bar4b"      NL
                       "@@ -1 +1,3 @@"  NL
                       "+Aa\n"
                       "+Bb\n"
                       " Cc\n",
                       NULL, pool));

  diff_opts->ignore_eol_style = TRUE;
  SVN_ERR(two_way_diff("foo4c", "bar4c",
                       "Cc\n",

                       "Aa\r"
                       "Bb\r"
                       "Cc\r",

                       "--- foo4c"      NL
                       "+++ bar4c"      NL
                       "@@ -1 +1,3 @@"  NL
                       "+Aa\r"
                       "+Bb\r"
                       " Cc\n",
                       diff_opts, pool));
  diff_opts->ignore_eol_style = FALSE;

  SVN_ERR(two_way_diff("foo5", "bar5",
                       "Aa\n"
                       "Bb\n"
                       "Cc\n",

                       "Aa\n",

                       "--- foo5"       NL
                       "+++ bar5"       NL
                       "@@ -1,3 +1 @@"  NL
                       " Aa\n"
                       "-Bb\n"
                       "-Cc\n",
                       NULL, pool));

  SVN_ERR(two_way_diff("foo5b", "bar5b",
                       "Aa\n"
                       "Bb\n"
                       "Cc\n",

                       "Cc\n",

                       "--- foo5b"      NL
                       "+++ bar5b"      NL
                       "@@ -1,3 +1 @@"  NL
                       "-Aa\n"
                       "-Bb\n"
                       " Cc\n",
                       NULL, pool));

  diff_opts->ignore_eol_style = TRUE;
  SVN_ERR(two_way_diff("foo5c", "bar5c",
                       "Aa\r\n"
                       "Bb\r\n"
                       "Cc\r\n",

                       "Cc\n",

                       "--- foo5c"      NL
                       "+++ bar5c"      NL
                       "@@ -1,3 +1 @@"  NL
                       "-Aa\r\n"
                       "-Bb\r\n"
                       " Cc\r\n",
                       diff_opts, pool));


  SVN_ERR(two_way_diff("foo5d", "bar5d",
                       "Aa\r\n"
                       "\r\n"
                       "Bb\r\n"
                       "\r\n"
                       "Cc\r\n"
                       "\r\n",

                       "Aa\n"
                       "\n"
                       "Bb\n"
                       "\n"
                       "Cc\n"
                       "\n",

                       "",
                       diff_opts, pool));
  diff_opts->ignore_eol_style = FALSE;

  SVN_ERR(two_way_diff("foo6", "bar6",
                       "Aa\n"
                       "Bb\n"
                       "Cc\n",

                       "Aa\n"
                       "Bb\n"
                       "Cc\n",

                       "",
                       NULL, pool));

  SVN_ERR(two_way_diff("foo6b", "bar6b",
                       "Aa\n"
                       "Bb\n"
                       "Cc\n",

                       "Aa\n"
                       "Xx\n"
                       "Cc\n",

                       "--- foo6b"        NL
                       "+++ bar6b"        NL
                       "@@ -1,3 +1,3 @@"  NL
                       " Aa\n"
                       "-Bb\n"
                       "+Xx\n"
                       " Cc\n",
                       NULL, pool));

  SVN_ERR(two_way_diff("foo6c", "bar6c",
                       "Aa\r\n"
                       "Bb\r\n"
                       "Cc\r\n",

                       "Aa\r\n"
                       "Xx\r\n"
                       "Cc\r\n",

                       "--- foo6c"        NL
                       "+++ bar6c"        NL
                       "@@ -1,3 +1,3 @@"  NL
                       " Aa\r\n"
                       "-Bb\r\n"
                       "+Xx\r\n"
                       " Cc\r\n",
                       NULL, pool));

  SVN_ERR(two_way_diff("foo6d", "bar6d",
                       "Aa\r"
                       "Bb\r"
                       "Cc\r",

                       "Aa\r"
                       "Xx\r"
                       "Cc\r",

                       "--- foo6d"        NL
                       "+++ bar6d"        NL
                       "@@ -1,3 +1,3 @@"  NL
                       " Aa\r"
                       "-Bb\r"
                       "+Xx\r"
                       " Cc\r",
                       NULL, pool));

  diff_opts->ignore_space = svn_diff_file_ignore_space_change;
  SVN_ERR(two_way_diff("foo6e", "bar6e",
                       " A a \n"
                       " B b \r"
                       " C c \r\n",

                       " A  a   \n"
                       "   B b  \r"
                       "    C    c    \r\n",

                       "",
                       diff_opts, pool));
  diff_opts->ignore_space = svn_diff_file_ignore_space_none;

  diff_opts->ignore_space = svn_diff_file_ignore_space_all;
  SVN_ERR(two_way_diff("foo6f", "bar6f",
                       "Aa\n"
                       "Bb\r"
                       "Cc\r\n",

                       " A  a   \n"
                       "   B b  \r"
                       "    C    c    \r\n",

                       "",
                       diff_opts, pool));
  diff_opts->ignore_space = svn_diff_file_ignore_space_none;

  diff_opts->ignore_space = svn_diff_file_ignore_space_all;
  diff_opts->ignore_eol_style = TRUE;
  SVN_ERR(two_way_diff("foo6f", "bar6f",
                       "Aa\n"
                       "Bb\r"
                       "Cc\r\n",

                       " A  a   \r"
                       "   B b  \r\n"
                       "    C    c    \n",

                       "",
                       diff_opts, pool));
  diff_opts->ignore_space = svn_diff_file_ignore_space_none;
  diff_opts->ignore_eol_style = FALSE;

  SVN_ERR(two_way_diff("foo7", "bar7",
                       "Aa\n",

                       "Bb\n",

                       "--- foo7"     NL
                       "+++ bar7"     NL
                       "@@ -1 +1 @@"  NL
                       "-Aa\n"
                       "+Bb\n",
                       NULL, pool));

  SVN_ERR(two_way_diff("foo7a", "bar7a",
                       "Aa\n"
                       "Cc\n",

                       "Bb\n"
                       "Cc\n",

                       "--- foo7a"        NL
                       "+++ bar7a"        NL
                       "@@ -1,2 +1,2 @@"  NL
                       "-Aa\n"
                       "+Bb\n"
                       " Cc\n",
                       NULL, pool));

  SVN_ERR(two_way_diff("foo7b", "bar7b",
                       "Aa\r"
                       "Cc\n",

                       "Bb\n"
                       "Cc\n",

                       "--- foo7b"        NL
                       "+++ bar7b"        NL
                       "@@ -1,2 +1,2 @@"  NL
                       "-Aa\r"
                       "+Bb\n"
                       " Cc\n",
                       NULL, pool));

  SVN_ERR(two_way_diff("foo8", "bar8",
                       "Aa\n"
                       "Bb\n"
                       "Cc\n",

                       "Xx\n"
                       "Yy\n",

                       "--- foo8"         NL
                       "+++ bar8"         NL
                       "@@ -1,3 +1,2 @@"  NL
                       "-Aa\n"
                       "-Bb\n"
                       "-Cc\n"
                       "+Xx\n"
                       "+Yy\n",
                       NULL, pool));

  SVN_ERR(two_way_diff("foo9", "bar9",
                       "Aa\n"
                       "Bb\n"
                       "Cc\n",

                       "Bb\n",

                       "--- foo9"       NL
                       "+++ bar9"       NL
                       "@@ -1,3 +1 @@"  NL
                       "-Aa\n"
                       " Bb\n"
                       "-Cc\n",
                       NULL, pool));

  SVN_ERR(two_way_diff("foo10", "bar10",
                       "Aa\n"
                       "Bb\n"
                       "Cc",

                       "Aa\n"
                       "Xx\n"
                       "Yy\n",

                       "--- foo10"                     NL
                       "+++ bar10"                     NL
                       "@@ -1,3 +1,3 @@"               NL
                       " Aa\n"
                       "-Bb\n"
                       "-Cc"                           NL
                       "\\ No newline at end of file"  NL
                       "+Xx\n"
                       "+Yy\n",
                       NULL, pool));

  SVN_ERR(two_way_diff("foo11", "bar11",
                       "Aa\n"
                       "Xx\n"
                       "Yy\n",

                       "Aa\n"
                       "Bb\n"
                       "Cc",

                       "--- foo11"                     NL
                       "+++ bar11"                     NL
                       "@@ -1,3 +1,3 @@"               NL
                       " Aa\n"
                       "-Xx\n"
                       "-Yy\n"
                       "+Bb\n"
                       "+Cc"                           NL
                       "\\ No newline at end of file"  NL,
                       NULL, pool));

  SVN_ERR(two_way_diff("foo12", "bar12",
                       "Aa\n"
                       "Xx\n"
                       "Yy",

                       "Aa\n"
                       "Bb\n"
                       "Cc",

                       "--- foo12"                     NL
                       "+++ bar12"                     NL
                       "@@ -1,3 +1,3 @@"               NL
                       " Aa\n"
                       "-Xx\n"
                       "-Yy"                           NL
                       "\\ No newline at end of file"  NL
                       "+Bb\n"
                       "+Cc"                           NL
                       "\\ No newline at end of file"  NL,
                       NULL, pool));

  SVN_ERR(two_way_diff("foo13", "bar13",
                       "Aa\n"
                       "Bb\n"
                       "Cc\n"
                       "Dd\n"
                       "Ee\n"
                       "Ff\n"
                       "Gg\n",

                       "Xx\n"
                       "Aa\n"
                       "Bb\n"
                       "Cc\n"
                       "Dd\n"
                       "Ee\n"
                       "Ff\n"
                       "Gg\n"
                       "Yy\n",

                       "--- foo13"        NL
                       "+++ bar13"        NL
                       "@@ -1,3 +1,4 @@"  NL
                       "+Xx\n"
                       " Aa\n"
                       " Bb\n"
                       " Cc\n"
                       "@@ -5,3 +6,4 @@"  NL
                       " Ee\n"
                       " Ff\n"
                       " Gg\n"
                       "+Yy\n",
                       NULL, pool));

  SVN_ERR(two_way_diff("foo14", "bar14",
                       "Aa\n"
                       "Bb\n"
                       "Cc\n"
                       "Dd\n"
                       "Ee\n"
                       "Ff\n"
                       "Gg\n",

                       "Bb\n"
                       "Aa\n"
                       "Cc\n"
                       "Dd\n"
                       "Ee\n"
                       "Gg\n"
                       "Ff\n",

                       "--- foo14"        NL
                       "+++ bar14"        NL
                       "@@ -1,7 +1,7 @@"  NL
                       "+Bb\n"
                       " Aa\n"
                       "-Bb\n"
                       " Cc\n"
                       " Dd\n"
                       " Ee\n"
                       "+Gg\n"
                       " Ff\n"
                       "-Gg\n",
                       NULL, pool));

  SVN_ERR(two_way_diff("foo16", "bar16",
                       "Aa\n"
                       "\n"
                       "Cc\n",

                       "Aa\n"
                       "Bb\n"
                       "Cc\n",

                       "--- foo16"        NL
                       "+++ bar16"        NL
                       "@@ -1,3 +1,3 @@"  NL
                       " Aa\n"
                       "-\n"
                       "+Bb\n"
                       " Cc\n",
                       NULL, pool));

  return SVN_NO_ERROR;
}


static svn_error_t *
test_two_way_unified_suspect(apr_pool_t *pool)
{
  SVN_ERR(two_way_diff("foo15a", "bar15a",
                       "Aa\n"
                       "Bb\n"
                       "Cc\n"
                       "Dd\n"
                       "Ee\n"
                       "Ff\n"
                       "Gg\n"
                       "Hh\n"
                       "Ii\n",

                       "Aa\n"
                       "Bb\n"
                       "Cc\n"
                       "Dd\n"
                       "Ff\n"
                       "Gg\n"
                       "Hh\n"
                       "Ii\n",

                       "--- foo15a"       NL
                       "+++ bar15a"       NL
                       "@@ -2,7 +2,6 @@"  NL
                       " Bb\n"
                       " Cc\n"
                       " Dd\n"
                       "-Ee\n"
                       " Ff\n"
                       " Gg\n"
                       " Hh\n",
                       NULL, pool));

  SVN_ERR(two_way_diff("foo15b", "bar15b",
                       "Aa\n"
                       "Bb\n"
                       "Cc\n"
                       "Dd\n"
                       "Ee\n"
                       "Ff\n"
                       "Gg\n"
                       "Hh\n"
                       "Ii\n",

                       "Aa\n"
                       "Bb\n"
                       "Cc\n"
                       "Dd\n"
                       "Ee\n"
                       "Xx\n"
                       "Yy\n"
                       "Ff\n"
                       "Gg\n"
                       "Hh\n"
                       "Ii\n",

                       "--- foo15b"       NL
                       "+++ bar15b"       NL
                       "@@ -3,6 +3,8 @@"  NL
                       " Cc\n"
                       " Dd\n"
                       " Ee\n"
                       "+Xx\n"
                       "+Yy\n"
                       " Ff\n"
                       " Gg\n"
                       " Hh\n",
                       NULL, pool));

  SVN_ERR(two_way_diff("foo15c", "bar15c",
                       "Aa\n"
                       "Bb\n"
                       "Cc\n"
                       "Dd\n"
                       "Ee\n"
                       "Ff\n"
                       "Gg\n"
                       "Hh\n"
                       "Ii\n",

                       "Aa\n"
                       "Bb\n"
                       "Cc\n"
                       "Dd\n"
                       "Xx\n"
                       "Yy\n"
                       "Ff\n"
                       "Gg\n"
                       "Hh\n"
                       "Ii\n",

                       "--- foo15c"       NL
                       "+++ bar15c"       NL
                       "@@ -2,7 +2,8 @@"  NL
                       " Bb\n"
                       " Cc\n"
                       " Dd\n"
                       "-Ee\n"
                       "+Xx\n"
                       "+Yy\n"
                       " Ff\n"
                       " Gg\n"
                       " Hh\n",
                       NULL, pool));

  return SVN_NO_ERROR;
}


static svn_error_t *
test_three_way_merge_no_overlap(apr_pool_t *pool)
{
  svn_diff_file_options_t *diff_opts = svn_diff_file_options_create(pool);

  SVN_ERR(three_way_merge("zig1", "zag1", "zog1",
                          "Aa\n"
                          "Bb\n"
                          "Cc\n",

                          "Xx\n"
                          "Aa\n"
                          "Bb\n"
                          "Cc\n",

                          "Aa\n"
                          "Bb\n"
                          "Cc\n"
                          "Yy\n",

                          "Xx\n"
                          "Aa\n"
                          "Bb\n"
                          "Cc\n"
                          "Yy\n",
                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  SVN_ERR(three_way_merge("zig1a", "zag1a", "zog1a",
                          "Aa\r\n"
                          "Bb\r\n"
                          "Cc\r\n",

                          "Xx\r\n"
                          "Aa\r\n"
                          "Bb\r\n"
                          "Cc\r\n",

                          "Aa\r\n"
                          "Bb\r\n"
                          "Cc\r\n"
                          "Yy\r\n",

                          "Xx\r\n"
                          "Aa\r\n"
                          "Bb\r\n"
                          "Cc\r\n"
                          "Yy\r\n",
                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  SVN_ERR(three_way_merge("zig1b", "zag1b", "zog1b",
                          "Aa\r"
                          "Bb\r"
                          "Cc\r",

                          "Xx\r"
                          "Aa\r"
                          "Bb\r"
                          "Cc\r",

                          "Aa\r"
                          "Bb\r"
                          "Cc\r"
                          "Yy\r",

                          "Xx\r"
                          "Aa\r"
                          "Bb\r"
                          "Cc\r"
                          "Yy\r",
                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  diff_opts->ignore_space = svn_diff_file_ignore_space_all;
  SVN_ERR(three_way_merge("zig1c", "zag1c", "zog1c",
                          "Aa\n"
                          "Bb\n"
                          "Cc\n",

                          "X x\n"
                          "A a\n"
                          "B b\n"
                          "C c\n",

                          "Aa\n"
                          "Bb\n"
                          "Cc\n"
                          "Yy\n",

                          "X x\n"
                          "A a\n"
                          "B b\n"
                          "C c\n"
                          "Yy\n",
                          diff_opts,
                          svn_diff_conflict_display_modified_latest,
                          pool));
  diff_opts->ignore_space = svn_diff_file_ignore_space_none;

  SVN_ERR(three_way_merge("zig2", "zag2", "zog2",
                          "Aa\n"
                          "Bb\n"
                          "Cc\n",

                          "Xx\n"
                          "Aa\n"
                          "Bb\n"
                          "Cc\n"
                          "Yy\n",

                          "Aa\n"
                          "Bb\n"
                          "Zz\n"
                          "Cc\n",

                          "Xx\n"
                          "Aa\n"
                          "Bb\n"
                          "Zz\n"
                          "Cc\n"
                          "Yy\n",
                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  SVN_ERR(three_way_merge("zig3a", "zag3a", "zog3a",
                          "Aa\n"
                          "Bb\n"
                          "Cc\n",

                          "Aa\n"
                          "Bb\n"
                          "Cc",

                          "Xx\n"
                          "Bb\n"
                          "Cc\n",

                          "Xx\n"
                          "Bb\n"
                          "Cc",
                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  SVN_ERR(three_way_merge("zig3b", "zag3b", "zog3b",
                          "Aa\n"
                          "Bb\n"
                          "Cc\n",

                          "Xx\n"
                          "Bb\n"
                          "Cc\n",

                          "Aa\n"
                          "Bb\n"
                          "Cc",

                          "Xx\n"
                          "Bb\n"
                          "Cc",
                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  diff_opts->ignore_space = svn_diff_file_ignore_space_all;
  diff_opts->ignore_eol_style = TRUE;
  SVN_ERR(three_way_merge("zig2c", "zag2c", "zog2c",
                          "Aa\n"
                          "Bb\n"
                          "Cc\n",

                          " Xx\r\n"
                          " Aa\r\n"
                          " Bb\r\n"
                          " Cc\r\n"
                          " Yy\r\n",

                          "Aa\n"
                          "Bb\n"
                          "Zz\n"
                          "Cc\n",

                          " Xx\r\n"
                          " Aa\r\n"
                          " Bb\r\n"
                          "Zz\n"
                          " Cc\r\n"
                          " Yy\r\n",
                          diff_opts,
                          svn_diff_conflict_display_modified_latest,
                          pool));
  diff_opts->ignore_space = svn_diff_file_ignore_space_none;
  diff_opts->ignore_eol_style = FALSE;

  SVN_ERR(three_way_merge("zig4", "zag4", "zog4",
                          "Aa\n"
                          "Bb\n"
                          "Cc\n"
                          "Dd\n"
                          "Ee\n"
                          "Ff\n"
                          "Gg\n"
                          "Hh\n"
                          "Ii\n",

                          "Aa\n"
                          "Bb\n"
                          "Cc\n"
                          "Dd\n"
                          "Ee\n"
                          "Ff\n"
                          "Yy\n"
                          "Zz\n"
                          "Hh\n"
                          "Ii\n",

                          "Bb\n"
                          "Cc\n"
                          "Dd\n"
                          "Ee\n"
                          "Ff\n"
                          "Gg\n"
                          "Hh\n"
                          "Ii\n",

                          "Bb\n"
                          "Cc\n"
                          "Dd\n"
                          "Ee\n"
                          "Ff\n"
                          "Yy\n"
                          "Zz\n"
                          "Hh\n"
                          "Ii\n",
                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  SVN_ERR(three_way_merge("zig5", "zag5", "zog5",
                          "Aa\r\n"
                          "Bb\n"
                          "Cc\n",

                          "Xx\r\n"
                          "Aa\r\n"
                          "Bb\n"
                          "Cc\n",

                          "Aa\r\n"
                          "Bb\n"
                          "Cc\n"
                          "Yy\r\n",

                          "Xx\r\n"
                          "Aa\r\n"
                          "Bb\n"
                          "Cc\n"
                          "Yy\r\n",
                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  SVN_ERR(three_way_merge("zig6", "zag6", "zog6",
                          "AaAaAaAaAaAa\n"
                          "Bb\n"
                          "Cc\n",

                          "Xx\n"
                          "Bb\n"
                          "Cc\n",

                          "AaAaAaAaAaAa\n"
                          "Bb\n"
                          "CcCcCcCcCcCc\n"
                          "Yy\n",

                          "Xx\n"
                          "Bb\n"
                          "CcCcCcCcCcCc\n"
                          "Yy\n",
                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  SVN_ERR(three_way_merge("zig7", "zag7", "zog7",
                          "Aa\n"
                          "Bb\n"
                          "Cc\n",

                          "Aa\n"
                          "Bb\n"
                          "Cc\n"
                          "Dd",

                          "Aa\n"
                          "Bb\n"
                          "Cc\n",

                          "Aa\n"
                          "Bb\n"
                          "Cc\n"
                          "Dd",
                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  diff_opts->ignore_space = svn_diff_file_ignore_space_all;
  diff_opts->ignore_eol_style = FALSE;
  SVN_ERR(three_way_merge("zig8", "zag8", "zog8",
                          "Aa\n"
                          "Bb\n"
                          "Cc\n",

                          "   Aa\n"
                          "B b\n"
                          "C c\n",

                          "A a\n"
                          "Bb \n"
                          " Cc\n"
                          "New line in zog8\n",

                          "   Aa\n"
                          "B b\n"
                          "C c\n"
                          "New line in zog8\n",
                          diff_opts,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  return SVN_NO_ERROR;
}


static svn_error_t *
test_three_way_merge_with_overlap(apr_pool_t *pool)
{
  SVN_ERR(three_way_merge("splish1", "splash1", "splosh1",
                          "Aa\n"
                          "Bb\n"
                          "Cc\n"
                          "Dd\n"
                          "Ee\n",

                          "Aa\n"
                          "Xx\n"
                          "Bb\n"
                          "Cc\n"
                          "Yy\n"
                          "Ee\n",

                          "Aa\n"
                          "Bb\n"
                          "Cc\n"
                          "Yy\n"
                          "Ee\n"
                          "Zz\n",

                          "Aa\n"
                          "Xx\n"
                          "Bb\n"
                          "Cc\n"
                          "Yy\n"
                          "Ee\n"
                          "Zz\n",
                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  SVN_ERR(three_way_merge("splish2", "splash2", "splosh2",
                          "Aa\n"
                          "Bb\n"
                          "Cc\n"
                          "Dd\n"
                          "Ee\n"
                          "Ff\n",

                          "Aa\n"
                          "Yy\n"
                          "Zz\n"
                          "Dd\n"
                          "Pp\n"
                          "Qq\n"
                          "Ff\n",

                          "Pp\n"
                          "Qq\n"
                          "Aa\n"
                          "Bb\n"
                          "Cc\n"
                          "Dd\n"
                          "Pp\n"
                          "Qq\n"
                          "Ff\n"
                          "Pp\n"
                          "Qq\n",

                          "Pp\n"
                          "Qq\n"
                          "Aa\n"
                          "Yy\n"
                          "Zz\n"
                          "Dd\n"
                          "Pp\n"
                          "Qq\n"
                          "Ff\n"
                          "Pp\n"
                          "Qq\n",
                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  SVN_ERR(three_way_merge("splish3", "splash3", "splosh3",
                          "Aa\n"
                          "Bb\n"
                          "Cc\n",

                          "Xx\n"
                          "Aa\n"
                          "Bb\n"
                          "Cc",

                          "Aa\n"
                          "Xx\n"
                          "Bb\n"
                          "Cc",

                          "Xx\n"
                          "Aa\n"
                          "Xx\n"
                          "Bb\n"
                          "Cc",
                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  SVN_ERR(three_way_merge("splish4", "splash4", "splosh4",
                          "Aa\n"
                          "Bb\n"
                          "Cc\n"
                          "Dd\n"
                          "Ee\n"
                          "Ff\n"
                          "Gg\n"
                          "Hh\n",

                          "Aa\n"
                          "Ff\n"
                          "Gg\n"
                          "Hh\n"
                          "Bb\n"
                          "Cc\n"
                          "Xx\n"
                          "Dd\n"
                          "Ee\n"
                          "Yy\n"
                          "Ff\n"
                          "Gg\n"
                          "Hh\n",

                          "Aa\n"
                          "Bb\n"
                          "Cc\n"
                          "Xx\n"
                          "Dd\n"
                          "Ee\n"
                          "Ff\n"
                          "Gg\n"
                          "Zz\n"
                          "Hh\n",

                          "Aa\n"
                          "Ff\n"
                          "Gg\n"
                          "Hh\n"
                          "Bb\n"
                          "Cc\n"
                          "Xx\n"
                          "Dd\n"
                          "Ee\n"
                          "Yy\n"
                          "Ff\n"
                          "Gg\n"
                          "Zz\n"
                          "Hh\n",
                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  return SVN_NO_ERROR;
}


static svn_error_t *
test_three_way_merge_with_conflict(apr_pool_t *pool)
{
  SVN_ERR(three_way_merge("dig1", "dug1", "dag1",
                          "Aa\n"
                          "Bb\n"
                          "Cc\n",

                          "",

                          "",

                          "",
                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  SVN_ERR(three_way_merge("dig2", "dug2", "dag2",
                          "Aa\n"
                          "Bb\n"
                          "Cc\n",

                          "Aa\n"
                          "Bb\n"
                          "Cc\n"
                          "Dd\n"
                          "Ee\n"
                          "Ff\n",

                          "",

                          "<<<<<<< dug2\n"
                          "Aa\n"
                          "Bb\n"
                          "Cc\n"
                          "Dd\n"
                          "Ee\n"
                          "Ff\n"
                          "=======\n"
                          ">>>>>>> dag2\n",
                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  SVN_ERR(three_way_merge("dig2a", "dug2a", "dag2a",
                          "Aa\r\n"
                          "Bb\r\n"
                          "Cc\r\n",

                          "Aa\r\n"
                          "Bb\r\n"
                          "Cc\r\n"
                          "Dd\r\n"
                          "Ee\r\n"
                          "Ff\r\n",

                          "",

                          "<<<<<<< dug2a\r\n"
                          "Aa\r\n"
                          "Bb\r\n"
                          "Cc\r\n"
                          "Dd\r\n"
                          "Ee\r\n"
                          "Ff\r\n"
                          "=======\r\n"
                          ">>>>>>> dag2a\r\n",
                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  SVN_ERR(three_way_merge("dig2b", "dug2b", "dag2b",
                          "Aa\n"
                          "Bb\n"
                          "Cc\n",

                          "Aa\r"
                          "Bb\r"
                          "Cc\r"
                          "Dd\r"
                          "Ee\r"
                          "Ff\r",

                          "",

                          "<<<<<<< dug2b\r"
                          "Aa\r"
                          "Bb\r"
                          "Cc\r"
                          "Dd\r"
                          "Ee\r"
                          "Ff\r"
                          "=======\r"
                          ">>>>>>> dag2b\r",
                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  SVN_ERR(three_way_merge("dig3", "dug3", "dag3",
                          "Aa\n"
                          "Bb\n"
                          "Cc\n",

                          "Aa\n"
                          "Bb\n"
                          "Cc\n"
                          "Dd\n"
                          "Ee\n"
                          "Ff\n",

                          "Aa\n"
                          "Bb\n",

                          "Aa\n"
                          "Bb\n"
                          "<<<<<<< dug3\n"
                          "Cc\n"
                          "Dd\n"
                          "Ee\n"
                          "Ff\n"
                          "=======\n"
                          ">>>>>>> dag3\n",
                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  SVN_ERR(three_way_merge("dig4", "dug4", "dag4",
                          "Aa\n"
                          "Bb\n"
                          "Cc\n",

                          "Aa\n"
                          "Bb\n"
                          "Cc\n"
                          "Dd",

                          "Aa\n"
                          "Bb\n"
                          "Cc\n"
                          "Ee",

                          "Aa\n"
                          "Bb\n"
                          "Cc\n"
                          "<<<<<<< dug4\n"
                          "Dd=======\n"
                          "Ee>>>>>>> dag4\n",
                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  return SVN_NO_ERROR;
}


static svn_error_t *
test_three_way_merge_conflict_styles(apr_pool_t *pool)
{
  static const char *original =
    "a\n"
    "b\n"
    "c\n"
    "d\n"
    "e\n"
    "f\n"
    "g\n"
    "h\n"
    "i\n"
    "j\n"
    "k\n"
    "l\n"
    "m\n"
    "n\n"
    "o\n"
    "p\n"
    "q\n"
    "r\n"
    "s\n"
    "t\n"
    "u\n"
    "v\n"
    "w\n"
    "x\n"
    "y\n"
    "z\n"
    ;
  static const char *modified =
    "A\n"
    "b\n"
    "c\n"
    "d\n"
    "e\n"
    "f\n"
    "g\n"
    "h\n"
    "iMOD\n"
    "j\n"
    "k\n"
    "l\n"
    "m\n"
    "N\n"
    "O\n"
    "hello\n"
    "world\n"
    "yay\n"
    "P\n"
    "Q\n"
    "r\n"
    "s\n"
    "t\n"
    "u\n"
    "v\n"
    "w\n"
    "x\n"
    "y\n"
    "z\n"
    ;
  static const char *latest =
    "a\n"
    "b\n"
    "c\n"
    "d\n"
    "e\n"
    "f\n"
    "g\n"
    "h\n"
    "i\n"
    "j\n"
    "k1\n"
    "l2\n"
    "m3\n"
    "n4\n"
    "o5\n"
    "hello\n"
    "world\n"
    "yay\n"
    "p\n"
    "q\n"
    "r\n"
    "sLAT\n"
    "t\n"
    "u\n"
    "v\n"
    "w\n"
    "x\n"
    "y\n"
    "Z\n"
    ;
  /* So, 'modified' capitalized N through Q; 'latest' added numbers to
     'k' through 'o'; and they both inserted "hello world yay" in the
     middle.  Also, there are non-conflicting changes to the first and
     last lines. */

  SVN_ERR(three_way_merge("style-normal1", "style-normal2", "style-normal3",
                          original, modified, latest,
                          "A\n"
                          "b\n"
                          "c\n"
                          "d\n"
                          "e\n"
                          "f\n"
                          "g\n"
                          "h\n"
                          "iMOD\n"
                          "j\n"
                          "<<<<<<< style-normal2\n"
                          "k\n"
                          "l\n"
                          "m\n"
                          "N\n"
                          "O\n"
                          "hello\n"
                          "world\n"
                          "yay\n"
                          "P\n"
                          "Q\n"
                          "=======\n"
                          "k1\n"
                          "l2\n"
                          "m3\n"
                          "n4\n"
                          "o5\n"
                          "hello\n"
                          "world\n"
                          "yay\n"
                          "p\n"
                          "q\n"
                          ">>>>>>> style-normal3\n"
                          "r\n"
                          "sLAT\n"
                          "t\n"
                          "u\n"
                          "v\n"
                          "w\n"
                          "x\n"
                          "y\n"
                          "Z\n",
                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  SVN_ERR(three_way_merge("style-resolved1", "style-resolved2",
                          "style-resolved3",
                          original, modified, latest,
                          "A\n"
                          "b\n"
                          "c\n"
                          "d\n"
                          "e\n"
                          "f\n"
                          "g\n"
                          "h\n"
                          "iMOD\n"
                          "j\n"
                          "<<<<<<< style-resolved2\n"
                          "k\n"
                          "l\n"
                          "m\n"
                          "N\n"
                          "O\n"
                          "=======\n"
                          "k1\n"
                          "l2\n"
                          "m3\n"
                          "n4\n"
                          "o5\n"
                          ">>>>>>> style-resolved3\n"
                          "hello\n"
                          "world\n"
                          "yay\n"
                          "<<<<<<< style-resolved2\n"
                          "P\n"
                          "Q\n"
                          "=======\n"
                          "p\n"
                          "q\n"
                          ">>>>>>> style-resolved3\n"
                          "r\n"
                          "sLAT\n"
                          "t\n"
                          "u\n"
                          "v\n"
                          "w\n"
                          "x\n"
                          "y\n"
                          "Z\n",
                          NULL,
                          svn_diff_conflict_display_resolved_modified_latest,
                          pool));

  SVN_ERR(three_way_merge("style-three1", "style-three2", "style-three3",
                          original, modified, latest,
                          "A\n"
                          "b\n"
                          "c\n"
                          "d\n"
                          "e\n"
                          "f\n"
                          "g\n"
                          "h\n"
                          "iMOD\n"
                          "j\n"
                          "<<<<<<< style-three2\n"
                          "k\n"
                          "l\n"
                          "m\n"
                          "N\n"
                          "O\n"
                          "hello\n"
                          "world\n"
                          "yay\n"
                          "P\n"
                          "Q\n"
                          "||||||| style-three1\n"
                          "k\n"
                          "l\n"
                          "m\n"
                          "n\n"
                          "o\n"
                          "p\n"
                          "q\n"
                          "=======\n"
                          "k1\n"
                          "l2\n"
                          "m3\n"
                          "n4\n"
                          "o5\n"
                          "hello\n"
                          "world\n"
                          "yay\n"
                          "p\n"
                          "q\n"
                          ">>>>>>> style-three3\n"
                          "r\n"
                          "sLAT\n"
                          "t\n"
                          "u\n"
                          "v\n"
                          "w\n"
                          "x\n"
                          "y\n"
                          "Z\n",
                          NULL,
                          svn_diff_conflict_display_modified_original_latest,
                          pool));

  SVN_ERR(three_way_merge("style-only1", "style-only2", "style-only3",
                          original, modified, latest,
                          "@@\n"
                          "h\n"
                          "iMOD\n"
                          "j\n"
                          "<<<<<<< style-only2 (11,10)\n"
                          "k\n"
                          "l\n"
                          "m\n"
                          "N\n"
                          "O\n"
                          "hello\n"
                          "world\n"
                          "yay\n"
                          "P\n"
                          "Q\n"
                          "||||||| style-only1 (11,7)\n"
                          "k\n"
                          "l\n"
                          "m\n"
                          "n\n"
                          "o\n"
                          "p\n"
                          "q\n"
                          "=======\n"
                          "k1\n"
                          "l2\n"
                          "m3\n"
                          "n4\n"
                          "o5\n"
                          "hello\n"
                          "world\n"
                          "yay\n"
                          "p\n"
                          "q\n"
                          ">>>>>>> style-only3 (11,10)\n"
                          "r\n"
                          "sLAT\n"
                          "t\n",
                          NULL,
                          svn_diff_conflict_display_only_conflicts,
                          pool));

  SVN_ERR(three_way_merge("style-mod1", "style-mod2", "style-mod3",
                          original, modified, latest,
                          "A\n"
                          "b\n"
                          "c\n"
                          "d\n"
                          "e\n"
                          "f\n"
                          "g\n"
                          "h\n"
                          "iMOD\n"
                          "j\n"
                          "k\n"
                          "l\n"
                          "m\n"
                          "N\n"
                          "O\n"
                          "hello\n"
                          "world\n"
                          "yay\n"
                          "P\n"
                          "Q\n"
                          "r\n"
                          "sLAT\n"
                          "t\n"
                          "u\n"
                          "v\n"
                          "w\n"
                          "x\n"
                          "y\n"
                          "Z\n",
                          NULL,
                          svn_diff_conflict_display_modified,
                          pool));

  SVN_ERR(three_way_merge("style-latest1", "style-latest2", "style-latest3",
                          original, modified, latest,
                          "A\n"
                          "b\n"
                          "c\n"
                          "d\n"
                          "e\n"
                          "f\n"
                          "g\n"
                          "h\n"
                          "iMOD\n"
                          "j\n"
                          "k1\n"
                          "l2\n"
                          "m3\n"
                          "n4\n"
                          "o5\n"
                          "hello\n"
                          "world\n"
                          "yay\n"
                          "p\n"
                          "q\n"
                          "r\n"
                          "sLAT\n"
                          "t\n"
                          "u\n"
                          "v\n"
                          "w\n"
                          "x\n"
                          "y\n"
                          "Z\n",
                          NULL,
                          svn_diff_conflict_display_latest,
                          pool));

  return SVN_NO_ERROR;
}


#define MAKE_STRING(cstr) { (cstr), sizeof((cstr))-1 }

static svn_error_t *
test_diff4(apr_pool_t *pool)
{
  svn_diff_t *diff;
  svn_stream_t *actual, *expected;
  svn_boolean_t same;
  static svn_string_t B2 = MAKE_STRING(
    "int main (int argc, char **argv)\n"
    "{\n"
    "  /* line minus-five of context */\n"
    "  /* line minus-four of context */\n"
    "  /* line minus-three of context */\n"
    "  /* line -1 of context */\n"
    "  printf (\"Hello, world!\\n\");\n"
    "  /* newly inserted line of context */\n"
    "  /* line plus-one of context */\n"
    "  /* line plus-two of context */\n"
    "  /* line plus-three of context */\n"
    "  /* line plus-four of context */\n"
    "  /* line plus-five of context */\n"
    "}\n");
  static svn_string_t B2new = MAKE_STRING(
    "int main (int argc, char **argv)\n"
    "{\n"
    "  /* line minus-five of context */\n"
    "  /* line minus-four of context */\n"
    "  /* line minus-three of context */\n"
    "  /* line -1 of context */\n"
    "  printf (\"Good-bye, cruel world!\\n\");\n"
    "  /* newly inserted line of context */\n"
    "  /* line plus-one of context */\n"
    "  /* line plus-two of context */\n"
    "  /* line plus-three of context */\n"
    "  /* line plus-four of context */\n"
    "  /* line plus-five of context */\n"
    "}\n");
  static svn_string_t T1 = MAKE_STRING(
    "int main (int argc, char **argv)\n"
    "{\n"
    "  /* line minus-five of context */\n"
    "  /* line minus-four of context */\n"
    "  /* line minus-three of context */\n"
    "  /* line minus-two of context */\n"
    "  /* line minus-one of context */\n"
    "  printf (\"Hello, world!\\n\");\n"
    "  /* line plus-one of context */\n"
    "  /* line plus-two of context */\n"
    "  /* line plus-three of context */\n"
    "  /* line plus-four of context */\n"
    "  /* line plus-five of context */\n"
    "}\n");
  static svn_string_t T2 = MAKE_STRING(
    "#include <stdio.h>\n"
    "\n"
    "int main (int argc, char **argv)\n"
    "{\n"
    "  /* line minus-five of context */\n"
    "  /* line minus-four of context */\n"
    "  /* line minus-three of context */\n"
    "  /* line minus-two of context */\n"
    "  /* line minus-one of context */\n"
    "  printf (\"Hello, world!\\n\");\n"
    "  /* line plus-one of context */\n"
    "  /* line plus-two of context */\n"
    "  /* line plus-three of context */\n"
    "  /* line plus-four of context */\n"
    "  /* line plus-five of context */\n"
    "}\n");
  static svn_string_t T3 = MAKE_STRING(
    "#include <stdio.h>\n"
    "\n"
    "int main (int argc, char **argv)\n"
    "{\n"
    "  /* line minus-five of context */\n"
    "  /* line minus-four of context */\n"
    "  /* line minus-three of context */\n"
    "  /* line minus-two of context */\n"
    "  /* line minus-one of context */\n"
    "  printf (\"Good-bye, cruel world!\\n\");\n"
    "  /* line plus-one of context */\n"
    "  /* line plus-two of context */\n"
    "  /* line plus-three of context */\n"
    "  /* line plus-four of context */\n"
    "  /* line plus-five of context */\n"
    "}\n");

  const char *B2_path = svn_test_data_path("B2", pool);
  const char *T1_path = svn_test_data_path("T1", pool);
  const char *T2_path = svn_test_data_path("T2", pool);
  const char *T3_path = svn_test_data_path("T3", pool);

  SVN_ERR(make_file(B2_path, B2.data, pool));
  SVN_ERR(make_file(T1_path, T1.data, pool));
  SVN_ERR(make_file(T2_path, T2.data, pool));
  SVN_ERR(make_file(T3_path, T3.data, pool));

  /* Usage: tools/diff/diff4 <mine> <older> <yours> <ancestor> */
  /* tools/diff/diff4 B2 T2 T3 T1 > B2new */
  SVN_ERR(svn_diff_file_diff4(&diff, T2_path, B2_path, T3_path, T1_path, pool));

  /* Sanity. */
  SVN_TEST_ASSERT(! svn_diff_contains_conflicts(diff));
  SVN_TEST_ASSERT(svn_diff_contains_diffs(diff));

  /* Comparison. */
  expected = svn_stream_from_string(&B2new, pool);

  actual = svn_stream_from_stringbuf(
             svn_stringbuf_create_ensure(417, pool), /* 417 == wc -c < B2new */
             pool);
  SVN_ERR(svn_diff_file_output_merge(actual, diff,
                                     T2_path, B2_path, T3_path,
                                     NULL, NULL, NULL, NULL,
                                     FALSE,
                                     FALSE,
                                     pool));
  SVN_ERR(svn_stream_contents_same2(&same, actual, expected, pool));
  SVN_TEST_ASSERT(same);

  return SVN_NO_ERROR;
}


static svn_error_t *
random_trivial_merge(apr_pool_t *pool)
{
  int i;
  apr_pool_t *subpool = svn_pool_create(pool);

  const char *base_filename1 = "trivial1";
  const char *base_filename2 = "trivial2";

  const char *filename1 = svn_test_data_path(base_filename1, pool);
  const char *filename2 = svn_test_data_path(base_filename2, pool);

  seed_val();

  for (i = 0; i < 5; ++i)
    {
      int min_lines = 1000;
      int max_lines = 1100;
      int var_lines = 50;
      int block_lines = 10;
      svn_stringbuf_t *contents1, *contents2;

      SVN_ERR(make_random_file(filename1,
                               min_lines, max_lines, var_lines, block_lines,
                               i % 3, subpool));
      SVN_ERR(make_random_file(filename2,
                               min_lines, max_lines, var_lines, block_lines,
                               i % 2, subpool));

      SVN_ERR(svn_stringbuf_from_file2(&contents1, filename1, subpool));
      SVN_ERR(svn_stringbuf_from_file2(&contents2, filename2, subpool));

      SVN_ERR(three_way_merge(base_filename1, base_filename2, base_filename1,
                              contents1->data, contents2->data,
                              contents1->data, contents2->data, NULL,
                              svn_diff_conflict_display_modified_latest,
                              subpool));
      SVN_ERR(three_way_merge(base_filename2, base_filename1, base_filename2,
                              contents2->data, contents1->data,
                              contents2->data, contents1->data, NULL,
                              svn_diff_conflict_display_modified_latest,
                              subpool));
      svn_pool_clear(subpool);
    }
  svn_pool_destroy(subpool);

  return SVN_NO_ERROR;
}


/* The "original" file has a number of distinct lines.  We generate two
   random modifications by selecting two subsets of the original lines and
   for each selected line either adding an additional line, replacing the
   line, or deleting the line.  The two subsets are chosen so that each
   selected line is distinct and no two selected lines are adjacent. This
   means the two sets of changes should merge without conflict.  */
static svn_error_t *
random_three_way_merge(apr_pool_t *pool)
{
  int i;
  apr_pool_t *subpool = svn_pool_create(pool);

  const char *base_filename1 = "original";
  const char *base_filename2 = "modified1";
  const char *base_filename3 = "modified2";
  const char *base_filename4 = "combined";

  const char *filename1 = svn_test_data_path(base_filename1, pool);
  const char *filename2 = svn_test_data_path(base_filename2, pool);
  const char *filename3 = svn_test_data_path(base_filename3, pool);
  const char *filename4 = svn_test_data_path(base_filename4, pool);

  seed_val();

  for (i = 0; i < 20; ++i)
    {
      svn_stringbuf_t *original, *modified1, *modified2, *combined;
      /* Pick NUM_LINES large enough so that the 'strip identical suffix' code
         gets triggered with reasonable probability.  (Currently it ignores
         50 lines or more, and empirically N=4000 suffices to trigger that
         behaviour most of the time.) */
      int num_lines = 4000, num_src = 10, num_dst = 10;
      svn_boolean_t *lines = apr_pcalloc(subpool, sizeof(*lines) * num_lines);
      struct random_mod *src_lines = apr_palloc(subpool,
                                                sizeof(*src_lines) * num_src);
      struct random_mod *dst_lines = apr_palloc(subpool,
                                                sizeof(*dst_lines) * num_dst);
      struct random_mod *mrg_lines = apr_palloc(subpool,
                                                (sizeof(*mrg_lines)
                                                 * (num_src + num_dst)));

      select_lines(src_lines, num_src, lines, num_lines);
      select_lines(dst_lines, num_dst, lines, num_lines);
      memcpy(mrg_lines, src_lines, sizeof(*mrg_lines) * num_src);
      memcpy(mrg_lines + num_src, dst_lines, sizeof(*mrg_lines) * num_dst);

      SVN_ERR(make_random_merge_file(filename1, num_lines, NULL, 0, pool));
      SVN_ERR(make_random_merge_file(filename2, num_lines, src_lines, num_src,
                                     pool));
      SVN_ERR(make_random_merge_file(filename3, num_lines, dst_lines, num_dst,
                                     pool));
      SVN_ERR(make_random_merge_file(filename4, num_lines, mrg_lines,
                                     num_src + num_dst, pool));

      SVN_ERR(svn_stringbuf_from_file2(&original, filename1, pool));
      SVN_ERR(svn_stringbuf_from_file2(&modified1, filename2, pool));
      SVN_ERR(svn_stringbuf_from_file2(&modified2, filename3, pool));
      SVN_ERR(svn_stringbuf_from_file2(&combined, filename4, pool));

      SVN_ERR(three_way_merge(base_filename1, base_filename2, base_filename3,
                              original->data, modified1->data,
                              modified2->data, combined->data, NULL,
                              svn_diff_conflict_display_modified_latest,
                              subpool));
      SVN_ERR(three_way_merge(base_filename1, base_filename3, base_filename2,
                              original->data, modified2->data,
                              modified1->data, combined->data, NULL,
                              svn_diff_conflict_display_modified_latest,
                              subpool));

      SVN_ERR(svn_io_remove_file2(filename4, TRUE, pool));

      svn_pool_clear(subpool);
    }
  svn_pool_destroy(subpool);

  return SVN_NO_ERROR;
}

/* This is similar to random_three_way_merge above, except this time half
   of the original-to-modified1 changes are already present in modified2
   (or, equivalently, half the original-to-modified2 changes are already
   present in modified1).  Since the overlapping changes match exactly the
   merge should work without a conflict. */
static svn_error_t *
merge_with_part_already_present(apr_pool_t *pool)
{
  int i;
  apr_pool_t *subpool = svn_pool_create(pool);

  const char *base_filename1 = "pap-original";
  const char *base_filename2 = "pap-modified1";
  const char *base_filename3 = "pap-modified2";
  const char *base_filename4 = "pap-combined";

  const char *filename1 = svn_test_data_path(base_filename1, pool);
  const char *filename2 = svn_test_data_path(base_filename2, pool);
  const char *filename3 = svn_test_data_path(base_filename3, pool);
  const char *filename4 = svn_test_data_path(base_filename4, pool);

  seed_val();

  for (i = 0; i < 20; ++i)
    {
      svn_stringbuf_t *original, *modified1, *modified2, *combined;
      int num_lines = 200, num_src = 20, num_dst = 20;
      svn_boolean_t *lines = apr_pcalloc(subpool, sizeof(*lines) * num_lines);
      struct random_mod *src_lines = apr_palloc(subpool,
                                                sizeof(*src_lines) * num_src);
      struct random_mod *dst_lines = apr_palloc(subpool,
                                                sizeof(*dst_lines) * num_dst);
      struct random_mod *mrg_lines = apr_palloc(subpool,
                                                (sizeof(*mrg_lines)
                                                 * (num_src + num_dst / 2)));

      select_lines(src_lines, num_src, lines, num_lines);
      /* Select half the destination changes at random */
      select_lines(dst_lines, num_dst / 2, lines, num_lines);
      /* Copy the other half from the source changes */
      memcpy(dst_lines + num_dst / 2, src_lines,
             sizeof(*dst_lines) * (num_dst - num_dst / 2));
      memcpy(mrg_lines, src_lines, sizeof(*mrg_lines) * num_src);
      memcpy(mrg_lines + num_src, dst_lines,
             sizeof(*mrg_lines) * num_dst / 2);

      SVN_ERR(make_random_merge_file(filename1, num_lines, NULL, 0, pool));
      SVN_ERR(make_random_merge_file(filename2, num_lines, src_lines, num_src,
                                     pool));
      SVN_ERR(make_random_merge_file(filename3, num_lines, dst_lines, num_dst,
                                     pool));
      SVN_ERR(make_random_merge_file(filename4, num_lines, mrg_lines,
                                     num_src + num_dst / 2, pool));

      SVN_ERR(svn_stringbuf_from_file2(&original, filename1, pool));
      SVN_ERR(svn_stringbuf_from_file2(&modified1, filename2, pool));
      SVN_ERR(svn_stringbuf_from_file2(&modified2, filename3, pool));
      SVN_ERR(svn_stringbuf_from_file2(&combined, filename4, pool));

      SVN_ERR(three_way_merge(base_filename1, base_filename2, base_filename3,
                              original->data, modified1->data,
                              modified2->data, combined->data, NULL,
                              svn_diff_conflict_display_modified_latest,
                              subpool));
      SVN_ERR(three_way_merge(base_filename1, base_filename3, base_filename2,
                              original->data, modified2->data,
                              modified1->data, combined->data, NULL,
                              svn_diff_conflict_display_modified_latest,
                              subpool));

      SVN_ERR(svn_io_remove_file2(filename4, TRUE, pool));

      svn_pool_clear(subpool);
    }
  svn_pool_destroy(subpool);

  return SVN_NO_ERROR;
}

/* Merge is more "aggressive" about resolving conflicts than traditional
 * patch or diff3.  Some people consider this behaviour to be a bug, see
 * http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=35014
 */
static svn_error_t *
merge_adjacent_changes(apr_pool_t *pool)
{
  SVN_ERR(three_way_merge("adj1", "adj2", "adj3",

                          "foo\n"
                          "bar\n"
                          "baz\n",

                          "foo\n"
                          "new_bar\n"
                          "baz\n",

                          "zig\n"
                          "foo\n"
                          "bar\n"
                          "new_baz\n",

                          "zig\n"
                          "foo\n"
                          "new_bar\n"
                          "new_baz\n",

                          NULL,
                          svn_diff_conflict_display_modified_latest,
                          pool));

  return SVN_NO_ERROR;
}

/* Issue #4133, 'When sequences of whitespace characters at head of line
   strides chunk boundary, "diff -x -w" showing wrong change'.
   The magic number used in this test, 1<<17, is
   CHUNK_SIZE from ../../libsvn_diff/diff_file.c
 */
static svn_error_t *
test_norm_offset(apr_pool_t *pool)
{
  apr_size_t chunk_size = 1 << 17;
  const char *pattern1 = "       \n";
  const char *pattern2 = "\n\n\n\n\n\n\n\n";
  const char *pattern3 = "                        @@@@@@@\n";
  const char *pattern4 = "               \n";
  svn_stringbuf_t *original, *modified;
  svn_diff_file_options_t *diff_opts = svn_diff_file_options_create(pool);

  /* The original contents become like this

     $ hexdump -C norm-offset-original
     00000000  20 20 20 20 20 20 20 0a  0a 0a 0a 0a 0a 0a 0a 0a  |       .........|
     00000010  0a 0a 0a 0a 0a 0a 0a 0a  0a 0a 0a 0a 0a 0a 0a 0a  |................|
     *
     0001fff0  0a 0a 0a 0a 0a 0a 0a 0a  20 20 20 20 20 20 20 20  |........        |
     00020000  20 20 20 20 20 20 20 20  20 20 20 20 20 20 20 20  |                |
     00020010  40 40 40 40 40 40 40 0a  0a 0a 0a 0a 0a 0a 0a 0a  |@@@@@@@.........|
     00020020  0a 0a 0a 0a 0a 0a 0a 0a  0a 0a 0a 0a 0a 0a 0a 0a  |................|
     *
     000203f0  20 20 20 20 20 20 20 20  20 20 20 20 20 20 20 0a  |               .|
     00020400
  */
  original = svn_stringbuf_create_ensure(chunk_size + 1024, pool);
  svn_stringbuf_appendcstr(original, pattern1);
  while (original->len < chunk_size - 8)
    {
      svn_stringbuf_appendcstr(original, pattern2);
    }
  svn_stringbuf_appendcstr(original, pattern3);
  while (original->len < chunk_size +1024 - 16)
    {
      svn_stringbuf_appendcstr(original, pattern2);
    }
  svn_stringbuf_appendcstr(original, pattern4);

  /* The modified contents become like this.

     $ hexdump -C norm-offset-modified
     00000000  20 20 20 20 20 20 20 20  20 20 20 20 20 20 20 0a  |               .|
     00000010  0a 0a 0a 0a 0a 0a 0a 0a  0a 0a 0a 0a 0a 0a 0a 0a  |................|
     *
     00020000  20 20 20 20 20 20 20 20  20 20 20 20 20 20 20 20  |                |
     00020010  20 20 20 20 20 20 20 20  40 40 40 40 40 40 40 0a  |        @@@@@@@.|
     00020020  0a 0a 0a 0a 0a 0a 0a 0a  0a 0a 0a 0a 0a 0a 0a 0a  |................|
     *
     000203f0  0a 0a 0a 0a 0a 0a 0a 0a  20 20 20 20 20 20 20 0a  |........       .|
     00020400
  */
  modified = svn_stringbuf_create_ensure(chunk_size + 1024, pool);
  svn_stringbuf_appendcstr(modified, pattern4);
  while (modified->len < chunk_size)
    {
      svn_stringbuf_appendcstr(modified, pattern2);
    }
  svn_stringbuf_appendcstr(modified, pattern3);
  while (modified->len < chunk_size +1024 - 8)
    {
      svn_stringbuf_appendcstr(modified, pattern2);
    }
  svn_stringbuf_appendcstr(modified, pattern1);

  /* Diff them.  Modulo whitespace, they are identical. */
  diff_opts->ignore_space = svn_diff_file_ignore_space_all;
  SVN_ERR(two_way_diff("norm-offset-original", "norm-offset-modified",
                       original->data, modified->data, "",
                       diff_opts, pool));

  return SVN_NO_ERROR;
}

/* Issue #4283, 'When identical suffix started at a chunk boundary,
   incorrect diff was generated'.
   The magic number used in this test, (1<<17) and 50 are CHUNK_SIZE
   and SUFFIX_LINES_TO_KEEP from ../../libsvn_diff/diff_file.c, respectively.
 */
#define ORIGINAL_CONTENTS_PATTERN "0123456789abcde\n"
#define INSERTED_LINE "0123456789ABCDE\n"
static svn_error_t *
test_identical_suffix(apr_pool_t *pool)
{
  apr_size_t lines_in_chunk = (1 << 17)
                              / (sizeof(ORIGINAL_CONTENTS_PATTERN) - 1);
  /* To let identical suffix start at a chunk boundary,
     insert a line at before (SUFFIX_LINES_TO_KEEP + 1) lines
     from tail of the previous chunk. */
  apr_size_t insert_pos = lines_in_chunk
#ifdef SUFFIX_LINES_TO_KEEP
                          - SUFFIX_LINES_TO_KEEP
#else
                          - 50
#endif
                          - 1;
  apr_size_t i;
  svn_stringbuf_t *original, *modified;

  /* The original contents become like this.

     $ hexdump -C identical-suffix-original
     00000000  30 31 32 33 34 35 36 37  38 39 61 62 63 64 65 0a  |0123456789abcde.|
     *
     00020400
   */
  original = svn_stringbuf_create_ensure((1 << 17) + 1024, pool);
  for (i = 0; i < lines_in_chunk + 64; i++)
    {
      svn_stringbuf_appendbytes(original, ORIGINAL_CONTENTS_PATTERN,
                                sizeof(ORIGINAL_CONTENTS_PATTERN) - 1);
    }

  /* The modified contents become like this.

     $ hexdump -C identical-suffix-modified
     00000000  30 31 32 33 34 35 36 37  38 39 61 62 63 64 65 0a  |0123456789abcde.|
     *
     00000400  30 31 32 33 34 35 36 37  38 39 41 42 43 44 45 0a  |0123456789ABCDE.|
     00000410  30 31 32 33 34 35 36 37  38 39 61 62 63 64 65 0a  |0123456789abcde.|
     *
     0001fcd0  30 31 32 33 34 35 36 37  38 39 41 42 43 44 45 0a  |0123456789ABCDE.|
     0001fce0  30 31 32 33 34 35 36 37  38 39 61 62 63 64 65 0a  |0123456789abcde.|
     *
     00020420
   */
  modified = svn_stringbuf_dup(original, pool);
  svn_stringbuf_insert(modified,
                       64 * (sizeof(ORIGINAL_CONTENTS_PATTERN) - 1),
                       INSERTED_LINE, sizeof(INSERTED_LINE) - 1);
  svn_stringbuf_insert(modified,
                       insert_pos * (sizeof(ORIGINAL_CONTENTS_PATTERN) - 1),
                       INSERTED_LINE, sizeof(INSERTED_LINE) - 1);

  SVN_ERR(two_way_diff("identical-suffix-original",
                       "identical-suffix-modified",
                       original->data, modified->data,
                       apr_psprintf(pool,
                                    "--- identical-suffix-original" NL
                                    "+++ identical-suffix-modified" NL
                                    "@@ -62,6 +62,7 @@" NL
                                    " " ORIGINAL_CONTENTS_PATTERN
                                    " " ORIGINAL_CONTENTS_PATTERN
                                    " " ORIGINAL_CONTENTS_PATTERN
                                    "+" INSERTED_LINE
                                    " " ORIGINAL_CONTENTS_PATTERN
                                    " " ORIGINAL_CONTENTS_PATTERN
                                    " " ORIGINAL_CONTENTS_PATTERN
                                    "@@ -%u,6 +%u,7 @@" NL
                                    " " ORIGINAL_CONTENTS_PATTERN
                                    " " ORIGINAL_CONTENTS_PATTERN
                                    " " ORIGINAL_CONTENTS_PATTERN
                                    "+" INSERTED_LINE
                                    " " ORIGINAL_CONTENTS_PATTERN
                                    " " ORIGINAL_CONTENTS_PATTERN
                                    " " ORIGINAL_CONTENTS_PATTERN,
                                    1 + (unsigned int)insert_pos - 3 - 1,
                                    1 + (unsigned int)insert_pos - 3),
                       NULL, pool));

  return SVN_NO_ERROR;
}
#undef ORIGINAL_CONTENTS_PATTERN
#undef INSERTED_LINE

/* The magic number used in this test, 1<<17, is
   CHUNK_SIZE from ../../libsvn_diff/diff_file.c
 */
static svn_error_t *
test_token_compare(apr_pool_t *pool)
{
  apr_size_t chunk_size = 1 << 17;
  const char *pattern = "ABCDEFG\n";
  svn_stringbuf_t *original, *modified;
  svn_diff_file_options_t *diff_opts = svn_diff_file_options_create(pool);

  diff_opts->ignore_space = svn_diff_file_ignore_space_all;

  original = svn_stringbuf_create_ensure(chunk_size * 2 + 8, pool);
  /* CHUNK_SIZE bytes */
  while (original->len < chunk_size - 8)
    {
      svn_stringbuf_appendcstr(original, pattern);
    }
  svn_stringbuf_appendcstr(original, "    @@@\n");

  modified = svn_stringbuf_create_ensure(chunk_size * 2 + 9, pool);
  /* CHUNK_SIZE+1 bytes, one ' ' more than original */
  while (modified->len < chunk_size - 8)
    {
      svn_stringbuf_appendcstr(modified, pattern);
    }
  svn_stringbuf_appendcstr(modified, "     @@@\n");

  /* regression test for reading exceeding the file size */
  SVN_ERR(two_way_diff("token-compare-original1", "token-compare-modified1",
                       original->data, modified->data, "",
                       diff_opts, pool));

  svn_stringbuf_appendcstr(original, "aaaaaaa\n");
  svn_stringbuf_appendcstr(modified, "bbbbbbb\n");

  /* regression test for comparison beyond the end-of-line */
  SVN_ERR(two_way_diff("token-compare-original2", "token-compare-modified2",
                       original->data, modified->data,
                       apr_psprintf(pool,
                                    "--- token-compare-original2" NL
                                    "+++ token-compare-modified2" NL
                                    "@@ -%u,4 +%u,4 @@"  NL
                                    " ABCDEFG\n"
                                    " ABCDEFG\n"
                                    "     @@@\n"
                                    "-aaaaaaa\n"
                                    "+bbbbbbb\n",
                                    (unsigned int)chunk_size/8 - 2,
                                    (unsigned int)chunk_size/8 - 2),
                       diff_opts, pool));

  /* CHUNK_SIZE*2 bytes */
  while (original->len <= chunk_size * 2 - 8)
    {
      svn_stringbuf_appendcstr(original, pattern);
    }

  /* CHUNK_SIZE*2+1 bytes, one ' ' more than original */
  while (modified->len <= chunk_size * 2 - 7)
    {
      svn_stringbuf_appendcstr(modified, pattern);
    }

  SVN_ERR(two_way_diff("token-compare-original2", "token-compare-modified2",
                       original->data, modified->data,
                       apr_psprintf(pool,
                                    "--- token-compare-original2" NL
                                    "+++ token-compare-modified2" NL
                                    "@@ -%u,7 +%u,7 @@"  NL
                                    " ABCDEFG\n"
                                    " ABCDEFG\n"
                                    "     @@@\n"
                                    "-aaaaaaa\n"
                                    "+bbbbbbb\n"
                                    " ABCDEFG\n"
                                    " ABCDEFG\n"
                                    " ABCDEFG\n",
                                    (unsigned int)chunk_size/8 - 2,
                                    (unsigned int)chunk_size/8 - 2),
                       diff_opts, pool));

  return SVN_NO_ERROR;
}

static svn_error_t *
two_way_issue_3362_v1(apr_pool_t *pool)
{
  svn_diff_file_options_t *diff_opts = svn_diff_file_options_create(pool);

  SVN_ERR(two_way_diff("issue-3362-1-v1",
                       "issue-3362-2-v1",
                        /* File 1 */
                                "line_1\n"
                                "line_2\n"
                                "line_3\n"
                                "line_4\n"
                                "line_5\n"
                                "line_6\n"
                                "line_7\n"
                                "line_8\n"
                                "line_9\n"
                                "line_10\n"
                                "line_11\n"
                                "line_12\n"
                                "line_13\n"
                                "line_14\n"
                                "line_15\n"
                                "line_16\n"
                                "line_17\n"
                                "line_18\n"
                                "line_19\n"
                                "line_20\n"
                                "line_21\n"
                                "line_22\n"
                                "line_23\n"
                                "line_24\n"
                                "line_25\n"
                                "line_26\n"
                                "line_27\n"
                                "line_28\n"
                                "line_29\n"
                                "line_30\n",
                        /* File 2 */
                                "line_1a\n"
                                "line_2a\n"
                                "line_3a\n"
                                "line_1\n"
                                "line_2\n"
                                "line_3\n"
                                "line_4\n"
                                "line_5a\n"
                                "line_6b\n"
                                "line_7c\n"
                                "line_8\n"
                                "line_9\n"
                                "line_10\n"
                                "line_11a\n"
                                "line_11b\n"
                                "line_11c\n"
                                "line_12\n"
                                "line_13\n"
                                "line_14\n"
                                "line_15\n"
                                "line_16\n"
                                "line_17\n"
                                "line_18\n"
                                "line_19a\n"
                                "line_19b\n"
                                "line_19c\n"
                                "line_20\n"
                                "line_21\n"
                                "line_22\n"
                                "line_23\n"
                                "line_24\n"
                                "line_25\n"
                                "line_26\n"
                                "line_27\n"
                                "line_27a\n",
                        /* Expected */
                        "--- issue-3362-1-v1" APR_EOL_STR
                        "+++ issue-3362-2-v1" APR_EOL_STR
                        "@@ -1,14 +1,19 @@" APR_EOL_STR
                        "+line_1a\n"
                        "+line_2a\n"
                        "+line_3a\n"
                        " line_1\n" /* 1.7 mem diff: line missing */
                        " line_2\n"
                        " line_3\n"
                        " line_4\n"
                        "-line_5\n"
                        "-line_6\n"
                        "-line_7\n"
                        "+line_5a\n"
                        "+line_6b\n"
                        "+line_7c\n"
                        " line_8\n"
                        " line_9\n"
                        " line_10\n"
                        "-line_11\n"
                        "+line_11a\n"
                        "+line_11b\n"
                        "+line_11c\n"
                        " line_12\n"
                        " line_13\n"
                        " line_14\n" /* 1.7 mem diff: line missing */
                        "@@ -16,7 +21,9 @@" APR_EOL_STR
                        " line_16\n"
                        " line_17\n"
                        " line_18\n"
                        "-line_19\n"
                        "+line_19a\n"
                        "+line_19b\n"
                        "+line_19c\n"
                        " line_20\n"
                        " line_21\n"
                        " line_22\n"
                        "@@ -25,6 +32,4 @@" APR_EOL_STR
                        " line_25\n"
                        " line_26\n"
                        " line_27\n"
                        "-line_28\n"
                        "-line_29\n"
                        "-line_30\n"
                        "+line_27a\n",
                        diff_opts, pool));

  return SVN_NO_ERROR;
}

static svn_error_t *
two_way_issue_3362_v2(apr_pool_t *pool)
{
  svn_diff_file_options_t *diff_opts = svn_diff_file_options_create(pool);

  SVN_ERR(two_way_diff("issue-3362-1-v2",
                       "issue-3362-2-v2",
                        /* File 1 */
                                "line_1\n"
                                "line_2\n"
                                "line_3\n"
                                "line_4\n"
                                "line_5\n"
                                "line_6\n"
                                "line_7\n"
                                "line_8\n"
                                "line_9\n"
                                "line_10\n"
                                "line_11\n"
                                "line_12\n"
                                "line_13\n"
                                "line_14\n"
                                "line_15\n"
                                "line_16\n"
                                "line_17\n"
                                "line_18\n"
                                "line_19\n"
                                "line_20\n"
                                "line_21\n"
                                "line_22\n"
                                "line_23\n"
                                "line_24\n"
                                "line_25\n"
                                "line_26\n"
                                "line_27\n"
                                "line_28\n"
                                "line_29\n"
                                "line_30\n",
                        /* File 2 */
                                "line_1a\n"
                                "line_1b\n"
                                "line_1c\n"
                                "line_1\n"
                                "line_2\n"
                                "line_3\n"
                                "line_4\n"
                                "line_5a\n"
                                "line_5b\n"
                                "line_5c\n"
                                "line_6\n"
                                "line_7\n"
                                "line_8\n"
                                "line_9\n"
                                "line_10\n"
                                "line_11a\n"
                                "line_11b\n"
                                "line_11c\n"
                                "line_12\n"
                                "line_13\n"
                                "line_14\n"
                                "line_15\n"
                                "line_16\n"
                                "line_17\n"
                                "line_18\n"
                                "line_19a\n"
                                "line_19b\n"
                                "line_19c\n"
                                "line_20\n"
                                "line_21\n"
                                "line_22\n"
                                "line_23\n"
                                "line_24\n"
                                "line_25\n"
                                "line_26\n"
                                "line_27a\n"
                                "line_27b\n"
                                "line_27c\n"
                                "line_28\n"
                                "line_29\n"
                                "line_30\n",
                        /* Expected */
                        "--- issue-3362-1-v2" APR_EOL_STR
                        "+++ issue-3362-2-v2" APR_EOL_STR
                        "@@ -1,14 +1,21 @@" APR_EOL_STR
                        "+line_1a\n"
                        "+line_1b\n"
                        "+line_1c\n"
                        " line_1\n" /* 1.7 mem diff: line missing */
                        " line_2\n"
                        " line_3\n"
                        " line_4\n"
                        "-line_5\n"
                        "+line_5a\n"
                        "+line_5b\n"
                        "+line_5c\n"
                        " line_6\n"
                        " line_7\n"
                        " line_8\n"
                        " line_9\n"
                        " line_10\n"
                        "-line_11\n"
                        "+line_11a\n"
                        "+line_11b\n"
                        "+line_11c\n"
                        " line_12\n"
                        " line_13\n"
                        " line_14\n" /* 1.7 mem diff: line missing */
                        "@@ -16,7 +23,9 @@" APR_EOL_STR
                        " line_16\n"
                        " line_17\n"
                        " line_18\n"
                        "-line_19\n"
                        "+line_19a\n"
                        "+line_19b\n"
                        "+line_19c\n"
                        " line_20\n"
                        " line_21\n"
                        " line_22\n"
                        "@@ -24,7 +33,9 @@" APR_EOL_STR
                        " line_24\n"
                        " line_25\n"
                        " line_26\n"
                        "-line_27\n"
                        "+line_27a\n"
                        "+line_27b\n"
                        "+line_27c\n"
                        " line_28\n"
                        " line_29\n"
                        " line_30\n",
                        diff_opts, pool));

  return SVN_NO_ERROR;
}

static svn_error_t *
three_way_double_add(apr_pool_t *pool)
{
  SVN_ERR(three_way_merge("doubleadd1", "doubleadd2", "doubleadd3",
                          "A\n"
                          "B\n"
                          "C\n"
                          "J\n"
                          "K\n"
                          "L",

                          "A\n"
                          "B\n"
                          "C\n"
                          "D\n" /* New line 1a */
                          "E\n" /* New line 2a */
                          "F\n" /* New line 3a*/
                          "J\n"
                          "K\n"
                          "L",

                          "A\n"
                          "B\n"
                          "O\n" /* Change C to O */
                          "P\n" /* New line 1b */
                          "Q\n" /* New line 2b */
                          "R\n" /* New line 3b */
                          "J\n"
                          "K\n"
                          "L",

                          /* With s/C/O/ we expect something like this,
                             but the current (1.9/trunk) result is a
                             succeeded merge to a combined result.

                             ### I'm guessing this result needs tweaks before it
                                 will be a PASS. */
                          "A\n"
                          "B\n"
                          "<<<<<<< doubleadd2\n"
                          "C\n"
                          "D\n" /* New line 1a */
                          "E\n" /* New line 2a */
                          "F\n" /* New line 3a*/
                          "=======\n"
                          "O\n"
                          "P\n" /* New line 1b */
                          "Q\n" /* New line 2b */
                          "R\n" /* New line 3b */
                          ">>>>>>> doubleadd3\n"
                          "J\n"
                          "K\n"
                          "L",
                          NULL,
                          svn_diff_conflict_display_modified_original_latest,
                          pool));

  SVN_ERR(three_way_merge("doubleadd1", "doubleadd2", "doubleadd3",
                          "A\n"
                          "B\n"
                          "C\n"
                          "J\n"
                          "K\n"
                          "L",

                          "A\n"
                          "B\n"
                          "C\n"
                          "D\n" /* New line 1a */
                          "E\n" /* New line 2a */
                          "F\n" /* New line 3a*/
                          "K\n"
                          "L",

                          "A\n"
                          "B\n"
                          "O\n" /* Change C to O */
                          "P\n" /* New line 1b */
                          "Q\n" /* New line 2b */
                          "R\n" /* New line 3b */
                          "J\n"
                          "K\n"
                          "L",

                          /* With s/C/O/ we expect something like this,
                          but the current (1.9/trunk) result is a
                          succeeded merge to a combined result.

                          ### I'm guessing this result needs tweaks before it
                          will be a PASS. */
                          "A\n"
                          "B\n"
                          "<<<<<<< doubleadd2\n"
                          "C\n"
                          "D\n" /* New line 1a */
                          "E\n" /* New line 2a */
                          "F\n" /* New line 3a*/
                          "=======\n"
                          "O\n"
                          "P\n" /* New line 1b */
                          "Q\n" /* New line 2b */
                          "R\n" /* New line 3b */
                          "J\n"
                          ">>>>>>> doubleadd3\n"
                          "K\n"
                          "L",
                          NULL,
                          svn_diff_conflict_display_modified_original_latest,
                          pool));

  return SVN_NO_ERROR;
}

/* ========================================================================== */


static int max_threads = 4;

static struct svn_test_descriptor_t test_funcs[] =
  {
    SVN_TEST_NULL,
    SVN_TEST_PASS2(dump_core,
                   "these dump core"),
    SVN_TEST_PASS2(test_two_way_unified,
                   "2-way unified diff and trivial merge"),
    SVN_TEST_PASS2(test_two_way_unified_suspect,
                   "2-way unified diff where output is suspect"),
    SVN_TEST_PASS2(test_three_way_merge_no_overlap,
                   "3-way merge, non-overlapping changes"),
    SVN_TEST_PASS2(test_three_way_merge_with_overlap,
                   "3-way merge, non-conflicting overlapping changes"),
    SVN_TEST_PASS2(test_three_way_merge_with_conflict,
                   "3-way merge, conflicting overlapping changes"),
    SVN_TEST_PASS2(random_trivial_merge,
                   "random trivial merge"),
    SVN_TEST_PASS2(random_three_way_merge,
                   "random 3-way merge"),
    SVN_TEST_PASS2(merge_with_part_already_present,
                   "merge with part already present"),
    SVN_TEST_PASS2(merge_adjacent_changes,
                   "3-way merge, adjacent changes"),
    SVN_TEST_PASS2(test_three_way_merge_conflict_styles,
                   "3-way merge with conflict styles"),
    SVN_TEST_PASS2(test_diff4,
                   "4-way merge; see variance-adjusted-patching.html"),
    SVN_TEST_PASS2(test_norm_offset,
                   "offset of the normalized token"),
    SVN_TEST_PASS2(test_identical_suffix,
                   "identical suffix starts at the boundary of a chunk"),
    SVN_TEST_PASS2(test_token_compare,
                   "compare tokens at the chunk boundary"),
    SVN_TEST_PASS2(two_way_issue_3362_v1,
                   "2-way issue #3362 test v1"),
    SVN_TEST_PASS2(two_way_issue_3362_v2,
                   "2-way issue #3362 test v2"),
    SVN_TEST_XFAIL2(three_way_double_add,
                   "3-way merge, double add"),
    SVN_TEST_NULL
  };

SVN_TEST_MAIN
