]> git.ipfire.org Git - thirdparty/git.git/blob - reftable/readwrite_test.c
reftable: make reftable_record a tagged union
[thirdparty/git.git] / reftable / readwrite_test.c
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 "system.h"
10
11 #include "basics.h"
12 #include "block.h"
13 #include "blocksource.h"
14 #include "constants.h"
15 #include "reader.h"
16 #include "record.h"
17 #include "test_framework.h"
18 #include "reftable-tests.h"
19 #include "reftable-writer.h"
20
21 static const int update_index = 5;
22
23 static void test_buffer(void)
24 {
25 struct strbuf buf = STRBUF_INIT;
26 struct reftable_block_source source = { NULL };
27 struct reftable_block out = { NULL };
28 int n;
29 uint8_t in[] = "hello";
30 strbuf_add(&buf, in, sizeof(in));
31 block_source_from_strbuf(&source, &buf);
32 EXPECT(block_source_size(&source) == 6);
33 n = block_source_read_block(&source, &out, 0, sizeof(in));
34 EXPECT(n == sizeof(in));
35 EXPECT(!memcmp(in, out.data, n));
36 reftable_block_done(&out);
37
38 n = block_source_read_block(&source, &out, 1, 2);
39 EXPECT(n == 2);
40 EXPECT(!memcmp(out.data, "el", 2));
41
42 reftable_block_done(&out);
43 block_source_close(&source);
44 strbuf_release(&buf);
45 }
46
47 static void write_table(char ***names, struct strbuf *buf, int N,
48 int block_size, uint32_t hash_id)
49 {
50 struct reftable_write_options opts = {
51 .block_size = block_size,
52 .hash_id = hash_id,
53 };
54 struct reftable_writer *w =
55 reftable_new_writer(&strbuf_add_void, buf, &opts);
56 struct reftable_ref_record ref = { NULL };
57 int i = 0, n;
58 struct reftable_log_record log = { NULL };
59 const struct reftable_stats *stats = NULL;
60 *names = reftable_calloc(sizeof(char *) * (N + 1));
61 reftable_writer_set_limits(w, update_index, update_index);
62 for (i = 0; i < N; i++) {
63 uint8_t hash[GIT_SHA256_RAWSZ] = { 0 };
64 char name[100];
65 int n;
66
67 set_test_hash(hash, i);
68
69 snprintf(name, sizeof(name), "refs/heads/branch%02d", i);
70
71 ref.refname = name;
72 ref.update_index = update_index;
73 ref.value_type = REFTABLE_REF_VAL1;
74 ref.value.val1 = hash;
75 (*names)[i] = xstrdup(name);
76
77 n = reftable_writer_add_ref(w, &ref);
78 EXPECT(n == 0);
79 }
80
81 for (i = 0; i < N; i++) {
82 uint8_t hash[GIT_SHA256_RAWSZ] = { 0 };
83 char name[100];
84 int n;
85
86 set_test_hash(hash, i);
87
88 snprintf(name, sizeof(name), "refs/heads/branch%02d", i);
89
90 log.refname = name;
91 log.update_index = update_index;
92 log.value_type = REFTABLE_LOG_UPDATE;
93 log.value.update.new_hash = hash;
94 log.value.update.message = "message";
95
96 n = reftable_writer_add_log(w, &log);
97 EXPECT(n == 0);
98 }
99
100 n = reftable_writer_close(w);
101 EXPECT(n == 0);
102
103 stats = writer_stats(w);
104 for (i = 0; i < stats->ref_stats.blocks; i++) {
105 int off = i * opts.block_size;
106 if (off == 0) {
107 off = header_size(
108 (hash_id == GIT_SHA256_FORMAT_ID) ? 2 : 1);
109 }
110 EXPECT(buf->buf[off] == 'r');
111 }
112
113 EXPECT(stats->log_stats.blocks > 0);
114 reftable_writer_free(w);
115 }
116
117 static void test_log_buffer_size(void)
118 {
119 struct strbuf buf = STRBUF_INIT;
120 struct reftable_write_options opts = {
121 .block_size = 4096,
122 };
123 int err;
124 int i;
125 struct reftable_log_record
126 log = { .refname = "refs/heads/master",
127 .update_index = 0xa,
128 .value_type = REFTABLE_LOG_UPDATE,
129 .value = { .update = {
130 .name = "Han-Wen Nienhuys",
131 .email = "hanwen@google.com",
132 .tz_offset = 100,
133 .time = 0x5e430672,
134 .message = "commit: 9\n",
135 } } };
136 struct reftable_writer *w =
137 reftable_new_writer(&strbuf_add_void, &buf, &opts);
138
139 /* This tests buffer extension for log compression. Must use a random
140 hash, to ensure that the compressed part is larger than the original.
141 */
142 uint8_t hash1[GIT_SHA1_RAWSZ], hash2[GIT_SHA1_RAWSZ];
143 for (i = 0; i < GIT_SHA1_RAWSZ; i++) {
144 hash1[i] = (uint8_t)(rand() % 256);
145 hash2[i] = (uint8_t)(rand() % 256);
146 }
147 log.value.update.old_hash = hash1;
148 log.value.update.new_hash = hash2;
149 reftable_writer_set_limits(w, update_index, update_index);
150 err = reftable_writer_add_log(w, &log);
151 EXPECT_ERR(err);
152 err = reftable_writer_close(w);
153 EXPECT_ERR(err);
154 reftable_writer_free(w);
155 strbuf_release(&buf);
156 }
157
158 static void test_log_write_read(void)
159 {
160 int N = 2;
161 char **names = reftable_calloc(sizeof(char *) * (N + 1));
162 int err;
163 struct reftable_write_options opts = {
164 .block_size = 256,
165 };
166 struct reftable_ref_record ref = { NULL };
167 int i = 0;
168 struct reftable_log_record log = { NULL };
169 int n;
170 struct reftable_iterator it = { NULL };
171 struct reftable_reader rd = { NULL };
172 struct reftable_block_source source = { NULL };
173 struct strbuf buf = STRBUF_INIT;
174 struct reftable_writer *w =
175 reftable_new_writer(&strbuf_add_void, &buf, &opts);
176 const struct reftable_stats *stats = NULL;
177 reftable_writer_set_limits(w, 0, N);
178 for (i = 0; i < N; i++) {
179 char name[256];
180 struct reftable_ref_record ref = { NULL };
181 snprintf(name, sizeof(name), "b%02d%0*d", i, 130, 7);
182 names[i] = xstrdup(name);
183 ref.refname = name;
184 ref.update_index = i;
185
186 err = reftable_writer_add_ref(w, &ref);
187 EXPECT_ERR(err);
188 }
189 for (i = 0; i < N; i++) {
190 uint8_t hash1[GIT_SHA1_RAWSZ], hash2[GIT_SHA1_RAWSZ];
191 struct reftable_log_record log = { NULL };
192 set_test_hash(hash1, i);
193 set_test_hash(hash2, i + 1);
194
195 log.refname = names[i];
196 log.update_index = i;
197 log.value_type = REFTABLE_LOG_UPDATE;
198 log.value.update.old_hash = hash1;
199 log.value.update.new_hash = hash2;
200
201 err = reftable_writer_add_log(w, &log);
202 EXPECT_ERR(err);
203 }
204
205 n = reftable_writer_close(w);
206 EXPECT(n == 0);
207
208 stats = writer_stats(w);
209 EXPECT(stats->log_stats.blocks > 0);
210 reftable_writer_free(w);
211 w = NULL;
212
213 block_source_from_strbuf(&source, &buf);
214
215 err = init_reader(&rd, &source, "file.log");
216 EXPECT_ERR(err);
217
218 err = reftable_reader_seek_ref(&rd, &it, names[N - 1]);
219 EXPECT_ERR(err);
220
221 err = reftable_iterator_next_ref(&it, &ref);
222 EXPECT_ERR(err);
223
224 /* end of iteration. */
225 err = reftable_iterator_next_ref(&it, &ref);
226 EXPECT(0 < err);
227
228 reftable_iterator_destroy(&it);
229 reftable_ref_record_release(&ref);
230
231 err = reftable_reader_seek_log(&rd, &it, "");
232 EXPECT_ERR(err);
233
234 i = 0;
235 while (1) {
236 int err = reftable_iterator_next_log(&it, &log);
237 if (err > 0) {
238 break;
239 }
240
241 EXPECT_ERR(err);
242 EXPECT_STREQ(names[i], log.refname);
243 EXPECT(i == log.update_index);
244 i++;
245 reftable_log_record_release(&log);
246 }
247
248 EXPECT(i == N);
249 reftable_iterator_destroy(&it);
250
251 /* cleanup. */
252 strbuf_release(&buf);
253 free_names(names);
254 reader_close(&rd);
255 }
256
257 static void test_log_zlib_corruption(void)
258 {
259 struct reftable_write_options opts = {
260 .block_size = 256,
261 };
262 struct reftable_iterator it = { 0 };
263 struct reftable_reader rd = { 0 };
264 struct reftable_block_source source = { 0 };
265 struct strbuf buf = STRBUF_INIT;
266 struct reftable_writer *w =
267 reftable_new_writer(&strbuf_add_void, &buf, &opts);
268 const struct reftable_stats *stats = NULL;
269 uint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };
270 uint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };
271 char message[100] = { 0 };
272 int err, i, n;
273
274 struct reftable_log_record log = {
275 .refname = "refname",
276 .value_type = REFTABLE_LOG_UPDATE,
277 .value = {
278 .update = {
279 .new_hash = hash1,
280 .old_hash = hash2,
281 .name = "My Name",
282 .email = "myname@invalid",
283 .message = message,
284 },
285 },
286 };
287
288 for (i = 0; i < sizeof(message) - 1; i++)
289 message[i] = (uint8_t)(rand() % 64 + ' ');
290
291 reftable_writer_set_limits(w, 1, 1);
292
293 err = reftable_writer_add_log(w, &log);
294 EXPECT_ERR(err);
295
296 n = reftable_writer_close(w);
297 EXPECT(n == 0);
298
299 stats = writer_stats(w);
300 EXPECT(stats->log_stats.blocks > 0);
301 reftable_writer_free(w);
302 w = NULL;
303
304 /* corrupt the data. */
305 buf.buf[50] ^= 0x99;
306
307 block_source_from_strbuf(&source, &buf);
308
309 err = init_reader(&rd, &source, "file.log");
310 EXPECT_ERR(err);
311
312 err = reftable_reader_seek_log(&rd, &it, "refname");
313 EXPECT(err == REFTABLE_ZLIB_ERROR);
314
315 reftable_iterator_destroy(&it);
316
317 /* cleanup. */
318 strbuf_release(&buf);
319 reader_close(&rd);
320 }
321
322 static void test_table_read_write_sequential(void)
323 {
324 char **names;
325 struct strbuf buf = STRBUF_INIT;
326 int N = 50;
327 struct reftable_iterator it = { NULL };
328 struct reftable_block_source source = { NULL };
329 struct reftable_reader rd = { NULL };
330 int err = 0;
331 int j = 0;
332
333 write_table(&names, &buf, N, 256, GIT_SHA1_FORMAT_ID);
334
335 block_source_from_strbuf(&source, &buf);
336
337 err = init_reader(&rd, &source, "file.ref");
338 EXPECT_ERR(err);
339
340 err = reftable_reader_seek_ref(&rd, &it, "");
341 EXPECT_ERR(err);
342
343 while (1) {
344 struct reftable_ref_record ref = { NULL };
345 int r = reftable_iterator_next_ref(&it, &ref);
346 EXPECT(r >= 0);
347 if (r > 0) {
348 break;
349 }
350 EXPECT(0 == strcmp(names[j], ref.refname));
351 EXPECT(update_index == ref.update_index);
352
353 j++;
354 reftable_ref_record_release(&ref);
355 }
356 EXPECT(j == N);
357 reftable_iterator_destroy(&it);
358 strbuf_release(&buf);
359 free_names(names);
360
361 reader_close(&rd);
362 }
363
364 static void test_table_write_small_table(void)
365 {
366 char **names;
367 struct strbuf buf = STRBUF_INIT;
368 int N = 1;
369 write_table(&names, &buf, N, 4096, GIT_SHA1_FORMAT_ID);
370 EXPECT(buf.len < 200);
371 strbuf_release(&buf);
372 free_names(names);
373 }
374
375 static void test_table_read_api(void)
376 {
377 char **names;
378 struct strbuf buf = STRBUF_INIT;
379 int N = 50;
380 struct reftable_reader rd = { NULL };
381 struct reftable_block_source source = { NULL };
382 int err;
383 int i;
384 struct reftable_log_record log = { NULL };
385 struct reftable_iterator it = { NULL };
386
387 write_table(&names, &buf, N, 256, GIT_SHA1_FORMAT_ID);
388
389 block_source_from_strbuf(&source, &buf);
390
391 err = init_reader(&rd, &source, "file.ref");
392 EXPECT_ERR(err);
393
394 err = reftable_reader_seek_ref(&rd, &it, names[0]);
395 EXPECT_ERR(err);
396
397 err = reftable_iterator_next_log(&it, &log);
398 EXPECT(err == REFTABLE_API_ERROR);
399
400 strbuf_release(&buf);
401 for (i = 0; i < N; i++) {
402 reftable_free(names[i]);
403 }
404 reftable_iterator_destroy(&it);
405 reftable_free(names);
406 reader_close(&rd);
407 strbuf_release(&buf);
408 }
409
410 static void test_table_read_write_seek(int index, int hash_id)
411 {
412 char **names;
413 struct strbuf buf = STRBUF_INIT;
414 int N = 50;
415 struct reftable_reader rd = { NULL };
416 struct reftable_block_source source = { NULL };
417 int err;
418 int i = 0;
419
420 struct reftable_iterator it = { NULL };
421 struct strbuf pastLast = STRBUF_INIT;
422 struct reftable_ref_record ref = { NULL };
423
424 write_table(&names, &buf, N, 256, hash_id);
425
426 block_source_from_strbuf(&source, &buf);
427
428 err = init_reader(&rd, &source, "file.ref");
429 EXPECT_ERR(err);
430 EXPECT(hash_id == reftable_reader_hash_id(&rd));
431
432 if (!index) {
433 rd.ref_offsets.index_offset = 0;
434 } else {
435 EXPECT(rd.ref_offsets.index_offset > 0);
436 }
437
438 for (i = 1; i < N; i++) {
439 int err = reftable_reader_seek_ref(&rd, &it, names[i]);
440 EXPECT_ERR(err);
441 err = reftable_iterator_next_ref(&it, &ref);
442 EXPECT_ERR(err);
443 EXPECT(0 == strcmp(names[i], ref.refname));
444 EXPECT(REFTABLE_REF_VAL1 == ref.value_type);
445 EXPECT(i == ref.value.val1[0]);
446
447 reftable_ref_record_release(&ref);
448 reftable_iterator_destroy(&it);
449 }
450
451 strbuf_addstr(&pastLast, names[N - 1]);
452 strbuf_addstr(&pastLast, "/");
453
454 err = reftable_reader_seek_ref(&rd, &it, pastLast.buf);
455 if (err == 0) {
456 struct reftable_ref_record ref = { NULL };
457 int err = reftable_iterator_next_ref(&it, &ref);
458 EXPECT(err > 0);
459 } else {
460 EXPECT(err > 0);
461 }
462
463 strbuf_release(&pastLast);
464 reftable_iterator_destroy(&it);
465
466 strbuf_release(&buf);
467 for (i = 0; i < N; i++) {
468 reftable_free(names[i]);
469 }
470 reftable_free(names);
471 reader_close(&rd);
472 }
473
474 static void test_table_read_write_seek_linear(void)
475 {
476 test_table_read_write_seek(0, GIT_SHA1_FORMAT_ID);
477 }
478
479 static void test_table_read_write_seek_linear_sha256(void)
480 {
481 test_table_read_write_seek(0, GIT_SHA256_FORMAT_ID);
482 }
483
484 static void test_table_read_write_seek_index(void)
485 {
486 test_table_read_write_seek(1, GIT_SHA1_FORMAT_ID);
487 }
488
489 static void test_table_refs_for(int indexed)
490 {
491 int N = 50;
492 char **want_names = reftable_calloc(sizeof(char *) * (N + 1));
493 int want_names_len = 0;
494 uint8_t want_hash[GIT_SHA1_RAWSZ];
495
496 struct reftable_write_options opts = {
497 .block_size = 256,
498 };
499 struct reftable_ref_record ref = { NULL };
500 int i = 0;
501 int n;
502 int err;
503 struct reftable_reader rd;
504 struct reftable_block_source source = { NULL };
505
506 struct strbuf buf = STRBUF_INIT;
507 struct reftable_writer *w =
508 reftable_new_writer(&strbuf_add_void, &buf, &opts);
509
510 struct reftable_iterator it = { NULL };
511 int j;
512
513 set_test_hash(want_hash, 4);
514
515 for (i = 0; i < N; i++) {
516 uint8_t hash[GIT_SHA1_RAWSZ];
517 char fill[51] = { 0 };
518 char name[100];
519 uint8_t hash1[GIT_SHA1_RAWSZ];
520 uint8_t hash2[GIT_SHA1_RAWSZ];
521 struct reftable_ref_record ref = { NULL };
522
523 memset(hash, i, sizeof(hash));
524 memset(fill, 'x', 50);
525 /* Put the variable part in the start */
526 snprintf(name, sizeof(name), "br%02d%s", i, fill);
527 name[40] = 0;
528 ref.refname = name;
529
530 set_test_hash(hash1, i / 4);
531 set_test_hash(hash2, 3 + i / 4);
532 ref.value_type = REFTABLE_REF_VAL2;
533 ref.value.val2.value = hash1;
534 ref.value.val2.target_value = hash2;
535
536 /* 80 bytes / entry, so 3 entries per block. Yields 17
537 */
538 /* blocks. */
539 n = reftable_writer_add_ref(w, &ref);
540 EXPECT(n == 0);
541
542 if (!memcmp(hash1, want_hash, GIT_SHA1_RAWSZ) ||
543 !memcmp(hash2, want_hash, GIT_SHA1_RAWSZ)) {
544 want_names[want_names_len++] = xstrdup(name);
545 }
546 }
547
548 n = reftable_writer_close(w);
549 EXPECT(n == 0);
550
551 reftable_writer_free(w);
552 w = NULL;
553
554 block_source_from_strbuf(&source, &buf);
555
556 err = init_reader(&rd, &source, "file.ref");
557 EXPECT_ERR(err);
558 if (!indexed) {
559 rd.obj_offsets.is_present = 0;
560 }
561
562 err = reftable_reader_seek_ref(&rd, &it, "");
563 EXPECT_ERR(err);
564 reftable_iterator_destroy(&it);
565
566 err = reftable_reader_refs_for(&rd, &it, want_hash);
567 EXPECT_ERR(err);
568
569 j = 0;
570 while (1) {
571 int err = reftable_iterator_next_ref(&it, &ref);
572 EXPECT(err >= 0);
573 if (err > 0) {
574 break;
575 }
576
577 EXPECT(j < want_names_len);
578 EXPECT(0 == strcmp(ref.refname, want_names[j]));
579 j++;
580 reftable_ref_record_release(&ref);
581 }
582 EXPECT(j == want_names_len);
583
584 strbuf_release(&buf);
585 free_names(want_names);
586 reftable_iterator_destroy(&it);
587 reader_close(&rd);
588 }
589
590 static void test_table_refs_for_no_index(void)
591 {
592 test_table_refs_for(0);
593 }
594
595 static void test_table_refs_for_obj_index(void)
596 {
597 test_table_refs_for(1);
598 }
599
600 static void test_write_empty_table(void)
601 {
602 struct reftable_write_options opts = { 0 };
603 struct strbuf buf = STRBUF_INIT;
604 struct reftable_writer *w =
605 reftable_new_writer(&strbuf_add_void, &buf, &opts);
606 struct reftable_block_source source = { NULL };
607 struct reftable_reader *rd = NULL;
608 struct reftable_ref_record rec = { NULL };
609 struct reftable_iterator it = { NULL };
610 int err;
611
612 reftable_writer_set_limits(w, 1, 1);
613
614 err = reftable_writer_close(w);
615 EXPECT(err == REFTABLE_EMPTY_TABLE_ERROR);
616 reftable_writer_free(w);
617
618 EXPECT(buf.len == header_size(1) + footer_size(1));
619
620 block_source_from_strbuf(&source, &buf);
621
622 err = reftable_new_reader(&rd, &source, "filename");
623 EXPECT_ERR(err);
624
625 err = reftable_reader_seek_ref(rd, &it, "");
626 EXPECT_ERR(err);
627
628 err = reftable_iterator_next_ref(&it, &rec);
629 EXPECT(err > 0);
630
631 reftable_iterator_destroy(&it);
632 reftable_reader_free(rd);
633 strbuf_release(&buf);
634 }
635
636 static void test_write_key_order(void)
637 {
638 struct reftable_write_options opts = { 0 };
639 struct strbuf buf = STRBUF_INIT;
640 struct reftable_writer *w =
641 reftable_new_writer(&strbuf_add_void, &buf, &opts);
642 struct reftable_ref_record refs[2] = {
643 {
644 .refname = "b",
645 .update_index = 1,
646 .value_type = REFTABLE_REF_SYMREF,
647 .value = {
648 .symref = "target",
649 },
650 }, {
651 .refname = "a",
652 .update_index = 1,
653 .value_type = REFTABLE_REF_SYMREF,
654 .value = {
655 .symref = "target",
656 },
657 }
658 };
659 int err;
660
661 reftable_writer_set_limits(w, 1, 1);
662 err = reftable_writer_add_ref(w, &refs[0]);
663 EXPECT_ERR(err);
664 err = reftable_writer_add_ref(w, &refs[1]);
665 EXPECT(err == REFTABLE_API_ERROR);
666 reftable_writer_close(w);
667 reftable_writer_free(w);
668 strbuf_release(&buf);
669 }
670
671 static void test_corrupt_table_empty(void)
672 {
673 struct strbuf buf = STRBUF_INIT;
674 struct reftable_block_source source = { NULL };
675 struct reftable_reader rd = { NULL };
676 int err;
677
678 block_source_from_strbuf(&source, &buf);
679 err = init_reader(&rd, &source, "file.log");
680 EXPECT(err == REFTABLE_FORMAT_ERROR);
681 }
682
683 static void test_corrupt_table(void)
684 {
685 uint8_t zeros[1024] = { 0 };
686 struct strbuf buf = STRBUF_INIT;
687 struct reftable_block_source source = { NULL };
688 struct reftable_reader rd = { NULL };
689 int err;
690 strbuf_add(&buf, zeros, sizeof(zeros));
691
692 block_source_from_strbuf(&source, &buf);
693 err = init_reader(&rd, &source, "file.log");
694 EXPECT(err == REFTABLE_FORMAT_ERROR);
695 strbuf_release(&buf);
696 }
697
698 int readwrite_test_main(int argc, const char *argv[])
699 {
700 RUN_TEST(test_log_zlib_corruption);
701 RUN_TEST(test_corrupt_table);
702 RUN_TEST(test_corrupt_table_empty);
703 RUN_TEST(test_log_write_read);
704 RUN_TEST(test_write_key_order);
705 RUN_TEST(test_table_read_write_seek_linear_sha256);
706 RUN_TEST(test_log_buffer_size);
707 RUN_TEST(test_table_write_small_table);
708 RUN_TEST(test_buffer);
709 RUN_TEST(test_table_read_api);
710 RUN_TEST(test_table_read_write_sequential);
711 RUN_TEST(test_table_read_write_seek_linear);
712 RUN_TEST(test_table_read_write_seek_index);
713 RUN_TEST(test_table_refs_for_no_index);
714 RUN_TEST(test_table_refs_for_obj_index);
715 RUN_TEST(test_write_empty_table);
716 return 0;
717 }