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 <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 | |
OLD | NEW |