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 #define DISABLE_SIGN_COMPARE_WARNINGS
10
11 #include "unit-test.h"
12 #include "dir.h"
13 #include "lib-reftable.h"
14 #include "reftable/merged.h"
15 #include "reftable/reftable-error.h"
16 #include "reftable/stack.h"
17 #include "reftable/table.h"
18 #include "strbuf.h"
19 #include "tempfile.h"
20 #include <dirent.h>
21
22 static void clear_dir(const char *dirname)
23 {
24 struct strbuf path = REFTABLE_BUF_INIT;
25 strbuf_addstr(&path, dirname);
26 remove_dir_recursively(&path, 0);
27 strbuf_release(&path);
28 }
29
30 static int count_dir_entries(const char *dirname)
31 {
32 DIR *dir = opendir(dirname);
33 int len = 0;
34 struct dirent *d;
35 if (!dir)
36 return 0;
37
38 while ((d = readdir(dir))) {
39 /*
40 * Besides skipping over "." and "..", we also need to
41 * skip over other files that have a leading ".". This
42 * is due to behaviour of NFS, which will rename files
43 * to ".nfs*" to emulate delete-on-last-close.
44 *
45 * In any case this should be fine as the reftable
46 * library will never write files with leading dots
47 * anyway.
48 */
49 if (starts_with(d->d_name, "."))
50 continue;
51 len++;
52 }
53 closedir(dir);
54 return len;
55 }
56
57 /*
58 * Work linenumber into the tempdir, so we can see which tests forget to
59 * cleanup.
60 */
61 static char *get_tmp_template(int linenumber)
62 {
63 const char *tmp = getenv("TMPDIR");
64 static char template[1024];
65 snprintf(template, sizeof(template) - 1, "%s/stack_test-%d.XXXXXX",
66 tmp ? tmp : "/tmp", linenumber);
67 return template;
68 }
69
70 static char *get_tmp_dir(int linenumber)
71 {
72 char *dir = get_tmp_template(linenumber);
73 cl_assert(mkdtemp(dir) != NULL);
74 return dir;
75 }
76
77 void test_reftable_stack__read_file(void)
78 {
79 char *fn = get_tmp_template(__LINE__);
80 struct tempfile *tmp = mks_tempfile(fn);
81 int fd = get_tempfile_fd(tmp);
82 char out[1024] = "line1\n\nline2\nline3";
83 int n, err;
84 char **names = NULL;
85 const char *want[] = { "line1", "line2", "line3" };
86
87 cl_assert(fd > 0);
88 n = write_in_full(fd, out, strlen(out));
89 cl_assert_equal_i(n, strlen(out));
90 err = close(fd);
91 cl_assert(err >= 0);
92
93 err = read_lines(fn, &names);
94 cl_assert(!err);
95
96 for (size_t i = 0; names[i]; i++)
97 cl_assert_equal_s(want[i], names[i]);
98 free_names(names);
99 (void) remove(fn);
100 delete_tempfile(&tmp);
101 }
102
103 static int write_test_ref(struct reftable_writer *wr, void *arg)
104 {
105 struct reftable_ref_record *ref = arg;
106 cl_assert_equal_i(reftable_writer_set_limits(wr,
107 ref->update_index, ref->update_index), 0);
108 return reftable_writer_add_ref(wr, ref);
109 }
110
111 static void write_n_ref_tables(struct reftable_stack *st,
112 size_t n)
113 {
114 struct reftable_write_options opts = {
115 .disable_auto_compact = 1,
116 };
117
118 for (size_t i = 0; i < n; i++) {
119 struct reftable_ref_record ref = {
120 .update_index = reftable_stack_next_update_index(st),
121 .value_type = REFTABLE_REF_VAL1,
122 };
123 char buf[128];
124
125 snprintf(buf, sizeof(buf), "refs/heads/branch-%04"PRIuMAX, (uintmax_t)i);
126 ref.refname = buf;
127 cl_reftable_set_hash(ref.value.val1, i, REFTABLE_HASH_SHA1);
128
129 cl_assert_equal_i(reftable_stack_add(st,
130 &write_test_ref, &ref, &opts, 0), 0);
131 }
132 }
133
134 struct write_log_arg {
135 struct reftable_log_record *log;
136 uint64_t update_index;
137 };
138
139 static int write_test_log(struct reftable_writer *wr, void *arg)
140 {
141 struct write_log_arg *wla = arg;
142
143 cl_assert_equal_i(reftable_writer_set_limits(wr,
144 wla->update_index,
145 wla->update_index), 0);
146 return reftable_writer_add_log(wr, wla->log);
147 }
148
149 void test_reftable_stack__add_one(void)
150 {
151 char *dir = get_tmp_dir(__LINE__);
152 struct reftable_buf scratch = REFTABLE_BUF_INIT;
153 int mask = umask(002);
154 struct reftable_write_options opts = {
155 .default_permissions = 0660,
156 };
157 struct reftable_stack *st = NULL;
158 struct reftable_ref_record ref = {
159 .refname = (char *) "HEAD",
160 .update_index = 1,
161 .value_type = REFTABLE_REF_SYMREF,
162 .value.symref = (char *) "master",
163 };
164 struct reftable_ref_record dest = { 0 };
165 struct stat stat_result = { 0 };
166 int err;
167
168 err = reftable_new_stack(&st, dir, NULL);
169 cl_assert(!err);
170
171 err = reftable_stack_add(st, write_test_ref, &ref, &opts, 0);
172 cl_assert(!err);
173
174 err = reftable_stack_read_ref(st, ref.refname, &dest);
175 cl_assert(!err);
176 cl_assert(reftable_ref_record_equal(&ref, &dest,
177 REFTABLE_HASH_SIZE_SHA1));
178 cl_assert(st->tables_len > 0);
179
180 #ifndef GIT_WINDOWS_NATIVE
181 cl_assert_equal_i(reftable_buf_addstr(&scratch, dir), 0);
182 cl_assert_equal_i(reftable_buf_addstr(&scratch,
183 "/tables.list"), 0);
184 cl_assert_equal_i(stat(scratch.buf, &stat_result), 0);
185 cl_assert_equal_i((stat_result.st_mode & 0777),
186 opts.default_permissions);
187
188 reftable_buf_reset(&scratch);
189 cl_assert_equal_i(reftable_buf_addstr(&scratch, dir), 0);
190 cl_assert_equal_i(reftable_buf_addstr(&scratch, "/"), 0);
191 /* do not try at home; not an external API for reftable. */
192 cl_assert(!reftable_buf_addstr(&scratch, st->tables[0]->name));
193 err = stat(scratch.buf, &stat_result);
194 cl_assert(!err);
195 cl_assert_equal_i((stat_result.st_mode & 0777),
196 opts.default_permissions);
197 #else
198 (void) stat_result;
199 #endif
200
201 reftable_ref_record_release(&dest);
202 reftable_stack_destroy(st);
203 reftable_buf_release(&scratch);
204 clear_dir(dir);
205 umask(mask);
206 }
207
208 void test_reftable_stack__uptodate(void)
209 {
210 struct reftable_stack *st1 = NULL;
211 struct reftable_stack *st2 = NULL;
212 char *dir = get_tmp_dir(__LINE__);
213
214 struct reftable_ref_record ref1 = {
215 .refname = (char *) "HEAD",
216 .update_index = 1,
217 .value_type = REFTABLE_REF_SYMREF,
218 .value.symref = (char *) "master",
219 };
220 struct reftable_ref_record ref2 = {
221 .refname = (char *) "branch2",
222 .update_index = 2,
223 .value_type = REFTABLE_REF_SYMREF,
224 .value.symref = (char *) "master",
225 };
226
227
228 /* simulate multi-process access to the same stack
229 by creating two stacks for the same directory.
230 */
231 cl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0);
232 cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0);
233 cl_assert_equal_i(reftable_stack_add(st1, write_test_ref,
234 &ref1, NULL, 0), 0);
235 cl_assert_equal_i(reftable_stack_add(st2, write_test_ref,
236 &ref2, NULL, 0), REFTABLE_OUTDATED_ERROR);
237 cl_assert_equal_i(reftable_stack_reload(st2), 0);
238 cl_assert_equal_i(reftable_stack_add(st2, write_test_ref,
239 &ref2, NULL, 0), 0);
240 reftable_stack_destroy(st1);
241 reftable_stack_destroy(st2);
242 clear_dir(dir);
243 }
244
245 void test_reftable_stack__transaction_api(void)
246 {
247 char *dir = get_tmp_dir(__LINE__);
248 struct reftable_stack *st = NULL;
249 struct reftable_addition *add = NULL;
250
251 struct reftable_ref_record ref = {
252 .refname = (char *) "HEAD",
253 .update_index = 1,
254 .value_type = REFTABLE_REF_SYMREF,
255 .value.symref = (char *) "master",
256 };
257 struct reftable_ref_record dest = { 0 };
258
259 cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
260
261 reftable_addition_destroy(add);
262
263 cl_assert_equal_i(reftable_stack_new_addition(&add, st, NULL, 0), 0);
264 cl_assert_equal_i(reftable_addition_add(add, write_test_ref,
265 &ref), 0);
266 cl_assert_equal_i(reftable_addition_commit(add), 0);
267
268 reftable_addition_destroy(add);
269
270 cl_assert_equal_i(reftable_stack_read_ref(st, ref.refname,
271 &dest), 0);
272 cl_assert_equal_i(REFTABLE_REF_SYMREF, dest.value_type);
273 cl_assert(reftable_ref_record_equal(&ref, &dest,
274 REFTABLE_HASH_SIZE_SHA1) != 0);
275
276 reftable_ref_record_release(&dest);
277 reftable_stack_destroy(st);
278 clear_dir(dir);
279 }
280
281 void test_reftable_stack__transaction_with_reload(void)
282 {
283 char *dir = get_tmp_dir(__LINE__);
284 struct reftable_stack *st1 = NULL, *st2 = NULL;
285 struct reftable_addition *add = NULL;
286 struct reftable_ref_record refs[2] = {
287 {
288 .refname = (char *) "refs/heads/a",
289 .update_index = 1,
290 .value_type = REFTABLE_REF_VAL1,
291 .value.val1 = { '1' },
292 },
293 {
294 .refname = (char *) "refs/heads/b",
295 .update_index = 2,
296 .value_type = REFTABLE_REF_VAL1,
297 .value.val1 = { '1' },
298 },
299 };
300 struct reftable_ref_record ref = { 0 };
301
302 cl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0);
303 cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0);
304 cl_assert_equal_i(reftable_stack_new_addition(&add, st1, NULL, 0), 0);
305 cl_assert_equal_i(reftable_addition_add(add, write_test_ref,
306 &refs[0]), 0);
307 cl_assert_equal_i(reftable_addition_commit(add), 0);
308 reftable_addition_destroy(add);
309
310 /*
311 * The second stack is now outdated, which we should notice. We do not
312 * create the addition and lock the stack by default, but allow the
313 * reload to happen when REFTABLE_STACK_NEW_ADDITION_RELOAD is set.
314 */
315 cl_assert_equal_i(reftable_stack_new_addition(&add, st2, NULL, 0),
316 REFTABLE_OUTDATED_ERROR);
317 cl_assert_equal_i(reftable_stack_new_addition(&add, st2, NULL,
318 REFTABLE_STACK_NEW_ADDITION_RELOAD), 0);
319 cl_assert_equal_i(reftable_addition_add(add, write_test_ref,
320 &refs[1]), 0);
321 cl_assert_equal_i(reftable_addition_commit(add), 0);
322 reftable_addition_destroy(add);
323
324 for (size_t i = 0; i < ARRAY_SIZE(refs); i++) {
325 cl_assert_equal_i(reftable_stack_read_ref(st2,
326 refs[i].refname, &ref) , 0);
327 cl_assert(reftable_ref_record_equal(&refs[i], &ref,
328 REFTABLE_HASH_SIZE_SHA1) != 0);
329 }
330
331 reftable_ref_record_release(&ref);
332 reftable_stack_destroy(st1);
333 reftable_stack_destroy(st2);
334 clear_dir(dir);
335 }
336
337 void test_reftable_stack__transaction_api_performs_auto_compaction(void)
338 {
339 char *dir = get_tmp_dir(__LINE__);
340 struct reftable_addition *add = NULL;
341 struct reftable_stack *st = NULL;
342 size_t n = 20;
343
344 cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
345
346 for (size_t i = 0; i <= n; i++) {
347 struct reftable_ref_record ref = {
348 .update_index = reftable_stack_next_update_index(st),
349 .value_type = REFTABLE_REF_SYMREF,
350 .value.symref = (char *) "master",
351 };
352 char name[100];
353 struct reftable_write_options write_opts = {
354 .disable_auto_compact = (i != n),
355 };
356
357 snprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i);
358 ref.refname = name;
359
360 /*
361 * Disable auto-compaction for all but the last runs. Like this
362 * we can ensure that we indeed honor this setting and have
363 * better control over when exactly auto compaction runs.
364 */
365 cl_assert_equal_i(reftable_stack_new_addition(&add,
366 st, &write_opts, 0), 0);
367 cl_assert_equal_i(reftable_addition_add(add,
368 write_test_ref, &ref), 0);
369 cl_assert_equal_i(reftable_addition_commit(add), 0);
370
371 reftable_addition_destroy(add);
372
373 /*
374 * The stack length should grow continuously for all runs where
375 * auto compaction is disabled. When enabled, we should merge
376 * all tables in the stack.
377 */
378 if (i != n)
379 cl_assert_equal_i(st->merged->tables_len, i + 1);
380 else
381 cl_assert_equal_i(st->merged->tables_len, 1);
382 }
383
384 reftable_stack_destroy(st);
385 clear_dir(dir);
386 }
387
388 void test_reftable_stack__auto_compaction_fails_gracefully(void)
389 {
390 struct reftable_ref_record ref = {
391 .refname = (char *) "refs/heads/master",
392 .update_index = 1,
393 .value_type = REFTABLE_REF_VAL1,
394 .value.val1 = {0x01},
395 };
396 struct reftable_stack *st;
397 struct reftable_buf table_path = REFTABLE_BUF_INIT;
398 char *dir = get_tmp_dir(__LINE__);
399 int err;
400
401 cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
402 cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
403 &ref, NULL, 0), 0);
404 cl_assert_equal_i(st->merged->tables_len, 1);
405 cl_assert_equal_i(st->stats.attempts, 0);
406 cl_assert_equal_i(st->stats.failures, 0);
407
408 /*
409 * Lock the newly written table such that it cannot be compacted.
410 * Adding a new table to the stack should not be impacted by this, even
411 * though auto-compaction will now fail.
412 */
413 cl_assert(!reftable_buf_addstr(&table_path, dir));
414 cl_assert(!reftable_buf_addstr(&table_path, "/"));
415 cl_assert(!reftable_buf_addstr(&table_path,
416 st->tables[0]->name));
417 cl_assert(!reftable_buf_addstr(&table_path, ".lock"));
418 write_file_buf(table_path.buf, "", 0);
419
420 ref.update_index = 2;
421 err = reftable_stack_add(st, write_test_ref, &ref, NULL, 0);
422 cl_assert(!err);
423 cl_assert_equal_i(st->merged->tables_len, 2);
424 cl_assert_equal_i(st->stats.attempts, 1);
425 cl_assert_equal_i(st->stats.failures, 1);
426
427 reftable_stack_destroy(st);
428 reftable_buf_release(&table_path);
429 clear_dir(dir);
430 }
431
432 static int write_error(struct reftable_writer *wr UNUSED, void *arg)
433 {
434 return *((int *)arg);
435 }
436
437 void test_reftable_stack__update_index_check(void)
438 {
439 char *dir = get_tmp_dir(__LINE__);
440 struct reftable_stack *st = NULL;
441 struct reftable_ref_record ref1 = {
442 .refname = (char *) "name1",
443 .update_index = 1,
444 .value_type = REFTABLE_REF_SYMREF,
445 .value.symref = (char *) "master",
446 };
447 struct reftable_ref_record ref2 = {
448 .refname = (char *) "name2",
449 .update_index = 1,
450 .value_type = REFTABLE_REF_SYMREF,
451 .value.symref = (char *) "master",
452 };
453
454 cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
455 cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
456 &ref1, NULL, 0), 0);
457 cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
458 &ref2, NULL, 0), REFTABLE_API_ERROR);
459 reftable_stack_destroy(st);
460 clear_dir(dir);
461 }
462
463 void test_reftable_stack__lock_failure(void)
464 {
465 char *dir = get_tmp_dir(__LINE__);
466 struct reftable_stack *st = NULL;
467 int i;
468
469 cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
470 for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--)
471 cl_assert_equal_i(reftable_stack_add(st, write_error,
472 &i, NULL, 0), i);
473
474 reftable_stack_destroy(st);
475 clear_dir(dir);
476 }
477
478 void test_reftable_stack__add(void)
479 {
480 struct reftable_write_options opts = {
481 .exact_log_message = 1,
482 .default_permissions = 0660,
483 .disable_auto_compact = 1,
484 };
485 struct reftable_stack *st = NULL;
486 char *dir = get_tmp_dir(__LINE__);
487 struct reftable_ref_record refs[2] = { 0 };
488 struct reftable_log_record logs[2] = { 0 };
489 struct reftable_buf path = REFTABLE_BUF_INIT;
490 struct stat stat_result;
491 size_t i, N = ARRAY_SIZE(refs);
492 int err = 0;
493
494 err = reftable_new_stack(&st, dir, NULL);
495 cl_assert(!err);
496
497 for (i = 0; i < N; i++) {
498 char buf[256];
499 snprintf(buf, sizeof(buf), "branch%02"PRIuMAX, (uintmax_t)i);
500 refs[i].refname = xstrdup(buf);
501 refs[i].update_index = i + 1;
502 refs[i].value_type = REFTABLE_REF_VAL1;
503 cl_reftable_set_hash(refs[i].value.val1, i,
504 REFTABLE_HASH_SHA1);
505
506 logs[i].refname = xstrdup(buf);
507 logs[i].update_index = N + i + 1;
508 logs[i].value_type = REFTABLE_LOG_UPDATE;
509 logs[i].value.update.email = xstrdup("identity@invalid");
510 cl_reftable_set_hash(logs[i].value.update.new_hash, i,
511 REFTABLE_HASH_SHA1);
512 }
513
514 for (i = 0; i < N; i++)
515 cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
516 &refs[i], &opts, 0), 0);
517
518 for (i = 0; i < N; i++) {
519 struct write_log_arg arg = {
520 .log = &logs[i],
521 .update_index = reftable_stack_next_update_index(st),
522 };
523 cl_assert_equal_i(reftable_stack_add(st, write_test_log,
524 &arg, &opts, 0), 0);
525 }
526
527 cl_assert_equal_i(reftable_stack_compact_all(st, &opts, NULL), 0);
528
529 for (i = 0; i < N; i++) {
530 struct reftable_ref_record dest = { 0 };
531
532 cl_assert_equal_i(reftable_stack_read_ref(st,
533 refs[i].refname, &dest), 0);
534 cl_assert(reftable_ref_record_equal(&dest, refs + i,
535 REFTABLE_HASH_SIZE_SHA1) != 0);
536 reftable_ref_record_release(&dest);
537 }
538
539 for (i = 0; i < N; i++) {
540 struct reftable_log_record dest = { 0 };
541 cl_assert_equal_i(reftable_stack_read_log(st,
542 refs[i].refname, &dest), 0);
543 cl_assert(reftable_log_record_equal(&dest, logs + i,
544 REFTABLE_HASH_SIZE_SHA1) != 0);
545 reftable_log_record_release(&dest);
546 }
547
548 #ifndef GIT_WINDOWS_NATIVE
549 cl_assert_equal_i(reftable_buf_addstr(&path, dir), 0);
550 cl_assert_equal_i(reftable_buf_addstr(&path, "/tables.list"), 0);
551 cl_assert_equal_i(stat(path.buf, &stat_result), 0);
552 cl_assert_equal_i((stat_result.st_mode & 0777), opts.default_permissions);
553
554 reftable_buf_reset(&path);
555 cl_assert_equal_i(reftable_buf_addstr(&path, dir), 0);
556 cl_assert_equal_i(reftable_buf_addstr(&path, "/"), 0);
557 /* do not try at home; not an external API for reftable. */
558 cl_assert(!reftable_buf_addstr(&path, st->tables[0]->name));
559 err = stat(path.buf, &stat_result);
560 cl_assert(!err);
561 cl_assert_equal_i((stat_result.st_mode & 0777),
562 opts.default_permissions);
563 #else
564 (void) stat_result;
565 #endif
566
567 /* cleanup */
568 reftable_stack_destroy(st);
569 for (i = 0; i < N; i++) {
570 reftable_ref_record_release(&refs[i]);
571 reftable_log_record_release(&logs[i]);
572 }
573 reftable_buf_release(&path);
574 clear_dir(dir);
575 }
576
577 void test_reftable_stack__iterator(void)
578 {
579 struct reftable_stack *st = NULL;
580 char *dir = get_tmp_dir(__LINE__);
581 struct reftable_ref_record refs[10] = { 0 };
582 struct reftable_log_record logs[10] = { 0 };
583 struct reftable_iterator it = { 0 };
584 size_t N = ARRAY_SIZE(refs), i;
585 int err;
586
587 cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
588
589 for (i = 0; i < N; i++) {
590 refs[i].refname = xstrfmt("branch%02"PRIuMAX, (uintmax_t)i);
591 refs[i].update_index = i + 1;
592 refs[i].value_type = REFTABLE_REF_VAL1;
593 cl_reftable_set_hash(refs[i].value.val1, i,
594 REFTABLE_HASH_SHA1);
595
596 logs[i].refname = xstrfmt("branch%02"PRIuMAX, (uintmax_t)i);
597 logs[i].update_index = i + 1;
598 logs[i].value_type = REFTABLE_LOG_UPDATE;
599 logs[i].value.update.email = xstrdup("johndoe@invalid");
600 logs[i].value.update.message = xstrdup("commit\n");
601 cl_reftable_set_hash(logs[i].value.update.new_hash, i,
602 REFTABLE_HASH_SHA1);
603 }
604
605 for (i = 0; i < N; i++)
606 cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
607 &refs[i], NULL, 0), 0);
608
609 for (i = 0; i < N; i++) {
610 struct write_log_arg arg = {
611 .log = &logs[i],
612 .update_index = reftable_stack_next_update_index(st),
613 };
614
615 cl_assert_equal_i(reftable_stack_add(st, write_test_log,
616 &arg, NULL, 0), 0);
617 }
618
619 reftable_stack_init_ref_iterator(st, &it);
620 reftable_iterator_seek_ref(&it, refs[0].refname);
621 for (i = 0; ; i++) {
622 struct reftable_ref_record ref = { 0 };
623
624 err = reftable_iterator_next_ref(&it, &ref);
625 if (err > 0)
626 break;
627 cl_assert(!err);
628 cl_assert(reftable_ref_record_equal(&ref, &refs[i],
629 REFTABLE_HASH_SIZE_SHA1) != 0);
630 reftable_ref_record_release(&ref);
631 }
632 cl_assert_equal_i(i, N);
633
634 reftable_iterator_destroy(&it);
635
636 cl_assert_equal_i(reftable_stack_init_log_iterator(st, &it), 0);
637
638 reftable_iterator_seek_log(&it, logs[0].refname);
639 for (i = 0; ; i++) {
640 struct reftable_log_record log = { 0 };
641
642 err = reftable_iterator_next_log(&it, &log);
643 if (err > 0)
644 break;
645 cl_assert(!err);
646 cl_assert(reftable_log_record_equal(&log, &logs[i],
647 REFTABLE_HASH_SIZE_SHA1) != 0);
648 reftable_log_record_release(&log);
649 }
650 cl_assert_equal_i(i, N);
651
652 reftable_stack_destroy(st);
653 reftable_iterator_destroy(&it);
654 for (i = 0; i < N; i++) {
655 reftable_ref_record_release(&refs[i]);
656 reftable_log_record_release(&logs[i]);
657 }
658 clear_dir(dir);
659 }
660
661 void test_reftable_stack__log_normalize(void)
662 {
663 struct reftable_stack *st = NULL;
664 char *dir = get_tmp_dir(__LINE__);
665 struct reftable_log_record input = {
666 .refname = (char *) "branch",
667 .update_index = 1,
668 .value_type = REFTABLE_LOG_UPDATE,
669 .value = {
670 .update = {
671 .new_hash = { 1 },
672 .old_hash = { 2 },
673 },
674 },
675 };
676 struct reftable_log_record dest = {
677 .update_index = 0,
678 };
679 struct write_log_arg arg = {
680 .log = &input,
681 .update_index = 1,
682 };
683
684 cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
685
686 input.value.update.message = (char *) "one\ntwo";
687 cl_assert_equal_i(reftable_stack_add(st, write_test_log,
688 &arg, NULL, 0), REFTABLE_API_ERROR);
689
690 input.value.update.message = (char *) "one";
691 cl_assert_equal_i(reftable_stack_add(st, write_test_log,
692 &arg, NULL, 0), 0);
693 cl_assert_equal_i(reftable_stack_read_log(st, input.refname,
694 &dest), 0);
695 cl_assert_equal_s(dest.value.update.message, "one\n");
696
697 input.value.update.message = (char *) "two\n";
698 arg.update_index = 2;
699 cl_assert_equal_i(reftable_stack_add(st, write_test_log,
700 &arg, NULL, 0), 0);
701 cl_assert_equal_i(reftable_stack_read_log(st, input.refname,
702 &dest), 0);
703 cl_assert_equal_s(dest.value.update.message, "two\n");
704
705 /* cleanup */
706 reftable_stack_destroy(st);
707 reftable_log_record_release(&dest);
708 clear_dir(dir);
709 }
710
711 void test_reftable_stack__tombstone(void)
712 {
713 char *dir = get_tmp_dir(__LINE__);
714 struct reftable_stack *st = NULL;
715 struct reftable_ref_record refs[2] = { 0 };
716 struct reftable_log_record logs[2] = { 0 };
717 size_t i, N = ARRAY_SIZE(refs);
718 struct reftable_ref_record dest = { 0 };
719 struct reftable_log_record log_dest = { 0 };
720
721 cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
722
723 /* even entries add the refs, odd entries delete them. */
724 for (i = 0; i < N; i++) {
725 const char *buf = "branch";
726 refs[i].refname = xstrdup(buf);
727 refs[i].update_index = i + 1;
728 if (i % 2 == 0) {
729 refs[i].value_type = REFTABLE_REF_VAL1;
730 cl_reftable_set_hash(refs[i].value.val1, i,
731 REFTABLE_HASH_SHA1);
732 }
733
734 logs[i].refname = xstrdup(buf);
735 /*
736 * update_index is part of the key so should be constant.
737 * The value itself should be less than the writer's upper
738 * limit.
739 */
740 logs[i].update_index = 1;
741 if (i % 2 == 0) {
742 logs[i].value_type = REFTABLE_LOG_UPDATE;
743 cl_reftable_set_hash(logs[i].value.update.new_hash, i, REFTABLE_HASH_SHA1);
744 logs[i].value.update.email =
745 xstrdup("identity@invalid");
746 }
747 }
748 for (i = 0; i < N; i++)
749 cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
750 &refs[i], NULL, 0), 0);
751
752 for (i = 0; i < N; i++) {
753 struct write_log_arg arg = {
754 .log = &logs[i],
755 .update_index = reftable_stack_next_update_index(st),
756 };
757 cl_assert_equal_i(reftable_stack_add(st, write_test_log,
758 &arg, NULL, 0), 0);
759 }
760
761 cl_assert_equal_i(reftable_stack_read_ref(st, "branch",
762 &dest), 1);
763 reftable_ref_record_release(&dest);
764
765 cl_assert_equal_i(reftable_stack_read_log(st, "branch",
766 &log_dest), 1);
767 reftable_log_record_release(&log_dest);
768
769 cl_assert_equal_i(reftable_stack_compact_all(st, NULL, NULL), 0);
770 cl_assert_equal_i(reftable_stack_read_ref(st, "branch",
771 &dest), 1);
772 cl_assert_equal_i(reftable_stack_read_log(st, "branch",
773 &log_dest), 1);
774 reftable_ref_record_release(&dest);
775 reftable_log_record_release(&log_dest);
776
777 /* cleanup */
778 reftable_stack_destroy(st);
779 for (i = 0; i < N; i++) {
780 reftable_ref_record_release(&refs[i]);
781 reftable_log_record_release(&logs[i]);
782 }
783 clear_dir(dir);
784 }
785
786 void test_reftable_stack__hash_id(void)
787 {
788 char *dir = get_tmp_dir(__LINE__);
789 struct reftable_stack *st = NULL;
790
791 struct reftable_ref_record ref = {
792 .refname = (char *) "master",
793 .value_type = REFTABLE_REF_SYMREF,
794 .value.symref = (char *) "target",
795 .update_index = 1,
796 };
797 struct reftable_stack_options opts32 = { .hash_id = REFTABLE_HASH_SHA256 };
798 struct reftable_stack *st32 = NULL;
799 struct reftable_stack *st_default = NULL;
800 struct reftable_ref_record dest = { 0 };
801
802 cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
803 cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
804 &ref, NULL, 0), 0);
805
806 /* can't read it with the wrong hash ID. */
807 cl_assert_equal_i(reftable_new_stack(&st32, dir,
808 &opts32), REFTABLE_FORMAT_ERROR);
809
810 /* check that we can read it back with default opts too. */
811 cl_assert_equal_i(reftable_new_stack(&st_default, dir,
812 NULL), 0);
813 cl_assert_equal_i(reftable_stack_read_ref(st_default, "master",
814 &dest), 0);
815 cl_assert(reftable_ref_record_equal(&ref, &dest,
816 REFTABLE_HASH_SIZE_SHA1) != 0);
817 reftable_ref_record_release(&dest);
818 reftable_stack_destroy(st);
819 reftable_stack_destroy(st_default);
820 clear_dir(dir);
821 }
822
823 void test_reftable_stack__suggest_compaction_segment(void)
824 {
825 uint64_t sizes[] = { 512, 64, 17, 16, 9, 9, 9, 16, 2, 16 };
826 struct segment min =
827 suggest_compaction_segment(sizes, ARRAY_SIZE(sizes), 2);
828 cl_assert_equal_i(min.start, 1);
829 cl_assert_equal_i(min.end, 10);
830 }
831
832 void test_reftable_stack__suggest_compaction_segment_nothing(void)
833 {
834 uint64_t sizes[] = { 64, 32, 16, 8, 4, 2 };
835 struct segment result =
836 suggest_compaction_segment(sizes, ARRAY_SIZE(sizes), 2);
837 cl_assert_equal_i(result.start, result.end);
838 }
839
840 void test_reftable_stack__reflog_expire(void)
841 {
842 char *dir = get_tmp_dir(__LINE__);
843 struct reftable_stack *st = NULL;
844 struct reftable_log_record logs[20] = { 0 };
845 size_t i, N = ARRAY_SIZE(logs) - 1;
846 struct reftable_log_expiry_config expiry = {
847 .time = 10,
848 };
849 struct reftable_log_record log = { 0 };
850
851 cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
852
853 for (i = 1; i <= N; i++) {
854 char buf[256];
855 snprintf(buf, sizeof(buf), "branch%02"PRIuMAX, (uintmax_t)i);
856
857 logs[i].refname = xstrdup(buf);
858 logs[i].update_index = i;
859 logs[i].value_type = REFTABLE_LOG_UPDATE;
860 logs[i].value.update.time = i;
861 logs[i].value.update.email = xstrdup("identity@invalid");
862 cl_reftable_set_hash(logs[i].value.update.new_hash, i,
863 REFTABLE_HASH_SHA1);
864 }
865
866 for (i = 1; i <= N; i++) {
867 struct write_log_arg arg = {
868 .log = &logs[i],
869 .update_index = reftable_stack_next_update_index(st),
870 };
871 cl_assert_equal_i(reftable_stack_add(st, write_test_log,
872 &arg, NULL, 0), 0);
873 }
874
875 cl_assert_equal_i(reftable_stack_compact_all(st, NULL, NULL), 0);
876 cl_assert_equal_i(reftable_stack_compact_all(st, NULL, &expiry), 0);
877 cl_assert_equal_i(reftable_stack_read_log(st, logs[9].refname,
878 &log), 1);
879 cl_assert_equal_i(reftable_stack_read_log(st, logs[11].refname,
880 &log), 0);
881
882 expiry.min_update_index = 15;
883 cl_assert_equal_i(reftable_stack_compact_all(st, NULL, &expiry), 0);
884 cl_assert_equal_i(reftable_stack_read_log(st, logs[14].refname,
885 &log), 1);
886 cl_assert_equal_i(reftable_stack_read_log(st, logs[16].refname,
887 &log), 0);
888
889 /* cleanup */
890 reftable_stack_destroy(st);
891 for (i = 0; i <= N; i++)
892 reftable_log_record_release(&logs[i]);
893 clear_dir(dir);
894 reftable_log_record_release(&log);
895 }
896
897 static int write_nothing(struct reftable_writer *wr, void *arg UNUSED)
898 {
899 cl_assert_equal_i(reftable_writer_set_limits(wr, 1, 1), 0);
900 return 0;
901 }
902
903 void test_reftable_stack__empty_add(void)
904 {
905 struct reftable_stack *st = NULL;
906 char *dir = get_tmp_dir(__LINE__);
907 struct reftable_stack *st2 = NULL;
908
909 cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
910 cl_assert_equal_i(reftable_stack_add(st, write_nothing,
911 NULL, NULL, 0), 0);
912 cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0);
913 clear_dir(dir);
914 reftable_stack_destroy(st);
915 reftable_stack_destroy(st2);
916 }
917
918 static int fastlogN(uint64_t sz, uint64_t N)
919 {
920 int l = 0;
921 if (sz == 0)
922 return 0;
923 for (; sz; sz /= N)
924 l++;
925 return l - 1;
926 }
927
928 void test_reftable_stack__auto_compaction(void)
929 {
930 struct reftable_write_options opts = {
931 .disable_auto_compact = 1,
932 };
933 struct reftable_stack *st = NULL;
934 char *dir = get_tmp_dir(__LINE__);
935 size_t i, N = 100;
936 int err;
937
938 cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
939
940 for (i = 0; i < N; i++) {
941 char name[100];
942 struct reftable_ref_record ref = {
943 .refname = name,
944 .update_index = reftable_stack_next_update_index(st),
945 .value_type = REFTABLE_REF_SYMREF,
946 .value.symref = (char *) "master",
947 };
948 snprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i);
949
950 err = reftable_stack_add(st, write_test_ref, &ref, &opts, 0);
951 cl_assert(!err);
952
953 err = reftable_stack_auto_compact(st, &opts);
954 cl_assert(!err);
955 cl_assert(i < 2 || st->merged->tables_len < 2 * fastlogN(i, 2));
956 }
957
958 cl_assert(reftable_stack_compaction_stats(st)->entries_written <
959 (uint64_t)(N * fastlogN(N, 2)));
960
961 reftable_stack_destroy(st);
962 clear_dir(dir);
963 }
964
965 void test_reftable_stack__auto_compaction_factor(void)
966 {
967 struct reftable_write_options opts = {
968 .auto_compaction_factor = 5,
969 };
970 struct reftable_stack *st = NULL;
971 char *dir = get_tmp_dir(__LINE__);
972 size_t N = 100;
973 int err;
974
975 cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
976
977 for (size_t i = 0; i < N; i++) {
978 char name[20];
979 struct reftable_ref_record ref = {
980 .refname = name,
981 .update_index = reftable_stack_next_update_index(st),
982 .value_type = REFTABLE_REF_VAL1,
983 };
984 xsnprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i);
985
986 err = reftable_stack_add(st, &write_test_ref, &ref, &opts, 0);
987 cl_assert(!err);
988
989 cl_assert(i < 5 || st->merged->tables_len < 5 * fastlogN(i, 5));
990 }
991
992 reftable_stack_destroy(st);
993 clear_dir(dir);
994 }
995
996 void test_reftable_stack__auto_compaction_with_locked_tables(void)
997 {
998 struct reftable_write_options opts = {
999 .disable_auto_compact = 1,
1000 };
1001 struct reftable_stack *st = NULL;
1002 struct reftable_buf buf = REFTABLE_BUF_INIT;
1003 char *dir = get_tmp_dir(__LINE__);
1004 int err;
1005
1006 cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
1007
1008 write_n_ref_tables(st, 5);
1009 cl_assert_equal_i(st->merged->tables_len, 5);
1010
1011 /*
1012 * Given that all tables we have written should be roughly the same
1013 * size, we expect that auto-compaction will want to compact all of the
1014 * tables. Locking any of the tables will keep it from doing so.
1015 */
1016 cl_assert(!reftable_buf_addstr(&buf, dir));
1017 cl_assert(!reftable_buf_addstr(&buf, "/"));
1018 cl_assert(!reftable_buf_addstr(&buf, st->tables[2]->name));
1019 cl_assert(!reftable_buf_addstr(&buf, ".lock"));
1020 write_file_buf(buf.buf, "", 0);
1021
1022 /*
1023 * When parts of the stack are locked, then auto-compaction does a best
1024 * effort compaction of those tables which aren't locked. So while this
1025 * would in theory compact all tables, due to the preexisting lock we
1026 * only compact the newest two tables.
1027 */
1028 err = reftable_stack_auto_compact(st, &opts);
1029 cl_assert(!err);
1030 cl_assert_equal_i(st->stats.failures, 0);
1031 cl_assert_equal_i(st->merged->tables_len, 4);
1032
1033 reftable_stack_destroy(st);
1034 reftable_buf_release(&buf);
1035 clear_dir(dir);
1036 }
1037
1038 void test_reftable_stack__add_performs_auto_compaction(void)
1039 {
1040 struct reftable_stack *st = NULL;
1041 char *dir = get_tmp_dir(__LINE__);
1042 size_t i, n = 20;
1043
1044 cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
1045
1046 for (i = 0; i <= n; i++) {
1047 struct reftable_ref_record ref = {
1048 .update_index = reftable_stack_next_update_index(st),
1049 .value_type = REFTABLE_REF_SYMREF,
1050 .value.symref = (char *) "master",
1051 };
1052 struct reftable_write_options write_opts = {
1053 .disable_auto_compact = (i != n),
1054 };
1055 bool required = false;
1056 char buf[128];
1057
1058 /*
1059 * Disable auto-compaction for all but the last runs. Like this
1060 * we can ensure that we indeed honor this setting and have
1061 * better control over when exactly auto compaction runs.
1062 */
1063 snprintf(buf, sizeof(buf), "branch-%04"PRIuMAX, (uintmax_t)i);
1064 ref.refname = buf;
1065
1066 cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
1067 &ref, &write_opts, 0), 0);
1068
1069 /*
1070 * The stack length should grow continuously for all runs where
1071 * auto compaction is disabled. When enabled, we should merge
1072 * all tables in the stack.
1073 */
1074 cl_assert_equal_i(reftable_stack_compaction_required(st, NULL, true, &required), 0);
1075 if (i != n) {
1076 cl_assert_equal_i(st->merged->tables_len, i + 1);
1077 if (i < 1)
1078 cl_assert_equal_b(required, false);
1079 else
1080 cl_assert_equal_b(required, true);
1081 } else {
1082 cl_assert_equal_i(st->merged->tables_len, 1);
1083 cl_assert_equal_b(required, false);
1084 }
1085 }
1086
1087 reftable_stack_destroy(st);
1088 clear_dir(dir);
1089 }
1090
1091 void test_reftable_stack__compaction_with_locked_tables(void)
1092 {
1093 struct reftable_write_options opts = {
1094 .disable_auto_compact = 1,
1095 };
1096 struct reftable_stack *st = NULL;
1097 struct reftable_buf buf = REFTABLE_BUF_INIT;
1098 char *dir = get_tmp_dir(__LINE__);
1099 int err;
1100
1101 cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
1102
1103 write_n_ref_tables(st, 3);
1104 cl_assert_equal_i(st->merged->tables_len, 3);
1105
1106 /* Lock one of the tables that we're about to compact. */
1107 cl_assert(!reftable_buf_addstr(&buf, dir));
1108 cl_assert(!reftable_buf_addstr(&buf, "/"));
1109 cl_assert(!reftable_buf_addstr(&buf, st->tables[1]->name));
1110 cl_assert(!reftable_buf_addstr(&buf, ".lock"));
1111 write_file_buf(buf.buf, "", 0);
1112
1113 /*
1114 * Compaction is expected to fail given that we were not able to
1115 * compact all tables.
1116 */
1117 err = reftable_stack_compact_all(st, &opts, NULL);
1118 cl_assert_equal_i(err, REFTABLE_LOCK_ERROR);
1119 cl_assert_equal_i(st->stats.failures, 1);
1120 cl_assert_equal_i(st->merged->tables_len, 3);
1121
1122 reftable_stack_destroy(st);
1123 reftable_buf_release(&buf);
1124 clear_dir(dir);
1125 }
1126
1127 void test_reftable_stack__compaction_concurrent(void)
1128 {
1129 struct reftable_stack *st1 = NULL, *st2 = NULL;
1130 char *dir = get_tmp_dir(__LINE__);
1131
1132 cl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0);
1133 write_n_ref_tables(st1, 3);
1134
1135 cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0);
1136 cl_assert_equal_i(reftable_stack_compact_all(st1, NULL, NULL), 0);
1137
1138 reftable_stack_destroy(st1);
1139 reftable_stack_destroy(st2);
1140
1141 cl_assert_equal_i(count_dir_entries(dir), 2);
1142 clear_dir(dir);
1143 }
1144
1145 static void unclean_stack_close(struct reftable_stack *st)
1146 {
1147 /* break abstraction boundary to simulate unclean shutdown. */
1148 for (size_t i = 0; i < st->tables_len; i++)
1149 reftable_table_decref(st->tables[i]);
1150 st->tables_len = 0;
1151 REFTABLE_FREE_AND_NULL(st->tables);
1152 }
1153
1154 void test_reftable_stack__compaction_concurrent_clean(void)
1155 {
1156 struct reftable_stack *st1 = NULL, *st2 = NULL, *st3 = NULL;
1157 char *dir = get_tmp_dir(__LINE__);
1158
1159 cl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0);
1160 write_n_ref_tables(st1, 3);
1161
1162 cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0);
1163 cl_assert_equal_i(reftable_stack_compact_all(st1, NULL, NULL), 0);
1164
1165 unclean_stack_close(st1);
1166 unclean_stack_close(st2);
1167
1168 cl_assert_equal_i(reftable_new_stack(&st3, dir, NULL), 0);
1169 cl_assert_equal_i(reftable_stack_clean(st3), 0);
1170 cl_assert_equal_i(count_dir_entries(dir), 2);
1171
1172 reftable_stack_destroy(st1);
1173 reftable_stack_destroy(st2);
1174 reftable_stack_destroy(st3);
1175
1176 clear_dir(dir);
1177 }
1178
1179 void test_reftable_stack__read_across_reload(void)
1180 {
1181 struct reftable_stack *st1 = NULL, *st2 = NULL;
1182 struct reftable_ref_record rec = { 0 };
1183 struct reftable_iterator it = { 0 };
1184 char *dir = get_tmp_dir(__LINE__);
1185 int err;
1186
1187 /* Create a first stack and set up an iterator for it. */
1188 cl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0);
1189 write_n_ref_tables(st1, 2);
1190 cl_assert_equal_i(st1->merged->tables_len, 2);
1191 reftable_stack_init_ref_iterator(st1, &it);
1192 cl_assert_equal_i(reftable_iterator_seek_ref(&it, ""), 0);
1193
1194 /* Set up a second stack for the same directory and compact it. */
1195 err = reftable_new_stack(&st2, dir, NULL);
1196 cl_assert(!err);
1197 cl_assert_equal_i(st2->merged->tables_len, 2);
1198 err = reftable_stack_compact_all(st2, NULL, NULL);
1199 cl_assert(!err);
1200 cl_assert_equal_i(st2->merged->tables_len, 1);
1201
1202 /*
1203 * Verify that we can continue to use the old iterator even after we
1204 * have reloaded its stack.
1205 */
1206 err = reftable_stack_reload(st1);
1207 cl_assert(!err);
1208 cl_assert_equal_i(st1->merged->tables_len, 1);
1209 err = reftable_iterator_next_ref(&it, &rec);
1210 cl_assert(!err);
1211 cl_assert_equal_s(rec.refname, "refs/heads/branch-0000");
1212 err = reftable_iterator_next_ref(&it, &rec);
1213 cl_assert(!err);
1214 cl_assert_equal_s(rec.refname, "refs/heads/branch-0001");
1215 err = reftable_iterator_next_ref(&it, &rec);
1216 cl_assert(err > 0);
1217
1218 reftable_ref_record_release(&rec);
1219 reftable_iterator_destroy(&it);
1220 reftable_stack_destroy(st1);
1221 reftable_stack_destroy(st2);
1222 clear_dir(dir);
1223 }
1224
1225 void test_reftable_stack__reload_with_missing_table(void)
1226 {
1227 struct reftable_stack *st = NULL;
1228 struct reftable_ref_record rec = { 0 };
1229 struct reftable_iterator it = { 0 };
1230 struct reftable_buf table_path = REFTABLE_BUF_INIT, content = REFTABLE_BUF_INIT;
1231 char *dir = get_tmp_dir(__LINE__);
1232 int err;
1233
1234 /* Create a first stack and set up an iterator for it. */
1235 cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
1236 write_n_ref_tables(st, 2);
1237 cl_assert_equal_i(st->merged->tables_len, 2);
1238 reftable_stack_init_ref_iterator(st, &it);
1239 cl_assert_equal_i(reftable_iterator_seek_ref(&it, ""), 0);
1240
1241 /*
1242 * Update the tables.list file with some garbage data, while reusing
1243 * our old tables. This should trigger a partial reload of the stack,
1244 * where we try to reuse our old tables.
1245 */
1246 cl_assert(!reftable_buf_addstr(&content, st->tables[0]->name));
1247 cl_assert(!reftable_buf_addstr(&content, "\n"));
1248 cl_assert(!reftable_buf_addstr(&content, st->tables[1]->name));
1249 cl_assert(!reftable_buf_addstr(&content, "\n"));
1250 cl_assert(!reftable_buf_addstr(&content, "garbage\n"));
1251 cl_assert(!reftable_buf_addstr(&table_path, st->list_file));
1252 cl_assert(!reftable_buf_addstr(&table_path, ".lock"));
1253 write_file_buf(table_path.buf, content.buf, content.len);
1254 cl_assert_equal_i(rename(table_path.buf, st->list_file), 0);
1255
1256 err = reftable_stack_reload(st);
1257 cl_assert_equal_i(err, -4);
1258 cl_assert_equal_i(st->merged->tables_len, 2);
1259
1260 /*
1261 * Even though the reload has failed, we should be able to continue
1262 * using the iterator.
1263 */
1264 cl_assert_equal_i(reftable_iterator_next_ref(&it, &rec), 0);
1265 cl_assert_equal_s(rec.refname, "refs/heads/branch-0000");
1266 cl_assert_equal_i(reftable_iterator_next_ref(&it, &rec), 0);
1267 cl_assert_equal_s(rec.refname, "refs/heads/branch-0001");
1268 cl_assert(reftable_iterator_next_ref(&it, &rec) > 0);
1269
1270 reftable_ref_record_release(&rec);
1271 reftable_iterator_destroy(&it);
1272 reftable_stack_destroy(st);
1273 reftable_buf_release(&table_path);
1274 reftable_buf_release(&content);
1275 clear_dir(dir);
1276 }
1277
1278 static int write_limits_after_ref(struct reftable_writer *wr, void *arg)
1279 {
1280 struct reftable_ref_record *ref = arg;
1281 cl_assert_equal_i(reftable_writer_set_limits(wr,
1282 ref->update_index, ref->update_index), 0);
1283 cl_assert_equal_i(reftable_writer_add_ref(wr, ref), 0);
1284 return reftable_writer_set_limits(wr, ref->update_index, ref->update_index);
1285 }
1286
1287 void test_reftable_stack__invalid_limit_updates(void)
1288 {
1289 struct reftable_ref_record ref = {
1290 .refname = (char *) "HEAD",
1291 .update_index = 1,
1292 .value_type = REFTABLE_REF_SYMREF,
1293 .value.symref = (char *) "master",
1294 };
1295 struct reftable_write_options opts = {
1296 .default_permissions = 0660,
1297 };
1298 struct reftable_addition *add = NULL;
1299 char *dir = get_tmp_dir(__LINE__);
1300 struct reftable_stack *st = NULL;
1301
1302 cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
1303
1304 reftable_addition_destroy(add);
1305
1306 cl_assert_equal_i(reftable_stack_new_addition(&add, st, &opts, 0), 0);
1307
1308 /*
1309 * write_limits_after_ref also updates the update indexes after adding
1310 * the record. This should cause an err to be returned, since the limits
1311 * must be set at the start.
1312 */
1313 cl_assert_equal_i(reftable_addition_add(add,
1314 write_limits_after_ref, &ref), REFTABLE_API_ERROR);
1315
1316 reftable_addition_destroy(add);
1317 reftable_stack_destroy(st);
1318 clear_dir(dir);
1319 }