diff options
Diffstat (limited to 'sdch/open-vcdiff/src/rolling_hash_test.cc')
-rw-r--r-- | sdch/open-vcdiff/src/rolling_hash_test.cc | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/sdch/open-vcdiff/src/rolling_hash_test.cc b/sdch/open-vcdiff/src/rolling_hash_test.cc new file mode 100644 index 0000000..685f7dd --- /dev/null +++ b/sdch/open-vcdiff/src/rolling_hash_test.cc @@ -0,0 +1,231 @@ +// Copyright 2007 Google Inc. +// Authors: Jeff Dean, 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 "rolling_hash.h" +#include <stdint.h> // uint32_t +#include <stdlib.h> // rand, srand +#include <vector> +#include "testing.h" + +namespace open_vcdiff { +namespace { + +static const uint32_t kBase = RollingHashUtil::kBase; + +class RollingHashSimpleTest : public testing::Test { + protected: + RollingHashSimpleTest() { } + virtual ~RollingHashSimpleTest() { } + + void TestModBase(uint32_t operand) { + EXPECT_EQ(operand % kBase, RollingHashUtil::ModBase(operand)); + EXPECT_EQ(static_cast<uint32_t>((-static_cast<int32_t>(operand)) % kBase), + RollingHashUtil::FindModBaseInverse(operand)); + EXPECT_EQ(0U, RollingHashUtil::ModBase( + operand + RollingHashUtil::FindModBaseInverse(operand))); + } + + void TestHashFirstTwoBytes(char first_value, char second_value) { + char buf[2]; + buf[0] = first_value; + buf[1] = second_value; + EXPECT_EQ(RollingHashUtil::HashFirstTwoBytes(buf), + RollingHashUtil::HashStep(RollingHashUtil::HashStep(0, + first_value), + second_value)); + EXPECT_EQ(RollingHashUtil::HashFirstTwoBytes(buf), + RollingHashUtil::HashStep(static_cast<unsigned char>(first_value), + second_value)); + } +}; + +#ifdef GTEST_HAS_DEATH_TEST +typedef RollingHashSimpleTest RollingHashDeathTest; +#endif // GTEST_HAS_DEATH_TEST + +TEST_F(RollingHashSimpleTest, KBaseIsAPowerOfTwo) { + EXPECT_EQ(0U, kBase & (kBase - 1)); +} + +TEST_F(RollingHashSimpleTest, TestModBaseForValues) { + TestModBase(0); + TestModBase(10); + TestModBase(static_cast<uint32_t>(-10)); + TestModBase(kBase - 1); + TestModBase(kBase); + TestModBase(kBase + 1); + TestModBase(0x7FFFFFFF); + TestModBase(0x80000000); + TestModBase(0xFFFFFFFE); + TestModBase(0xFFFFFFFF); +} + +TEST_F(RollingHashSimpleTest, VerifyHashFirstTwoBytes) { + TestHashFirstTwoBytes(0x00, 0x00); + TestHashFirstTwoBytes(0x00, 0xFF); + TestHashFirstTwoBytes(0xFF, 0x00); + TestHashFirstTwoBytes(0xFF, 0xFF); + TestHashFirstTwoBytes(0x00, 0x80); + TestHashFirstTwoBytes(0x7F, 0xFF); + TestHashFirstTwoBytes(0x7F, 0x80); + TestHashFirstTwoBytes(0x01, 0x8F); +} + +#ifdef GTEST_HAS_DEATH_TEST +TEST_F(RollingHashDeathTest, InstantiateRollingHashWithoutCallingInit) { + EXPECT_DEBUG_DEATH(RollingHash<16> bad_hash, "Init"); +} +#endif // GTEST_HAS_DEATH_TEST + +class RollingHashTest : public testing::Test { + public: + static const int kUpdateHashBlocks = 1000; + static const int kLargestBlockSize = 128; + + static void MakeRandomBuffer(char* buffer, int buffer_size) { + for (int i = 0; i < buffer_size; ++i) { + buffer[i] = PortableRandomInRange<unsigned char>(0xFF); + } + } + + template<int kBlockSize> static void BM_DefaultHash(int iterations, + const char *buffer) { + RollingHash<kBlockSize> hasher; + static uint32_t result_array[kUpdateHashBlocks]; + for (int iter = 0; iter < iterations; ++iter) { + for (int i = 0; i < kUpdateHashBlocks; ++i) { + result_array[i] = hasher.Hash(&buffer[i]); + } + } + } + + template<int kBlockSize> static void BM_UpdateHash(int iterations, + const char *buffer) { + RollingHash<kBlockSize> hasher; + static uint32_t result_array[kUpdateHashBlocks]; + for (int iter = 0; iter < iterations; ++iter) { + uint32_t running_hash = hasher.Hash(buffer); + for (int i = 0; i < kUpdateHashBlocks; ++i) { + running_hash = hasher.UpdateHash(running_hash, + buffer[i], + buffer[i + kBlockSize]); + result_array[i] = running_hash; + } + } + } + + protected: + static const int kUpdateHashTestIterations = 400; + static const int kTimingTestSize = 1 << 14; // 16K iterations + + RollingHashTest() { } + virtual ~RollingHashTest() { } + + template<int kBlockSize> void UpdateHashMatchesHashForBlockSize() { + RollingHash<kBlockSize>::Init(); + RollingHash<kBlockSize> hasher; + for (int x = 0; x < kUpdateHashTestIterations; ++x) { + int random_buffer_size = + PortableRandomInRange(kUpdateHashBlocks - 1) + kBlockSize; + MakeRandomBuffer(buffer_, random_buffer_size); + uint32_t running_hash = hasher.Hash(buffer_); + for (int i = kBlockSize; i < random_buffer_size; ++i) { + // UpdateHash() calculates the hash value incrementally. + running_hash = hasher.UpdateHash(running_hash, + buffer_[i - kBlockSize], + buffer_[i]); + // Hash() calculates the hash value from scratch. Verify that both + // methods return the same hash value. + EXPECT_EQ(running_hash, hasher.Hash(&buffer_[i + 1 - kBlockSize])); + } + } + } + + template<int kBlockSize> double DefaultHashTimingTest() { + // Execution time is expected to be O(kBlockSize) per hash operation, + // so scale the number of iterations accordingly + const int kTimingTestIterations = kTimingTestSize / kBlockSize; + CycleTimer timer; + timer.Start(); + BM_DefaultHash<kBlockSize>(kTimingTestIterations, buffer_); + timer.Stop(); + return static_cast<double>(timer.GetInUsec()) + / (kTimingTestIterations * kUpdateHashBlocks); + } + + template<int kBlockSize> double RollingTimingTest() { + // Execution time is expected to be O(1) per hash operation, + // so leave the number of iterations constant + const int kTimingTestIterations = kTimingTestSize; + CycleTimer timer; + timer.Start(); + BM_UpdateHash<kBlockSize>(kTimingTestIterations, buffer_); + timer.Stop(); + return static_cast<double>(timer.GetInUsec()) + / (kTimingTestIterations * kUpdateHashBlocks); + } + + double FindPercentage(double original, double modified) { + if (original < 0.0001) { + return 0.0; + } else { + return ((modified - original) / original) * 100.0; + } + } + + template<int kBlockSize> void RunTimingTestForBlockSize() { + RollingHash<kBlockSize>::Init(); + MakeRandomBuffer(buffer_, sizeof(buffer_)); + const double time_for_default_hash = DefaultHashTimingTest<kBlockSize>(); + const double time_for_rolling_hash = RollingTimingTest<kBlockSize>(); + printf("%d\t%f\t%f (%f%%)\n", + kBlockSize, + time_for_default_hash, + time_for_rolling_hash, + FindPercentage(time_for_default_hash, time_for_rolling_hash)); + CHECK_GT(time_for_default_hash, 0.0); + CHECK_GT(time_for_rolling_hash, 0.0); + if (kBlockSize > 16) { + EXPECT_GT(time_for_default_hash, time_for_rolling_hash); + } + } + + char buffer_[kUpdateHashBlocks + kLargestBlockSize]; +}; + +TEST_F(RollingHashTest, UpdateHashMatchesHashFromScratch) { + srand(1); // test should be deterministic, including calls to rand() + UpdateHashMatchesHashForBlockSize<4>(); + UpdateHashMatchesHashForBlockSize<8>(); + UpdateHashMatchesHashForBlockSize<16>(); + UpdateHashMatchesHashForBlockSize<32>(); + UpdateHashMatchesHashForBlockSize<64>(); + UpdateHashMatchesHashForBlockSize<128>(); +} + +TEST_F(RollingHashTest, TimingTests) { + srand(1); // test should be deterministic, including calls to rand() + printf("BlkSize\tHash (us)\tUpdateHash (us)\n"); + RunTimingTestForBlockSize<4>(); + RunTimingTestForBlockSize<8>(); + RunTimingTestForBlockSize<16>(); + RunTimingTestForBlockSize<32>(); + RunTimingTestForBlockSize<64>(); + RunTimingTestForBlockSize<128>(); +} + +} // anonymous namespace +} // namespace open_vcdiff |