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