// Copyright 2008 Google Inc.
// Author: Lincoln Smith
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <config.h>
#include "blockhash.h"
#include <limits.h>  // INT_MIN
#include <string.h>  // memcpy, memcmp, strlen
#include <iostream>
#include <memory>  // auto_ptr
#include "encodetable.h"
#include "rolling_hash.h"
#include "testing.h"

namespace open_vcdiff {

const int kBlockSize = BlockHash::kBlockSize;

class BlockHashTest : public testing::Test {
 protected:
  static const int kTimingTestSize = 1 << 21;  // 2M
  static const int kTimingTestIterations = 32;

  BlockHashTest() {
    dh_.reset(BlockHash::CreateDictionaryHash(sample_text,
                                              strlen(sample_text)));
    th_.reset(BlockHash::CreateTargetHash(sample_text, strlen(sample_text), 0));
    EXPECT_TRUE(dh_.get() != NULL);
    EXPECT_TRUE(th_.get() != NULL);
  }

  // BlockHashTest is a friend to BlockHash.  Expose the protected functions
  // that will be tested by the children of BlockHashTest.
  static bool BlockContentsMatch(const char* block1, const char* block2) {
    return BlockHash::BlockContentsMatch(block1, block2);
  }

  int FirstMatchingBlock(const BlockHash& block_hash,
                         uint32_t hash_value,
                         const char* block_ptr) const {
    return block_hash.FirstMatchingBlock(hash_value, block_ptr);
  }

  int NextMatchingBlock(const BlockHash& block_hash,
                        int block_number,
                        const char* block_ptr) const {
    return block_hash.NextMatchingBlock(block_number, block_ptr);
  }

  static int MatchingBytesToLeft(const char* source_match_start,
                                 const char* target_match_start,
                                 int max_bytes) {
    return BlockHash::MatchingBytesToLeft(source_match_start,
                                          target_match_start,
                                          max_bytes);
  }

  static int MatchingBytesToRight(const char* source_match_end,
                                  const char* target_match_end,
                                  int max_bytes) {
    return BlockHash::MatchingBytesToRight(source_match_end,
                                           target_match_end,
                                           max_bytes);
  }

  static int StringLengthAsInt(const char* s) {
    return static_cast<int>(strlen(s));
  }

  void InitBlocksToDifferAtNthByte(int n) {
    CHECK(n < kBlockSize);
    memset(compare_buffer_1_, 0xBE, kTimingTestSize);
    memset(compare_buffer_2_, 0xBE, kTimingTestSize);
    for (int index = n; index < kTimingTestSize; index += kBlockSize) {
      compare_buffer_1_[index] = 0x00;
      compare_buffer_2_[index] = 0x01;
    }
  }

  void TestAndPrintTimesForCompareFunctions(bool should_be_identical);

  void TimingTestForBlocksThatDifferAtByte(int n) {
    InitBlocksToDifferAtNthByte(n);
    std::cout << "Comparing blocks that differ at byte " << n << std::endl;
    TestAndPrintTimesForCompareFunctions(false);
  }

  // Copy sample_text_without_spaces and search_string_without_spaces
  // into newly allocated sample_text and search_string buffers,
  // but pad them with space characters so that every character
  // in sample_text_without_spaces matches (kBlockSize - 1)
  // space characters in sample_text, followed by that character.
  // For example:
  // Since sample_text_without_spaces begins "The only thing"...,
  // if kBlockSize is 4, then 3 space characters will be inserted
  // between each letter of sample_text, as follows:
  // "   T   h   e       o   n   l   y       t   h   i   n   g"...
  // This makes testing simpler, because finding a kBlockSize-byte match
  // between the sample text and search string only depends on the
  // trailing letter in each block.
  static void MakeEachLetterABlock(const char* string_without_spaces,
                                   const char** result) {
    const size_t length_without_spaces = strlen(string_without_spaces);
    char* padded_text = new char[(kBlockSize * length_without_spaces) + 1];
    memset(padded_text, ' ', kBlockSize * length_without_spaces);
    char* padded_text_ptr = padded_text + (kBlockSize - 1);
    for (size_t i = 0; i < length_without_spaces; ++i) {
      *padded_text_ptr = string_without_spaces[i];
      padded_text_ptr += kBlockSize;
    }
    padded_text[kBlockSize * length_without_spaces] = '\0';
    *result = padded_text;
  }

  static void SetUpTestCase() {
    MakeEachLetterABlock(sample_text_without_spaces, &sample_text);
    MakeEachLetterABlock(search_string_without_spaces, &search_string);
    MakeEachLetterABlock(search_string_altered_without_spaces,
                         &search_string_altered);
    MakeEachLetterABlock(search_to_end_without_spaces, &search_to_end_string);
    MakeEachLetterABlock(search_to_beginning_without_spaces,
                         &search_to_beginning_string);
    MakeEachLetterABlock(sample_text_many_matches_without_spaces,
                         &sample_text_many_matches);
    MakeEachLetterABlock(search_string_many_matches_without_spaces,
                         &search_string_many_matches);
    MakeEachLetterABlock("y", &test_string_y);
    MakeEachLetterABlock("e", &test_string_e);
    char* new_test_string_unaligned_e = new char[kBlockSize];
    memset(new_test_string_unaligned_e, ' ', kBlockSize);
    new_test_string_unaligned_e[kBlockSize - 2] = 'e';
    test_string_unaligned_e = new_test_string_unaligned_e;
    char* new_test_string_all_Qs = new char[kBlockSize];
    memset(new_test_string_all_Qs, 'Q', kBlockSize);
    test_string_all_Qs = new_test_string_all_Qs;
    hashed_y = RollingHash<kBlockSize>::Hash(test_string_y);
    hashed_e = RollingHash<kBlockSize>::Hash(test_string_e);
    hashed_f =
        RollingHash<kBlockSize>::Hash(&search_string[index_of_f_in_fearsome]);
    hashed_unaligned_e = RollingHash<kBlockSize>::Hash(test_string_unaligned_e);
    hashed_all_Qs = RollingHash<kBlockSize>::Hash(test_string_all_Qs);
  }

