| Index: sync/internal_api/public/base/unique_position_unittest.cc
|
| diff --git a/sync/internal_api/public/base/unique_position_unittest.cc b/sync/internal_api/public/base/unique_position_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..bda9520e44ba65d16372424e7c5b5c10d58bb936
|
| --- /dev/null
|
| +++ b/sync/internal_api/public/base/unique_position_unittest.cc
|
| @@ -0,0 +1,520 @@
|
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "sync/internal_api/public/base/unique_position.h"
|
| +
|
| +#include <algorithm>
|
| +#include <string>
|
| +
|
| +#include "base/base64.h"
|
| +#include "base/basictypes.h"
|
| +#include "base/logging.h"
|
| +#include "base/sha1.h"
|
| +#include "base/string_number_conversions.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +
|
| +namespace syncer {
|
| +
|
| +namespace {
|
| +
|
| +class UniquePositionTest : public ::testing::Test {
|
| + protected:
|
| + // Accessor to fetch the length of the position's internal representation
|
| + // We try to avoid having any test expectations on it because this is an
|
| + // implementation detail.
|
| + //
|
| + // If you run the tests with --v=1, we'll print out some of the lengths
|
| + // so you can see how well the algorithm performs in various insertion
|
| + // scenarios.
|
| + size_t GetLength(const UniquePosition& pos) {
|
| + return pos.ToInternalValue().length();
|
| + }
|
| +};
|
| +
|
| +const size_t kMinLength = UniquePosition::kSuffixLength;
|
| +const size_t kGenericPredecessorLength = kMinLength + 2;
|
| +const size_t kGenericSuccessorLength = kMinLength + 1;
|
| +const size_t kBigPositionLength = kMinLength;
|
| +const size_t kSmallPositionLength = kMinLength;
|
| +
|
| +// Be careful when adding more prefixes to this list.
|
| +// We have to manually ensure each has a unique suffix.
|
| +const UniquePosition kGenericPredecessor = UniquePosition::FromBytes(
|
| + (std::string(kGenericPredecessorLength, '\x23') + '\xFF'));
|
| +const UniquePosition kGenericSuccessor = UniquePosition::FromBytes(
|
| + std::string(kGenericSuccessorLength, '\xAB') + '\xFF');
|
| +const UniquePosition kBigPosition = UniquePosition::FromBytes(
|
| + std::string(kBigPositionLength - 1, '\xFF') + '\xFE' + '\xFF');
|
| +const UniquePosition kBigPositionLessTwo = UniquePosition::FromBytes(
|
| + std::string(kBigPositionLength - 1, '\xFF') + '\xFC' + '\xFF');
|
| +const UniquePosition kBiggerPosition = UniquePosition::FromBytes(
|
| + std::string(kBigPositionLength, '\xFF') + '\xFF');
|
| +const UniquePosition kSmallPosition = UniquePosition::FromBytes(
|
| + std::string(kSmallPositionLength - 1, '\x00') + '\x01' + '\xFF');
|
| +const UniquePosition kSmallPositionPlusOne = UniquePosition::FromBytes(
|
| + std::string(kSmallPositionLength - 1, '\x00') + '\x02' + '\xFF');
|
| +
|
| +const std::string kMinSuffix =
|
| + std::string(UniquePosition::kSuffixLength - 1, '\x00') + '\x01';
|
| +const std::string kMaxSuffix(UniquePosition::kSuffixLength, '\xFF');
|
| +const std::string kNormalSuffix(UniquePosition::kSuffixLength, '\xAB');
|
| +
|
| +::testing::AssertionResult LessThan(const char* m_expr,
|
| + const char* n_expr,
|
| + const UniquePosition &m,
|
| + const UniquePosition &n) {
|
| + if (m.LessThan(n))
|
| + return ::testing::AssertionSuccess();
|
| +
|
| + return ::testing::AssertionFailure()
|
| + << m_expr << " is not less than " << n_expr
|
| + << " (" << m.ToDebugString() << " and " << n.ToDebugString() << ")";
|
| +}
|
| +
|
| +class RelativePositioningTest : public UniquePositionTest { };
|
| +
|
| +const UniquePosition kPositionArray[] = {
|
| + kGenericPredecessor,
|
| + kGenericSuccessor,
|
| + kBigPosition,
|
| + kBigPositionLessTwo,
|
| + kBiggerPosition,
|
| + kSmallPosition,
|
| + kSmallPositionPlusOne,
|
| +};
|
| +
|
| +const UniquePosition kSortedPositionArray[] = {
|
| + kSmallPosition,
|
| + kSmallPositionPlusOne,
|
| + kGenericPredecessor,
|
| + kGenericSuccessor,
|
| + kBigPositionLessTwo,
|
| + kBigPosition,
|
| + kBiggerPosition,
|
| +};
|
| +
|
| +static const size_t kNumPositions = arraysize(kPositionArray);
|
| +static const size_t kNumSortedPositions = arraysize(kSortedPositionArray);
|
| +
|
| +struct PositionLessThan {
|
| + bool operator()(const UniquePosition& a, const UniquePosition& b) {
|
| + return a.LessThan(b);
|
| + }
|
| +};
|
| +
|
| +static bool IsSuffixInUse(
|
| + const UniquePosition& pos, const std::string& suffix) {
|
| + const std::string& pos_bytes = pos.ToInternalValue();
|
| + const size_t prefix_len = pos_bytes.length() - UniquePosition::kSuffixLength;
|
| + return pos_bytes.substr(prefix_len, std::string::npos) == suffix;
|
| +}
|
| +
|
| +// Test some basic properties of comparison and equality.
|
| +TEST_F(RelativePositioningTest, ComparisonSanityTest1) {
|
| + const UniquePosition& a = kPositionArray[0];
|
| + ASSERT_TRUE(a.IsValid());
|
| +
|
| + // Necessarily true for any non-invalid positions.
|
| + EXPECT_TRUE(a.Equals(a));
|
| + EXPECT_FALSE(a.LessThan(a));
|
| +}
|
| +
|
| +// Test some more properties of comparison and equality.
|
| +TEST_F(RelativePositioningTest, ComparisonSanityTest2) {
|
| + const UniquePosition& a = kPositionArray[0];
|
| + const UniquePosition& b = kPositionArray[1];
|
| +
|
| + // These should pass for the specific a and b we have chosen (a < b).
|
| + EXPECT_FALSE(a.Equals(b));
|
| + EXPECT_TRUE(a.LessThan(b));
|
| + EXPECT_FALSE(b.LessThan(a));
|
| +}
|
| +
|
| +// Exercise comparision functions by sorting and re-sorting the list.
|
| +TEST_F(RelativePositioningTest, SortPositions) {
|
| + ASSERT_EQ(kNumPositions, kNumSortedPositions);
|
| + UniquePosition positions[arraysize(kPositionArray)];
|
| + for (size_t i = 0; i < kNumPositions; ++i) {
|
| + positions[i] = kPositionArray[i];
|
| + }
|
| +
|
| + std::sort(&positions[0], &positions[kNumPositions], PositionLessThan());
|
| + for (size_t i = 0; i < kNumPositions; ++i) {
|
| + EXPECT_TRUE(positions[i].Equals(kSortedPositionArray[i]))
|
| + << "i: " << i << ", "
|
| + << positions[i].ToDebugString() << " != "
|
| + << kSortedPositionArray[i].ToDebugString();
|
| + }
|
| +
|
| +}
|
| +
|
| +// Some more exercise for the comparison function.
|
| +TEST_F(RelativePositioningTest, ReverseSortPositions) {
|
| + ASSERT_EQ(kNumPositions, kNumSortedPositions);
|
| + UniquePosition positions[arraysize(kPositionArray)];
|
| + for (size_t i = 0; i < kNumPositions; ++i) {
|
| + positions[i] = kPositionArray[i];
|
| + }
|
| +
|
| + std::reverse(&positions[0], &positions[kNumPositions]);
|
| + std::sort(&positions[0], &positions[kNumPositions], PositionLessThan());
|
| + for (size_t i = 0; i < kNumPositions; ++i) {
|
| + EXPECT_TRUE(positions[i].Equals(kSortedPositionArray[i]))
|
| + << "i: " << i << ", "
|
| + << positions[i].ToDebugString() << " != "
|
| + << kSortedPositionArray[i].ToDebugString();
|
| + }
|
| +}
|
| +
|
| +class PositionInsertTest :
|
| + public RelativePositioningTest,
|
| + public ::testing::WithParamInterface<std::string> { };
|
| +
|
| +// Exercise InsertBetween with various insertion operations.
|
| +TEST_P(PositionInsertTest, InsertBetween) {
|
| + const std::string suffix = GetParam();
|
| + ASSERT_TRUE(UniquePosition::IsValidSuffix(suffix));
|
| +
|
| + for (size_t i = 0; i < kNumSortedPositions; ++i) {
|
| + const UniquePosition& predecessor = kSortedPositionArray[i];
|
| + // Verify our suffixes are unique before we continue.
|
| + if (IsSuffixInUse(predecessor, suffix))
|
| + continue;
|
| +
|
| + for (size_t j = i + 1; j < kNumSortedPositions; ++j) {
|
| + const UniquePosition& successor = kSortedPositionArray[j];
|
| +
|
| + // Another guard against non-unique suffixes.
|
| + if (IsSuffixInUse(successor, suffix))
|
| + continue;
|
| +
|
| + UniquePosition midpoint =
|
| + UniquePosition::Between(predecessor, successor, suffix);
|
| +
|
| + EXPECT_PRED_FORMAT2(LessThan, predecessor, midpoint);
|
| + EXPECT_PRED_FORMAT2(LessThan, midpoint, successor);
|
| + }
|
| + }
|
| +}
|
| +
|
| +TEST_P(PositionInsertTest, InsertBefore) {
|
| + const std::string suffix = GetParam();
|
| + for (size_t i = 0; i < kNumSortedPositions; ++i) {
|
| + const UniquePosition& successor = kSortedPositionArray[i];
|
| + // Verify our suffixes are unique before we continue.
|
| + if (IsSuffixInUse(successor, suffix))
|
| + continue;
|
| +
|
| + UniquePosition before = UniquePosition::Before(successor, suffix);
|
| +
|
| + EXPECT_PRED_FORMAT2(LessThan, before, successor);
|
| + }
|
| +}
|
| +
|
| +TEST_P(PositionInsertTest, InsertAfter) {
|
| + const std::string suffix = GetParam();
|
| + for (size_t i = 0; i < kNumSortedPositions; ++i) {
|
| + const UniquePosition& predecessor = kSortedPositionArray[i];
|
| + // Verify our suffixes are unique before we continue.
|
| + if (IsSuffixInUse(predecessor, suffix))
|
| + continue;
|
| +
|
| + UniquePosition after = UniquePosition::After(predecessor, suffix);
|
| +
|
| + EXPECT_PRED_FORMAT2(LessThan, predecessor, after);
|
| + }
|
| +}
|
| +
|
| +TEST_P(PositionInsertTest, StressInsertAfter) {
|
| + // Use two different suffixes to not violate our suffix uniqueness guarantee.
|
| + const std::string& suffix_a = GetParam();
|
| + std::string suffix_b = suffix_a;
|
| + suffix_b[10] = suffix_b[10] ^ 0xff;
|
| +
|
| + UniquePosition pos = UniquePosition::InitialPosition(suffix_a);
|
| + for (int i = 0; i < 1024; i++) {
|
| + const std::string& suffix = (i % 2 == 0) ? suffix_b : suffix_a;
|
| + UniquePosition next_pos = UniquePosition::After(pos, suffix);
|
| + ASSERT_PRED_FORMAT2(LessThan, pos, next_pos);
|
| + pos = next_pos;
|
| + }
|
| +
|
| + VLOG(1) << "Length: " << GetLength(pos);
|
| +}
|
| +
|
| +TEST_P(PositionInsertTest, StressInsertBefore) {
|
| + // Use two different suffixes to not violate our suffix uniqueness guarantee.
|
| + const std::string& suffix_a = GetParam();
|
| + std::string suffix_b = suffix_a;
|
| + suffix_b[10] = suffix_b[10] ^ 0xff;
|
| +
|
| + UniquePosition pos = UniquePosition::InitialPosition(suffix_a);
|
| + for (int i = 0; i < 1024; i++) {
|
| + const std::string& suffix = (i % 2 == 0) ? suffix_b : suffix_a;
|
| + UniquePosition prev_pos = UniquePosition::Before(pos, suffix);
|
| + ASSERT_PRED_FORMAT2(LessThan, prev_pos, pos);
|
| + pos = prev_pos;
|
| + }
|
| +
|
| + VLOG(1) << "Length: " << GetLength(pos);
|
| +}
|
| +
|
| +TEST_P(PositionInsertTest, StressLeftInsertBetween) {
|
| + // Use different suffixes to not violate our suffix uniqueness guarantee.
|
| + const std::string& suffix_a = GetParam();
|
| + std::string suffix_b = suffix_a;
|
| + suffix_b[10] = suffix_b[10] ^ 0xff;
|
| + std::string suffix_c = suffix_a;
|
| + suffix_c[10] = suffix_c[10] ^ 0xf0;
|
| +
|
| + UniquePosition right_pos = UniquePosition::InitialPosition(suffix_c);
|
| + UniquePosition left_pos = UniquePosition::Before(right_pos, suffix_a);
|
| + for (int i = 0; i < 1024; i++) {
|
| + const std::string& suffix = (i % 2 == 0) ? suffix_b : suffix_a;
|
| + UniquePosition new_pos =
|
| + UniquePosition::Between(left_pos, right_pos, suffix);
|
| + ASSERT_PRED_FORMAT2(LessThan, left_pos, new_pos);
|
| + ASSERT_PRED_FORMAT2(LessThan, new_pos, right_pos);
|
| + left_pos = new_pos;
|
| + }
|
| +
|
| + VLOG(1) << "Lengths: " << GetLength(left_pos) << ", " << GetLength(right_pos);
|
| +}
|
| +
|
| +TEST_P(PositionInsertTest, StressRightInsertBetween) {
|
| + // Use different suffixes to not violate our suffix uniqueness guarantee.
|
| + const std::string& suffix_a = GetParam();
|
| + std::string suffix_b = suffix_a;
|
| + suffix_b[10] = suffix_b[10] ^ 0xff;
|
| + std::string suffix_c = suffix_a;
|
| + suffix_c[10] = suffix_c[10] ^ 0xf0;
|
| +
|
| + UniquePosition right_pos = UniquePosition::InitialPosition(suffix_a);
|
| + UniquePosition left_pos = UniquePosition::Before(right_pos, suffix_c);
|
| + for (int i = 0; i < 1024; i++) {
|
| + const std::string& suffix = (i % 2 == 0) ? suffix_b : suffix_a;
|
| + UniquePosition new_pos =
|
| + UniquePosition::Between(left_pos, right_pos, suffix);
|
| + ASSERT_PRED_FORMAT2(LessThan, left_pos, new_pos);
|
| + ASSERT_PRED_FORMAT2(LessThan, new_pos, right_pos);
|
| + right_pos = new_pos;
|
| + }
|
| +
|
| + VLOG(1) << "Lengths: " << GetLength(left_pos) << ", " << GetLength(right_pos);
|
| +}
|
| +
|
| +// Generates suffixes similar to those generated by the directory.
|
| +// This may become obsolete if the suffix generation code is modified.
|
| +class SuffixGenerator {
|
| + public:
|
| + explicit SuffixGenerator(const std::string& cache_guid)
|
| + : cache_guid_(cache_guid),
|
| + next_id_(-65535) {
|
| + }
|
| +
|
| + std::string NextSuffix() {
|
| + // This is not entirely realistic, but that should be OK. The current
|
| + // suffix format is a base64'ed SHA1 hash, which should be fairly close to
|
| + // random anyway.
|
| + std::string input = cache_guid_ + base::Int64ToString(next_id_--);
|
| + std::string output;
|
| + CHECK(base::Base64Encode(base::SHA1HashString(input), &output));
|
| + return output;
|
| + }
|
| +
|
| + private:
|
| + const std::string cache_guid_;
|
| + int64 next_id_;
|
| +};
|
| +
|
| +// Cache guids generated in the same style as real clients.
|
| +static const char kCacheGuidStr1[] = "tuiWdG8hV+8y4RT9N5Aikg==";
|
| +static const char kCacheGuidStr2[] = "yaKb7zHtY06aue9a0vlZgw==";
|
| +
|
| +class PositionScenariosTest : public UniquePositionTest {
|
| + public:
|
| + PositionScenariosTest()
|
| + : generator1_(std::string(kCacheGuidStr1, arraysize(kCacheGuidStr1)-1)),
|
| + generator2_(std::string(kCacheGuidStr2, arraysize(kCacheGuidStr2)-1)) {
|
| + }
|
| +
|
| + std::string NextClient1Suffix() {
|
| + return generator1_.NextSuffix();
|
| + }
|
| +
|
| + std::string NextClient2Suffix() {
|
| + return generator2_.NextSuffix();
|
| + }
|
| +
|
| + private:
|
| + SuffixGenerator generator1_;
|
| + SuffixGenerator generator2_;
|
| +};
|
| +
|
| +// One client creating new bookmarks, always inserting at the end.
|
| +TEST_F(PositionScenariosTest, OneClientInsertAtEnd) {
|
| + UniquePosition pos =
|
| + UniquePosition::InitialPosition(NextClient1Suffix());
|
| + for (int i = 0; i < 1024; i++) {
|
| + const std::string suffix = NextClient1Suffix();
|
| + UniquePosition new_pos = UniquePosition::After(pos, suffix);
|
| + ASSERT_PRED_FORMAT2(LessThan, pos, new_pos);
|
| + pos = new_pos;
|
| + }
|
| +
|
| + VLOG(1) << "Length: " << GetLength(pos);
|
| +
|
| + // Normally we wouldn't want to make an assertion about the internal
|
| + // representation of our data, but we make an exception for this case.
|
| + // If this scenario causes lengths to explode, we have a big problem.
|
| + EXPECT_LT(GetLength(pos), 500U);
|
| +}
|
| +
|
| +// Two clients alternately inserting entries at the end, with a strong
|
| +// bias towards insertions by the first client.
|
| +TEST_F(PositionScenariosTest, TwoClientsInsertAtEnd_A) {
|
| + UniquePosition pos =
|
| + UniquePosition::InitialPosition(NextClient1Suffix());
|
| + for (int i = 0; i < 1024; i++) {
|
| + std::string suffix;
|
| + if (i % 5 == 0) {
|
| + suffix = NextClient2Suffix();
|
| + } else {
|
| + suffix = NextClient1Suffix();
|
| + }
|
| +
|
| + UniquePosition new_pos = UniquePosition::After(pos, suffix);
|
| + ASSERT_PRED_FORMAT2(LessThan, pos, new_pos);
|
| + pos = new_pos;
|
| + }
|
| +
|
| + VLOG(1) << "Length: " << GetLength(pos);
|
| + EXPECT_LT(GetLength(pos), 500U);
|
| +}
|
| +
|
| +// Two clients alternately inserting entries at the end.
|
| +TEST_F(PositionScenariosTest, TwoClientsInsertAtEnd_B) {
|
| + UniquePosition pos =
|
| + UniquePosition::InitialPosition(NextClient1Suffix());
|
| + for (int i = 0; i < 1024; i++) {
|
| + std::string suffix;
|
| + if (i % 2 == 0) {
|
| + suffix = NextClient1Suffix();
|
| + } else {
|
| + suffix = NextClient2Suffix();
|
| + }
|
| +
|
| + UniquePosition new_pos = UniquePosition::After(pos, suffix);
|
| + ASSERT_PRED_FORMAT2(LessThan, pos, new_pos);
|
| + pos = new_pos;
|
| + }
|
| +
|
| + VLOG(1) << "Length: " << GetLength(pos);
|
| + EXPECT_LT(GetLength(pos), 500U);
|
| +}
|
| +
|
| +INSTANTIATE_TEST_CASE_P(MinSuffix, PositionInsertTest,
|
| + ::testing::Values(kMinSuffix));
|
| +INSTANTIATE_TEST_CASE_P(MaxSuffix, PositionInsertTest,
|
| + ::testing::Values(kMaxSuffix));
|
| +INSTANTIATE_TEST_CASE_P(NormalSuffix, PositionInsertTest,
|
| + ::testing::Values(kNormalSuffix));
|
| +
|
| +class PositionFromIntTest : public UniquePositionTest {
|
| + public:
|
| + PositionFromIntTest()
|
| + : generator_(std::string(kCacheGuidStr1, arraysize(kCacheGuidStr1)-1)) {
|
| + }
|
| +
|
| + protected:
|
| + std::string NextSuffix() {
|
| + return generator_.NextSuffix();
|
| + }
|
| +
|
| + private:
|
| + SuffixGenerator generator_;
|
| +};
|
| +
|
| +const int64 kTestValues[] = {
|
| + 0LL,
|
| + 1LL, -1LL,
|
| + 2LL, -2LL,
|
| + 3LL, -3LL,
|
| + 0x79LL, -0x79LL,
|
| + 0x80LL, -0x80LL,
|
| + 0x81LL, -0x81LL,
|
| + 0xFELL, -0xFELL,
|
| + 0xFFLL, -0xFFLL,
|
| + 0x100LL, -0x100LL,
|
| + 0x101LL, -0x101LL,
|
| + 0xFA1AFELL, -0xFA1AFELL,
|
| + 0xFFFFFFFELL, -0xFFFFFFFELL,
|
| + 0xFFFFFFFFLL, -0xFFFFFFFFLL,
|
| + 0x100000000LL, -0x100000000LL,
|
| + 0x100000001LL, -0x100000001LL,
|
| + 0xFFFFFFFFFFLL, -0xFFFFFFFFFFLL,
|
| + 0x112358132134LL, -0x112358132134LL,
|
| + 0xFEFFBEEFABC1234LL, -0xFEFFBEEFABC1234LL,
|
| + kint64max,
|
| + kint64min,
|
| + kint64min + 1,
|
| + kint64max - 1
|
| +};
|
| +
|
| +const size_t kNumTestValues = arraysize(kTestValues);
|
| +
|
| +TEST_F(PositionFromIntTest, IsValid) {
|
| + for (size_t i = 0; i < kNumTestValues; ++i) {
|
| + const UniquePosition pos =
|
| + UniquePosition::FromInt64(kTestValues[i], NextSuffix());
|
| + EXPECT_TRUE(pos.IsValid()) << "i = " << i << "; " << pos.ToDebugString();
|
| + }
|
| +}
|
| +
|
| +TEST_F(PositionFromIntTest, RoundTripConversion) {
|
| + for (size_t i = 0; i < kNumTestValues; ++i) {
|
| + const int64 expected_value = kTestValues[i];
|
| + const UniquePosition pos =
|
| + UniquePosition::FromInt64(kTestValues[i], NextSuffix());
|
| + const int64 value = pos.ToInt64();
|
| + EXPECT_EQ(expected_value, value) << "i = " << i;
|
| + }
|
| +}
|
| +
|
| +template <typename T, typename LessThan = std::less<T> >
|
| +class IndexedLessThan {
|
| + public:
|
| + IndexedLessThan(const T* values) : values_(values) {}
|
| +
|
| + bool operator()(int i1, int i2) {
|
| + return less_than_(values_[i1], values_[i2]);
|
| + }
|
| +
|
| + private:
|
| + const T* values_;
|
| + LessThan less_than_;
|
| +};
|
| +
|
| +TEST_F(PositionFromIntTest, ConsistentOrdering) {
|
| + UniquePosition positions[kNumTestValues];
|
| + std::vector<int> original_ordering(kNumTestValues);
|
| + std::vector<int> int64_ordering(kNumTestValues);
|
| + std::vector<int> position_ordering(kNumTestValues);
|
| + for (size_t i = 0; i < kNumTestValues; ++i) {
|
| + positions[i] = UniquePosition::FromInt64(
|
| + kTestValues[i], NextSuffix());
|
| + original_ordering[i] = int64_ordering[i] = position_ordering[i] = i;
|
| + }
|
| +
|
| + std::sort(int64_ordering.begin(), int64_ordering.end(),
|
| + IndexedLessThan<int64>(kTestValues));
|
| + std::sort(position_ordering.begin(), position_ordering.end(),
|
| + IndexedLessThan<UniquePosition, PositionLessThan>(positions));
|
| + EXPECT_NE(original_ordering, int64_ordering);
|
| + EXPECT_EQ(int64_ordering, position_ordering);
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +} // namespace syncer
|
|
|