]> git.ipfire.org Git - thirdparty/git.git/blob - reftable/stack_test.c
Merge branch 'ps/reftable-unit-test-nfs-workaround'
[thirdparty/git.git] / reftable / stack_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 "stack.h"
10
11 #include "system.h"
12
13 #include "reftable-reader.h"
14 #include "merged.h"
15 #include "basics.h"
16 #include "record.h"
17 #include "test_framework.h"
18 #include "reftable-tests.h"
19 #include "reader.h"
20
21 #include <sys/types.h>
22 #include <dirent.h>
23
24 static void clear_dir(const char *dirname)
25 {
26 struct strbuf path = STRBUF_INIT;
27 strbuf_addstr(&path, dirname);
28 remove_dir_recursively(&path, 0);
29 strbuf_release(&path);
30 }
31
32 static int count_dir_entries(const char *dirname)
33 {
34 DIR *dir = opendir(dirname);
35 int len = 0;
36 struct dirent *d;
37 if (!dir)
38 return 0;
39
40 while ((d = readdir(dir))) {
41 /*
42 * Besides skipping over "." and "..", we also need to
43 * skip over other files that have a leading ".". This
44 * is due to behaviour of NFS, which will rename files
45 * to ".nfs*" to emulate delete-on-last-close.
46 *
47 * In any case this should be fine as the reftable
48 * library will never write files with leading dots
49 * anyway.
50 */
51 if (starts_with(d->d_name, "."))
52 continue;
53 len++;
54 }
55 closedir(dir);
56 return len;
57 }
58
59 /*
60 * Work linenumber into the tempdir, so we can see which tests forget to
61 * cleanup.
62 */
63 static char *get_tmp_template(int linenumber)
64 {
65 const char *tmp = getenv("TMPDIR");
66 static char template[1024];
67 snprintf(template, sizeof(template) - 1, "%s/stack_test-%d.XXXXXX",
68 tmp ? tmp : "/tmp", linenumber);
69 return template;
70 }
71
72 static char *get_tmp_dir(int linenumber)
73 {
74 char *dir = get_tmp_template(linenumber);
75 EXPECT(mkdtemp(dir));
76 return dir;
77 }
78
79 static void test_read_file(void)
80 {
81 char *fn = get_tmp_template(__LINE__);
82 int fd = mkstemp(fn);
83 char out[1024] = "line1\n\nline2\nline3";
84 int n, err;
85 char **names = NULL;
86 char *want[] = { "line1", "line2", "line3" };
87 int i = 0;
88
89 EXPECT(fd > 0);
90 n = write_in_full(fd, out, strlen(out));
91 EXPECT(n == strlen(out));
92 err = close(fd);
93 EXPECT(err >= 0);
94
95 err = read_lines(fn, &names);
96 EXPECT_ERR(err);
97
98 for (i = 0; names[i]; i++) {
99 EXPECT(0 == strcmp(want[i], names[i]));
100 }
101 free_names(names);
102 (void) remove(fn);
103 }
104
105 static void test_parse_names(void)
106 {
107 char buf[] = "line\n";
108 char **names = NULL;
109 parse_names(buf, strlen(buf), &names);
110
111 EXPECT(NULL != names[0]);
112 EXPECT(0 == strcmp(names[0], "line"));
113 EXPECT(NULL == names[1]);
114 free_names(names);
115 }
116
117 static void test_names_equal(void)
118 {
119 char *a[] = { "a", "b", "c", NULL };
120 char *b[] = { "a", "b", "d", NULL };
121 char *c[] = { "a", "b", NULL };
122
123 EXPECT(names_equal(a, a));
124 EXPECT(!names_equal(a, b));
125 EXPECT(!names_equal(a, c));
126 }
127
128 static int write_test_ref(struct reftable_writer *wr, void *arg)
129 {
130 struct reftable_ref_record *ref = arg;
131 reftable_writer_set_limits(wr, ref->update_index, ref->update_index);
132 return reftable_writer_add_ref(wr, ref);
133 }
134
135 struct write_log_arg {
136 struct reftable_log_record *log;
137 uint64_t update_index;
138 };
139
140 static int write_test_log(struct reftable_writer *wr, void *arg)
141 {
142 struct write_log_arg *wla = arg;
143
144 reftable_writer_set_limits(wr, wla->update_index, wla->update_index);
145 return reftable_writer_add_log(wr, wla->log);
146 }
147
148 static void test_reftable_stack_add_one(void)
149 {
150 char *dir = get_tmp_dir(__LINE__);
151 struct strbuf scratch = STRBUF_INIT;
152 int mask = umask(002);
153 struct reftable_write_options cfg = {
154 .default_permissions = 0660,
155 };
156 struct reftable_stack *st = NULL;
157 int err;
158 struct reftable_ref_record ref = {
159 .refname = "HEAD",
160 .update_index = 1,
161 .value_type = REFTABLE_REF_SYMREF,
162 .value.symref = "master",
163 };
164 struct reftable_ref_record dest = { NULL };
165 struct stat stat_result = { 0 };
166 err = reftable_new_stack(&st, dir, cfg);
167 EXPECT_ERR(err);
168
169 err = reftable_stack_add(st, &write_test_ref, &ref);
170 EXPECT_ERR(err);
171
172 err = reftable_stack_read_ref(st, ref.refname, &dest);
173 EXPECT_ERR(err);
174 EXPECT(0 == strcmp("master", dest.value.symref));
175 EXPECT(st->readers_len > 0);
176
177 printf("testing print functionality:\n");
178 err = reftable_stack_print_directory(dir, GIT_SHA1_FORMAT_ID);
179 EXPECT_ERR(err);
180
181 err = reftable_stack_print_directory(dir, GIT_SHA256_FORMAT_ID);
182 EXPECT(err == REFTABLE_FORMAT_ERROR);
183
184 #ifndef GIT_WINDOWS_NATIVE
185 strbuf_addstr(&scratch, dir);
186 strbuf_addstr(&scratch, "/tables.list");
187 err = stat(scratch.buf, &stat_result);
188 EXPECT(!err);
189 EXPECT((stat_result.st_mode & 0777) == cfg.default_permissions);
190
191 strbuf_reset(&scratch);
192 strbuf_addstr(&scratch, dir);
193 strbuf_addstr(&scratch, "/");
194 /* do not try at home; not an external API for reftable. */
195 strbuf_addstr(&scratch, st->readers[0]->name);
196 err = stat(scratch.buf, &stat_result);
197 EXPECT(!err);
198 EXPECT((stat_result.st_mode & 0777) == cfg.default_permissions);
199 #else
200 (void) stat_result;
201 #endif
202
203 reftable_ref_record_release(&dest);
204 reftable_stack_destroy(st);
205 strbuf_release(&scratch);
206 clear_dir(dir);
207 umask(mask);
208 }
209
210 static void test_reftable_stack_uptodate(void)
211 {
212 struct reftable_write_options cfg = { 0 };
213 struct reftable_stack *st1 = NULL;
214 struct reftable_stack *st2 = NULL;
215 char *dir = get_tmp_dir(__LINE__);
216
217 int err;
218 struct reftable_ref_record ref1 = {
219 .refname = "HEAD",
220 .update_index = 1,
221 .value_type = REFTABLE_REF_SYMREF,
222 .value.symref = "master",
223 };
224 struct reftable_ref_record ref2 = {
225 .refname = "branch2",
226 .update_index = 2,
227 .value_type = REFTABLE_REF_SYMREF,
228 .value.symref = "master",
229 };
230
231
232 /* simulate multi-process access to the same stack
233 by creating two stacks for the same directory.
234 */
235 err = reftable_new_stack(&st1, dir, cfg);
236 EXPECT_ERR(err);
237
238 err = reftable_new_stack(&st2, dir, cfg);
239 EXPECT_ERR(err);
240
241 err = reftable_stack_add(st1, &write_test_ref, &ref1);
242 EXPECT_ERR(err);
243
244 err = reftable_stack_add(st2, &write_test_ref, &ref2);
245 EXPECT(err == REFTABLE_LOCK_ERROR);
246
247 err = reftable_stack_reload(st2);
248 EXPECT_ERR(err);
249
250 err = reftable_stack_add(st2, &write_test_ref, &ref2);
251 EXPECT_ERR(err);
252 reftable_stack_destroy(st1);
253 reftable_stack_destroy(st2);
254 clear_dir(dir);
255 }
256
257 static void test_reftable_stack_transaction_api(void)
258 {
259 char *dir = get_tmp_dir(__LINE__);
260
261 struct reftable_write_options cfg = { 0 };
262 struct reftable_stack *st = NULL;
263 int err;
264 struct reftable_addition *add = NULL;
265
266 struct reftable_ref_record ref = {
267 .refname = "HEAD",
268 .update_index = 1,
269 .value_type = REFTABLE_REF_SYMREF,
270 .value.symref = "master",
271 };
272 struct reftable_ref_record dest = { NULL };
273
274
275 err = reftable_new_stack(&st, dir, cfg);
276 EXPECT_ERR(err);
277
278 reftable_addition_destroy(add);
279
280 err = reftable_stack_new_addition(&add, st);
281 EXPECT_ERR(err);
282
283 err = reftable_addition_add(add, &write_test_ref, &ref);
284 EXPECT_ERR(err);
285
286 err = reftable_addition_commit(add);
287 EXPECT_ERR(err);
288
289 reftable_addition_destroy(add);
290
291 err = reftable_stack_read_ref(st, ref.refname, &dest);
292 EXPECT_ERR(err);
293 EXPECT(REFTABLE_REF_SYMREF == dest.value_type);
294 EXPECT(0 == strcmp("master", dest.value.symref));
295
296 reftable_ref_record_release(&dest);
297 reftable_stack_destroy(st);
298 clear_dir(dir);
299 }
300
301 static void test_reftable_stack_transaction_api_performs_auto_compaction(void)
302 {
303 char *dir = get_tmp_dir(__LINE__);
304 struct reftable_write_options cfg = {0};
305 struct reftable_addition *add = NULL;
306 struct reftable_stack *st = NULL;
307 int i, n = 20, err;
308
309 err = reftable_new_stack(&st, dir, cfg);
310 EXPECT_ERR(err);
311
312 for (i = 0; i <= n; i++) {
313 struct reftable_ref_record ref = {
314 .update_index = reftable_stack_next_update_index(st),
315 .value_type = REFTABLE_REF_SYMREF,
316 .value.symref = "master",
317 };
318 char name[100];
319
320 snprintf(name, sizeof(name), "branch%04d", i);
321 ref.refname = name;
322
323 /*
324 * Disable auto-compaction for all but the last runs. Like this
325 * we can ensure that we indeed honor this setting and have
326 * better control over when exactly auto compaction runs.
327 */
328 st->disable_auto_compact = i != n;
329
330 err = reftable_stack_new_addition(&add, st);
331 EXPECT_ERR(err);
332
333 err = reftable_addition_add(add, &write_test_ref, &ref);
334 EXPECT_ERR(err);
335
336 err = reftable_addition_commit(add);
337 EXPECT_ERR(err);
338
339 reftable_addition_destroy(add);
340
341 /*
342 * The stack length should grow continuously for all runs where
343 * auto compaction is disabled. When enabled, we should merge
344 * all tables in the stack.
345 */
346 if (i != n)
347 EXPECT(st->merged->stack_len == i + 1);
348 else
349 EXPECT(st->merged->stack_len == 1);
350 }
351
352 reftable_stack_destroy(st);
353 clear_dir(dir);
354 }
355
356 static void test_reftable_stack_validate_refname(void)
357 {
358 struct reftable_write_options cfg = { 0 };
359 struct reftable_stack *st = NULL;
360 int err;
361 char *dir = get_tmp_dir(__LINE__);
362
363 int i;
364 struct reftable_ref_record ref = {
365 .refname = "a/b",
366 .update_index = 1,
367 .value_type = REFTABLE_REF_SYMREF,
368 .value.symref = "master",
369 };
370 char *additions[] = { "a", "a/b/c" };
371
372 err = reftable_new_stack(&st, dir, cfg);
373 EXPECT_ERR(err);
374
375 err = reftable_stack_add(st, &write_test_ref, &ref);
376 EXPECT_ERR(err);
377
378 for (i = 0; i < ARRAY_SIZE(additions); i++) {
379 struct reftable_ref_record ref = {
380 .refname = additions[i],
381 .update_index = 1,
382 .value_type = REFTABLE_REF_SYMREF,
383 .value.symref = "master",
384 };
385
386 err = reftable_stack_add(st, &write_test_ref, &ref);
387 EXPECT(err == REFTABLE_NAME_CONFLICT);
388 }
389
390 reftable_stack_destroy(st);
391 clear_dir(dir);
392 }
393
394 static int write_error(struct reftable_writer *wr, void *arg)
395 {
396 return *((int *)arg);
397 }
398
399 static void test_reftable_stack_update_index_check(void)
400 {
401 char *dir = get_tmp_dir(__LINE__);
402
403 struct reftable_write_options cfg = { 0 };
404 struct reftable_stack *st = NULL;
405 int err;
406 struct reftable_ref_record ref1 = {
407 .refname = "name1",
408 .update_index = 1,
409 .value_type = REFTABLE_REF_SYMREF,
410 .value.symref = "master",
411 };
412 struct reftable_ref_record ref2 = {
413 .refname = "name2",
414 .update_index = 1,
415 .value_type = REFTABLE_REF_SYMREF,
416 .value.symref = "master",
417 };
418
419 err = reftable_new_stack(&st, dir, cfg);
420 EXPECT_ERR(err);
421
422 err = reftable_stack_add(st, &write_test_ref, &ref1);
423 EXPECT_ERR(err);
424
425 err = reftable_stack_add(st, &write_test_ref, &ref2);
426 EXPECT(err == REFTABLE_API_ERROR);
427 reftable_stack_destroy(st);
428 clear_dir(dir);
429 }
430
431 static void test_reftable_stack_lock_failure(void)
432 {
433 char *dir = get_tmp_dir(__LINE__);
434
435 struct reftable_write_options cfg = { 0 };
436 struct reftable_stack *st = NULL;
437 int err, i;
438
439 err = reftable_new_stack(&st, dir, cfg);
440 EXPECT_ERR(err);
441 for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--) {
442 err = reftable_stack_add(st, &write_error, &i);
443 EXPECT(err == i);
444 }
445
446 reftable_stack_destroy(st);
447 clear_dir(dir);
448 }
449
450 static void test_reftable_stack_add(void)
451 {
452 int i = 0;
453 int err = 0;
454 struct reftable_write_options cfg = {
455 .exact_log_message = 1,
456 .default_permissions = 0660,
457 };
458 struct reftable_stack *st = NULL;
459 char *dir = get_tmp_dir(__LINE__);
460 struct reftable_ref_record refs[2] = { { NULL } };
461 struct reftable_log_record logs[2] = { { NULL } };
462 struct strbuf path = STRBUF_INIT;
463 struct stat stat_result;
464 int N = ARRAY_SIZE(refs);
465
466 err = reftable_new_stack(&st, dir, cfg);
467 EXPECT_ERR(err);
468 st->disable_auto_compact = 1;
469
470 for (i = 0; i < N; i++) {
471 char buf[256];
472 snprintf(buf, sizeof(buf), "branch%02d", i);
473 refs[i].refname = xstrdup(buf);
474 refs[i].update_index = i + 1;
475 refs[i].value_type = REFTABLE_REF_VAL1;
476 set_test_hash(refs[i].value.val1, i);
477
478 logs[i].refname = xstrdup(buf);
479 logs[i].update_index = N + i + 1;
480 logs[i].value_type = REFTABLE_LOG_UPDATE;
481 logs[i].value.update.email = xstrdup("identity@invalid");
482 set_test_hash(logs[i].value.update.new_hash, i);
483 }
484
485 for (i = 0; i < N; i++) {
486 int err = reftable_stack_add(st, &write_test_ref, &refs[i]);
487 EXPECT_ERR(err);
488 }
489
490 for (i = 0; i < N; i++) {
491 struct write_log_arg arg = {
492 .log = &logs[i],
493 .update_index = reftable_stack_next_update_index(st),
494 };
495 int err = reftable_stack_add(st, &write_test_log, &arg);
496 EXPECT_ERR(err);
497 }
498
499 err = reftable_stack_compact_all(st, NULL);
500 EXPECT_ERR(err);
501
502 for (i = 0; i < N; i++) {
503 struct reftable_ref_record dest = { NULL };
504
505 int err = reftable_stack_read_ref(st, refs[i].refname, &dest);
506 EXPECT_ERR(err);
507 EXPECT(reftable_ref_record_equal(&dest, refs + i,
508 GIT_SHA1_RAWSZ));
509 reftable_ref_record_release(&dest);
510 }
511
512 for (i = 0; i < N; i++) {
513 struct reftable_log_record dest = { NULL };
514 int err = reftable_stack_read_log(st, refs[i].refname, &dest);
515 EXPECT_ERR(err);
516 EXPECT(reftable_log_record_equal(&dest, logs + i,
517 GIT_SHA1_RAWSZ));
518 reftable_log_record_release(&dest);
519 }
520
521 #ifndef GIT_WINDOWS_NATIVE
522 strbuf_addstr(&path, dir);
523 strbuf_addstr(&path, "/tables.list");
524 err = stat(path.buf, &stat_result);
525 EXPECT(!err);
526 EXPECT((stat_result.st_mode & 0777) == cfg.default_permissions);
527
528 strbuf_reset(&path);
529 strbuf_addstr(&path, dir);
530 strbuf_addstr(&path, "/");
531 /* do not try at home; not an external API for reftable. */
532 strbuf_addstr(&path, st->readers[0]->name);
533 err = stat(path.buf, &stat_result);
534 EXPECT(!err);
535 EXPECT((stat_result.st_mode & 0777) == cfg.default_permissions);
536 #else
537 (void) stat_result;
538 #endif
539
540 /* cleanup */
541 reftable_stack_destroy(st);
542 for (i = 0; i < N; i++) {
543 reftable_ref_record_release(&refs[i]);
544 reftable_log_record_release(&logs[i]);
545 }
546 strbuf_release(&path);
547 clear_dir(dir);
548 }
549
550 static void test_reftable_stack_log_normalize(void)
551 {
552 int err = 0;
553 struct reftable_write_options cfg = {
554 0,
555 };
556 struct reftable_stack *st = NULL;
557 char *dir = get_tmp_dir(__LINE__);
558 struct reftable_log_record input = {
559 .refname = "branch",
560 .update_index = 1,
561 .value_type = REFTABLE_LOG_UPDATE,
562 .value = {
563 .update = {
564 .new_hash = { 1 },
565 .old_hash = { 2 },
566 },
567 },
568 };
569 struct reftable_log_record dest = {
570 .update_index = 0,
571 };
572 struct write_log_arg arg = {
573 .log = &input,
574 .update_index = 1,
575 };
576
577 err = reftable_new_stack(&st, dir, cfg);
578 EXPECT_ERR(err);
579
580 input.value.update.message = "one\ntwo";
581 err = reftable_stack_add(st, &write_test_log, &arg);
582 EXPECT(err == REFTABLE_API_ERROR);
583
584 input.value.update.message = "one";
585 err = reftable_stack_add(st, &write_test_log, &arg);
586 EXPECT_ERR(err);
587
588 err = reftable_stack_read_log(st, input.refname, &dest);
589 EXPECT_ERR(err);
590 EXPECT(0 == strcmp(dest.value.update.message, "one\n"));
591
592 input.value.update.message = "two\n";
593 arg.update_index = 2;
594 err = reftable_stack_add(st, &write_test_log, &arg);
595 EXPECT_ERR(err);
596 err = reftable_stack_read_log(st, input.refname, &dest);
597 EXPECT_ERR(err);
598 EXPECT(0 == strcmp(dest.value.update.message, "two\n"));
599
600 /* cleanup */
601 reftable_stack_destroy(st);
602 reftable_log_record_release(&dest);
603 clear_dir(dir);
604 }
605
606 static void test_reftable_stack_tombstone(void)
607 {
608 int i = 0;
609 char *dir = get_tmp_dir(__LINE__);
610
611 struct reftable_write_options cfg = { 0 };
612 struct reftable_stack *st = NULL;
613 int err;
614 struct reftable_ref_record refs[2] = { { NULL } };
615 struct reftable_log_record logs[2] = { { NULL } };
616 int N = ARRAY_SIZE(refs);
617 struct reftable_ref_record dest = { NULL };
618 struct reftable_log_record log_dest = { NULL };
619
620
621 err = reftable_new_stack(&st, dir, cfg);
622 EXPECT_ERR(err);
623
624 /* even entries add the refs, odd entries delete them. */
625 for (i = 0; i < N; i++) {
626 const char *buf = "branch";
627 refs[i].refname = xstrdup(buf);
628 refs[i].update_index = i + 1;
629 if (i % 2 == 0) {
630 refs[i].value_type = REFTABLE_REF_VAL1;
631 set_test_hash(refs[i].value.val1, i);
632 }
633
634 logs[i].refname = xstrdup(buf);
635 /* update_index is part of the key. */
636 logs[i].update_index = 42;
637 if (i % 2 == 0) {
638 logs[i].value_type = REFTABLE_LOG_UPDATE;
639 set_test_hash(logs[i].value.update.new_hash, i);
640 logs[i].value.update.email =
641 xstrdup("identity@invalid");
642 }
643 }
644 for (i = 0; i < N; i++) {
645 int err = reftable_stack_add(st, &write_test_ref, &refs[i]);
646 EXPECT_ERR(err);
647 }
648
649 for (i = 0; i < N; i++) {
650 struct write_log_arg arg = {
651 .log = &logs[i],
652 .update_index = reftable_stack_next_update_index(st),
653 };
654 int err = reftable_stack_add(st, &write_test_log, &arg);
655 EXPECT_ERR(err);
656 }
657
658 err = reftable_stack_read_ref(st, "branch", &dest);
659 EXPECT(err == 1);
660 reftable_ref_record_release(&dest);
661
662 err = reftable_stack_read_log(st, "branch", &log_dest);
663 EXPECT(err == 1);
664 reftable_log_record_release(&log_dest);
665
666 err = reftable_stack_compact_all(st, NULL);
667 EXPECT_ERR(err);
668
669 err = reftable_stack_read_ref(st, "branch", &dest);
670 EXPECT(err == 1);
671
672 err = reftable_stack_read_log(st, "branch", &log_dest);
673 EXPECT(err == 1);
674 reftable_ref_record_release(&dest);
675 reftable_log_record_release(&log_dest);
676
677 /* cleanup */
678 reftable_stack_destroy(st);
679 for (i = 0; i < N; i++) {
680 reftable_ref_record_release(&refs[i]);
681 reftable_log_record_release(&logs[i]);
682 }
683 clear_dir(dir);
684 }
685
686 static void test_reftable_stack_hash_id(void)
687 {
688 char *dir = get_tmp_dir(__LINE__);
689
690 struct reftable_write_options cfg = { 0 };
691 struct reftable_stack *st = NULL;
692 int err;
693
694 struct reftable_ref_record ref = {
695 .refname = "master",
696 .value_type = REFTABLE_REF_SYMREF,
697 .value.symref = "target",
698 .update_index = 1,
699 };
700 struct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };
701 struct reftable_stack *st32 = NULL;
702 struct reftable_write_options cfg_default = { 0 };
703 struct reftable_stack *st_default = NULL;
704 struct reftable_ref_record dest = { NULL };
705
706 err = reftable_new_stack(&st, dir, cfg);
707 EXPECT_ERR(err);
708
709 err = reftable_stack_add(st, &write_test_ref, &ref);
710 EXPECT_ERR(err);
711
712 /* can't read it with the wrong hash ID. */
713 err = reftable_new_stack(&st32, dir, cfg32);
714 EXPECT(err == REFTABLE_FORMAT_ERROR);
715
716 /* check that we can read it back with default config too. */
717 err = reftable_new_stack(&st_default, dir, cfg_default);
718 EXPECT_ERR(err);
719
720 err = reftable_stack_read_ref(st_default, "master", &dest);
721 EXPECT_ERR(err);
722
723 EXPECT(reftable_ref_record_equal(&ref, &dest, GIT_SHA1_RAWSZ));
724 reftable_ref_record_release(&dest);
725 reftable_stack_destroy(st);
726 reftable_stack_destroy(st_default);
727 clear_dir(dir);
728 }
729
730 static void test_log2(void)
731 {
732 EXPECT(1 == fastlog2(3));
733 EXPECT(2 == fastlog2(4));
734 EXPECT(2 == fastlog2(5));
735 }
736
737 static void test_sizes_to_segments(void)
738 {
739 uint64_t sizes[] = { 2, 3, 4, 5, 7, 9 };
740 /* .................0 1 2 3 4 5 */
741
742 size_t seglen = 0;
743 struct segment *segs =
744 sizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes));
745 EXPECT(segs[2].log == 3);
746 EXPECT(segs[2].start == 5);
747 EXPECT(segs[2].end == 6);
748
749 EXPECT(segs[1].log == 2);
750 EXPECT(segs[1].start == 2);
751 EXPECT(segs[1].end == 5);
752 reftable_free(segs);
753 }
754
755 static void test_sizes_to_segments_empty(void)
756 {
757 size_t seglen = 0;
758 struct segment *segs = sizes_to_segments(&seglen, NULL, 0);
759 EXPECT(seglen == 0);
760 reftable_free(segs);
761 }
762
763 static void test_sizes_to_segments_all_equal(void)
764 {
765 uint64_t sizes[] = { 5, 5 };
766 size_t seglen = 0;
767 struct segment *segs =
768 sizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes));
769 EXPECT(seglen == 1);
770 EXPECT(segs[0].start == 0);
771 EXPECT(segs[0].end == 2);
772 reftable_free(segs);
773 }
774
775 static void test_suggest_compaction_segment(void)
776 {
777 uint64_t sizes[] = { 128, 64, 17, 16, 9, 9, 9, 16, 16 };
778 /* .................0 1 2 3 4 5 6 */
779 struct segment min =
780 suggest_compaction_segment(sizes, ARRAY_SIZE(sizes));
781 EXPECT(min.start == 2);
782 EXPECT(min.end == 7);
783 }
784
785 static void test_suggest_compaction_segment_nothing(void)
786 {
787 uint64_t sizes[] = { 64, 32, 16, 8, 4, 2 };
788 struct segment result =
789 suggest_compaction_segment(sizes, ARRAY_SIZE(sizes));
790 EXPECT(result.start == result.end);
791 }
792
793 static void test_reflog_expire(void)
794 {
795 char *dir = get_tmp_dir(__LINE__);
796
797 struct reftable_write_options cfg = { 0 };
798 struct reftable_stack *st = NULL;
799 struct reftable_log_record logs[20] = { { NULL } };
800 int N = ARRAY_SIZE(logs) - 1;
801 int i = 0;
802 int err;
803 struct reftable_log_expiry_config expiry = {
804 .time = 10,
805 };
806 struct reftable_log_record log = { NULL };
807
808
809 err = reftable_new_stack(&st, dir, cfg);
810 EXPECT_ERR(err);
811
812 for (i = 1; i <= N; i++) {
813 char buf[256];
814 snprintf(buf, sizeof(buf), "branch%02d", i);
815
816 logs[i].refname = xstrdup(buf);
817 logs[i].update_index = i;
818 logs[i].value_type = REFTABLE_LOG_UPDATE;
819 logs[i].value.update.time = i;
820 logs[i].value.update.email = xstrdup("identity@invalid");
821 set_test_hash(logs[i].value.update.new_hash, i);
822 }
823
824 for (i = 1; i <= N; i++) {
825 struct write_log_arg arg = {
826 .log = &logs[i],
827 .update_index = reftable_stack_next_update_index(st),
828 };
829 int err = reftable_stack_add(st, &write_test_log, &arg);
830 EXPECT_ERR(err);
831 }
832
833 err = reftable_stack_compact_all(st, NULL);
834 EXPECT_ERR(err);
835
836 err = reftable_stack_compact_all(st, &expiry);
837 EXPECT_ERR(err);
838
839 err = reftable_stack_read_log(st, logs[9].refname, &log);
840 EXPECT(err == 1);
841
842 err = reftable_stack_read_log(st, logs[11].refname, &log);
843 EXPECT_ERR(err);
844
845 expiry.min_update_index = 15;
846 err = reftable_stack_compact_all(st, &expiry);
847 EXPECT_ERR(err);
848
849 err = reftable_stack_read_log(st, logs[14].refname, &log);
850 EXPECT(err == 1);
851
852 err = reftable_stack_read_log(st, logs[16].refname, &log);
853 EXPECT_ERR(err);
854
855 /* cleanup */
856 reftable_stack_destroy(st);
857 for (i = 0; i <= N; i++) {
858 reftable_log_record_release(&logs[i]);
859 }
860 clear_dir(dir);
861 reftable_log_record_release(&log);
862 }
863
864 static int write_nothing(struct reftable_writer *wr, void *arg)
865 {
866 reftable_writer_set_limits(wr, 1, 1);
867 return 0;
868 }
869
870 static void test_empty_add(void)
871 {
872 struct reftable_write_options cfg = { 0 };
873 struct reftable_stack *st = NULL;
874 int err;
875 char *dir = get_tmp_dir(__LINE__);
876
877 struct reftable_stack *st2 = NULL;
878
879
880 err = reftable_new_stack(&st, dir, cfg);
881 EXPECT_ERR(err);
882
883 err = reftable_stack_add(st, &write_nothing, NULL);
884 EXPECT_ERR(err);
885
886 err = reftable_new_stack(&st2, dir, cfg);
887 EXPECT_ERR(err);
888 clear_dir(dir);
889 reftable_stack_destroy(st);
890 reftable_stack_destroy(st2);
891 }
892
893 static void test_reftable_stack_auto_compaction(void)
894 {
895 struct reftable_write_options cfg = { 0 };
896 struct reftable_stack *st = NULL;
897 char *dir = get_tmp_dir(__LINE__);
898
899 int err, i;
900 int N = 100;
901
902 err = reftable_new_stack(&st, dir, cfg);
903 EXPECT_ERR(err);
904
905 st->disable_auto_compact = 1; /* call manually below for coverage. */
906 for (i = 0; i < N; i++) {
907 char name[100];
908 struct reftable_ref_record ref = {
909 .refname = name,
910 .update_index = reftable_stack_next_update_index(st),
911 .value_type = REFTABLE_REF_SYMREF,
912 .value.symref = "master",
913 };
914 snprintf(name, sizeof(name), "branch%04d", i);
915
916 err = reftable_stack_add(st, &write_test_ref, &ref);
917 EXPECT_ERR(err);
918
919 err = reftable_stack_auto_compact(st);
920 EXPECT_ERR(err);
921 EXPECT(i < 3 || st->merged->stack_len < 2 * fastlog2(i));
922 }
923
924 EXPECT(reftable_stack_compaction_stats(st)->entries_written <
925 (uint64_t)(N * fastlog2(N)));
926
927 reftable_stack_destroy(st);
928 clear_dir(dir);
929 }
930
931 static void test_reftable_stack_add_performs_auto_compaction(void)
932 {
933 struct reftable_write_options cfg = { 0 };
934 struct reftable_stack *st = NULL;
935 struct strbuf refname = STRBUF_INIT;
936 char *dir = get_tmp_dir(__LINE__);
937 int err, i, n = 20;
938
939 err = reftable_new_stack(&st, dir, cfg);
940 EXPECT_ERR(err);
941
942 for (i = 0; i <= n; i++) {
943 struct reftable_ref_record ref = {
944 .update_index = reftable_stack_next_update_index(st),
945 .value_type = REFTABLE_REF_SYMREF,
946 .value.symref = "master",
947 };
948
949 /*
950 * Disable auto-compaction for all but the last runs. Like this
951 * we can ensure that we indeed honor this setting and have
952 * better control over when exactly auto compaction runs.
953 */
954 st->disable_auto_compact = i != n;
955
956 strbuf_reset(&refname);
957 strbuf_addf(&refname, "branch-%04d", i);
958 ref.refname = refname.buf;
959
960 err = reftable_stack_add(st, &write_test_ref, &ref);
961 EXPECT_ERR(err);
962
963 /*
964 * The stack length should grow continuously for all runs where
965 * auto compaction is disabled. When enabled, we should merge
966 * all tables in the stack.
967 */
968 if (i != n)
969 EXPECT(st->merged->stack_len == i + 1);
970 else
971 EXPECT(st->merged->stack_len == 1);
972 }
973
974 reftable_stack_destroy(st);
975 strbuf_release(&refname);
976 clear_dir(dir);
977 }
978
979 static void test_reftable_stack_compaction_concurrent(void)
980 {
981 struct reftable_write_options cfg = { 0 };
982 struct reftable_stack *st1 = NULL, *st2 = NULL;
983 char *dir = get_tmp_dir(__LINE__);
984
985 int err, i;
986 int N = 3;
987
988 err = reftable_new_stack(&st1, dir, cfg);
989 EXPECT_ERR(err);
990
991 for (i = 0; i < N; i++) {
992 char name[100];
993 struct reftable_ref_record ref = {
994 .refname = name,
995 .update_index = reftable_stack_next_update_index(st1),
996 .value_type = REFTABLE_REF_SYMREF,
997 .value.symref = "master",
998 };
999 snprintf(name, sizeof(name), "branch%04d", i);
1000
1001 err = reftable_stack_add(st1, &write_test_ref, &ref);
1002 EXPECT_ERR(err);
1003 }
1004
1005 err = reftable_new_stack(&st2, dir, cfg);
1006 EXPECT_ERR(err);
1007
1008 err = reftable_stack_compact_all(st1, NULL);
1009 EXPECT_ERR(err);
1010
1011 reftable_stack_destroy(st1);
1012 reftable_stack_destroy(st2);
1013
1014 EXPECT(count_dir_entries(dir) == 2);
1015 clear_dir(dir);
1016 }
1017
1018 static void unclean_stack_close(struct reftable_stack *st)
1019 {
1020 /* break abstraction boundary to simulate unclean shutdown. */
1021 int i = 0;
1022 for (; i < st->readers_len; i++) {
1023 reftable_reader_free(st->readers[i]);
1024 }
1025 st->readers_len = 0;
1026 FREE_AND_NULL(st->readers);
1027 }
1028
1029 static void test_reftable_stack_compaction_concurrent_clean(void)
1030 {
1031 struct reftable_write_options cfg = { 0 };
1032 struct reftable_stack *st1 = NULL, *st2 = NULL, *st3 = NULL;
1033 char *dir = get_tmp_dir(__LINE__);
1034
1035 int err, i;
1036 int N = 3;
1037
1038 err = reftable_new_stack(&st1, dir, cfg);
1039 EXPECT_ERR(err);
1040
1041 for (i = 0; i < N; i++) {
1042 char name[100];
1043 struct reftable_ref_record ref = {
1044 .refname = name,
1045 .update_index = reftable_stack_next_update_index(st1),
1046 .value_type = REFTABLE_REF_SYMREF,
1047 .value.symref = "master",
1048 };
1049 snprintf(name, sizeof(name), "branch%04d", i);
1050
1051 err = reftable_stack_add(st1, &write_test_ref, &ref);
1052 EXPECT_ERR(err);
1053 }
1054
1055 err = reftable_new_stack(&st2, dir, cfg);
1056 EXPECT_ERR(err);
1057
1058 err = reftable_stack_compact_all(st1, NULL);
1059 EXPECT_ERR(err);
1060
1061 unclean_stack_close(st1);
1062 unclean_stack_close(st2);
1063
1064 err = reftable_new_stack(&st3, dir, cfg);
1065 EXPECT_ERR(err);
1066
1067 err = reftable_stack_clean(st3);
1068 EXPECT_ERR(err);
1069 EXPECT(count_dir_entries(dir) == 2);
1070
1071 reftable_stack_destroy(st1);
1072 reftable_stack_destroy(st2);
1073 reftable_stack_destroy(st3);
1074
1075 clear_dir(dir);
1076 }
1077
1078 int stack_test_main(int argc, const char *argv[])
1079 {
1080 RUN_TEST(test_empty_add);
1081 RUN_TEST(test_log2);
1082 RUN_TEST(test_names_equal);
1083 RUN_TEST(test_parse_names);
1084 RUN_TEST(test_read_file);
1085 RUN_TEST(test_reflog_expire);
1086 RUN_TEST(test_reftable_stack_add);
1087 RUN_TEST(test_reftable_stack_add_one);
1088 RUN_TEST(test_reftable_stack_auto_compaction);
1089 RUN_TEST(test_reftable_stack_add_performs_auto_compaction);
1090 RUN_TEST(test_reftable_stack_compaction_concurrent);
1091 RUN_TEST(test_reftable_stack_compaction_concurrent_clean);
1092 RUN_TEST(test_reftable_stack_hash_id);
1093 RUN_TEST(test_reftable_stack_lock_failure);
1094 RUN_TEST(test_reftable_stack_log_normalize);
1095 RUN_TEST(test_reftable_stack_tombstone);
1096 RUN_TEST(test_reftable_stack_transaction_api);
1097 RUN_TEST(test_reftable_stack_transaction_api_performs_auto_compaction);
1098 RUN_TEST(test_reftable_stack_update_index_check);
1099 RUN_TEST(test_reftable_stack_uptodate);
1100 RUN_TEST(test_reftable_stack_validate_refname);
1101 RUN_TEST(test_sizes_to_segments);
1102 RUN_TEST(test_sizes_to_segments_all_equal);
1103 RUN_TEST(test_sizes_to_segments_empty);
1104 RUN_TEST(test_suggest_compaction_segment);
1105 RUN_TEST(test_suggest_compaction_segment_nothing);
1106 return 0;
1107 }