]> git.ipfire.org Git - thirdparty/git.git/blob - reftable/stack_test.c
Merge branch 'ps/reftable-write-optim'
[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_OUTDATED_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->config.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_auto_compaction_fails_gracefully(void)
357 {
358 struct reftable_ref_record ref = {
359 .refname = "refs/heads/master",
360 .update_index = 1,
361 .value_type = REFTABLE_REF_VAL1,
362 .value.val1 = {0x01},
363 };
364 struct reftable_write_options cfg = {0};
365 struct reftable_stack *st;
366 struct strbuf table_path = STRBUF_INIT;
367 char *dir = get_tmp_dir(__LINE__);
368 int err;
369
370 err = reftable_new_stack(&st, dir, cfg);
371 EXPECT_ERR(err);
372
373 err = reftable_stack_add(st, write_test_ref, &ref);
374 EXPECT_ERR(err);
375 EXPECT(st->merged->stack_len == 1);
376 EXPECT(st->stats.attempts == 0);
377 EXPECT(st->stats.failures == 0);
378
379 /*
380 * Lock the newly written table such that it cannot be compacted.
381 * Adding a new table to the stack should not be impacted by this, even
382 * though auto-compaction will now fail.
383 */
384 strbuf_addf(&table_path, "%s/%s.lock", dir, st->readers[0]->name);
385 write_file_buf(table_path.buf, "", 0);
386
387 ref.update_index = 2;
388 err = reftable_stack_add(st, write_test_ref, &ref);
389 EXPECT_ERR(err);
390 EXPECT(st->merged->stack_len == 2);
391 EXPECT(st->stats.attempts == 1);
392 EXPECT(st->stats.failures == 1);
393
394 reftable_stack_destroy(st);
395 strbuf_release(&table_path);
396 clear_dir(dir);
397 }
398
399 static int write_error(struct reftable_writer *wr, void *arg)
400 {
401 return *((int *)arg);
402 }
403
404 static void test_reftable_stack_update_index_check(void)
405 {
406 char *dir = get_tmp_dir(__LINE__);
407
408 struct reftable_write_options cfg = { 0 };
409 struct reftable_stack *st = NULL;
410 int err;
411 struct reftable_ref_record ref1 = {
412 .refname = "name1",
413 .update_index = 1,
414 .value_type = REFTABLE_REF_SYMREF,
415 .value.symref = "master",
416 };
417 struct reftable_ref_record ref2 = {
418 .refname = "name2",
419 .update_index = 1,
420 .value_type = REFTABLE_REF_SYMREF,
421 .value.symref = "master",
422 };
423
424 err = reftable_new_stack(&st, dir, cfg);
425 EXPECT_ERR(err);
426
427 err = reftable_stack_add(st, &write_test_ref, &ref1);
428 EXPECT_ERR(err);
429
430 err = reftable_stack_add(st, &write_test_ref, &ref2);
431 EXPECT(err == REFTABLE_API_ERROR);
432 reftable_stack_destroy(st);
433 clear_dir(dir);
434 }
435
436 static void test_reftable_stack_lock_failure(void)
437 {
438 char *dir = get_tmp_dir(__LINE__);
439
440 struct reftable_write_options cfg = { 0 };
441 struct reftable_stack *st = NULL;
442 int err, i;
443
444 err = reftable_new_stack(&st, dir, cfg);
445 EXPECT_ERR(err);
446 for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--) {
447 err = reftable_stack_add(st, &write_error, &i);
448 EXPECT(err == i);
449 }
450
451 reftable_stack_destroy(st);
452 clear_dir(dir);
453 }
454
455 static void test_reftable_stack_add(void)
456 {
457 int i = 0;
458 int err = 0;
459 struct reftable_write_options cfg = {
460 .exact_log_message = 1,
461 .default_permissions = 0660,
462 .disable_auto_compact = 1,
463 };
464 struct reftable_stack *st = NULL;
465 char *dir = get_tmp_dir(__LINE__);
466 struct reftable_ref_record refs[2] = { { NULL } };
467 struct reftable_log_record logs[2] = { { NULL } };
468 struct strbuf path = STRBUF_INIT;
469 struct stat stat_result;
470 int N = ARRAY_SIZE(refs);
471
472 err = reftable_new_stack(&st, dir, cfg);
473 EXPECT_ERR(err);
474
475 for (i = 0; i < N; i++) {
476 char buf[256];
477 snprintf(buf, sizeof(buf), "branch%02d", i);
478 refs[i].refname = xstrdup(buf);
479 refs[i].update_index = i + 1;
480 refs[i].value_type = REFTABLE_REF_VAL1;
481 set_test_hash(refs[i].value.val1, i);
482
483 logs[i].refname = xstrdup(buf);
484 logs[i].update_index = N + i + 1;
485 logs[i].value_type = REFTABLE_LOG_UPDATE;
486 logs[i].value.update.email = xstrdup("identity@invalid");
487 set_test_hash(logs[i].value.update.new_hash, i);
488 }
489
490 for (i = 0; i < N; i++) {
491 int err = reftable_stack_add(st, &write_test_ref, &refs[i]);
492 EXPECT_ERR(err);
493 }
494
495 for (i = 0; i < N; i++) {
496 struct write_log_arg arg = {
497 .log = &logs[i],
498 .update_index = reftable_stack_next_update_index(st),
499 };
500 int err = reftable_stack_add(st, &write_test_log, &arg);
501 EXPECT_ERR(err);
502 }
503
504 err = reftable_stack_compact_all(st, NULL);
505 EXPECT_ERR(err);
506
507 for (i = 0; i < N; i++) {
508 struct reftable_ref_record dest = { NULL };
509
510 int err = reftable_stack_read_ref(st, refs[i].refname, &dest);
511 EXPECT_ERR(err);
512 EXPECT(reftable_ref_record_equal(&dest, refs + i,
513 GIT_SHA1_RAWSZ));
514 reftable_ref_record_release(&dest);
515 }
516
517 for (i = 0; i < N; i++) {
518 struct reftable_log_record dest = { NULL };
519 int err = reftable_stack_read_log(st, refs[i].refname, &dest);
520 EXPECT_ERR(err);
521 EXPECT(reftable_log_record_equal(&dest, logs + i,
522 GIT_SHA1_RAWSZ));
523 reftable_log_record_release(&dest);
524 }
525
526 #ifndef GIT_WINDOWS_NATIVE
527 strbuf_addstr(&path, dir);
528 strbuf_addstr(&path, "/tables.list");
529 err = stat(path.buf, &stat_result);
530 EXPECT(!err);
531 EXPECT((stat_result.st_mode & 0777) == cfg.default_permissions);
532
533 strbuf_reset(&path);
534 strbuf_addstr(&path, dir);
535 strbuf_addstr(&path, "/");
536 /* do not try at home; not an external API for reftable. */
537 strbuf_addstr(&path, st->readers[0]->name);
538 err = stat(path.buf, &stat_result);
539 EXPECT(!err);
540 EXPECT((stat_result.st_mode & 0777) == cfg.default_permissions);
541 #else
542 (void) stat_result;
543 #endif
544
545 /* cleanup */
546 reftable_stack_destroy(st);
547 for (i = 0; i < N; i++) {
548 reftable_ref_record_release(&refs[i]);
549 reftable_log_record_release(&logs[i]);
550 }
551 strbuf_release(&path);
552 clear_dir(dir);
553 }
554
555 static void test_reftable_stack_log_normalize(void)
556 {
557 int err = 0;
558 struct reftable_write_options cfg = {
559 0,
560 };
561 struct reftable_stack *st = NULL;
562 char *dir = get_tmp_dir(__LINE__);
563 struct reftable_log_record input = {
564 .refname = "branch",
565 .update_index = 1,
566 .value_type = REFTABLE_LOG_UPDATE,
567 .value = {
568 .update = {
569 .new_hash = { 1 },
570 .old_hash = { 2 },
571 },
572 },
573 };
574 struct reftable_log_record dest = {
575 .update_index = 0,
576 };
577 struct write_log_arg arg = {
578 .log = &input,
579 .update_index = 1,
580 };
581
582 err = reftable_new_stack(&st, dir, cfg);
583 EXPECT_ERR(err);
584
585 input.value.update.message = "one\ntwo";
586 err = reftable_stack_add(st, &write_test_log, &arg);
587 EXPECT(err == REFTABLE_API_ERROR);
588
589 input.value.update.message = "one";
590 err = reftable_stack_add(st, &write_test_log, &arg);
591 EXPECT_ERR(err);
592
593 err = reftable_stack_read_log(st, input.refname, &dest);
594 EXPECT_ERR(err);
595 EXPECT(0 == strcmp(dest.value.update.message, "one\n"));
596
597 input.value.update.message = "two\n";
598 arg.update_index = 2;
599 err = reftable_stack_add(st, &write_test_log, &arg);
600 EXPECT_ERR(err);
601 err = reftable_stack_read_log(st, input.refname, &dest);
602 EXPECT_ERR(err);
603 EXPECT(0 == strcmp(dest.value.update.message, "two\n"));
604
605 /* cleanup */
606 reftable_stack_destroy(st);
607 reftable_log_record_release(&dest);
608 clear_dir(dir);
609 }
610
611 static void test_reftable_stack_tombstone(void)
612 {
613 int i = 0;
614 char *dir = get_tmp_dir(__LINE__);
615
616 struct reftable_write_options cfg = { 0 };
617 struct reftable_stack *st = NULL;
618 int err;
619 struct reftable_ref_record refs[2] = { { NULL } };
620 struct reftable_log_record logs[2] = { { NULL } };
621 int N = ARRAY_SIZE(refs);
622 struct reftable_ref_record dest = { NULL };
623 struct reftable_log_record log_dest = { NULL };
624
625
626 err = reftable_new_stack(&st, dir, cfg);
627 EXPECT_ERR(err);
628
629 /* even entries add the refs, odd entries delete them. */
630 for (i = 0; i < N; i++) {
631 const char *buf = "branch";
632 refs[i].refname = xstrdup(buf);
633 refs[i].update_index = i + 1;
634 if (i % 2 == 0) {
635 refs[i].value_type = REFTABLE_REF_VAL1;
636 set_test_hash(refs[i].value.val1, i);
637 }
638
639 logs[i].refname = xstrdup(buf);
640 /* update_index is part of the key. */
641 logs[i].update_index = 42;
642 if (i % 2 == 0) {
643 logs[i].value_type = REFTABLE_LOG_UPDATE;
644 set_test_hash(logs[i].value.update.new_hash, i);
645 logs[i].value.update.email =
646 xstrdup("identity@invalid");
647 }
648 }
649 for (i = 0; i < N; i++) {
650 int err = reftable_stack_add(st, &write_test_ref, &refs[i]);
651 EXPECT_ERR(err);
652 }
653
654 for (i = 0; i < N; i++) {
655 struct write_log_arg arg = {
656 .log = &logs[i],
657 .update_index = reftable_stack_next_update_index(st),
658 };
659 int err = reftable_stack_add(st, &write_test_log, &arg);
660 EXPECT_ERR(err);
661 }
662
663 err = reftable_stack_read_ref(st, "branch", &dest);
664 EXPECT(err == 1);
665 reftable_ref_record_release(&dest);
666
667 err = reftable_stack_read_log(st, "branch", &log_dest);
668 EXPECT(err == 1);
669 reftable_log_record_release(&log_dest);
670
671 err = reftable_stack_compact_all(st, NULL);
672 EXPECT_ERR(err);
673
674 err = reftable_stack_read_ref(st, "branch", &dest);
675 EXPECT(err == 1);
676
677 err = reftable_stack_read_log(st, "branch", &log_dest);
678 EXPECT(err == 1);
679 reftable_ref_record_release(&dest);
680 reftable_log_record_release(&log_dest);
681
682 /* cleanup */
683 reftable_stack_destroy(st);
684 for (i = 0; i < N; i++) {
685 reftable_ref_record_release(&refs[i]);
686 reftable_log_record_release(&logs[i]);
687 }
688 clear_dir(dir);
689 }
690
691 static void test_reftable_stack_hash_id(void)
692 {
693 char *dir = get_tmp_dir(__LINE__);
694
695 struct reftable_write_options cfg = { 0 };
696 struct reftable_stack *st = NULL;
697 int err;
698
699 struct reftable_ref_record ref = {
700 .refname = "master",
701 .value_type = REFTABLE_REF_SYMREF,
702 .value.symref = "target",
703 .update_index = 1,
704 };
705 struct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };
706 struct reftable_stack *st32 = NULL;
707 struct reftable_write_options cfg_default = { 0 };
708 struct reftable_stack *st_default = NULL;
709 struct reftable_ref_record dest = { NULL };
710
711 err = reftable_new_stack(&st, dir, cfg);
712 EXPECT_ERR(err);
713
714 err = reftable_stack_add(st, &write_test_ref, &ref);
715 EXPECT_ERR(err);
716
717 /* can't read it with the wrong hash ID. */
718 err = reftable_new_stack(&st32, dir, cfg32);
719 EXPECT(err == REFTABLE_FORMAT_ERROR);
720
721 /* check that we can read it back with default config too. */
722 err = reftable_new_stack(&st_default, dir, cfg_default);
723 EXPECT_ERR(err);
724
725 err = reftable_stack_read_ref(st_default, "master", &dest);
726 EXPECT_ERR(err);
727
728 EXPECT(reftable_ref_record_equal(&ref, &dest, GIT_SHA1_RAWSZ));
729 reftable_ref_record_release(&dest);
730 reftable_stack_destroy(st);
731 reftable_stack_destroy(st_default);
732 clear_dir(dir);
733 }
734
735 static void test_suggest_compaction_segment(void)
736 {
737 uint64_t sizes[] = { 512, 64, 17, 16, 9, 9, 9, 16, 2, 16 };
738 struct segment min =
739 suggest_compaction_segment(sizes, ARRAY_SIZE(sizes));
740 EXPECT(min.start == 1);
741 EXPECT(min.end == 10);
742 }
743
744 static void test_suggest_compaction_segment_nothing(void)
745 {
746 uint64_t sizes[] = { 64, 32, 16, 8, 4, 2 };
747 struct segment result =
748 suggest_compaction_segment(sizes, ARRAY_SIZE(sizes));
749 EXPECT(result.start == result.end);
750 }
751
752 static void test_reflog_expire(void)
753 {
754 char *dir = get_tmp_dir(__LINE__);
755
756 struct reftable_write_options cfg = { 0 };
757 struct reftable_stack *st = NULL;
758 struct reftable_log_record logs[20] = { { NULL } };
759 int N = ARRAY_SIZE(logs) - 1;
760 int i = 0;
761 int err;
762 struct reftable_log_expiry_config expiry = {
763 .time = 10,
764 };
765 struct reftable_log_record log = { NULL };
766
767
768 err = reftable_new_stack(&st, dir, cfg);
769 EXPECT_ERR(err);
770
771 for (i = 1; i <= N; i++) {
772 char buf[256];
773 snprintf(buf, sizeof(buf), "branch%02d", i);
774
775 logs[i].refname = xstrdup(buf);
776 logs[i].update_index = i;
777 logs[i].value_type = REFTABLE_LOG_UPDATE;
778 logs[i].value.update.time = i;
779 logs[i].value.update.email = xstrdup("identity@invalid");
780 set_test_hash(logs[i].value.update.new_hash, i);
781 }
782
783 for (i = 1; i <= N; i++) {
784 struct write_log_arg arg = {
785 .log = &logs[i],
786 .update_index = reftable_stack_next_update_index(st),
787 };
788 int err = reftable_stack_add(st, &write_test_log, &arg);
789 EXPECT_ERR(err);
790 }
791
792 err = reftable_stack_compact_all(st, NULL);
793 EXPECT_ERR(err);
794
795 err = reftable_stack_compact_all(st, &expiry);
796 EXPECT_ERR(err);
797
798 err = reftable_stack_read_log(st, logs[9].refname, &log);
799 EXPECT(err == 1);
800
801 err = reftable_stack_read_log(st, logs[11].refname, &log);
802 EXPECT_ERR(err);
803
804 expiry.min_update_index = 15;
805 err = reftable_stack_compact_all(st, &expiry);
806 EXPECT_ERR(err);
807
808 err = reftable_stack_read_log(st, logs[14].refname, &log);
809 EXPECT(err == 1);
810
811 err = reftable_stack_read_log(st, logs[16].refname, &log);
812 EXPECT_ERR(err);
813
814 /* cleanup */
815 reftable_stack_destroy(st);
816 for (i = 0; i <= N; i++) {
817 reftable_log_record_release(&logs[i]);
818 }
819 clear_dir(dir);
820 reftable_log_record_release(&log);
821 }
822
823 static int write_nothing(struct reftable_writer *wr, void *arg)
824 {
825 reftable_writer_set_limits(wr, 1, 1);
826 return 0;
827 }
828
829 static void test_empty_add(void)
830 {
831 struct reftable_write_options cfg = { 0 };
832 struct reftable_stack *st = NULL;
833 int err;
834 char *dir = get_tmp_dir(__LINE__);
835
836 struct reftable_stack *st2 = NULL;
837
838
839 err = reftable_new_stack(&st, dir, cfg);
840 EXPECT_ERR(err);
841
842 err = reftable_stack_add(st, &write_nothing, NULL);
843 EXPECT_ERR(err);
844
845 err = reftable_new_stack(&st2, dir, cfg);
846 EXPECT_ERR(err);
847 clear_dir(dir);
848 reftable_stack_destroy(st);
849 reftable_stack_destroy(st2);
850 }
851
852 static int fastlog2(uint64_t sz)
853 {
854 int l = 0;
855 if (sz == 0)
856 return 0;
857 for (; sz; sz /= 2)
858 l++;
859 return l - 1;
860 }
861
862 static void test_reftable_stack_auto_compaction(void)
863 {
864 struct reftable_write_options cfg = {
865 .disable_auto_compact = 1,
866 };
867 struct reftable_stack *st = NULL;
868 char *dir = get_tmp_dir(__LINE__);
869
870 int err, i;
871 int N = 100;
872
873 err = reftable_new_stack(&st, dir, cfg);
874 EXPECT_ERR(err);
875
876 for (i = 0; i < N; i++) {
877 char name[100];
878 struct reftable_ref_record ref = {
879 .refname = name,
880 .update_index = reftable_stack_next_update_index(st),
881 .value_type = REFTABLE_REF_SYMREF,
882 .value.symref = "master",
883 };
884 snprintf(name, sizeof(name), "branch%04d", i);
885
886 err = reftable_stack_add(st, &write_test_ref, &ref);
887 EXPECT_ERR(err);
888
889 err = reftable_stack_auto_compact(st);
890 EXPECT_ERR(err);
891 EXPECT(i < 3 || st->merged->stack_len < 2 * fastlog2(i));
892 }
893
894 EXPECT(reftable_stack_compaction_stats(st)->entries_written <
895 (uint64_t)(N * fastlog2(N)));
896
897 reftable_stack_destroy(st);
898 clear_dir(dir);
899 }
900
901 static void test_reftable_stack_add_performs_auto_compaction(void)
902 {
903 struct reftable_write_options cfg = { 0 };
904 struct reftable_stack *st = NULL;
905 struct strbuf refname = STRBUF_INIT;
906 char *dir = get_tmp_dir(__LINE__);
907 int err, i, n = 20;
908
909 err = reftable_new_stack(&st, dir, cfg);
910 EXPECT_ERR(err);
911
912 for (i = 0; i <= n; i++) {
913 struct reftable_ref_record ref = {
914 .update_index = reftable_stack_next_update_index(st),
915 .value_type = REFTABLE_REF_SYMREF,
916 .value.symref = "master",
917 };
918
919 /*
920 * Disable auto-compaction for all but the last runs. Like this
921 * we can ensure that we indeed honor this setting and have
922 * better control over when exactly auto compaction runs.
923 */
924 st->config.disable_auto_compact = i != n;
925
926 strbuf_reset(&refname);
927 strbuf_addf(&refname, "branch-%04d", i);
928 ref.refname = refname.buf;
929
930 err = reftable_stack_add(st, &write_test_ref, &ref);
931 EXPECT_ERR(err);
932
933 /*
934 * The stack length should grow continuously for all runs where
935 * auto compaction is disabled. When enabled, we should merge
936 * all tables in the stack.
937 */
938 if (i != n)
939 EXPECT(st->merged->stack_len == i + 1);
940 else
941 EXPECT(st->merged->stack_len == 1);
942 }
943
944 reftable_stack_destroy(st);
945 strbuf_release(&refname);
946 clear_dir(dir);
947 }
948
949 static void test_reftable_stack_compaction_concurrent(void)
950 {
951 struct reftable_write_options cfg = { 0 };
952 struct reftable_stack *st1 = NULL, *st2 = NULL;
953 char *dir = get_tmp_dir(__LINE__);
954
955 int err, i;
956 int N = 3;
957
958 err = reftable_new_stack(&st1, dir, cfg);
959 EXPECT_ERR(err);
960
961 for (i = 0; i < N; i++) {
962 char name[100];
963 struct reftable_ref_record ref = {
964 .refname = name,
965 .update_index = reftable_stack_next_update_index(st1),
966 .value_type = REFTABLE_REF_SYMREF,
967 .value.symref = "master",
968 };
969 snprintf(name, sizeof(name), "branch%04d", i);
970
971 err = reftable_stack_add(st1, &write_test_ref, &ref);
972 EXPECT_ERR(err);
973 }
974
975 err = reftable_new_stack(&st2, dir, cfg);
976 EXPECT_ERR(err);
977
978 err = reftable_stack_compact_all(st1, NULL);
979 EXPECT_ERR(err);
980
981 reftable_stack_destroy(st1);
982 reftable_stack_destroy(st2);
983
984 EXPECT(count_dir_entries(dir) == 2);
985 clear_dir(dir);
986 }
987
988 static void unclean_stack_close(struct reftable_stack *st)
989 {
990 /* break abstraction boundary to simulate unclean shutdown. */
991 int i = 0;
992 for (; i < st->readers_len; i++) {
993 reftable_reader_free(st->readers[i]);
994 }
995 st->readers_len = 0;
996 FREE_AND_NULL(st->readers);
997 }
998
999 static void test_reftable_stack_compaction_concurrent_clean(void)
1000 {
1001 struct reftable_write_options cfg = { 0 };
1002 struct reftable_stack *st1 = NULL, *st2 = NULL, *st3 = NULL;
1003 char *dir = get_tmp_dir(__LINE__);
1004
1005 int err, i;
1006 int N = 3;
1007
1008 err = reftable_new_stack(&st1, dir, cfg);
1009 EXPECT_ERR(err);
1010
1011 for (i = 0; i < N; i++) {
1012 char name[100];
1013 struct reftable_ref_record ref = {
1014 .refname = name,
1015 .update_index = reftable_stack_next_update_index(st1),
1016 .value_type = REFTABLE_REF_SYMREF,
1017 .value.symref = "master",
1018 };
1019 snprintf(name, sizeof(name), "branch%04d", i);
1020
1021 err = reftable_stack_add(st1, &write_test_ref, &ref);
1022 EXPECT_ERR(err);
1023 }
1024
1025 err = reftable_new_stack(&st2, dir, cfg);
1026 EXPECT_ERR(err);
1027
1028 err = reftable_stack_compact_all(st1, NULL);
1029 EXPECT_ERR(err);
1030
1031 unclean_stack_close(st1);
1032 unclean_stack_close(st2);
1033
1034 err = reftable_new_stack(&st3, dir, cfg);
1035 EXPECT_ERR(err);
1036
1037 err = reftable_stack_clean(st3);
1038 EXPECT_ERR(err);
1039 EXPECT(count_dir_entries(dir) == 2);
1040
1041 reftable_stack_destroy(st1);
1042 reftable_stack_destroy(st2);
1043 reftable_stack_destroy(st3);
1044
1045 clear_dir(dir);
1046 }
1047
1048 int stack_test_main(int argc, const char *argv[])
1049 {
1050 RUN_TEST(test_empty_add);
1051 RUN_TEST(test_names_equal);
1052 RUN_TEST(test_parse_names);
1053 RUN_TEST(test_read_file);
1054 RUN_TEST(test_reflog_expire);
1055 RUN_TEST(test_reftable_stack_add);
1056 RUN_TEST(test_reftable_stack_add_one);
1057 RUN_TEST(test_reftable_stack_auto_compaction);
1058 RUN_TEST(test_reftable_stack_add_performs_auto_compaction);
1059 RUN_TEST(test_reftable_stack_compaction_concurrent);
1060 RUN_TEST(test_reftable_stack_compaction_concurrent_clean);
1061 RUN_TEST(test_reftable_stack_hash_id);
1062 RUN_TEST(test_reftable_stack_lock_failure);
1063 RUN_TEST(test_reftable_stack_log_normalize);
1064 RUN_TEST(test_reftable_stack_tombstone);
1065 RUN_TEST(test_reftable_stack_transaction_api);
1066 RUN_TEST(test_reftable_stack_transaction_api_performs_auto_compaction);
1067 RUN_TEST(test_reftable_stack_auto_compaction_fails_gracefully);
1068 RUN_TEST(test_reftable_stack_update_index_check);
1069 RUN_TEST(test_reftable_stack_uptodate);
1070 RUN_TEST(test_suggest_compaction_segment);
1071 RUN_TEST(test_suggest_compaction_segment_nothing);
1072 return 0;
1073 }