  static void TearDownTestCase() {
    delete[] sample_text;
    delete[] search_string;
    delete[] search_string_altered;
    delete[] search_to_end_string;
    delete[] search_to_beginning_string;
    delete[] sample_text_many_matches;
    delete[] search_string_many_matches;
    delete[] test_string_y;
    delete[] test_string_e;
    delete[] test_string_unaligned_e;
    delete[] test_string_all_Qs;
  }

  // Each block in the sample text and search string is kBlockSize bytes long,
  // and consists of (kBlockSize - 1) space characters
  // followed by a single letter of text.

  // Block numbers of certain characters within the sample text:
  // All six occurrences of "e", in order.
  static const int block_of_first_e = 2;
  static const int block_of_second_e = 16;
  static const int block_of_third_e = 21;
  static const int block_of_fourth_e = 27;
  static const int block_of_fifth_e = 35;
  static const int block_of_sixth_e = 42;

  static const int block_of_y_in_only = 7;
  // The block number is multiplied by kBlockSize to arrive at the
  // index, which points to the (kBlockSize - 1) space characters before
  // the letter specified.
  // Indices of certain characters within the sample text.
  static const int index_of_first_e = block_of_first_e * kBlockSize;
  static const int index_of_fourth_e = block_of_fourth_e * kBlockSize;
  static const int index_of_sixth_e = block_of_sixth_e * kBlockSize;
  static const int index_of_y_in_only = block_of_y_in_only * kBlockSize;
  static const int index_of_space_before_fear_is_fear = 25 * kBlockSize;
  static const int index_of_longest_match_ear_is_fear = 27 * kBlockSize;
  static const int index_of_i_in_fear_is_fear = 31 * kBlockSize;
  static const int index_of_space_before_fear_itself = 33 * kBlockSize;
  static const int index_of_space_before_itself = 38 * kBlockSize;
  static const int index_of_ababc = 4 * kBlockSize;

  // Indices of certain characters within the search strings.
  static const int index_of_second_w_in_what_we = 5 * kBlockSize;
  static const int index_of_second_e_in_what_we_hear = 9 * kBlockSize;
  static const int index_of_f_in_fearsome = 16 * kBlockSize;
  static const int index_of_space_in_eat_itself =  12 * kBlockSize;
  static const int index_of_i_in_itself = 13 * kBlockSize;
  static const int index_of_t_in_use_the = 4 * kBlockSize;
  static const int index_of_o_in_online = 8 * kBlockSize;

  static const char sample_text_without_spaces[];
  static const char search_string_without_spaces[];
  static const char search_string_altered_without_spaces[];
  static const char search_to_end_without_spaces[];
  static const char search_to_beginning_without_spaces[];
  static const char sample_text_many_matches_without_spaces[];
  static const char search_string_many_matches_without_spaces[];

  static const char* sample_text;
  static const char* search_string;
  static const char* search_string_altered;
  static const char* search_to_end_string;
  static const char* search_to_beginning_string;
  static const char* sample_text_many_matches;
  static const char* search_string_many_matches;

  static const char* test_string_y;
  static const char* test_string_e;
  static const char* test_string_all_Qs;
  static const char* test_string_unaligned_e;

  static uint32_t hashed_y;
  static uint32_t hashed_e;
  static uint32_t hashed_f;
  static uint32_t hashed_unaligned_e;
  static uint32_t hashed_all_Qs;

  // Boost scoped_ptr, if available, could be used instead of std::auto_ptr.
  std::auto_ptr<const BlockHash> dh_;  // hash table is populated at startup
  std::auto_ptr<BlockHash> th_;  // hash table not populated;
                                // used to test incremental adds

