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/memory/scoped_ptr.h"
14 #include "base/sha1.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "sync/protocol/unique_position.pb.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18
19 namespace syncer {
20
21 namespace {
22
23 class UniquePositionTest : public ::testing::Test {
24 protected:
25 // Accessor to fetch the length of the position's internal representation
26 // We try to avoid having any test expectations on it because this is an
27 // implementation detail.
28 //
29 // If you run the tests with --v=1, we'll print out some of the lengths
30 // so you can see how well the algorithm performs in various insertion
31 // scenarios.
GetLength(const UniquePosition & pos)32 size_t GetLength(const UniquePosition& pos) {
33 sync_pb::UniquePosition proto;
34 pos.ToProto(&proto);
35 return proto.ByteSize();
36 }
37 };
38
39 // This function exploits internal knowledge of how the protobufs are serialized
40 // to help us build UniquePositions from strings described in this file.
FromBytes(const std::string & bytes)41 static UniquePosition FromBytes(const std::string& bytes) {
42 sync_pb::UniquePosition proto;
43 proto.set_value(bytes);
44 return UniquePosition::FromProto(proto);
45 }
46
47 const size_t kMinLength = UniquePosition::kSuffixLength;
48 const size_t kGenericPredecessorLength = kMinLength + 2;
49 const size_t kGenericSuccessorLength = kMinLength + 1;
50 const size_t kBigPositionLength = kMinLength;
51 const size_t kSmallPositionLength = kMinLength;
52
53 // Be careful when adding more prefixes to this list.
54 // We have to manually ensure each has a unique suffix.
55 const UniquePosition kGenericPredecessor = FromBytes(
56 (std::string(kGenericPredecessorLength, '\x23') + '\xFF'));
57 const UniquePosition kGenericSuccessor = FromBytes(
58 std::string(kGenericSuccessorLength, '\xAB') + '\xFF');
59 const UniquePosition kBigPosition = FromBytes(
60 std::string(kBigPositionLength - 1, '\xFF') + '\xFE' + '\xFF');
61 const UniquePosition kBigPositionLessTwo = FromBytes(
62 std::string(kBigPositionLength - 1, '\xFF') + '\xFC' + '\xFF');
63 const UniquePosition kBiggerPosition = FromBytes(
64 std::string(kBigPositionLength, '\xFF') + '\xFF');
65 const UniquePosition kSmallPosition = FromBytes(
66 std::string(kSmallPositionLength - 1, '\x00') + '\x01' + '\xFF');
67 const UniquePosition kSmallPositionPlusOne = FromBytes(
68 std::string(kSmallPositionLength - 1, '\x00') + '\x02' + '\xFF');
69 const UniquePosition kHugePosition = FromBytes(
70 std::string(UniquePosition::kCompressBytesThreshold, '\xFF') + '\xAB');
71
72 const std::string kMinSuffix =
73 std::string(UniquePosition::kSuffixLength - 1, '\x00') + '\x01';
74 const std::string kMaxSuffix(UniquePosition::kSuffixLength, '\xFF');
75 const std::string kNormalSuffix(
76 "\x68\x44\x6C\x6B\x32\x58\x78\x34\x69\x70\x46\x34\x79\x49"
77 "\x44\x4F\x66\x4C\x58\x41\x31\x34\x68\x59\x56\x43\x6F\x3D");
78
LessThan(const char * m_expr,const char * n_expr,const UniquePosition & m,const UniquePosition & n)79 ::testing::AssertionResult LessThan(const char* m_expr,
80 const char* n_expr,
81 const UniquePosition &m,
82 const UniquePosition &n) {
83 if (m.LessThan(n))
84 return ::testing::AssertionSuccess();
85
86 return ::testing::AssertionFailure()
87 << m_expr << " is not less than " << n_expr
88 << " (" << m.ToDebugString() << " and " << n.ToDebugString() << ")";
89 }
90
Equals(const char * m_expr,const char * n_expr,const UniquePosition & m,const UniquePosition & n)91 ::testing::AssertionResult Equals(const char* m_expr,
92 const char* n_expr,
93 const UniquePosition &m,
94 const UniquePosition &n) {
95 if (m.Equals(n))
96 return ::testing::AssertionSuccess();
97
98 return ::testing::AssertionFailure()
99 << m_expr << " is not equal to " << n_expr
100 << " (" << m.ToDebugString() << " != " << n.ToDebugString() << ")";
101 }
102
103 // Test that the code can read the uncompressed serialization format.
TEST_F(UniquePositionTest,DeserializeObsoleteUncompressedPosition)104 TEST_F(UniquePositionTest, DeserializeObsoleteUncompressedPosition) {
105 // We no longer support the encoding data in this format. This hard-coded
106 // input is a serialization of kGenericPredecessor created by an older version
107 // of this code.
108 const char kSerializedCstr[] = {
109 '\x0a', '\x1f', '\x23', '\x23', '\x23', '\x23', '\x23', '\x23', '\x23',
110 '\x23', '\x23', '\x23', '\x23', '\x23', '\x23', '\x23', '\x23', '\x23',
111 '\x23', '\x23', '\x23', '\x23', '\x23', '\x23', '\x23', '\x23', '\x23',
112 '\x23', '\x23', '\x23', '\x23', '\x23', '\xff' };
113 const std::string serialized(kSerializedCstr, sizeof(kSerializedCstr));
114
115 sync_pb::UniquePosition proto;
116 proto.ParseFromString(serialized);
117
118 // Double-check that this test is testing what we think it tests.
119 EXPECT_TRUE(proto.has_value());
120 EXPECT_FALSE(proto.has_compressed_value());
121 EXPECT_FALSE(proto.has_uncompressed_length());
122
123 UniquePosition pos = UniquePosition::FromProto(proto);
124 EXPECT_PRED_FORMAT2(Equals, kGenericPredecessor, pos);
125 }
126
127 // Test that the code can read the gzip serialization format.
TEST_F(UniquePositionTest,DeserializeObsoleteGzippedPosition)128 TEST_F(UniquePositionTest, DeserializeObsoleteGzippedPosition) {
129 // We no longer support the encoding data in this format. This hard-coded
130 // input is a serialization of kHugePosition created by an older version of
131 // this code.
132 const char kSerializedCstr[] = {
133 '\x12', '\x0d', '\x78', '\x9c', '\xfb', '\xff', '\x7f', '\x60', '\xc1',
134 '\x6a', '\x00', '\xa2', '\x4c', '\x80', '\x2c', '\x18', '\x81', '\x01' };
135 const std::string serialized(kSerializedCstr, sizeof(kSerializedCstr));
136
137 sync_pb::UniquePosition proto;
138 proto.ParseFromString(serialized);
139
140 // Double-check that this test is testing what we think it tests.
141 EXPECT_FALSE(proto.has_value());
142 EXPECT_TRUE(proto.has_compressed_value());
143 EXPECT_TRUE(proto.has_uncompressed_length());
144
145 UniquePosition pos = UniquePosition::FromProto(proto);
146 EXPECT_PRED_FORMAT2(Equals, kHugePosition, pos);
147 }
148
149 class RelativePositioningTest : public UniquePositionTest { };
150
151 const UniquePosition kPositionArray[] = {
152 kGenericPredecessor,
153 kGenericSuccessor,
154 kBigPosition,
155 kBigPositionLessTwo,
156 kBiggerPosition,
157 kSmallPosition,
158 kSmallPositionPlusOne,
159 };
160
161 const UniquePosition kSortedPositionArray[] = {
162 kSmallPosition,
163 kSmallPositionPlusOne,
164 kGenericPredecessor,
165 kGenericSuccessor,
166 kBigPositionLessTwo,
167 kBigPosition,
168 kBiggerPosition,
169 };
170
171 static const size_t kNumPositions = arraysize(kPositionArray);
172 static const size_t kNumSortedPositions = arraysize(kSortedPositionArray);
173
174 struct PositionLessThan {
operator ()syncer::__anon8cf7e8c90111::PositionLessThan175 bool operator()(const UniquePosition& a, const UniquePosition& b) {
176 return a.LessThan(b);
177 }
178 };
179
180 // Returns true iff the given position's suffix matches the input parameter.
IsSuffixInUse(const UniquePosition & pos,const std::string & suffix)181 static bool IsSuffixInUse(
182 const UniquePosition& pos, const std::string& suffix) {
183 return pos.GetSuffixForTest() == suffix;
184 }
185
186 // Test some basic properties of comparison and equality.
TEST_F(RelativePositioningTest,ComparisonSanityTest1)187 TEST_F(RelativePositioningTest, ComparisonSanityTest1) {
188 const UniquePosition& a = kPositionArray[0];
189 ASSERT_TRUE(a.IsValid());
190
191 // Necessarily true for any non-invalid positions.
192 EXPECT_TRUE(a.Equals(a));
193 EXPECT_FALSE(a.LessThan(a));
194 }
195
196 // Test some more properties of comparison and equality.
TEST_F(RelativePositioningTest,ComparisonSanityTest2)197 TEST_F(RelativePositioningTest, ComparisonSanityTest2) {
198 const UniquePosition& a = kPositionArray[0];
199 const UniquePosition& b = kPositionArray[1];
200
201 // These should pass for the specific a and b we have chosen (a < b).
202 EXPECT_FALSE(a.Equals(b));
203 EXPECT_TRUE(a.LessThan(b));
204 EXPECT_FALSE(b.LessThan(a));
205 }
206
207 // Exercise comparision functions by sorting and re-sorting the list.
TEST_F(RelativePositioningTest,SortPositions)208 TEST_F(RelativePositioningTest, SortPositions) {
209 ASSERT_EQ(kNumPositions, kNumSortedPositions);
210 UniquePosition positions[arraysize(kPositionArray)];
211 for (size_t i = 0; i < kNumPositions; ++i) {
212 positions[i] = kPositionArray[i];
213 }
214
215 std::sort(&positions[0], &positions[kNumPositions], PositionLessThan());
216 for (size_t i = 0; i < kNumPositions; ++i) {
217 EXPECT_TRUE(positions[i].Equals(kSortedPositionArray[i]))
218 << "i: " << i << ", "
219 << positions[i].ToDebugString() << " != "
220 << kSortedPositionArray[i].ToDebugString();
221 }
222
223 }
224
225 // Some more exercise for the comparison function.
TEST_F(RelativePositioningTest,ReverseSortPositions)226 TEST_F(RelativePositioningTest, ReverseSortPositions) {
227 ASSERT_EQ(kNumPositions, kNumSortedPositions);
228 UniquePosition positions[arraysize(kPositionArray)];
229 for (size_t i = 0; i < kNumPositions; ++i) {
230 positions[i] = kPositionArray[i];
231 }
232
233 std::reverse(&positions[0], &positions[kNumPositions]);
234 std::sort(&positions[0], &positions[kNumPositions], PositionLessThan());
235 for (size_t i = 0; i < kNumPositions; ++i) {
236 EXPECT_TRUE(positions[i].Equals(kSortedPositionArray[i]))
237 << "i: " << i << ", "
238 << positions[i].ToDebugString() << " != "
239 << kSortedPositionArray[i].ToDebugString();
240 }
241 }
242
243 class PositionInsertTest :
244 public RelativePositioningTest,
245 public ::testing::WithParamInterface<std::string> { };
246
247 // Exercise InsertBetween with various insertion operations.
TEST_P(PositionInsertTest,InsertBetween)248 TEST_P(PositionInsertTest, InsertBetween) {
249 const std::string suffix = GetParam();
250 ASSERT_TRUE(UniquePosition::IsValidSuffix(suffix));
251
252 for (size_t i = 0; i < kNumSortedPositions; ++i) {
253 const UniquePosition& predecessor = kSortedPositionArray[i];
254 // Verify our suffixes are unique before we continue.
255 if (IsSuffixInUse(predecessor, suffix))
256 continue;
257
258 for (size_t j = i + 1; j < kNumSortedPositions; ++j) {
259 const UniquePosition& successor = kSortedPositionArray[j];
260
261 // Another guard against non-unique suffixes.
262 if (IsSuffixInUse(successor, suffix))
263 continue;
264
265 UniquePosition midpoint =
266 UniquePosition::Between(predecessor, successor, suffix);
267
268 EXPECT_PRED_FORMAT2(LessThan, predecessor, midpoint);
269 EXPECT_PRED_FORMAT2(LessThan, midpoint, successor);
270 }
271 }
272 }
273
TEST_P(PositionInsertTest,InsertBefore)274 TEST_P(PositionInsertTest, InsertBefore) {
275 const std::string suffix = GetParam();
276 for (size_t i = 0; i < kNumSortedPositions; ++i) {
277 const UniquePosition& successor = kSortedPositionArray[i];
278 // Verify our suffixes are unique before we continue.
279 if (IsSuffixInUse(successor, suffix))
280 continue;
281
282 UniquePosition before = UniquePosition::Before(successor, suffix);
283
284 EXPECT_PRED_FORMAT2(LessThan, before, successor);
285 }
286 }
287
TEST_P(PositionInsertTest,InsertAfter)288 TEST_P(PositionInsertTest, InsertAfter) {
289 const std::string suffix = GetParam();
290 for (size_t i = 0; i < kNumSortedPositions; ++i) {
291 const UniquePosition& predecessor = kSortedPositionArray[i];
292 // Verify our suffixes are unique before we continue.
293 if (IsSuffixInUse(predecessor, suffix))
294 continue;
295
296 UniquePosition after = UniquePosition::After(predecessor, suffix);
297
298 EXPECT_PRED_FORMAT2(LessThan, predecessor, after);
299 }
300 }
301
TEST_P(PositionInsertTest,StressInsertAfter)302 TEST_P(PositionInsertTest, StressInsertAfter) {
303 // Use two different suffixes to not violate our suffix uniqueness guarantee.
304 const std::string& suffix_a = GetParam();
305 std::string suffix_b = suffix_a;
306 suffix_b[10] = suffix_b[10] ^ 0xff;
307
308 UniquePosition pos = UniquePosition::InitialPosition(suffix_a);
309 for (int i = 0; i < 1024; i++) {
310 const std::string& suffix = (i % 2 == 0) ? suffix_b : suffix_a;
311 UniquePosition next_pos = UniquePosition::After(pos, suffix);
312 ASSERT_PRED_FORMAT2(LessThan, pos, next_pos);
313 pos = next_pos;
314 }
315
316 VLOG(1) << "Length: " << GetLength(pos);
317 }
318
TEST_P(PositionInsertTest,StressInsertBefore)319 TEST_P(PositionInsertTest, StressInsertBefore) {
320 // Use two different suffixes to not violate our suffix uniqueness guarantee.
321 const std::string& suffix_a = GetParam();
322 std::string suffix_b = suffix_a;
323 suffix_b[10] = suffix_b[10] ^ 0xff;
324
325 UniquePosition pos = UniquePosition::InitialPosition(suffix_a);
326 for (int i = 0; i < 1024; i++) {
327 const std::string& suffix = (i % 2 == 0) ? suffix_b : suffix_a;
328 UniquePosition prev_pos = UniquePosition::Before(pos, suffix);
329 ASSERT_PRED_FORMAT2(LessThan, prev_pos, pos);
330 pos = prev_pos;
331 }
332
333 VLOG(1) << "Length: " << GetLength(pos);
334 }
335
TEST_P(PositionInsertTest,StressLeftInsertBetween)336 TEST_P(PositionInsertTest, StressLeftInsertBetween) {
337 // Use different suffixes to not violate our suffix uniqueness guarantee.
338 const std::string& suffix_a = GetParam();
339 std::string suffix_b = suffix_a;
340 suffix_b[10] = suffix_b[10] ^ 0xff;
341 std::string suffix_c = suffix_a;
342 suffix_c[10] = suffix_c[10] ^ 0xf0;
343
344 UniquePosition right_pos = UniquePosition::InitialPosition(suffix_c);
345 UniquePosition left_pos = UniquePosition::Before(right_pos, suffix_a);
346 for (int i = 0; i < 1024; i++) {
347 const std::string& suffix = (i % 2 == 0) ? suffix_b : suffix_a;
348 UniquePosition new_pos =
349 UniquePosition::Between(left_pos, right_pos, suffix);
350 ASSERT_PRED_FORMAT2(LessThan, left_pos, new_pos);
351 ASSERT_PRED_FORMAT2(LessThan, new_pos, right_pos);
352 left_pos = new_pos;
353 }
354
355 VLOG(1) << "Lengths: " << GetLength(left_pos) << ", " << GetLength(right_pos);
356 }
357
TEST_P(PositionInsertTest,StressRightInsertBetween)358 TEST_P(PositionInsertTest, StressRightInsertBetween) {
359 // Use different suffixes to not violate our suffix uniqueness guarantee.
360 const std::string& suffix_a = GetParam();
361 std::string suffix_b = suffix_a;
362 suffix_b[10] = suffix_b[10] ^ 0xff;
363 std::string suffix_c = suffix_a;
364 suffix_c[10] = suffix_c[10] ^ 0xf0;
365
366 UniquePosition right_pos = UniquePosition::InitialPosition(suffix_a);
367 UniquePosition left_pos = UniquePosition::Before(right_pos, suffix_c);
368 for (int i = 0; i < 1024; i++) {
369 const std::string& suffix = (i % 2 == 0) ? suffix_b : suffix_a;
370 UniquePosition new_pos =
371 UniquePosition::Between(left_pos, right_pos, suffix);
372 ASSERT_PRED_FORMAT2(LessThan, left_pos, new_pos);
373 ASSERT_PRED_FORMAT2(LessThan, new_pos, right_pos);
374 right_pos = new_pos;
375 }
376
377 VLOG(1) << "Lengths: " << GetLength(left_pos) << ", " << GetLength(right_pos);
378 }
379
380 // Generates suffixes similar to those generated by the directory.
381 // This may become obsolete if the suffix generation code is modified.
382 class SuffixGenerator {
383 public:
SuffixGenerator(const std::string & cache_guid)384 explicit SuffixGenerator(const std::string& cache_guid)
385 : cache_guid_(cache_guid),
386 next_id_(-65535) {
387 }
388
NextSuffix()389 std::string NextSuffix() {
390 // This is not entirely realistic, but that should be OK. The current
391 // suffix format is a base64'ed SHA1 hash, which should be fairly close to
392 // random anyway.
393 std::string input = cache_guid_ + base::Int64ToString(next_id_--);
394 std::string output;
395 base::Base64Encode(base::SHA1HashString(input), &output);
396 return output;
397 }
398
399 private:
400 const std::string cache_guid_;
401 int64 next_id_;
402 };
403
404 // Cache guids generated in the same style as real clients.
405 static const char kCacheGuidStr1[] = "tuiWdG8hV+8y4RT9N5Aikg==";
406 static const char kCacheGuidStr2[] = "yaKb7zHtY06aue9a0vlZgw==";
407
408 class PositionScenariosTest : public UniquePositionTest {
409 public:
PositionScenariosTest()410 PositionScenariosTest()
411 : generator1_(std::string(kCacheGuidStr1, arraysize(kCacheGuidStr1)-1)),
412 generator2_(std::string(kCacheGuidStr2, arraysize(kCacheGuidStr2)-1)) {
413 }
414
NextClient1Suffix()415 std::string NextClient1Suffix() {
416 return generator1_.NextSuffix();
417 }
418
NextClient2Suffix()419 std::string NextClient2Suffix() {
420 return generator2_.NextSuffix();
421 }
422
423 private:
424 SuffixGenerator generator1_;
425 SuffixGenerator generator2_;
426 };
427
428 // One client creating new bookmarks, always inserting at the end.
TEST_F(PositionScenariosTest,OneClientInsertAtEnd)429 TEST_F(PositionScenariosTest, OneClientInsertAtEnd) {
430 UniquePosition pos =
431 UniquePosition::InitialPosition(NextClient1Suffix());
432 for (int i = 0; i < 1024; i++) {
433 const std::string suffix = NextClient1Suffix();
434 UniquePosition new_pos = UniquePosition::After(pos, suffix);
435 ASSERT_PRED_FORMAT2(LessThan, pos, new_pos);
436 pos = new_pos;
437 }
438
439 VLOG(1) << "Length: " << GetLength(pos);
440
441 // Normally we wouldn't want to make an assertion about the internal
442 // representation of our data, but we make an exception for this case.
443 // If this scenario causes lengths to explode, we have a big problem.
444 EXPECT_LT(GetLength(pos), 500U);
445 }
446
447 // Two clients alternately inserting entries at the end, with a strong
448 // bias towards insertions by the first client.
TEST_F(PositionScenariosTest,TwoClientsInsertAtEnd_A)449 TEST_F(PositionScenariosTest, TwoClientsInsertAtEnd_A) {
450 UniquePosition pos =
451 UniquePosition::InitialPosition(NextClient1Suffix());
452 for (int i = 0; i < 1024; i++) {
453 std::string suffix;
454 if (i % 5 == 0) {
455 suffix = NextClient2Suffix();
456 } else {
457 suffix = NextClient1Suffix();
458 }
459
460 UniquePosition new_pos = UniquePosition::After(pos, suffix);
461 ASSERT_PRED_FORMAT2(LessThan, pos, new_pos);
462 pos = new_pos;
463 }
464
465 VLOG(1) << "Length: " << GetLength(pos);
466 EXPECT_LT(GetLength(pos), 500U);
467 }
468
469 // Two clients alternately inserting entries at the end.
TEST_F(PositionScenariosTest,TwoClientsInsertAtEnd_B)470 TEST_F(PositionScenariosTest, TwoClientsInsertAtEnd_B) {
471 UniquePosition pos =
472 UniquePosition::InitialPosition(NextClient1Suffix());
473 for (int i = 0; i < 1024; i++) {
474 std::string suffix;
475 if (i % 2 == 0) {
476 suffix = NextClient1Suffix();
477 } else {
478 suffix = NextClient2Suffix();
479 }
480
481 UniquePosition new_pos = UniquePosition::After(pos, suffix);
482 ASSERT_PRED_FORMAT2(LessThan, pos, new_pos);
483 pos = new_pos;
484 }
485
486 VLOG(1) << "Length: " << GetLength(pos);
487 EXPECT_LT(GetLength(pos), 500U);
488 }
489
490 INSTANTIATE_TEST_CASE_P(MinSuffix, PositionInsertTest,
491 ::testing::Values(kMinSuffix));
492 INSTANTIATE_TEST_CASE_P(MaxSuffix, PositionInsertTest,
493 ::testing::Values(kMaxSuffix));
494 INSTANTIATE_TEST_CASE_P(NormalSuffix, PositionInsertTest,
495 ::testing::Values(kNormalSuffix));
496
497 class PositionFromIntTest : public UniquePositionTest {
498 public:
PositionFromIntTest()499 PositionFromIntTest()
500 : generator_(std::string(kCacheGuidStr1, arraysize(kCacheGuidStr1)-1)) {
501 }
502
503 protected:
504 static const int64 kTestValues[];
505 static const size_t kNumTestValues;
506
NextSuffix()507 std::string NextSuffix() {
508 return generator_.NextSuffix();
509 }
510
511 private:
512 SuffixGenerator generator_;
513 };
514
515 const int64 PositionFromIntTest::kTestValues[] = {
516 0LL,
517 1LL, -1LL,
518 2LL, -2LL,
519 3LL, -3LL,
520 0x79LL, -0x79LL,
521 0x80LL, -0x80LL,
522 0x81LL, -0x81LL,
523 0xFELL, -0xFELL,
524 0xFFLL, -0xFFLL,
525 0x100LL, -0x100LL,
526 0x101LL, -0x101LL,
527 0xFA1AFELL, -0xFA1AFELL,
528 0xFFFFFFFELL, -0xFFFFFFFELL,
529 0xFFFFFFFFLL, -0xFFFFFFFFLL,
530 0x100000000LL, -0x100000000LL,
531 0x100000001LL, -0x100000001LL,
532 0xFFFFFFFFFFLL, -0xFFFFFFFFFFLL,
533 0x112358132134LL, -0x112358132134LL,
534 0xFEFFBEEFABC1234LL, -0xFEFFBEEFABC1234LL,
535 kint64max,
536 kint64min,
537 kint64min + 1,
538 kint64max - 1
539 };
540
541 const size_t PositionFromIntTest::kNumTestValues =
542 arraysize(PositionFromIntTest::kTestValues);
543
TEST_F(PositionFromIntTest,IsValid)544 TEST_F(PositionFromIntTest, IsValid) {
545 for (size_t i = 0; i < kNumTestValues; ++i) {
546 const UniquePosition pos =
547 UniquePosition::FromInt64(kTestValues[i], NextSuffix());
548 EXPECT_TRUE(pos.IsValid()) << "i = " << i << "; " << pos.ToDebugString();
549 }
550 }
551
TEST_F(PositionFromIntTest,RoundTripConversion)552 TEST_F(PositionFromIntTest, RoundTripConversion) {
553 for (size_t i = 0; i < kNumTestValues; ++i) {
554 const int64 expected_value = kTestValues[i];
555 const UniquePosition pos =
556 UniquePosition::FromInt64(kTestValues[i], NextSuffix());
557 const int64 value = pos.ToInt64();
558 EXPECT_EQ(expected_value, value) << "i = " << i;
559 }
560 }
561
562 template <typename T, typename LessThan = std::less<T> >
563 class IndexedLessThan {
564 public:
IndexedLessThan(const T * values)565 IndexedLessThan(const T* values) : values_(values) {}
566
operator ()(int i1,int i2)567 bool operator()(int i1, int i2) {
568 return less_than_(values_[i1], values_[i2]);
569 }
570
571 private:
572 const T* values_;
573 LessThan less_than_;
574 };
575
TEST_F(PositionFromIntTest,ConsistentOrdering)576 TEST_F(PositionFromIntTest, ConsistentOrdering) {
577 UniquePosition positions[kNumTestValues];
578 std::vector<int> original_ordering(kNumTestValues);
579 std::vector<int> int64_ordering(kNumTestValues);
580 std::vector<int> position_ordering(kNumTestValues);
581 for (size_t i = 0; i < kNumTestValues; ++i) {
582 positions[i] = UniquePosition::FromInt64(
583 kTestValues[i], NextSuffix());
584 original_ordering[i] = int64_ordering[i] = position_ordering[i] = i;
585 }
586
587 std::sort(int64_ordering.begin(), int64_ordering.end(),
588 IndexedLessThan<int64>(kTestValues));
589 std::sort(position_ordering.begin(), position_ordering.end(),
590 IndexedLessThan<UniquePosition, PositionLessThan>(positions));
591 EXPECT_NE(original_ordering, int64_ordering);
592 EXPECT_EQ(int64_ordering, position_ordering);
593 }
594
595 class CompressedPositionTest : public UniquePositionTest {
596 public:
CompressedPositionTest()597 CompressedPositionTest() {
598 positions_.push_back(
599 MakePosition( // Prefix starts with 256 0x00s
600 std::string("\x00\x00\x00\x00\xFF\xFF\xFE\xFF" "\x01", 9),
601 MakeSuffix('\x04')));
602 positions_.push_back(
603 MakePosition( // Prefix starts with four 0x00s
604 std::string("\x00\x00\x00\x00\xFF\xFF\xFF\xFB" "\x01", 9),
605 MakeSuffix('\x03')));
606 positions_.push_back(
607 MakePosition( // Prefix starts with four 0xFFs
608 std::string("\xFF\xFF\xFF\xFF\x00\x00\x00\x04" "\x01", 9),
609 MakeSuffix('\x01')));
610 positions_.push_back(
611 MakePosition( // Prefix starts with 256 0xFFs
612 std::string("\xFF\xFF\xFF\xFF\x00\x00\x01\x00" "\x01", 9),
613 MakeSuffix('\x02')));
614 }
615
616 private:
617 UniquePosition MakePosition(const std::string& compressed_prefix,
618 const std::string& compressed_suffix);
619 std::string MakeSuffix(char unique_value);
620
621 protected:
622 std::vector<UniquePosition> positions_;
623 };
624
MakePosition(const std::string & compressed_prefix,const std::string & compressed_suffix)625 UniquePosition CompressedPositionTest::MakePosition(
626 const std::string& compressed_prefix,
627 const std::string& compressed_suffix) {
628 sync_pb::UniquePosition proto;
629 proto.set_custom_compressed_v1(
630 std::string(compressed_prefix + compressed_suffix));
631 return UniquePosition::FromProto(proto);
632 }
633
MakeSuffix(char unique_value)634 std::string CompressedPositionTest::MakeSuffix(char unique_value) {
635 // We're dealing in compressed positions in this test. That means the
636 // suffix should be compressed, too. To avoid complication, we use suffixes
637 // that don't have any repeating digits, and therefore are identical in
638 // compressed and uncompressed form.
639 std::string suffix;
640 for (size_t i = 0; i < UniquePosition::kSuffixLength; ++i) {
641 suffix.push_back(static_cast<char>(i));
642 }
643 suffix[UniquePosition::kSuffixLength-1] = unique_value;
644 return suffix;
645 }
646
647 // Make sure that serialization and deserialization routines are correct.
TEST_F(CompressedPositionTest,SerializeAndDeserialize)648 TEST_F(CompressedPositionTest, SerializeAndDeserialize) {
649 for (std::vector<UniquePosition>::const_iterator it = positions_.begin();
650 it != positions_.end(); ++it) {
651 SCOPED_TRACE("iteration: " + it->ToDebugString());
652
653 sync_pb::UniquePosition proto;
654 it->ToProto(&proto);
655 UniquePosition deserialized = UniquePosition::FromProto(proto);
656
657 EXPECT_PRED_FORMAT2(Equals, *it, deserialized);
658 }
659 }
660
661 // Test that deserialization failures of protobufs where we know none of its
662 // fields is not catastrophic. This may happen if all the fields currently
663 // known to this client become deprecated in the future.
TEST_F(CompressedPositionTest,DeserializeProtobufFromTheFuture)664 TEST_F(CompressedPositionTest, DeserializeProtobufFromTheFuture) {
665 sync_pb::UniquePosition proto;
666 UniquePosition deserialized = UniquePosition::FromProto(proto);
667 EXPECT_FALSE(deserialized.IsValid());
668 }
669
670 // Make sure the comparison functions are working correctly.
671 // This requires values in the test harness to be hard-coded in ascending order.
TEST_F(CompressedPositionTest,OrderingTest)672 TEST_F(CompressedPositionTest, OrderingTest) {
673 for (size_t i = 0; i < positions_.size()-1; ++i) {
674 EXPECT_PRED_FORMAT2(LessThan, positions_[i], positions_[i+1]);
675 }
676 }
677
678 } // namespace
679
680 } // namespace syncer
681