Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(46)

Side by Side Diff: sync/internal_api/public/base/unique_position_unittest.cc

Issue 11569045: Initial UniquePositions implementation (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Update algorithm Created 7 years, 12 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "sync/internal_api/public/base/unique_position.h"
6
7 #include <algorithm>
8 #include <string>
9
10 #include "base/base64.h"
11 #include "base/basictypes.h"
12 #include "base/logging.h"
13 #include "base/sha1.h"
14 #include "base/string_number_conversions.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16
17 namespace syncer {
18
19 namespace {
20
21 class UniquePositionTest : public ::testing::Test {
22 protected:
23 // Accessor to fetch the length of the position's internal representation
24 // We try to avoid having any test expectations on it because this is an
25 // implementation detail.
26 //
27 // If you run the tests with --v=1, we'll print out some of the lengths
28 // so you can see how well the algorithm performs in various insertion
29 // scenarios.
30 size_t GetLength(UniquePosition &pos) {
akalin 2012/12/29 10:17:13 const ref, space after & and not before
rlarocque 2013/01/07 23:22:12 Done.
31 return pos.ToInternalValue().length();
32 }
33 };
34
35 const size_t kMinLength = UniquePosition::kSuffixLength;
36 const size_t kGenericPredecessorLength = kMinLength + 2;
37 const size_t kGenericSuccessorLength = kMinLength + 1;
38 const size_t kBigPositionLength = kMinLength;
39 const size_t kSmallPositionLength = kMinLength;
40
41 // Be careful when adding more prefixes to this list.
42 // We have to manually ensure each has a unique suffix.
43 const UniquePosition kGenericPredecessor = UniquePosition::FromBytes(
44 (std::string(kGenericPredecessorLength, '\x23') + '\xFF'));
45 const UniquePosition kGenericSuccessor = UniquePosition::FromBytes(
46 std::string(kGenericSuccessorLength, '\xAB') + '\xFF');
47 const UniquePosition kBigPosition = UniquePosition::FromBytes(
48 std::string(kBigPositionLength - 1, '\xFF') + '\xFE' + '\xFF');
49 const UniquePosition kBigPositionLessTwo = UniquePosition::FromBytes(
50 std::string(kBigPositionLength - 1, '\xFF') + '\xFC' + '\xFF');
51 const UniquePosition kBiggerPosition = UniquePosition::FromBytes(
52 std::string(kBigPositionLength, '\xFF') + '\xFF');
53 const UniquePosition kSmallPosition = UniquePosition::FromBytes(
54 std::string(kSmallPositionLength - 1, '\x00') + '\x01' + '\xFF');
55 const UniquePosition kSmallPositionPlusOne = UniquePosition::FromBytes(
56 std::string(kSmallPositionLength - 1, '\x00') + '\x02' + '\xFF');
57
58 const std::string kMinSuffix =
59 std::string(UniquePosition::kSuffixLength - 1, '\x00') + '\x01';
60 const std::string kMaxSuffix(UniquePosition::kSuffixLength, '\xFF');
61 const std::string kNormalSuffix(UniquePosition::kSuffixLength, '\xAB');
62
63 ::testing::AssertionResult LessThan(const char* m_expr,
64 const char* n_expr,
65 const UniquePosition &m,
66 const UniquePosition &n) {
67 if (m.LessThan(n))
68 return ::testing::AssertionSuccess();
69
70 return ::testing::AssertionFailure()
71 << m_expr << " is not less than " << n_expr
72 << " (" << m.ToDebugString() << " and " << n.ToDebugString() << ")";
73 }
74
75 class RelativePositioningTest : public UniquePositionTest { };
76
77 const UniquePosition kPositionArray[] = {
78 kGenericPredecessor,
79 kGenericSuccessor,
80 kBigPosition,
81 kBigPositionLessTwo,
82 kBiggerPosition,
83 kSmallPosition,
84 kSmallPositionPlusOne,
85 };
86
87 const UniquePosition kSortedPositionArray[] = {
88 kSmallPosition,
89 kSmallPositionPlusOne,
90 kGenericPredecessor,
91 kGenericSuccessor,
92 kBigPositionLessTwo,
93 kBigPosition,
94 kBiggerPosition,
95 };
96
97 static const size_t kNumPositions = arraysize(kPositionArray);
98 static const size_t kNumSortedPositions = arraysize(kSortedPositionArray);
99
100 struct PositionLessThan {
101 bool operator()(const UniquePosition& a, const UniquePosition& b) {
102 return a.LessThan(b);
103 }
104 };
105
106 static bool IsSuffixInUse(
107 const UniquePosition& pos, const std::string& suffix) {
108 const std::string& pos_bytes = pos.ToInternalValue();
109 const size_t prefix_len = pos_bytes.length() - UniquePosition::kSuffixLength;
110 return pos_bytes.substr(prefix_len, std::string::npos) == suffix;
111 }
112
113 // Exercise comparision functions by sorting and re-sorting the list.
114 TEST_F(RelativePositioningTest, SortPositions) {
115 ASSERT_EQ(kNumPositions, kNumSortedPositions);
116 UniquePosition positions[arraysize(kPositionArray)];
117 for (size_t i = 0; i < kNumPositions; ++i) {
118 positions[i] = kPositionArray[i];
119 }
120
121 std::sort(&positions[0], &positions[kNumPositions], PositionLessThan());
akalin 2012/12/29 10:17:13 sorting is undefined if the comparator is inconsis
rlarocque 2013/01/07 23:22:12 I've added some very basic sanity tests in some te
122 for (size_t i = 0; i < kNumPositions; ++i) {
123 EXPECT_TRUE(positions[i].Equals(kSortedPositionArray[i]))
124 << "i: " << i << ", "
125 << positions[i].ToDebugString() << " != "
126 << kSortedPositionArray[i].ToDebugString();
127 }
128
129 }
130
131 // Some more exercise for the comparison function.
132 TEST_F(RelativePositioningTest, ReverseSortPositions) {
133 ASSERT_EQ(kNumPositions, kNumSortedPositions);
134 UniquePosition positions[arraysize(kPositionArray)];
135 for (size_t i = 0; i < kNumPositions; ++i) {
136 positions[i] = kPositionArray[i];
137 }
138
139 std::reverse(&positions[0], &positions[kNumPositions]);
140 std::sort(&positions[0], &positions[kNumPositions], PositionLessThan());
141 for (size_t i = 0; i < kNumPositions; ++i) {
142 EXPECT_TRUE(positions[i].Equals(kSortedPositionArray[i]))
143 << "i: " << i << ", "
144 << positions[i].ToDebugString() << " != "
145 << kSortedPositionArray[i].ToDebugString();
146 }
147 }
148
149 class PositionInsertTest :
150 public RelativePositioningTest,
151 public ::testing::WithParamInterface<std::string> { };
152
153 // Exercise InsertBetween with various insertion operations.
154 TEST_P(PositionInsertTest, InsertBetween) {
155 const std::string suffix = GetParam();
156 ASSERT_TRUE(UniquePosition::IsValidSuffix(suffix));
157
158 for (size_t i = 0; i < kNumSortedPositions; ++i) {
159 const UniquePosition& predecessor = kSortedPositionArray[i];
160 // Verify our suffixes are unique before we continue.
161 if (IsSuffixInUse(predecessor, suffix))
162 continue;
163
164 for (size_t j = i + 1; j < kNumSortedPositions; ++j) {
165 const UniquePosition& successor = kSortedPositionArray[j];
166
167 // Another guard against non-unique suffixes.
168 if (IsSuffixInUse(successor, suffix))
169 continue;
170
171 UniquePosition midpoint =
172 UniquePosition::Between(predecessor, successor, suffix);
173
174 EXPECT_PRED_FORMAT2(LessThan, predecessor, midpoint);
175 EXPECT_PRED_FORMAT2(LessThan, midpoint, successor);
176 }
177 }
178 }
179
180 TEST_P(PositionInsertTest, InsertBefore) {
181 const std::string suffix = GetParam();
182 for (size_t i = 0; i < kNumSortedPositions; ++i) {
183 const UniquePosition& successor = kSortedPositionArray[i];
184 // Verify our suffixes are unique before we continue.
185 if (IsSuffixInUse(successor, suffix))
186 continue;
187
188 UniquePosition before = UniquePosition::Before(successor, suffix);
189
190 EXPECT_PRED_FORMAT2(LessThan, before, successor);
191 }
192 }
193
194 TEST_P(PositionInsertTest, InsertAfter) {
195 const std::string suffix = GetParam();
196 for (size_t i = 0; i < kNumSortedPositions; ++i) {
197 const UniquePosition& predecessor = kSortedPositionArray[i];
198 // Verify our suffixes are unique before we continue.
199 if (IsSuffixInUse(predecessor, suffix))
200 continue;
201
202 UniquePosition after = UniquePosition::After(predecessor, suffix);
203
204 EXPECT_PRED_FORMAT2(LessThan, predecessor, after);
205 }
206 }
207
208 TEST_P(PositionInsertTest, StressInsertAfter) {
209 // Use two different suffixes to not violate our suffix uniqueness guarantee.
210 const std::string& suffix_a = GetParam();
211 std::string suffix_b = suffix_a;
212 suffix_b[10] = suffix_b[10] ^ 0xff;
213
214 UniquePosition pos = UniquePosition::InitialPosition(suffix_a);
215 for (int i = 0; i < 1024; i++) {
216 const std::string& suffix = (i % 2 == 0) ? suffix_b : suffix_a;
217 UniquePosition next_pos = UniquePosition::After(pos, suffix);
218 ASSERT_PRED_FORMAT2(LessThan, pos, next_pos);
219 pos = next_pos;
220 }
221
222 VLOG(1) << "Length: " << GetLength(pos);
223 }
224
225 TEST_P(PositionInsertTest, StressInsertBefore) {
226 // Use two different suffixes to not violate our suffix uniqueness guarantee.
227 const std::string& suffix_a = GetParam();
228 std::string suffix_b = suffix_a;
229 suffix_b[10] = suffix_b[10] ^ 0xff;
230
231 UniquePosition pos = UniquePosition::InitialPosition(suffix_a);
232 for (int i = 0; i < 1024; i++) {
233 const std::string& suffix = (i % 2 == 0) ? suffix_b : suffix_a;
234 UniquePosition prev_pos = UniquePosition::Before(pos, suffix);
235 ASSERT_PRED_FORMAT2(LessThan, prev_pos, pos);
236 pos = prev_pos;
237 }
238
239 VLOG(1) << "Length: " << GetLength(pos);
240 }
241
242 TEST_P(PositionInsertTest, StressLeftInsertBetween) {
243 // Use different suffixes to not violate our suffix uniqueness guarantee.
244 const std::string& suffix_a = GetParam();
245 std::string suffix_b = suffix_a;
246 suffix_b[10] = suffix_b[10] ^ 0xff;
247 std::string suffix_c = suffix_a;
248 suffix_c[10] = suffix_c[10] ^ 0xf0;
249
250 UniquePosition right_pos = UniquePosition::InitialPosition(suffix_c);
251 UniquePosition left_pos = UniquePosition::Before(right_pos, suffix_a);
252 for (int i = 0; i < 1024; i++) {
253 const std::string& suffix = (i % 2 == 0) ? suffix_b : suffix_a;
254 UniquePosition new_pos =
255 UniquePosition::Between(left_pos, right_pos, suffix);
256 ASSERT_PRED_FORMAT2(LessThan, left_pos, new_pos);
257 ASSERT_PRED_FORMAT2(LessThan, new_pos, right_pos);
258 left_pos = new_pos;
259 }
260
261 VLOG(1) << "Lengths: " << GetLength(left_pos) << ", " << GetLength(right_pos);
262 }
263
264 TEST_P(PositionInsertTest, StressRightInsertBetween) {
265 // Use different suffixes to not violate our suffix uniqueness guarantee.
266 const std::string& suffix_a = GetParam();
267 std::string suffix_b = suffix_a;
268 suffix_b[10] = suffix_b[10] ^ 0xff;
269 std::string suffix_c = suffix_a;
270 suffix_c[10] = suffix_c[10] ^ 0xf0;
271
272 UniquePosition right_pos = UniquePosition::InitialPosition(suffix_a);
273 UniquePosition left_pos = UniquePosition::Before(right_pos, suffix_c);
274 for (int i = 0; i < 1024; i++) {
275 const std::string& suffix = (i % 2 == 0) ? suffix_b : suffix_a;
276 UniquePosition new_pos =
277 UniquePosition::Between(left_pos, right_pos, suffix);
278 ASSERT_PRED_FORMAT2(LessThan, left_pos, new_pos);
279 ASSERT_PRED_FORMAT2(LessThan, new_pos, right_pos);
280 right_pos = new_pos;
281 }
282
283 VLOG(1) << "Lengths: " << GetLength(left_pos) << ", " << GetLength(right_pos);
284 }
285
286 // Generates suffixes similar to those generated by the directory.
287 // This may become obsolete if the suffix generation code is modified.
288 class SuffixGenerator {
289 public:
290 explicit SuffixGenerator(const std::string& cache_guid)
291 : cache_guid_(cache_guid),
292 next_id_(-65535) {
293 }
294
295 std::string NextSuffix() {
296 // This is not entirely realistic, but that should be OK. The current
297 // suffix format is a base64'ed SHA1 hash, which should be fairly close to
298 // random anyway.
299 std::string input = cache_guid_ + base::Int64ToString(next_id_--);
300 std::string output;
301 CHECK(base::Base64Encode(base::SHA1HashString(input), &output));
302 return output;
303 }
304
305 private:
306 const std::string cache_guid_;
307 int64 next_id_;
308 };
309
310 // Cache guids generated in the same style as real clients.
311 static const char kCacheGuidStr1[] = "tuiWdG8hV+8y4RT9N5Aikg==";
312 static const char kCacheGuidStr2[] = "yaKb7zHtY06aue9a0vlZgw==";
313
314 class PositionScenariosTest : public UniquePositionTest {
315 public:
316 PositionScenariosTest()
317 : generator1_(std::string(kCacheGuidStr1, arraysize(kCacheGuidStr1)-1)),
318 generator2_(std::string(kCacheGuidStr2, arraysize(kCacheGuidStr2)-1)) {
319 }
320
321 std::string NextClient1Suffix() {
322 return generator1_.NextSuffix();
323 }
324
325 std::string NextClient2Suffix() {
326 return generator2_.NextSuffix();
327 }
328
329 private:
330 SuffixGenerator generator1_;
331 SuffixGenerator generator2_;
332 };
333
334 // One client creating new bookmarks, always inserting at the end.
335 TEST_F(PositionScenariosTest, OneClientInsertAtEnd) {
336 UniquePosition pos =
337 UniquePosition::InitialPosition(NextClient1Suffix());
338 for (int i = 0; i < 1024; i++) {
339 const std::string suffix = NextClient1Suffix();
340 UniquePosition new_pos = UniquePosition::After(pos, suffix);
341 ASSERT_PRED_FORMAT2(LessThan, pos, new_pos);
342 pos = new_pos;
343 }
344
345 VLOG(1) << "Length: " << GetLength(pos);
346
347 // Normally we wouldn't want to make an assertion about the internal
348 // representation of our data, but we make an exception for this case.
349 // If this scenario causes lengths to explode, we have a big problem.
350 EXPECT_LT(GetLength(pos), 500U);
351 }
352
353 // Two clients alternately inserting entries at the end, with a strong
354 // bias towards insertions by the first client.
355 TEST_F(PositionScenariosTest, TwoClientsInsertAtEnd_A) {
356 UniquePosition pos =
357 UniquePosition::InitialPosition(NextClient1Suffix());
358 for (int i = 0; i < 1024; i++) {
359 std::string suffix;
360 if (i % 5 == 0) {
361 suffix = NextClient2Suffix();
362 } else {
363 suffix = NextClient1Suffix();
364 }
365
366 UniquePosition new_pos = UniquePosition::After(pos, suffix);
367 ASSERT_PRED_FORMAT2(LessThan, pos, new_pos);
368 pos = new_pos;
369 }
370
371 VLOG(1) << "Length: " << GetLength(pos);
372 EXPECT_LT(GetLength(pos), 500U);
373 }
374
375 // Two clients alternately inserting entries at the end.
376 TEST_F(PositionScenariosTest, TwoClientsInsertAtEnd_B) {
377 UniquePosition pos =
378 UniquePosition::InitialPosition(NextClient1Suffix());
379 for (int i = 0; i < 1024; i++) {
380 std::string suffix;
381 if (i % 2 == 0) {
382 suffix = NextClient1Suffix();
383 } else {
384 suffix = NextClient2Suffix();
385 }
386
387 UniquePosition new_pos = UniquePosition::After(pos, suffix);
388 ASSERT_PRED_FORMAT2(LessThan, pos, new_pos);
389 pos = new_pos;
390 }
391
392 VLOG(1) << "Length: " << GetLength(pos);
393 EXPECT_LT(GetLength(pos), 500U);
394 }
395
396 INSTANTIATE_TEST_CASE_P(MinSuffix, PositionInsertTest,
397 ::testing::Values(kMinSuffix));
398 INSTANTIATE_TEST_CASE_P(MaxSuffix, PositionInsertTest,
399 ::testing::Values(kMaxSuffix));
400 INSTANTIATE_TEST_CASE_P(NormalSuffix, PositionInsertTest,
401 ::testing::Values(kNormalSuffix));
402
403 class PositionFromIntTest : public UniquePositionTest {
404 public:
405 PositionFromIntTest()
406 : generator_(std::string(kCacheGuidStr1, arraysize(kCacheGuidStr1)-1)) {
407 }
408
409 protected:
410 std::string NextSuffix() {
411 return generator_.NextSuffix();
412 }
413
414 private:
415 SuffixGenerator generator_;
416 };
417
418 const int64 kTestValues[] = {
419 0LL,
420 1LL, -1LL,
421 2LL, -2LL,
422 3LL, -3LL,
423 0x79LL, -0x79LL,
424 0x80LL, -0x80LL,
425 0x81LL, -0x81LL,
426 0xFELL, -0xFELL,
427 0xFFLL, -0xFFLL,
428 0x100LL, -0x100LL,
429 0x101LL, -0x101LL,
430 0xFA1AFELL, -0xFA1AFELL,
431 0xFFFFFFFELL, -0xFFFFFFFELL,
432 0xFFFFFFFFLL, -0xFFFFFFFFLL,
433 0x100000000LL, -0x100000000LL,
434 0x100000001LL, -0x100000001LL,
435 0xFFFFFFFFFFLL, -0xFFFFFFFFFFLL,
436 0x112358132134LL, -0x112358132134LL,
437 0xFEFFBEEFABC1234LL, -0xFEFFBEEFABC1234LL,
438 kint64max,
439 kint64min,
440 kint64min + 1,
441 kint64max - 1
442 };
443
444 const size_t kNumTestValues = arraysize(kTestValues);
445
446 TEST_F(PositionFromIntTest, IsValid) {
447 for (size_t i = 0; i < kNumTestValues; ++i) {
448 const UniquePosition pos =
449 UniquePosition::FromInt64(kTestValues[i], NextSuffix());
450 EXPECT_TRUE(pos.IsValid()) << "i = " << i << "; " << pos.ToDebugString();
451 }
452 }
453
454 TEST_F(PositionFromIntTest, RoundTripConversion) {
455 for (size_t i = 0; i < kNumTestValues; ++i) {
456 const int64 expected_value = kTestValues[i];
457 const UniquePosition pos =
458 UniquePosition::FromInt64(kTestValues[i], NextSuffix());
459 const int64 value = pos.ToInt64();
460 EXPECT_EQ(expected_value, value) << "i = " << i;
461 }
462 }
463
464 template <typename T, typename LessThan = std::less<T> >
465 class IndexedLessThan {
466 public:
467 IndexedLessThan(const T* values) : values_(values) {}
468
469 bool operator()(int i1, int i2) {
470 return less_than_(values_[i1], values_[i2]);
471 }
472
473 private:
474 const T* values_;
475 LessThan less_than_;
476 };
477
478 TEST_F(PositionFromIntTest, ConsistentOrdering) {
479 UniquePosition positions[kNumTestValues];
480 std::vector<int> original_ordering(kNumTestValues);
481 std::vector<int> int64_ordering(kNumTestValues);
482 std::vector<int> position_ordering(kNumTestValues);
483 for (size_t i = 0; i < kNumTestValues; ++i) {
484 positions[i] = UniquePosition::FromInt64(
485 kTestValues[i], NextSuffix());
486 original_ordering[i] = int64_ordering[i] = position_ordering[i] = i;
487 }
488
489 std::sort(int64_ordering.begin(), int64_ordering.end(),
490 IndexedLessThan<int64>(kTestValues));
491 std::sort(position_ordering.begin(), position_ordering.end(),
492 IndexedLessThan<UniquePosition, PositionLessThan>(positions));
493 EXPECT_NE(original_ordering, int64_ordering);
494 EXPECT_EQ(int64_ordering, position_ordering);
495 }
496
497 } // namespace
498
499 } // namespace syncer
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698