Raw
1 /*
2 Copyright 2020 Google LLC
3
4 Use of this source code is governed by a BSD-style
5 license that can be found in the LICENSE file or at
6 https://developers.google.com/open-source/licenses/bsd
7 */
8
9 #include "unit-test.h"
10 #include "lib-reftable.h"
11 #include "reftable/basics.h"
12 #include "reftable/constants.h"
13 #include "reftable/record.h"
14 #include "reftable/reftable-error.h"
15
16 static void t_copy(struct reftable_record *rec)
17 {
18 struct reftable_record copy;
19 uint8_t typ;
20
21 typ = reftable_record_type(rec);
22 cl_assert_equal_i(reftable_record_init(&copy, typ), 0);
23 reftable_record_copy_from(&copy, rec, REFTABLE_HASH_SIZE_SHA1);
24 /* do it twice to catch memory leaks */
25 reftable_record_copy_from(&copy, rec, REFTABLE_HASH_SIZE_SHA1);
26 cl_assert(reftable_record_equal(rec, &copy,
27 REFTABLE_HASH_SIZE_SHA1) != 0);
28
29 reftable_record_release(&copy);
30 }
31
32 void test_reftable_record__varint_roundtrip(void)
33 {
34 uint64_t inputs[] = { 0,
35 1,
36 27,
37 127,
38 128,
39 257,
40 4096,
41 ((uint64_t)1 << 63),
42 ((uint64_t)1 << 63) + ((uint64_t)1 << 63) - 1 };
43
44 for (size_t i = 0; i < ARRAY_SIZE(inputs); i++) {
45 uint8_t dest[10];
46
47 struct string_view out = {
48 .buf = dest,
49 .len = sizeof(dest),
50 };
51 uint64_t in = inputs[i];
52 int n = put_var_int(&out, in);
53 uint64_t got = 0;
54
55 cl_assert_gt_i(n, 0);
56 out.len = n;
57 n = get_var_int(&got, &out);
58 cl_assert_gt_i(n, 0);
59
60 cl_assert_equal_i(got, in);
61 }
62 }
63
64 void test_reftable_record__varint_overflow(void)
65 {
66 unsigned char buf[] = {
67 0xFF, 0xFF, 0xFF, 0xFF,
68 0xFF, 0xFF, 0xFF, 0xFF,
69 0xFF, 0x00,
70 };
71 struct string_view view = {
72 .buf = buf,
73 .len = sizeof(buf),
74 };
75 uint64_t value;
76 cl_assert_equal_i(get_var_int(&value, &view), -1);
77 }
78
79 static void set_hash(uint8_t *h, int j)
80 {
81 for (size_t i = 0; i < hash_size(REFTABLE_HASH_SHA1); i++)
82 h[i] = (j >> i) & 0xff;
83 }
84
85 void test_reftable_record__ref_record_comparison(void)
86 {
87 struct reftable_record in[3] = {
88 {
89 .type = REFTABLE_BLOCK_TYPE_REF,
90 .u.ref.refname = (char *) "refs/heads/master",
91 .u.ref.value_type = REFTABLE_REF_VAL1,
92 },
93 {
94 .type = REFTABLE_BLOCK_TYPE_REF,
95 .u.ref.refname = (char *) "refs/heads/master",
96 .u.ref.value_type = REFTABLE_REF_DELETION,
97 },
98 {
99 .type = REFTABLE_BLOCK_TYPE_REF,
100 .u.ref.refname = (char *) "HEAD",
101 .u.ref.value_type = REFTABLE_REF_SYMREF,
102 .u.ref.value.symref = (char *) "refs/heads/master",
103 },
104 };
105 int cmp;
106
107 cl_assert(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1) == 0);
108 cl_assert_equal_i(reftable_record_cmp(&in[0], &in[1], &cmp), 0);
109 cl_assert(!cmp);
110
111 cl_assert(reftable_record_equal(&in[1], &in[2],
112 REFTABLE_HASH_SIZE_SHA1) == 0);
113 cl_assert_equal_i(reftable_record_cmp(&in[1], &in[2], &cmp), 0);
114 cl_assert_gt_i(cmp, 0);
115
116 in[1].u.ref.value_type = in[0].u.ref.value_type;
117 cl_assert(reftable_record_equal(&in[0], &in[1],
118 REFTABLE_HASH_SIZE_SHA1) != 0);
119 cl_assert_equal_i(reftable_record_cmp(&in[0], &in[1], &cmp), 0);
120 cl_assert(!cmp);
121 }
122
123 void test_reftable_record__ref_record_compare_name(void)
124 {
125 struct reftable_ref_record recs[3] = {
126 {
127 .refname = (char *) "refs/heads/a"
128 },
129 {
130 .refname = (char *) "refs/heads/b"
131 },
132 {
133 .refname = (char *) "refs/heads/a"
134 },
135 };
136
137 cl_assert(reftable_ref_record_compare_name(&recs[0],
138 &recs[1]) < 0);
139 cl_assert(reftable_ref_record_compare_name(&recs[1],
140 &recs[0]) > 0);
141 cl_assert_equal_i(reftable_ref_record_compare_name(&recs[0],
142 &recs[2]), 0);
143 }
144
145 void test_reftable_record__ref_record_roundtrip(void)
146 {
147 struct reftable_buf scratch = REFTABLE_BUF_INIT;
148
149 for (int i = REFTABLE_REF_DELETION; i < REFTABLE_NR_REF_VALUETYPES; i++) {
150 struct reftable_record in = {
151 .type = REFTABLE_BLOCK_TYPE_REF,
152 .u.ref.value_type = i,
153 };
154 struct reftable_record out = { .type = REFTABLE_BLOCK_TYPE_REF };
155 struct reftable_buf key = REFTABLE_BUF_INIT;
156 uint8_t buffer[1024] = { 0 };
157 struct string_view dest = {
158 .buf = buffer,
159 .len = sizeof(buffer),
160 };
161 int n, m;
162
163 in.u.ref.value_type = i;
164 switch (i) {
165 case REFTABLE_REF_DELETION:
166 break;
167 case REFTABLE_REF_VAL1:
168 set_hash(in.u.ref.value.val1, 1);
169 break;
170 case REFTABLE_REF_VAL2:
171 set_hash(in.u.ref.value.val2.value, 1);
172 set_hash(in.u.ref.value.val2.target_value, 2);
173 break;
174 case REFTABLE_REF_SYMREF:
175 in.u.ref.value.symref = xstrdup("target");
176 break;
177 }
178 in.u.ref.refname = xstrdup("refs/heads/master");
179
180 t_copy(&in);
181
182 cl_assert_equal_i(reftable_record_val_type(&in), i);
183 cl_assert_equal_i(reftable_record_is_deletion(&in),
184 i == REFTABLE_REF_DELETION);
185
186 reftable_record_key(&in, &key);
187 n = reftable_record_encode(&in, dest, REFTABLE_HASH_SIZE_SHA1);
188 cl_assert_gt_i(n, 0);
189
190 /* decode into a non-zero reftable_record to test for leaks. */
191 m = reftable_record_decode(&out, key, i, dest, REFTABLE_HASH_SIZE_SHA1, &scratch);
192 cl_assert_equal_i(n, m);
193
194 cl_assert(reftable_ref_record_equal(&in.u.ref,
195 &out.u.ref,
196 REFTABLE_HASH_SIZE_SHA1) != 0);
197 reftable_record_release(&in);
198
199 reftable_buf_release(&key);
200 reftable_record_release(&out);
201 }
202
203 reftable_buf_release(&scratch);
204 }
205
206 void test_reftable_record__ref_record_decode_invalid_value_type(void)
207 {
208 struct reftable_buf scratch = REFTABLE_BUF_INIT;
209 struct reftable_record out = {
210 .type = REFTABLE_BLOCK_TYPE_REF,
211 };
212 struct reftable_buf key = REFTABLE_BUF_INIT;
213 uint8_t buffer[1024] = { 0 };
214 struct string_view dest = {
215 .buf = buffer,
216 .len = sizeof(buffer),
217 };
218
219 cl_must_pass(reftable_buf_addstr(&key, "refs/heads/master"));
220 cl_assert_equal_i(reftable_record_decode(&out, key, REFTABLE_NR_REF_VALUETYPES,
221 dest, REFTABLE_HASH_SIZE_SHA1, &scratch),
222 REFTABLE_FORMAT_ERROR);
223
224 reftable_record_release(&out);
225 reftable_buf_release(&key);
226 reftable_buf_release(&scratch);
227 }
228
229 void test_reftable_record__log_record_comparison(void)
230 {
231 struct reftable_record in[3] = {
232 {
233 .type = REFTABLE_BLOCK_TYPE_LOG,
234 .u.log.refname = (char *) "refs/heads/master",
235 .u.log.update_index = 42,
236 },
237 {
238 .type = REFTABLE_BLOCK_TYPE_LOG,
239 .u.log.refname = (char *) "refs/heads/master",
240 .u.log.update_index = 22,
241 },
242 {
243 .type = REFTABLE_BLOCK_TYPE_LOG,
244 .u.log.refname = (char *) "refs/heads/main",
245 .u.log.update_index = 22,
246 },
247 };
248 int cmp;
249
250 cl_assert_equal_i(reftable_record_equal(&in[0], &in[1],
251 REFTABLE_HASH_SIZE_SHA1), 0);
252 cl_assert_equal_i(reftable_record_equal(&in[1], &in[2],
253 REFTABLE_HASH_SIZE_SHA1), 0);
254 cl_assert_equal_i(reftable_record_cmp(&in[1], &in[2], &cmp), 0);
255 cl_assert_gt_i(cmp, 0);
256 /* comparison should be reversed for equal keys, because
257 * comparison is now performed on the basis of update indices */
258 cl_assert_equal_i(reftable_record_cmp(&in[0], &in[1], &cmp), 0);
259 cl_assert_lt_i(cmp, 0);
260
261 in[1].u.log.update_index = in[0].u.log.update_index;
262 cl_assert(reftable_record_equal(&in[0], &in[1],
263 REFTABLE_HASH_SIZE_SHA1) != 0);
264 cl_assert_equal_i(reftable_record_cmp(&in[0], &in[1], &cmp), 0);
265 }
266
267 void test_reftable_record__log_record_compare_key(void)
268 {
269 struct reftable_log_record logs[3] = {
270 {
271 .refname = (char *) "refs/heads/a",
272 .update_index = 1,
273 },
274 {
275 .refname = (char *) "refs/heads/b",
276 .update_index = 2,
277 },
278 {
279 .refname = (char *) "refs/heads/a",
280 .update_index = 3,
281 },
282 };
283
284 cl_assert(reftable_log_record_compare_key(&logs[0],
285 &logs[1]) < 0);
286 cl_assert(reftable_log_record_compare_key(&logs[1],
287 &logs[0]) > 0);
288
289 logs[1].update_index = logs[0].update_index;
290 cl_assert(reftable_log_record_compare_key(&logs[0],
291 &logs[1]) < 0);
292
293 cl_assert(reftable_log_record_compare_key(&logs[0],
294 &logs[2]) > 0);
295 cl_assert(reftable_log_record_compare_key(&logs[2],
296 &logs[0]) < 0);
297 logs[2].update_index = logs[0].update_index;
298 cl_assert_equal_i(reftable_log_record_compare_key(&logs[0], &logs[2]), 0);
299 }
300
301 void test_reftable_record__log_record_roundtrip(void)
302 {
303 struct reftable_log_record in[] = {
304 {
305 .refname = xstrdup("refs/heads/master"),
306 .update_index = 42,
307 .value_type = REFTABLE_LOG_UPDATE,
308 .value = {
309 .update = {
310 .name = xstrdup("han-wen"),
311 .email = xstrdup("hanwen@google.com"),
312 .message = xstrdup("test"),
313 .time = 1577123507,
314 .tz_offset = 100,
315 },
316 }
317 },
318 {
319 .refname = xstrdup("refs/heads/master"),
320 .update_index = 22,
321 .value_type = REFTABLE_LOG_DELETION,
322 },
323 {
324 .refname = xstrdup("branch"),
325 .update_index = 33,
326 .value_type = REFTABLE_LOG_UPDATE,
327 }
328 };
329 struct reftable_buf scratch = REFTABLE_BUF_INIT;
330 set_hash(in[0].value.update.new_hash, 1);
331 set_hash(in[0].value.update.old_hash, 2);
332 set_hash(in[2].value.update.new_hash, 3);
333 set_hash(in[2].value.update.old_hash, 4);
334
335 cl_assert_equal_i(reftable_log_record_is_deletion(&in[0]), 0);
336 cl_assert(reftable_log_record_is_deletion(&in[1]) != 0);
337 cl_assert_equal_i(reftable_log_record_is_deletion(&in[2]), 0);
338
339 for (size_t i = 0; i < ARRAY_SIZE(in); i++) {
340 struct reftable_record rec = { .type = REFTABLE_BLOCK_TYPE_LOG };
341 struct reftable_buf key = REFTABLE_BUF_INIT;
342 uint8_t buffer[1024] = { 0 };
343 struct string_view dest = {
344 .buf = buffer,
345 .len = sizeof(buffer),
346 };
347 /* populate out, to check for leaks. */
348 struct reftable_record out = {
349 .type = REFTABLE_BLOCK_TYPE_LOG,
350 .u.log = {
351 .refname = xstrdup("old name"),
352 .value_type = REFTABLE_LOG_UPDATE,
353 .value = {
354 .update = {
355 .name = xstrdup("old name"),
356 .email = xstrdup("old@email"),
357 .message = xstrdup("old message"),
358 },
359 },
360 },
361 };
362 int n, m, valtype;
363
364 rec.u.log = in[i];
365
366 t_copy(&rec);
367
368 reftable_record_key(&rec, &key);
369
370 n = reftable_record_encode(&rec, dest, REFTABLE_HASH_SIZE_SHA1);
371 cl_assert_ge_i(n, 0);
372 valtype = reftable_record_val_type(&rec);
373 m = reftable_record_decode(&out, key, valtype, dest,
374 REFTABLE_HASH_SIZE_SHA1, &scratch);
375 cl_assert_equal_i(n, m);
376
377 cl_assert(reftable_log_record_equal(&in[i], &out.u.log,
378 REFTABLE_HASH_SIZE_SHA1) != 0);
379 reftable_log_record_release(&in[i]);
380 reftable_buf_release(&key);
381 reftable_record_release(&out);
382 }
383
384 reftable_buf_release(&scratch);
385 }
386
387 void test_reftable_record__key_roundtrip(void)
388 {
389 uint8_t buffer[1024] = { 0 };
390 struct string_view dest = {
391 .buf = buffer,
392 .len = sizeof(buffer),
393 };
394 struct reftable_buf last_key = REFTABLE_BUF_INIT;
395 struct reftable_buf key = REFTABLE_BUF_INIT;
396 struct reftable_buf roundtrip = REFTABLE_BUF_INIT;
397 int restart;
398 uint8_t extra;
399 int n, m;
400 uint8_t rt_extra;
401
402 cl_assert_equal_i(reftable_buf_addstr(&last_key,
403 "refs/heads/master"), 0);
404 cl_assert_equal_i(reftable_buf_addstr(&key,
405 "refs/tags/bla"), 0);
406 extra = 6;
407 n = reftable_encode_key(&restart, dest, last_key, key, extra);
408 cl_assert(!restart);
409 cl_assert_gt_i(n, 0);
410
411 cl_assert_equal_i(reftable_buf_addstr(&roundtrip,
412 "refs/heads/master"), 0);
413 m = reftable_decode_key(&roundtrip, &rt_extra, dest);
414 cl_assert_equal_i(n, m);
415 cl_assert_equal_i(reftable_buf_cmp(&key, &roundtrip), 0);
416 cl_assert_equal_i(rt_extra, extra);
417
418 reftable_buf_release(&last_key);
419 reftable_buf_release(&key);
420 reftable_buf_release(&roundtrip);
421 }
422
423 void test_reftable_record__obj_record_comparison(void)
424 {
425
426 uint8_t id_bytes[] = { 0, 1, 2, 3, 4, 5, 6 };
427 uint64_t offsets[] = { 0, 16, 32, 48, 64, 80, 96, 112};
428 struct reftable_record in[3] = {
429 {
430 .type = REFTABLE_BLOCK_TYPE_OBJ,
431 .u.obj.hash_prefix = id_bytes,
432 .u.obj.hash_prefix_len = 7,
433 .u.obj.offsets = offsets,
434 .u.obj.offset_len = 8,
435 },
436 {
437 .type = REFTABLE_BLOCK_TYPE_OBJ,
438 .u.obj.hash_prefix = id_bytes,
439 .u.obj.hash_prefix_len = 7,
440 .u.obj.offsets = offsets,
441 .u.obj.offset_len = 5,
442 },
443 {
444 .type = REFTABLE_BLOCK_TYPE_OBJ,
445 .u.obj.hash_prefix = id_bytes,
446 .u.obj.hash_prefix_len = 5,
447 },
448 };
449 int cmp;
450
451 cl_assert_equal_i(reftable_record_equal(&in[0], &in[1],
452 REFTABLE_HASH_SIZE_SHA1), 0);
453 cl_assert_equal_i(reftable_record_cmp(&in[0], &in[1], &cmp), 0);
454 cl_assert(!cmp);
455
456 cl_assert_equal_i(reftable_record_equal(&in[1], &in[2],
457 REFTABLE_HASH_SIZE_SHA1), 0);
458 cl_assert_equal_i(reftable_record_cmp(&in[1], &in[2], &cmp), 0);
459 cl_assert_gt_i(cmp, 0);
460
461 in[1].u.obj.offset_len = in[0].u.obj.offset_len;
462 cl_assert(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1) != 0);
463 cl_assert_equal_i(reftable_record_cmp(&in[0], &in[1], &cmp), 0);
464 cl_assert(!cmp);
465 }
466
467 void test_reftable_record__obj_record_roundtrip(void)
468 {
469 uint8_t testHash1[REFTABLE_HASH_SIZE_SHA1] = { 1, 2, 3, 4, 0 };
470 uint64_t till9[] = { 1, 2, 3, 4, 500, 600, 700, 800, 9000 };
471 struct reftable_obj_record recs[3] = {
472 {
473 .hash_prefix = testHash1,
474 .hash_prefix_len = 5,
475 .offsets = till9,
476 .offset_len = 3,
477 },
478 {
479 .hash_prefix = testHash1,
480 .hash_prefix_len = 5,
481 .offsets = till9,
482 .offset_len = 9,
483 },
484 {
485 .hash_prefix = testHash1,
486 .hash_prefix_len = 5,
487 },
488 };
489 struct reftable_buf scratch = REFTABLE_BUF_INIT;
490
491 for (size_t i = 0; i < ARRAY_SIZE(recs); i++) {
492 uint8_t buffer[1024] = { 0 };
493 struct string_view dest = {
494 .buf = buffer,
495 .len = sizeof(buffer),
496 };
497 struct reftable_record in = {
498 .type = REFTABLE_BLOCK_TYPE_OBJ,
499 .u = {
500 .obj = recs[i],
501 },
502 };
503 struct reftable_buf key = REFTABLE_BUF_INIT;
504 struct reftable_record out = { .type = REFTABLE_BLOCK_TYPE_OBJ };
505 int n, m;
506 uint8_t extra;
507
508 cl_assert_equal_i(reftable_record_is_deletion(&in), 0);
509 t_copy(&in);
510 reftable_record_key(&in, &key);
511 n = reftable_record_encode(&in, dest, REFTABLE_HASH_SIZE_SHA1);
512 cl_assert_gt_i(n, 0);
513 extra = reftable_record_val_type(&in);
514 m = reftable_record_decode(&out, key, extra, dest,
515 REFTABLE_HASH_SIZE_SHA1, &scratch);
516 cl_assert_equal_i(n, m);
517
518 cl_assert(reftable_record_equal(&in, &out,
519 REFTABLE_HASH_SIZE_SHA1) != 0);
520 reftable_buf_release(&key);
521 reftable_record_release(&out);
522 }
523
524 reftable_buf_release(&scratch);
525 }
526
527 void test_reftable_record__index_record_comparison(void)
528 {
529 struct reftable_record in[3] = {
530 {
531 .type = REFTABLE_BLOCK_TYPE_INDEX,
532 .u.idx.offset = 22,
533 .u.idx.last_key = REFTABLE_BUF_INIT,
534 },
535 {
536 .type = REFTABLE_BLOCK_TYPE_INDEX,
537 .u.idx.offset = 32,
538 .u.idx.last_key = REFTABLE_BUF_INIT,
539 },
540 {
541 .type = REFTABLE_BLOCK_TYPE_INDEX,
542 .u.idx.offset = 32,
543 .u.idx.last_key = REFTABLE_BUF_INIT,
544 },
545 };
546 int cmp;
547
548 cl_assert_equal_i(reftable_buf_addstr(&in[0].u.idx.last_key,
549 "refs/heads/master"), 0);
550 cl_assert_equal_i(reftable_buf_addstr(&in[1].u.idx.last_key, "refs/heads/master"), 0);
551 cl_assert(reftable_buf_addstr(&in[2].u.idx.last_key,
552 "refs/heads/branch") == 0);
553
554 cl_assert_equal_i(reftable_record_equal(&in[0], &in[1],
555 REFTABLE_HASH_SIZE_SHA1), 0);
556 cl_assert_equal_i(reftable_record_cmp(&in[0], &in[1], &cmp), 0);
557 cl_assert(!cmp);
558
559 cl_assert_equal_i(reftable_record_equal(&in[1], &in[2],
560 REFTABLE_HASH_SIZE_SHA1), 0);
561 cl_assert_equal_i(reftable_record_cmp(&in[1], &in[2], &cmp), 0);
562 cl_assert_gt_i(cmp, 0);
563
564 in[1].u.idx.offset = in[0].u.idx.offset;
565 cl_assert(reftable_record_equal(&in[0], &in[1],
566 REFTABLE_HASH_SIZE_SHA1) != 0);
567 cl_assert_equal_i(reftable_record_cmp(&in[0], &in[1], &cmp), 0);
568 cl_assert(!cmp);
569
570 for (size_t i = 0; i < ARRAY_SIZE(in); i++)
571 reftable_record_release(&in[i]);
572 }
573
574 void test_reftable_record__index_record_roundtrip(void)
575 {
576 struct reftable_record in = {
577 .type = REFTABLE_BLOCK_TYPE_INDEX,
578 .u.idx = {
579 .offset = 42,
580 .last_key = REFTABLE_BUF_INIT,
581 },
582 };
583 uint8_t buffer[1024] = { 0 };
584 struct string_view dest = {
585 .buf = buffer,
586 .len = sizeof(buffer),
587 };
588 struct reftable_buf scratch = REFTABLE_BUF_INIT;
589 struct reftable_buf key = REFTABLE_BUF_INIT;
590 struct reftable_record out = {
591 .type = REFTABLE_BLOCK_TYPE_INDEX,
592 .u.idx = { .last_key = REFTABLE_BUF_INIT },
593 };
594 int n, m;
595 uint8_t extra;
596
597 cl_assert_equal_i(reftable_buf_addstr(&in.u.idx.last_key,
598 "refs/heads/master"), 0);
599 reftable_record_key(&in, &key);
600 t_copy(&in);
601
602 cl_assert_equal_i(reftable_record_is_deletion(&in), 0);
603 cl_assert_equal_i(reftable_buf_cmp(&key, &in.u.idx.last_key), 0);
604 n = reftable_record_encode(&in, dest, REFTABLE_HASH_SIZE_SHA1);
605 cl_assert(n > 0);
606
607 extra = reftable_record_val_type(&in);
608 m = reftable_record_decode(&out, key, extra, dest,
609 REFTABLE_HASH_SIZE_SHA1, &scratch);
610 cl_assert_equal_i(m, n);
611
612 cl_assert(reftable_record_equal(&in, &out,
613 REFTABLE_HASH_SIZE_SHA1) != 0);
614
615 reftable_record_release(&out);
616 reftable_buf_release(&key);
617 reftable_buf_release(&scratch);
618 reftable_buf_release(&in.u.idx.last_key);
619 }