  BlockHash::Match best_match_;
  char* compare_buffer_1_;
  char* compare_buffer_2_;
  int prime_result_;
};

#ifdef GTEST_HAS_DEATH_TEST
typedef BlockHashTest BlockHashDeathTest;
#endif  // GTEST_HAS_DEATH_TEST

// The C++ standard requires a separate definition of these static const values,
// even though their initializers are given within the class definition.
const int BlockHashTest::block_of_first_e;
const int BlockHashTest::block_of_second_e;
const int BlockHashTest::block_of_third_e;
const int BlockHashTest::block_of_fourth_e;
const int BlockHashTest::block_of_fifth_e;
const int BlockHashTest::block_of_sixth_e;
const int BlockHashTest::block_of_y_in_only;
const int BlockHashTest::index_of_first_e;
const int BlockHashTest::index_of_fourth_e;
const int BlockHashTest::index_of_sixth_e;
const int BlockHashTest::index_of_y_in_only;
const int BlockHashTest::index_of_space_before_fear_is_fear;
const int BlockHashTest::index_of_longest_match_ear_is_fear;
const int BlockHashTest::index_of_i_in_fear_is_fear;
const int BlockHashTest::index_of_space_before_fear_itself;
const int BlockHashTest::index_of_space_before_itself;
const int BlockHashTest::index_of_ababc;
const int BlockHashTest::index_of_second_w_in_what_we;
const int BlockHashTest::index_of_second_e_in_what_we_hear;
const int BlockHashTest::index_of_f_in_fearsome;
const int BlockHashTest::index_of_space_in_eat_itself;
const int BlockHashTest::index_of_i_in_itself;
const int BlockHashTest::index_of_t_in_use_the;
const int BlockHashTest::index_of_o_in_online;

const char BlockHashTest::sample_text_without_spaces[] =
    "The only thing we have to fear is fear itself";

const char BlockHashTest::search_string_without_spaces[] =
    "What we hear is fearsome";

const char BlockHashTest::search_string_altered_without_spaces[] =
    "Vhat ve hear is fearsomm";

const char BlockHashTest::search_to_end_without_spaces[] =
    "Pop will eat itself, eventually";

const char BlockHashTest::search_to_beginning_without_spaces[] =
    "Use The online dictionary";

const char BlockHashTest::sample_text_many_matches_without_spaces[] =
    "ababababcab";

const char BlockHashTest::search_string_many_matches_without_spaces[] =
    "ababc";

const char* BlockHashTest::sample_text = NULL;
const char* BlockHashTest::search_string = NULL;
const char* BlockHashTest::search_string_altered = NULL;
const char* BlockHashTest::search_to_end_string = NULL;
const char* BlockHashTest::search_to_beginning_string = NULL;
const char* BlockHashTest::sample_text_many_matches = NULL;
const char* BlockHashTest::search_string_many_matches = NULL;

const char* BlockHashTest::test_string_y = NULL;
const char* BlockHashTest::test_string_e = NULL;
const char* BlockHashTest::test_string_unaligned_e = NULL;
const char* BlockHashTest::test_string_all_Qs = NULL;

uint32_t BlockHashTest::hashed_y = 0;
uint32_t BlockHashTest::hashed_e = 0;
uint32_t BlockHashTest::hashed_f = 0;
uint32_t BlockHashTest::hashed_unaligned_e = 0;
uint32_t BlockHashTest::hashed_all_Qs = 0;

void BlockHashTest::TestAndPrintTimesForCompareFunctions(
    bool should_be_identical) {
  CHECK(compare_buffer_1_ != NULL);
  CHECK(compare_buffer_2_ != NULL);
  // Prime the memory cache.
  prime_result_ =
      memcmp(compare_buffer_1_, compare_buffer_2_, kTimingTestSize);
  const char* const block1_limit =
      &compare_buffer_1_[kTimingTestSize - kBlockSize];
  int block_compare_words_result = 0;
  CycleTimer block_compare_words_timer;
  block_compare_words_timer.Start();
  for (int i = 0; i < kTimingTestIterations; ++i) {
    const char* block1 = compare_buffer_1_;
    const char* block2 = compare_buffer_2_;
    while (block1 < block1_limit) {
      if (!BlockHash::BlockCompareWords(block1, block2)) {
        ++block_compare_words_result;
      }
      block1 += kBlockSize;
      block2 += kBlockSize;
    }
  }
  block_compare_words_timer.Stop();
  double time_for_block_compare_words =
      static_cast<double>(block_compare_words_timer.GetInUsec())
      / ((kTimingTestSize / kBlockSize) * kTimingTestIterations);
  int block_contents_match_result = 0;
  CycleTimer block_contents_match_timer;
  block_contents_match_timer.Start();
  for (int i = 0; i < kTimingTestIterations; ++i) {
    const char* block1 = compare_buffer_1_;
    const char* block2 = compare_buffer_2_;
    while (block1 < block1_limit) {
      if (!BlockHash::BlockContentsMatch(block1, block2)) {
        ++block_contents_match_result;
      }
      block1 += kBlockSize;
      block2 += kBlockSize;
    }
  }
  block_contents_match_timer.Stop();
  double time_for_block_contents_match =
      static_cast<double>(block_contents_match_timer.GetInUsec())
      / ((kTimingTestSize / kBlockSize) * kTimingTestIterations);
  EXPECT_EQ(block_contents_match_result, block_compare_words_result);
  if (should_be_identical) {
    CHECK_EQ(0, block_compare_words_result);
  } else {
    CHECK_GT(block_compare_words_result, 0);
  }
  std::cout << "BlockHash::BlockCompareWords: "
            << time_for_block_compare_words << " us per operation" << std::endl;
  std::cout << "BlockHash::BlockContentsMatch: "
            << time_for_block_contents_match << " us per operation"
            << std::endl;
  if (time_for_block_compare_words > 0) {
    double percent_change =
        (((time_for_block_contents_match - time_for_block_compare_words)
          / time_for_block_compare_words) * 100.0);
    if (percent_change >= 0.0) {
      std::cout << "BlockContentsMatch is " << percent_change << "%"
                << " SLOWER than BlockCompareWords" << std::endl;
    } else {
      std::cout << "BlockContentsMatch is " << (-percent_change) << "%"
                << " FASTER than BlockCompareWords" << std::endl;
    }
  }
#if defined(NDEBUG) && !defined(VCDIFF_USE_BLOCK_COMPARE_WORDS)
  // Only check timings for optimized build.  There's plenty of margin: this
  // check will fail only if BlockContentsMatch is at least twice as slow as
  // BlockCompareWords.
  EXPECT_GT(time_for_block_compare_words * 2.0, time_for_block_contents_match);
#endif  // NDEBUG && !VCDIFF_USE_BLOCK_COMPARE_WORDS
}

// The two strings passed to BlockHash::MatchingBytesToLeft do have matching
// characters -- in fact, they're the same string -- but since max_bytes is zero
// or negative, BlockHash::MatchingBytesToLeft should not read from the strings
// and should return 0.
TEST_F(BlockHashTest, MaxBytesZeroDoesNothing) {
  EXPECT_EQ(0, MatchingBytesToLeft(
      &search_string[index_of_f_in_fearsome],
      &search_string[index_of_f_in_fearsome],
      0));
  EXPECT_EQ(0, MatchingBytesToRight(
      &search_string[index_of_f_in_fearsome],
      &search_string[index_of_f_in_fearsome],
      0));
}

TEST_F(BlockHashTest, MaxBytesNegativeDoesNothing) {
  EXPECT_EQ(0, MatchingBytesToLeft(
      &search_string[index_of_f_in_fearsome],
      &search_string[index_of_f_in_fearsome],
      -1));
  EXPECT_EQ(0, MatchingBytesToLeft(
      &search_string[index_of_f_in_fearsome],
      &search_string[index_of_f_in_fearsome],
      INT_MIN));
  EXPECT_EQ(0, MatchingBytesToRight(
      &search_string[index_of_f_in_fearsome],
      &search_string[index_of_f_in_fearsome],
      -1));
  EXPECT_EQ(0, MatchingBytesToRight(
      &search_string[index_of_f_in_fearsome],
      &search_string[index_of_f_in_fearsome],
      INT_MIN));
}

TEST_F(BlockHashTest, MaxBytesOneMatch) {
  EXPECT_EQ(1, MatchingBytesToLeft(
      &search_string[index_of_f_in_fearsome],
      &search_string[index_of_f_in_fearsome],
      1));
  EXPECT_EQ(1, MatchingBytesToRight(
      &search_string[index_of_f_in_fearsome],
      &search_string[index_of_f_in_fearsome],
      1));
}

TEST_F(BlockHashTest, MaxBytesOneNoMatch) {
  EXPECT_EQ(0, MatchingBytesToLeft(
      &search_string[index_of_f_in_fearsome],
      &search_string[index_of_second_e_in_what_we_hear],
      1));
  EXPECT_EQ(0, MatchingBytesToRight(
      &search_string[index_of_f_in_fearsome],
      &search_string[index_of_second_e_in_what_we_hear - 1],
      1));
}

TEST_F(BlockHashTest, LeftLimitedByMaxBytes) {
  // The number of bytes that match between the original "we hear is fearsome"
  // and the altered "ve hear is fearsome".
  const int expected_length = kBlockSize * StringLengthAsInt("e hear is ");
  const int max_bytes = expected_length - 1;
  EXPECT_EQ(max_bytes, MatchingBytesToLeft(
      &search_string[index_of_f_in_fearsome],
      &search_string_altered[index_of_f_in_fearsome],
      max_bytes));
}

TEST_F(BlockHashTest, LeftNotLimited) {
  // The number of bytes that match between the original "we hear is fearsome"
  // and the altered "ve hear is fearsome".
  const int expected_length = kBlockSize * StringLengthAsInt("e hear is ");
  const int max_bytes = expected_length + 1;
  EXPECT_EQ(expected_length, MatchingBytesToLeft(
      &search_string[index_of_f_in_fearsome],
      &search_string_altered[index_of_f_in_fearsome],
      max_bytes));
  EXPECT_EQ(expected_length, MatchingBytesToLeft(
      &search_string[index_of_f_in_fearsome],
      &search_string_altered[index_of_f_in_fearsome],
      INT_MAX));
}

TEST_F(BlockHashTest, RightLimitedByMaxBytes) {
  // The number of bytes that match between the original "fearsome"
  // and the altered "fearsomm".
  const int expected_length = (kBlockSize * StringLengthAsInt("fearsom"))
                              + (kBlockSize - 1);  // spacing between letters
  const int max_bytes = expected_length - 1;
  EXPECT_EQ(max_bytes, MatchingBytesToRight(
      &search_string[index_of_f_in_fearsome],
      &search_string_altered[index_of_f_in_fearsome],
      max_bytes));
}

TEST_F(BlockHashTest, RightNotLimited) {
  // The number of bytes that match between the original "we hear is fearsome"
  // and the altered "ve hear is fearsome".
  const int expected_length = (kBlockSize * StringLengthAsInt("fearsom"))
                              + (kBlockSize - 1);  // spacing between letters
  const int max_bytes = expected_length + 1;
  EXPECT_EQ(expected_length, MatchingBytesToRight(
      &search_string[index_of_f_in_fearsome],
      &search_string_altered[index_of_f_in_fearsome],
      max_bytes));
  EXPECT_EQ(expected_length, MatchingBytesToRight(
      &search_string[index_of_f_in_fearsome],
      &search_string_altered[index_of_f_in_fearsome],
      INT_MAX));
}

// If this test fails in a non-x86 or non-gcc environment, consider adding
// -DVCDIFF_USE_BLOCK_COMPARE_WORDS to AM_CXXFLAGS in Makefile.am and
// Makefile.in, and reconstructing the Makefile.  That will cause blockhash.cc
// to use a special implementation (BlockCompareWords) to compare blocks
// rather than using standard memcmp.
TEST_F(BlockHashTest, BlockContentsMatchIsAsFastAsBlockCompareWords) {
  compare_buffer_1_ = new char[kTimingTestSize];
  compare_buffer_2_ = new char[kTimingTestSize];

  // The value 0xBE is arbitrarily chosen.  First test with identical contents
  // in the buffers, so that the comparison functions cannot short-circuit
  // and will return true.
  memset(compare_buffer_1_, 0xBE, kTimingTestSize);
  memset(compare_buffer_2_, 0xBE, kTimingTestSize);
  std::cout << "Comparing "
            << (kTimingTestSize / kBlockSize) << " identical values:"
            << std::endl;
  TestAndPrintTimesForCompareFunctions(true);

  // Now change one value in the middle of one buffer, so that the contents
  // are no longer the same.
  compare_buffer_1_[kTimingTestSize / 2] = 0x00;
  std::cout << "Comparing "
            << ((kTimingTestSize / kBlockSize) - 1) << " identical values"
            << " and one mismatch:" << std::endl;
  TestAndPrintTimesForCompareFunctions(false);

  // Set one of the bytes of each block to differ so that
  // none of the compare operations will return true, and run timing tests.
  // In practice, BlockHash::BlockContentsMatch will only be called
  // for two blocks whose hash values match, and the two most important
  // cases are: (1) the blocks are identical, or (2) none of their bytes match.
  TimingTestForBlocksThatDifferAtByte(0);
  TimingTestForBlocksThatDifferAtByte(1);
  TimingTestForBlocksThatDifferAtByte(kBlockSize / 2);
  TimingTestForBlocksThatDifferAtByte(kBlockSize - 1);

  delete[] compare_buffer_1_;
  delete[] compare_buffer_2_;
}

TEST_F(BlockHashTest, FindFailsBeforeHashing) {
  EXPECT_EQ(-1, FirstMatchingBlock(*th_, hashed_y, test_string_y));
}

TEST_F(BlockHashTest, HashOneFindOne) {
  for (int i = 0; i <= index_of_y_in_only; ++i) {
    th_->AddOneIndexHash(i, RollingHash<kBlockSize>::Hash(&sample_text[i]));
  }
  EXPECT_EQ(block_of_y_in_only, FirstMatchingBlock(*th_, hashed_y,
                                                   test_string_y));
  EXPECT_EQ(-1, NextMatchingBlock(*th_, block_of_y_in_only, test_string_y));
}

TEST_F(BlockHashTest, HashAllFindOne) {
  EXPECT_EQ(block_of_y_in_only, FirstMatchingBlock(*dh_, hashed_y,
                                                   test_string_y));
  EXPECT_EQ(-1, NextMatchingBlock(*dh_, block_of_y_in_only, test_string_y));
}

TEST_F(BlockHashTest, NonMatchingTextNotFound) {
  EXPECT_EQ(-1, FirstMatchingBlock(*dh_, hashed_all_Qs, test_string_all_Qs));
}

// Search for unaligned text.  The test string is contained in the
// sample text (unlike the non-matching string in NonMatchingTextNotFound,
// above), but it is not aligned on a block boundary.  FindMatchingBlock
// will only work if the test string is aligned on a block boundary.
//
//    "   T   h   e       o   n   l   y"
//              ^^^^ Here is the test string
//
TEST_F(BlockHashTest, UnalignedTextNotFound) {
  EXPECT_EQ(-1, FirstMatchingBlock(*dh_, hashed_unaligned_e,
                                   test_string_unaligned_e));
}

TEST_F(BlockHashTest, FindSixMatches) {
  EXPECT_EQ(block_of_first_e, FirstMatchingBlock(*dh_, hashed_e,
                                                 test_string_e));
  EXPECT_EQ(block_of_second_e, NextMatchingBlock(*dh_, block_of_first_e,
                                                 test_string_e));
  EXPECT_EQ(block_of_third_e, NextMatchingBlock(*dh_, block_of_second_e,
                                                test_string_e));
  EXPECT_EQ(block_of_fourth_e, NextMatchingBlock(*dh_, block_of_third_e,
                                                 test_string_e));
  EXPECT_EQ(block_of_fifth_e, NextMatchingBlock(*dh_, block_of_fourth_e,
                                                test_string_e));
  EXPECT_EQ(block_of_sixth_e, NextMatchingBlock(*dh_, block_of_fifth_e,
                                                test_string_e));
  EXPECT_EQ(-1, NextMatchingBlock(*dh_, block_of_sixth_e, test_string_e));

  // Starting over gives same result
  EXPECT_EQ(block_of_first_e, FirstMatchingBlock(*dh_, hashed_e,
                                                 test_string_e));
}

TEST_F(BlockHashTest, AddRangeFindThreeMatches) {
  // Add hash values only for those characters before the fourth instance
  // of "e" in the sample text.  Tests that the ending index
  // of AddAllBlocksThroughIndex() is not inclusive: only three matches
  // for "e" should be found.
  th_->AddAllBlocksThroughIndex(index_of_fourth_e);
  EXPECT_EQ(block_of_first_e, FirstMatchingBlock(*th_, hashed_e,
                                                 test_string_e));
  EXPECT_EQ(block_of_second_e, NextMatchingBlock(*th_, block_of_first_e,
                                                 test_string_e));
  EXPECT_EQ(block_of_third_e, NextMatchingBlock(*th_, block_of_second_e,
                                                test_string_e));
  EXPECT_EQ(-1, NextMatchingBlock(*th_, block_of_third_e, test_string_e));

  // Starting over gives same result
  EXPECT_EQ(block_of_first_e, FirstMatchingBlock(*th_, hashed_e,
                                                 test_string_e));
}

// Try indices that are not even multiples of the block size.
// Add three ranges and verify the results after each
// call to AddAllBlocksThroughIndex().
TEST_F(BlockHashTest, AddRangeWithUnalignedIndices) {
  th_->AddAllBlocksThroughIndex(index_of_first_e + 1);
  EXPECT_EQ(block_of_first_e, FirstMatchingBlock(*th_, hashed_e,
                                                 test_string_e));
  EXPECT_EQ(-1, NextMatchingBlock(*th_, block_of_first_e, test_string_e));

  // Starting over gives same result
  EXPECT_EQ(block_of_first_e, FirstMatchingBlock(*th_, hashed_e,
                                                 test_string_e));

  // Add the second range to expand the result set
  th_->AddAllBlocksThroughIndex(index_of_fourth_e - 3);
  EXPECT_EQ(block_of_first_e, FirstMatchingBlock(*th_, hashed_e,
                                                 test_string_e));
  EXPECT_EQ(block_of_second_e, NextMatchingBlock(*th_, block_of_first_e,
                                                 test_string_e));
  EXPECT_EQ(block_of_third_e, NextMatchingBlock(*th_, block_of_second_e,
                                                test_string_e));
  EXPECT_EQ(-1, NextMatchingBlock(*th_, block_of_third_e, test_string_e));

  // Starting over gives same result
  EXPECT_EQ(block_of_first_e, FirstMatchingBlock(*th_, hashed_e,
                                                 test_string_e));

  // Add the third range to expand the result set
  th_->AddAllBlocksThroughIndex(index_of_fourth_e + 1);

  EXPECT_EQ(block_of_first_e, FirstMatchingBlock(*th_, hashed_e,
                                                 test_string_e));
  EXPECT_EQ(block_of_second_e, NextMatchingBlock(*th_, block_of_first_e,
                                                 test_string_e));
  EXPECT_EQ(block_of_third_e, NextMatchingBlock(*th_, block_of_second_e,
                                                test_string_e));
  EXPECT_EQ(block_of_fourth_e, NextMatchingBlock(*th_, block_of_third_e,
                                                 test_string_e));
  EXPECT_EQ(-1, NextMatchingBlock(*th_, block_of_fourth_e, test_string_e));

  // Starting over gives same result
  EXPECT_EQ(block_of_first_e, FirstMatchingBlock(*th_, hashed_e,
                                                 test_string_e));
}

#ifdef GTEST_HAS_DEATH_TEST
TEST_F(BlockHashDeathTest, AddingRangesInDescendingOrderNoEffect) {
  th_->AddAllBlocksThroughIndex(index_of_fourth_e + 1);

  EXPECT_EQ(block_of_first_e, FirstMatchingBlock(*th_, hashed_e,
                                                 test_string_e));
  EXPECT_EQ(block_of_second_e, NextMatchingBlock(*th_, block_of_first_e,
                                                 test_string_e));
  EXPECT_EQ(block_of_third_e, NextMatchingBlock(*th_, block_of_second_e,
                                                test_string_e));
  EXPECT_EQ(block_of_fourth_e, NextMatchingBlock(*th_, block_of_third_e,
                                                 test_string_e));
  EXPECT_EQ(-1, NextMatchingBlock(*th_, block_of_fourth_e, test_string_e));

  // Starting over gives same result
  EXPECT_EQ(block_of_first_e, FirstMatchingBlock(*th_, hashed_e,
                                                 test_string_e));

  // These calls will produce DFATAL error messages, and will do nothing,
  // since the ranges have already been added.
  EXPECT_DEBUG_DEATH(th_->AddAllBlocksThroughIndex(index_of_fourth_e - 3),
                     "<");
  EXPECT_DEBUG_DEATH(th_->AddAllBlocksThroughIndex(index_of_first_e + 1),
                     "<");

  EXPECT_EQ(block_of_first_e, FirstMatchingBlock(*th_, hashed_e,
                                                 test_string_e));
  EXPECT_EQ(block_of_second_e, NextMatchingBlock(*th_, block_of_first_e,
                                                 test_string_e));
  EXPECT_EQ(block_of_third_e, NextMatchingBlock(*th_, block_of_second_e,
                                                test_string_e));
  EXPECT_EQ(block_of_fourth_e, NextMatchingBlock(*th_, block_of_third_e,
                                                 test_string_e));
  EXPECT_EQ(-1, NextMatchingBlock(*th_, block_of_fourth_e, test_string_e));

  // Starting over gives same result
  EXPECT_EQ(block_of_first_e, FirstMatchingBlock(*th_, hashed_e,
                                                 test_string_e));
}
#endif  // GTEST_HAS_DEATH_TEST

TEST_F(BlockHashTest, AddEntireRangeFindSixMatches) {
  th_->AddAllBlocksThroughIndex(StringLengthAsInt(sample_text));
  EXPECT_EQ(block_of_first_e, FirstMatchingBlock(*th_, hashed_e,
                                                 test_string_e));
  EXPECT_EQ(block_of_second_e, NextMatchingBlock(*th_, block_of_first_e,
                                                 test_string_e));
  EXPECT_EQ(block_of_third_e, NextMatchingBlock(*th_, block_of_second_e,
                                                test_string_e));
  EXPECT_EQ(block_of_fourth_e, NextMatchingBlock(*th_, block_of_third_e,
                                                 test_string_e));
  EXPECT_EQ(block_of_fifth_e, NextMatchingBlock(*th_, block_of_fourth_e,
                                                test_string_e));
  EXPECT_EQ(block_of_sixth_e, NextMatchingBlock(*th_, block_of_fifth_e,
                                                test_string_e));
  EXPECT_EQ(-1, NextMatchingBlock(*th_, block_of_sixth_e, test_string_e));

  // Starting over gives same result
  EXPECT_EQ(block_of_first_e, FirstMatchingBlock(*th_, hashed_e,
                                                 test_string_e));
}

TEST_F(BlockHashTest, ZeroSizeSourceAccepted) {
  BlockHash zero_sized_hash(sample_text, 0, 0);
  EXPECT_EQ(true, zero_sized_hash.Init(true));
  EXPECT_EQ(-1, FirstMatchingBlock(*th_, hashed_y, test_string_y));
}

#ifdef GTEST_HAS_DEATH_TEST
TEST_F(BlockHashDeathTest, BadNextMatchingBlockReturnsNoMatch) {
  EXPECT_DEBUG_DEATH(EXPECT_EQ(-1, NextMatchingBlock(*dh_, 0xFFFFFFFE, "    ")),
                     "invalid");
}

TEST_F(BlockHashDeathTest, CallingInitTwiceIsIllegal) {
  BlockHash bh(sample_text, strlen(sample_text), 0);
  EXPECT_TRUE(bh.Init(false));
  EXPECT_DEBUG_DEATH(EXPECT_FALSE(bh.Init(false)), "twice");
}

TEST_F(BlockHashDeathTest, CallingAddBlockBeforeInitIsIllegal) {
  BlockHash bh(sample_text, strlen(sample_text), 0);
  EXPECT_DEBUG_DEATH(bh.AddAllBlocksThroughIndex(index_of_first_e),
                     "called before");
}

TEST_F(BlockHashDeathTest, AddAllBlocksThroughIndexOutOfRange) {
  EXPECT_DEBUG_DEATH(th_->AddAllBlocksThroughIndex(strlen(sample_text) + 1),
                     "higher than end");
}
#endif  // GTEST_HAS_DEATH_TEST

TEST_F(BlockHashTest, UnknownFingerprintReturnsNoMatch) {
  EXPECT_EQ(-1, FirstMatchingBlock(*dh_, 0xFAFAFAFA, "FAFA"));
}

TEST_F(BlockHashTest, FindBestMatch) {
  dh_->FindBestMatch(hashed_f,
                     &search_string[index_of_f_in_fearsome],
                     search_string,
                     strlen(search_string),
                     &best_match_);
  EXPECT_EQ(index_of_longest_match_ear_is_fear, best_match_.source_offset());
  EXPECT_EQ(index_of_second_e_in_what_we_hear, best_match_.target_offset());
  // The match includes the spaces after the final character,
  // which is why (kBlockSize - 1) is added to the expected best size.
  EXPECT_EQ((strlen("ear is fear") * kBlockSize) + (kBlockSize - 1),
            best_match_.size());
}

TEST_F(BlockHashTest, FindBestMatchWithStartingOffset) {
  BlockHash th2(sample_text, strlen(sample_text), 0x10000);
  th2.Init(true);  // hash all blocks
  th2.FindBestMatch(hashed_f,
                    &search_string[index_of_f_in_fearsome],
                    search_string,
                    strlen(search_string),
                    &best_match_);
  // Offset should begin with dictionary_size
  EXPECT_EQ(0x10000 + (index_of_longest_match_ear_is_fear),
            best_match_.source_offset());
  EXPECT_EQ(index_of_second_e_in_what_we_hear, best_match_.target_offset());
  // The match includes the spaces after the final character,
  // which is why (kBlockSize - 1) is added to the expected best size.
  EXPECT_EQ((strlen("ear is fear") * kBlockSize) + (kBlockSize - 1),
            best_match_.size());
}

TEST_F(BlockHashTest, BestMatchReachesEndOfDictionary) {
  // Hash the "i" in "fear itself"
  uint32_t hash_value = RollingHash<kBlockSize>::Hash(
      &search_to_end_string[index_of_i_in_itself]);
  dh_->FindBestMatch(hash_value,
                     &search_to_end_string[index_of_i_in_itself],
                     search_to_end_string,
                     strlen(search_to_end_string),
                     &best_match_);
  EXPECT_EQ(index_of_space_before_itself, best_match_.source_offset());
  EXPECT_EQ(index_of_space_in_eat_itself, best_match_.target_offset());
  EXPECT_EQ(strlen(" itself") * kBlockSize, best_match_.size());
}

TEST_F(BlockHashTest, BestMatchReachesStartOfDictionary) {
  // Hash the "i" in "fear itself"
  uint32_t hash_value = RollingHash<kBlockSize>::Hash(
      &search_to_beginning_string[index_of_o_in_online]);
  dh_->FindBestMatch(hash_value,
                     &search_to_beginning_string[index_of_o_in_online],
                     search_to_beginning_string,
                     strlen(search_to_beginning_string),
                     &best_match_);
  EXPECT_EQ(0, best_match_.source_offset());  // beginning of dictionary
  EXPECT_EQ(index_of_t_in_use_the, best_match_.target_offset());
  // The match includes the spaces after the final character,
  // which is why (kBlockSize - 1) is added to the expected best size.
  EXPECT_EQ((strlen("The onl") * kBlockSize) + (kBlockSize - 1),
            best_match_.size());
}

TEST_F(BlockHashTest, BestMatchWithManyMatches) {
  BlockHash many_matches_hash(sample_text_many_matches,
                              strlen(sample_text_many_matches),
                              0);
  EXPECT_TRUE(many_matches_hash.Init(true));
  // Hash the "   a" at the beginning of the search string "ababc"
  uint32_t hash_value =
      RollingHash<kBlockSize>::Hash(search_string_many_matches);
  many_matches_hash.FindBestMatch(hash_value,
                                  search_string_many_matches,
                                  search_string_many_matches,
                                  strlen(search_string_many_matches),
                                  &best_match_);
  EXPECT_EQ(index_of_ababc, best_match_.source_offset());
  EXPECT_EQ(0, best_match_.target_offset());
  EXPECT_EQ(strlen(search_string_many_matches), best_match_.size());
}

TEST_F(BlockHashTest, HashCollisionFindsNoMatch) {
  char* collision_search_string = new char[strlen(search_string) + 1];
  memcpy(collision_search_string, search_string, strlen(search_string) + 1);
  char* fearsome_location = &collision_search_string[index_of_f_in_fearsome];

  // Tweak the collision string so that it has the same hash value
  // but different text.  The last four characters of the search string
  // should be "   f", and the bytes given below have the same hash value
  // as those characters.
  CHECK_GE(kBlockSize, 4);
  fearsome_location[kBlockSize - 4] = 0x84;
  fearsome_location[kBlockSize - 3] = 0xF1;
  fearsome_location[kBlockSize - 2] = 0x51;
  fearsome_location[kBlockSize - 1] = 0x00;
  EXPECT_EQ(hashed_f, RollingHash<kBlockSize>::Hash(fearsome_location));
  EXPECT_NE(0, memcmp(&search_string[index_of_f_in_fearsome],
                      fearsome_location,
                      kBlockSize));
  // No match should be found this time.
  dh_->FindBestMatch(hashed_f,
      fearsome_location,
      collision_search_string,
      strlen(search_string),  // since collision_search_string has embedded \0
      &best_match_);
  EXPECT_EQ(-1, best_match_.source_offset());
  EXPECT_EQ(-1, best_match_.target_offset());
  EXPECT_EQ(0U, best_match_.size());
  delete[] collision_search_string;
}

// If the footprint passed to FindBestMatch does not actually match
// the search string, it should not find any matches.
TEST_F(BlockHashTest, WrongFootprintFindsNoMatch) {
  dh_->FindBestMatch(hashed_e,  // Using hashed value of "e" instead of "f"!
                     &search_string[index_of_f_in_fearsome],
                     search_string,
                     strlen(search_string),
                     &best_match_);
  EXPECT_EQ(-1, best_match_.source_offset());
  EXPECT_EQ(-1, best_match_.target_offset());
  EXPECT_EQ(0U, best_match_.size());
}

// Use a dictionary containing 1M copies of the letter 'Q',
// and target data that also contains 1M Qs.  If FindBestMatch
// is not throttled to find a maximum number of matches, this
// will take a very long time -- several seconds at least.
// If this test appears to hang, it is because the throttling code
// (see BlockHash::kMaxMatchesToCheck for details) is not working.
TEST_F(BlockHashTest, SearchStringFindsTooManyMatches) {
  const int kTestSize = 1 << 20;  // 1M
  char* huge_dictionary = new char[kTestSize];
  memset(huge_dictionary, 'Q', kTestSize);
  BlockHash huge_bh(huge_dictionary, kTestSize, 0);
  EXPECT_TRUE(huge_bh.Init(/* populate_hash_table = */ true));
  char* huge_target = new char[kTestSize];
  memset(huge_target, 'Q', kTestSize);
  CycleTimer timer;
  timer.Start();
  huge_bh.FindBestMatch(hashed_all_Qs,
                        huge_target + (kTestSize / 2),  // middle of target
                        huge_target,
                        kTestSize,
                        &best_match_);
  timer.Stop();
  double elapsed_time_in_us = static_cast<double>(timer.GetInUsec());
  std::cout << "Time to search for best match with 1M matches: "
            << elapsed_time_in_us << " us" << std::endl;
  // All blocks match the candidate block.  FindBestMatch should have checked
  // a certain number of matches before giving up.  The best match
  // should include at least half the source and target, since the candidate
  // block was in the middle of the target data.
  EXPECT_GT((kTestSize / 2), best_match_.source_offset());
  EXPECT_GT((kTestSize / 2), best_match_.target_offset());
  EXPECT_LT(static_cast<size_t>(kTestSize / 2), best_match_.size());
  EXPECT_GT(5000000, elapsed_time_in_us);  // < 5 seconds
#ifdef NDEBUG
  EXPECT_GT(1000000, elapsed_time_in_us);  // < 1 second
#endif  // NDEBUG
  delete[] huge_target;
  delete[] huge_dictionary;
}

#ifdef GTEST_HAS_DEATH_TEST
TEST_F(BlockHashDeathTest, AddTooManyBlocks) {
  for (int i = 0; i < StringLengthAsInt(sample_text_without_spaces); ++i) {
    th_->AddOneIndexHash(i * kBlockSize, hashed_e);
  }
  // Didn't expect another block to be added
  EXPECT_DEBUG_DEATH(th_->AddOneIndexHash(StringLengthAsInt(sample_text),
                                          hashed_e),
                     "AddBlock");
}
#endif  // GTEST_HAS_DEATH_TEST

}  //  namespace open_vcdiff
